Skip to content

Commit e738ab0

Browse files
authored
Check resource generation when processing updates of some resources to skip config regeneration (#1422)
Problem: When processing updates to cluster resources, for some resources, we check their generation, so that we don't trigger state change (graph rebuild) if the generation didn't change. This is a performance optimization so that we don't rebuild the graph and as a result do not regenerate NGINX config and reload it. This is not a K8s-native solution, and may introduce complexity for the codebase. Solution: Use `GenerationChangedPredicate` in controller-runtime to filter out the resource events at the controller level.
1 parent 1eb1956 commit e738ab0

File tree

5 files changed

+25
-148
lines changed

5 files changed

+25
-148
lines changed

internal/mode/static/manager.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,22 +281,31 @@ func registerControllers(
281281
{
282282
objectType: &gatewayv1.GatewayClass{},
283283
options: []controller.Option{
284-
controller.WithK8sPredicate(predicate.GatewayClassPredicate{ControllerName: cfg.GatewayCtlrName}),
284+
controller.WithK8sPredicate(k8spredicate.And(
285+
k8spredicate.GenerationChangedPredicate{},
286+
predicate.GatewayClassPredicate{ControllerName: cfg.GatewayCtlrName})),
285287
},
286288
},
287289
{
288290
objectType: &gatewayv1.Gateway{},
289291
options: func() []controller.Option {
292+
options := []controller.Option{
293+
controller.WithK8sPredicate(k8spredicate.GenerationChangedPredicate{}),
294+
}
290295
if cfg.GatewayNsName != nil {
291-
return []controller.Option{
296+
options = append(
297+
options,
292298
controller.WithNamespacedNameFilter(filter.CreateSingleResourceFilter(*cfg.GatewayNsName)),
293-
}
299+
)
294300
}
295-
return nil
301+
return options
296302
}(),
297303
},
298304
{
299305
objectType: &gatewayv1.HTTPRoute{},
306+
options: []controller.Option{
307+
controller.WithK8sPredicate(k8spredicate.GenerationChangedPredicate{}),
308+
},
300309
},
301310
{
302311
objectType: &apiv1.Service{},
@@ -334,6 +343,9 @@ func registerControllers(
334343
},
335344
{
336345
objectType: &gatewayv1beta1.ReferenceGrant{},
346+
options: []controller.Option{
347+
controller.WithK8sPredicate(k8spredicate.GenerationChangedPredicate{}),
348+
},
337349
},
338350
{
339351
objectType: &crdWithGVK,

internal/mode/static/state/change_processor.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,22 +126,22 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl {
126126
{
127127
gvk: extractGVK(&v1.GatewayClass{}),
128128
store: newObjectStoreMapAdapter(clusterStore.GatewayClasses),
129-
predicate: generationChangedPredicate{},
129+
predicate: alwaysProcess{},
130130
},
131131
{
132132
gvk: extractGVK(&v1.Gateway{}),
133133
store: newObjectStoreMapAdapter(clusterStore.Gateways),
134-
predicate: generationChangedPredicate{},
134+
predicate: alwaysProcess{},
135135
},
136136
{
137137
gvk: extractGVK(&v1.HTTPRoute{}),
138138
store: newObjectStoreMapAdapter(clusterStore.HTTPRoutes),
139-
predicate: generationChangedPredicate{},
139+
predicate: alwaysProcess{},
140140
},
141141
{
142142
gvk: extractGVK(&v1beta1.ReferenceGrant{}),
143143
store: newObjectStoreMapAdapter(clusterStore.ReferenceGrants),
144-
predicate: generationChangedPredicate{},
144+
predicate: alwaysProcess{},
145145
},
146146
{
147147
gvk: extractGVK(&apiv1.Namespace{}),

internal/mode/static/state/change_processor_test.go

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -703,17 +703,6 @@ var _ = Describe("ChangeProcessor", func() {
703703
Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty())
704704
})
705705
})
706-
When("the first HTTPRoute without a generation changed is processed", func() {
707-
It("returns nil graph", func() {
708-
hr1UpdatedSameGen := hr1.DeepCopy()
709-
// hr1UpdatedSameGen.Generation has not been changed
710-
processor.CaptureUpsertChange(hr1UpdatedSameGen)
711-
712-
changed, graphCfg := processor.Process()
713-
Expect(changed).To(BeFalse())
714-
Expect(graphCfg).To(BeNil())
715-
})
716-
})
717706
When("the first HTTPRoute update with a generation changed is processed", func() {
718707
It("returns populated graph", func() {
719708
processor.CaptureUpsertChange(hr1Updated)
@@ -733,17 +722,6 @@ var _ = Describe("ChangeProcessor", func() {
733722
},
734723
)
735724
})
736-
When("the first Gateway update without generation changed is processed", func() {
737-
It("returns nil graph", func() {
738-
gwUpdatedSameGen := gw1.DeepCopy()
739-
// gwUpdatedSameGen.Generation has not been changed
740-
processor.CaptureUpsertChange(gwUpdatedSameGen)
741-
742-
changed, graphCfg := processor.Process()
743-
Expect(changed).To(BeFalse())
744-
Expect(graphCfg).To(BeNil())
745-
})
746-
})
747725
When("the first Gateway update with a generation changed is processed", func() {
748726
It("returns populated graph", func() {
749727
processor.CaptureUpsertChange(gw1Updated)
@@ -758,17 +736,6 @@ var _ = Describe("ChangeProcessor", func() {
758736
Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty())
759737
})
760738
})
761-
When("the GatewayClass update without generation change is processed", func() {
762-
It("returns nil graph", func() {
763-
gcUpdatedSameGen := gc.DeepCopy()
764-
// gcUpdatedSameGen.Generation has not been changed
765-
processor.CaptureUpsertChange(gcUpdatedSameGen)
766-
767-
changed, graphCfg := processor.Process()
768-
Expect(changed).To(BeFalse())
769-
Expect(graphCfg).To(BeNil())
770-
})
771-
})
772739
When("the GatewayClass update with generation change is processed", func() {
773740
It("returns populated graph", func() {
774741
processor.CaptureUpsertChange(gcUpdated)
@@ -1590,15 +1557,6 @@ var _ = Describe("ChangeProcessor", func() {
15901557
changed, _ := processor.Process()
15911558
Expect(changed).To(BeTrue())
15921559
})
1593-
It("should report not changed after multiple Upserts of the resource with same generation", func() {
1594-
processor.CaptureUpsertChange(gc)
1595-
processor.CaptureUpsertChange(gw1)
1596-
processor.CaptureUpsertChange(hr1)
1597-
processor.CaptureUpsertChange(rg1)
1598-
1599-
changed, _ := processor.Process()
1600-
Expect(changed).To(BeFalse())
1601-
})
16021560
When("a upsert of updated resources is followed by an upsert of the same generation", func() {
16031561
It("should report changed", func() {
16041562
// these are changing changes
@@ -1737,14 +1695,7 @@ var _ = Describe("ChangeProcessor", func() {
17371695
Expect(changed).To(BeTrue())
17381696
})
17391697

1740-
It("should report not changed after multiple Upserts of unrelated and unchanged resources", func() {
1741-
// unchanged Gateway API resources
1742-
fakeRelationshipCapturer.ExistsReturns(false)
1743-
processor.CaptureUpsertChange(gc)
1744-
processor.CaptureUpsertChange(gw1)
1745-
processor.CaptureUpsertChange(hr1)
1746-
processor.CaptureUpsertChange(rg1)
1747-
1698+
It("should report not changed after multiple Upserts of unrelated resources", func() {
17481699
// unrelated Kubernetes API resources
17491700
fakeRelationshipCapturer.ExistsReturns(false)
17501701
processor.CaptureUpsertChange(svc)

internal/mode/static/state/changed_predicate.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,12 @@ func (f funcPredicate) delete(object client.Object) bool {
2424
return f.stateChanged(object)
2525
}
2626

27-
// generationChangedPredicate implements stateChangedPredicate based on the generation of the object.
28-
// This predicate will return true on upsert if the object's generation has changed.
29-
// It always returns true on delete.
30-
type generationChangedPredicate struct{}
31-
32-
func (generationChangedPredicate) delete(_ client.Object) bool { return true }
27+
// FIXME(kevin85421): We should remove this predicate and update changeTrackingUpdater once #1432 is merged.
28+
type alwaysProcess struct{}
3329

34-
func (generationChangedPredicate) upsert(oldObject, newObject client.Object) bool {
35-
if oldObject == nil {
36-
return true
37-
}
30+
func (alwaysProcess) delete(_ client.Object) bool { return true }
3831

39-
if newObject == nil {
40-
panic("Cannot determine if generation has changed on upsert because new object is nil")
41-
}
42-
43-
return newObject.GetGeneration() != oldObject.GetGeneration()
44-
}
32+
func (alwaysProcess) upsert(_, _ client.Object) bool { return true }
4533

4634
// annotationChangedPredicate implements stateChangedPredicate based on the value of the annotation provided.
4735
// This predicate will return true on upsert if the annotation's value has changed.

internal/mode/static/state/changed_predicate_test.go

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -20,80 +20,6 @@ func TestFuncPredicate(t *testing.T) {
2020
g.Expect(p.upsert(nil, nil)).To(BeTrue())
2121
}
2222

23-
func TestGenerationChangedPredicate_Delete(t *testing.T) {
24-
p := generationChangedPredicate{}
25-
26-
g := NewWithT(t)
27-
g.Expect(p.delete(nil)).To(BeTrue())
28-
}
29-
30-
func TestGenerationChangedPredicate_Update(t *testing.T) {
31-
tests := []struct {
32-
oldObj client.Object
33-
newObj client.Object
34-
name string
35-
stateChanged bool
36-
expPanic bool
37-
}{
38-
{
39-
name: "generation has changed",
40-
oldObj: &v1.Pod{
41-
ObjectMeta: metav1.ObjectMeta{
42-
Generation: 1,
43-
},
44-
},
45-
newObj: &v1.Pod{
46-
ObjectMeta: metav1.ObjectMeta{
47-
Generation: 2,
48-
},
49-
},
50-
stateChanged: true,
51-
},
52-
{
53-
name: "generation has not changed",
54-
oldObj: &v1.Pod{
55-
ObjectMeta: metav1.ObjectMeta{
56-
Generation: 1,
57-
},
58-
},
59-
newObj: &v1.Pod{
60-
ObjectMeta: metav1.ObjectMeta{
61-
Generation: 1,
62-
},
63-
},
64-
stateChanged: false,
65-
},
66-
{
67-
name: "old object is nil",
68-
oldObj: nil,
69-
newObj: &v1.Pod{},
70-
stateChanged: true,
71-
},
72-
{
73-
name: "new object is nil",
74-
oldObj: &v1.Pod{},
75-
newObj: nil,
76-
expPanic: true,
77-
},
78-
}
79-
80-
p := generationChangedPredicate{}
81-
82-
for _, test := range tests {
83-
t.Run(test.name, func(t *testing.T) {
84-
g := NewWithT(t)
85-
if test.expPanic {
86-
upsert := func() {
87-
p.upsert(test.oldObj, test.newObj)
88-
}
89-
g.Expect(upsert).Should(Panic())
90-
} else {
91-
g.Expect(p.upsert(test.oldObj, test.newObj)).To(Equal(test.stateChanged))
92-
}
93-
})
94-
}
95-
}
96-
9723
func TestAnnotationChangedPredicate_Delete(t *testing.T) {
9824
p := annotationChangedPredicate{}
9925

0 commit comments

Comments
 (0)