Skip to content

Commit e6ccdad

Browse files
authored
DVO-191: [quickfix] revert threading (#314)
* reset reconcicler * revert PR issue * Suspend benchmark due to reverted changes * Restore later changes that were undone by the revert
1 parent e10ae38 commit e6ccdad

File tree

4 files changed

+90
-117
lines changed

4 files changed

+90
-117
lines changed

pkg/controller/generic_reconciler.go

Lines changed: 67 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ type GenericReconciler struct {
4444
cmWatcher *configmap.Watcher
4545
validationEngine validations.Interface
4646
apiResources []metav1.APIResource
47-
resourceGVKs []schema.GroupVersionKind
4847
}
4948

5049
// NewGenericReconciler returns a GenericReconciler struct
@@ -163,27 +162,10 @@ func (gr *GenericReconciler) reconcileEverything(ctx context.Context) error {
163162
once.Do(func() {
164163
apiResources, err := reconcileResourceList(gr.discovery, gr.client.Scheme())
165164
if err != nil {
166-
gr.logger.Error(
167-
err,
168-
"cannot read API resources",
169-
)
165+
gr.logger.Error(err, "retrieving API resources to reconcile")
170166
return
171167
}
172168
gr.apiResources = apiResources
173-
gvks := getNamespacedResourcesGVK(gr.apiResources)
174-
// sorting GVKs is very important for getting the consistent results
175-
// when trying to match the 'app' label values. We must be sure that
176-
// resources from the group apps/v1 are processed between first.
177-
sort.SliceStable(gvks, func(i, j int) bool {
178-
f := gvks[i]
179-
s := gvks[j]
180-
// sort resource by Kind in the same group
181-
if f.Group == s.Group {
182-
return f.Kind < s.Kind
183-
}
184-
return f.Group < s.Group
185-
})
186-
gr.resourceGVKs = gvks
187169
})
188170

189171
for i, resource := range gr.apiResources {
@@ -198,7 +180,8 @@ func (gr *GenericReconciler) reconcileEverything(ctx context.Context) error {
198180
return fmt.Errorf("getting watched namespaces: %w", err)
199181
}
200182

201-
errNR := gr.processNamespacedResources(ctx, namespaces)
183+
gvkResources := gr.getNamespacedResourcesGVK(gr.apiResources)
184+
errNR := gr.processNamespacedResources(ctx, gvkResources, namespaces)
202185
if errNR != nil {
203186
return fmt.Errorf("processing namespace scoped resources: %w", errNR)
204187
}
@@ -208,27 +191,26 @@ func (gr *GenericReconciler) reconcileEverything(ctx context.Context) error {
208191
return nil
209192
}
210193

211-
type unstructuredWithSelector struct {
212-
unstructured *unstructured.Unstructured
213-
selector labels.Selector
214-
}
215-
216-
type groupOfObjects struct {
217-
objects []*unstructured.Unstructured
218-
label string
219-
}
220-
221194
// groupAppObjects iterates over provided GroupVersionKind in given namespace
222195
// and returns map of objects grouped by their "app" label
223196
func (gr *GenericReconciler) groupAppObjects(ctx context.Context,
224-
namespace string, ch chan groupOfObjects) {
225-
defer close(ch)
197+
namespace string, gvks []schema.GroupVersionKind) (map[string][]*unstructured.Unstructured, error) {
226198
relatedObjects := make(map[string][]*unstructured.Unstructured)
227-
labelToLabelSet := make(map[string]*labels.Set)
228199

229-
var objectsWithNonEmptySelector []*unstructuredWithSelector
200+
// sorting GVKs is very important for getting the consistent results
201+
// when trying to match the 'app' label values. We must be sure that
202+
// resources from the group apps/v1 are processed between first.
203+
sort.Slice(gvks, func(i, j int) bool {
204+
f := gvks[i]
205+
s := gvks[j]
206+
// sort resource by Kind in the same group
207+
if f.Group == s.Group {
208+
return f.Kind < s.Kind
209+
}
210+
return f.Group < s.Group
211+
})
230212

231-
for _, gvk := range gr.resourceGVKs {
213+
for _, gvk := range gvks {
232214
list := unstructured.UnstructuredList{}
233215
listOptions := &client.ListOptions{
234216
Limit: gr.listLimit,
@@ -238,20 +220,15 @@ func (gr *GenericReconciler) groupAppObjects(ctx context.Context,
238220
for {
239221

240222
if err := gr.client.List(ctx, &list, listOptions); err != nil {
241-
continue
223+
return nil, fmt.Errorf("listing %s: %w", gvk.String(), err)
242224
}
243225

244226
for i := range list.Items {
245227
obj := &list.Items[i]
246228
unstructured.RemoveNestedField(obj.Object, "metadata", "managedFields")
247229
unstructured.RemoveNestedField(obj.Object, "status")
248-
processResourceLabels(obj, relatedObjects, labelToLabelSet)
249-
sel, err := getLabelSelector(obj)
250-
if err != nil || sel == labels.Nothing() {
251-
continue
252-
}
253-
objectsWithNonEmptySelector = append(objectsWithNonEmptySelector,
254-
&unstructuredWithSelector{unstructured: obj, selector: sel})
230+
processResourceLabels(obj, relatedObjects)
231+
gr.processResourceSelectors(obj, relatedObjects)
255232
}
256233

257234
listContinue := list.GetContinue()
@@ -261,63 +238,72 @@ func (gr *GenericReconciler) groupAppObjects(ctx context.Context,
261238
listOptions.Continue = listContinue
262239
}
263240
}
264-
for label := range relatedObjects {
265-
labelsSet := labelToLabelSet[label]
266-
for _, o := range objectsWithNonEmptySelector {
267-
if o.selector.Matches(labelsSet) {
268-
relatedObjects[label] = append(relatedObjects[label], o.unstructured)
269-
}
270-
}
271-
ch <- groupOfObjects{label: label, objects: relatedObjects[label]}
272-
}
273-
}
274-
275-
func getLabelSelector(obj *unstructured.Unstructured) (labels.Selector, error) {
276-
labelSelector := utils.GetLabelSelector(obj)
277-
return metav1.LabelSelectorAsSelector(labelSelector)
241+
return relatedObjects, nil
278242
}
279243

280244
// processResourceLabels reads resource labels and if the labels
281245
// are not empty then format them into string and put the string value
282246
// as key and the object as a value into "relatedObjects" map
283247
func processResourceLabels(obj *unstructured.Unstructured,
284-
relatedObjects map[string][]*unstructured.Unstructured, labelSetMapping map[string]*labels.Set) {
248+
relatedObjects map[string][]*unstructured.Unstructured) {
285249

286250
objLabels := utils.GetLabels(obj)
287251
if len(objLabels) == 0 {
288252
return
289253
}
290254
labelsString := labels.FormatLabels(objLabels)
291255
relatedObjects[labelsString] = append(relatedObjects[labelsString], obj)
292-
labelSetMapping[labelsString] = &objLabels
256+
}
257+
258+
// processResourceSelectors reads resource selector and then tries to match
259+
// the selector to known labels (keys in the relatedObjects map). If a match is found then
260+
// the object is added to the corresponding group (values in the relatedObjects map).
261+
func (gr *GenericReconciler) processResourceSelectors(obj *unstructured.Unstructured,
262+
relatedObjects map[string][]*unstructured.Unstructured) {
263+
labelSelector := utils.GetLabelSelector(obj)
264+
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
265+
if err != nil {
266+
gr.logger.Error(err, "cannot convert label selector for object", obj.GetKind(), obj.GetName())
267+
return
268+
}
269+
270+
if selector == labels.Nothing() {
271+
return
272+
}
273+
274+
for k := range relatedObjects {
275+
labelsSet, err := labels.ConvertSelectorToLabelsMap(k)
276+
if err != nil {
277+
gr.logger.Error(err, "cannot convert selector to labels map for", obj.GetKind(), obj.GetName())
278+
continue
279+
}
280+
if selector.Matches(labelsSet) {
281+
relatedObjects[k] = append(relatedObjects[k], obj)
282+
}
283+
}
293284
}
294285

295286
func (gr *GenericReconciler) processNamespacedResources(
296-
ctx context.Context, namespaces *[]namespace) error {
287+
ctx context.Context, gvks []schema.GroupVersionKind, namespaces *[]namespace) error {
297288

298-
var wg sync.WaitGroup
299-
wg.Add(len(*namespaces))
300289
for _, ns := range *namespaces {
301-
namespace := ns.name
302-
go func() {
303-
ch := make(chan groupOfObjects)
304-
go gr.groupAppObjects(ctx, namespace, ch)
305-
306-
for groupOfObjects := range ch {
307-
gr.logger.Info("reconcileNamespaceResources",
308-
"Reconciling group of", len(groupOfObjects.objects),
309-
"objects with labels", groupOfObjects.label,
310-
"in the namespace", namespace)
311-
err := gr.reconcileGroupOfObjects(ctx, groupOfObjects.objects, namespace)
312-
if err != nil {
313-
gr.logger.Error(err, "error reconciling group of ",
314-
len(groupOfObjects.objects), "objects ", "in the namespace", namespace)
315-
}
290+
relatedObjects, err := gr.groupAppObjects(ctx, ns.name, gvks)
291+
if err != nil {
292+
return err
293+
}
294+
for label, objects := range relatedObjects {
295+
gr.logger.Info("reconcileNamespaceResources",
296+
"Reconciling group of", len(objects), "objects with labels", label,
297+
"in the namespace", ns.name)
298+
err := gr.reconcileGroupOfObjects(ctx, objects, ns.name)
299+
if err != nil {
300+
return fmt.Errorf(
301+
"reconciling related objects with labels '%s': %w", label, err,
302+
)
316303
}
317-
wg.Done()
318-
}()
304+
}
319305
}
320-
wg.Wait()
306+
321307
return nil
322308
}
323309

@@ -411,7 +397,7 @@ func (gr *GenericReconciler) handleResourceDeletions() {
411397
}
412398

413399
// getNamespacedResourcesGVK filters APIResources and returns the ones within a namespace
414-
func getNamespacedResourcesGVK(resources []metav1.APIResource) []schema.GroupVersionKind {
400+
func (gr GenericReconciler) getNamespacedResourcesGVK(resources []metav1.APIResource) []schema.GroupVersionKind {
415401
namespacedResources := make([]schema.GroupVersionKind, 0)
416402
for _, resource := range resources {
417403
if resource.Namespaced {

pkg/controller/generic_reconciler_test.go

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1919
"k8s.io/apimachinery/pkg/runtime"
2020
"k8s.io/apimachinery/pkg/runtime/schema"
21-
"k8s.io/apimachinery/pkg/util/rand"
2221
kubefake "k8s.io/client-go/kubernetes/fake"
2322
"sigs.k8s.io/controller-runtime/pkg/client"
2423
clifake "sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -611,20 +610,15 @@ func TestGroupAppObjects(t *testing.T) {
611610
for _, tt := range tests {
612611
t.Run(tt.name, func(t *testing.T) {
613612
// create testing reconciler
614-
gr, err := createTestReconciler(nil, tt.gvks, tt.objs)
613+
gr, err := createTestReconciler(nil, tt.objs)
614+
assert.NoError(t, err)
615+
groupMap, err := gr.groupAppObjects(context.Background(), tt.namespace, tt.gvks)
615616
assert.NoError(t, err)
616-
ch := make(chan groupOfObjects)
617-
go gr.groupAppObjects(context.Background(), tt.namespace, ch)
618617

619-
resultMap := make(map[string][]string)
620-
for groupOfObjects := range ch {
621-
actualNames := unstructuredToNames(groupOfObjects.objects)
622-
resultMap[groupOfObjects.label] = actualNames
623-
}
624618
for expectedLabel, expectedNames := range tt.expectedNames {
625-
actualNames, ok := resultMap[expectedLabel]
619+
objects, ok := groupMap[expectedLabel]
626620
assert.True(t, ok, "can't find label %s", expectedLabel)
627-
//actualNames := unstructuredToNames(objects)
621+
actualNames := unstructuredToNames(objects)
628622
for _, expectedName := range expectedNames {
629623
assert.Contains(t, actualNames, expectedName,
630624
"can't find %s for label value %s", expectedName, expectedLabel)
@@ -692,7 +686,7 @@ func TestUnstructuredToTyped(t *testing.T) {
692686
err := v1.AddToScheme(tt.scheme)
693687
assert.NoError(t, err)
694688

695-
gr, err := createTestReconciler(tt.scheme, nil, nil)
689+
gr, err := createTestReconciler(tt.scheme, nil)
696690
assert.NoError(t, err)
697691
o, err := gr.unstructuredToTyped(tt.u)
698692
if tt.expectedError == nil {
@@ -742,8 +736,11 @@ func TestGetNamespacedResourcesGVK(t *testing.T) {
742736

743737
for _, ut := range unitTests {
744738
t.Run(ut.name, func(t *testing.T) {
739+
// Given
740+
gr := GenericReconciler{}
741+
745742
// When
746-
test := getNamespacedResourcesGVK(ut.arg)
743+
test := gr.getNamespacedResourcesGVK(ut.arg)
747744

748745
// Assert
749746
assert.Equal(t, ut.result, test)
@@ -864,12 +861,12 @@ func TestProcessNamespacedResources(t *testing.T) {
864861
err = policyv1.AddToScheme(sch)
865862
assert.NoError(t, err)
866863

867-
testReconciler, err := createTestReconciler(sch, tt.gvks, tt.objects)
864+
testReconciler, err := createTestReconciler(sch, tt.objects)
868865
assert.NoError(t, err)
869866

870867
// set some namespaces to be watched
871868
testReconciler.watchNamespaces.setCache(tt.namespaces)
872-
err = testReconciler.processNamespacedResources(context.Background(), tt.namespaces)
869+
err = testReconciler.processNamespacedResources(context.Background(), tt.gvks, tt.namespaces)
873870
assert.NoError(t, err)
874871
for _, o := range tt.objects {
875872
vr, ok := testReconciler.objectValidationCache.retrieve(o)
@@ -984,9 +981,9 @@ func TestHandleResourceDeletions(t *testing.T) {
984981
for _, testCase := range tests {
985982
tt := testCase
986983
t.Run(tt.name, func(t *testing.T) {
987-
testReconciler, err := createTestReconciler(nil, nil, nil)
984+
testReconciler, err := createTestReconciler(nil, nil)
988985
assert.NoError(t, err)
989-
testReconciler.watchNamespaces.setCache(&tt.testNamespaces) // nolint:gosec
986+
testReconciler.watchNamespaces.setCache(&tt.testNamespaces)
990987

991988
// store the test objects in the caches
992989
for _, co := range tt.testCurrentObjects {
@@ -1019,13 +1016,12 @@ func TestHandleResourceDeletions(t *testing.T) {
10191016

10201017
func TestListLimit(t *testing.T) {
10211018
os.Setenv(EnvResorucesPerListQuery, "2")
1022-
testReconciler, err := createTestReconciler(runtime.NewScheme(), nil, nil)
1019+
testReconciler, err := createTestReconciler(runtime.NewScheme(), nil)
10231020
assert.NoError(t, err)
10241021
assert.Equal(t, int64(2), testReconciler.listLimit)
10251022
}
10261023

1027-
func createTestReconciler(scheme *runtime.Scheme, gvks []schema.GroupVersionKind,
1028-
objects []client.Object) (*GenericReconciler, error) {
1024+
func createTestReconciler(scheme *runtime.Scheme, objects []client.Object) (*GenericReconciler, error) {
10291025
cliBuilder := clifake.NewClientBuilder()
10301026
if scheme != nil {
10311027
cliBuilder.WithScheme(scheme)
@@ -1040,14 +1036,12 @@ func createTestReconciler(scheme *runtime.Scheme, gvks []schema.GroupVersionKind
10401036
if err != nil {
10411037
return nil, err
10421038
}
1043-
testGenericReconciler, err := NewGenericReconciler(client, cli.Discovery(), &configmap.Watcher{}, ve)
1044-
if err != nil {
1045-
return nil, err
1046-
}
1047-
testGenericReconciler.resourceGVKs = gvks
1048-
return testGenericReconciler, nil
1039+
return NewGenericReconciler(client, cli.Discovery(), &configmap.Watcher{}, ve)
10491040
}
10501041

1042+
/* Benchmark used reverted changes in d80ec1f. Preserving it just in case the changes come back in near future.
1043+
// TODO :: Check the usefulness of preserving this later on the road
1044+
10511045
// BenchmarkGroupAppObjects measures the performance of grouping Kubernetes objects based on their labels.
10521046
// The benchmark focuses on a scenario where a Reconciler needs to group different objects based on the 'app' label.
10531047
// # Benchmark configuration:
@@ -1148,3 +1142,4 @@ func generateDeployments(count int, namespace string) []client.Object {
11481142
11491143
return objects
11501144
}
1145+
*/

pkg/controller/validationscache.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
package controller
22

33
import (
4-
"sync"
5-
64
"github.com/app-sre/deployment-validation-operator/pkg/validations"
75
"k8s.io/apimachinery/pkg/types"
86
"sigs.k8s.io/controller-runtime/pkg/client"
97
)
108

11-
var lock sync.Mutex
12-
139
type validationKey struct {
1410
group, version, kind, namespace, name string
1511
uid types.UID
@@ -72,8 +68,6 @@ func (vc *validationCache) has(key validationKey) bool {
7268
// constraint: cached outcomes will be updated in-place for a given object and
7369
// consecutive updates will not preserve previous state.
7470
func (vc *validationCache) store(obj client.Object, outcome validations.ValidationOutcome) {
75-
lock.Lock()
76-
defer lock.Unlock()
7771
key := newValidationKey(obj)
7872
(*vc)[key] = newValidationResource(
7973
newResourceversionVal(obj.GetResourceVersion()),
@@ -98,8 +92,6 @@ func (vc *validationCache) remove(obj client.Object) {
9892

9993
// removeKey deletes a key, and its value, from the instance
10094
func (vc *validationCache) removeKey(key validationKey) {
101-
lock.Lock()
102-
defer lock.Unlock()
10395
delete(*vc, key)
10496
}
10597

0 commit comments

Comments
 (0)