Skip to content

Commit 43227ab

Browse files
Fixed predicates to allow events for Etcd resource that have never been (#900) (#904)
reconciled before (#898) Co-authored-by: Madhav Bhargava <[email protected]>
1 parent b31c378 commit 43227ab

File tree

2 files changed

+89
-12
lines changed

2 files changed

+89
-12
lines changed

internal/controller/etcd/register.go

+20-2
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ func (r *Reconciler) buildPredicate() predicate.Predicate {
5959
// not been removed (since the last reconcile is not yet successfully completed).
6060
onReconcileAnnotationSetPredicate := predicate.And(
6161
r.hasReconcileAnnotation(),
62-
predicate.Or(lastReconcileHasFinished(), specUpdated()),
62+
predicate.Or(lastReconcileHasFinished(), specUpdated(), neverReconciled()),
6363
)
6464

6565
// If auto-reconcile has been enabled then it should allow reconciliation only on spec change.
6666
autoReconcileOnSpecChangePredicate := predicate.And(
6767
r.autoReconcileEnabled(),
68-
specUpdated(),
68+
predicate.Or(specUpdated(), neverReconciled()),
6969
)
7070

7171
return predicate.Or(
@@ -132,6 +132,24 @@ func lastReconcileHasFinished() predicate.Predicate {
132132
}
133133
}
134134

135+
// neverReconciled handles a specific case which is outlined in https://github.com/gardener/etcd-druid/issues/898
136+
// It is possible that the initial Create event was not processed. In such cases, the status will not be updated.
137+
// If there is an update event for such a resource then the predicates should allow the event to be processed.
138+
func neverReconciled() predicate.Predicate {
139+
return predicate.Funcs{
140+
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
141+
newEtcd, ok := updateEvent.ObjectNew.(*druidv1alpha1.Etcd)
142+
if !ok {
143+
return false
144+
}
145+
return newEtcd.Status.LastOperation == nil && newEtcd.Status.ObservedGeneration == nil
146+
},
147+
CreateFunc: func(_ event.CreateEvent) bool { return false },
148+
DeleteFunc: func(_ event.DeleteEvent) bool { return false },
149+
GenericFunc: func(_ event.GenericEvent) bool { return false },
150+
}
151+
}
152+
135153
func hasLastReconcileFinished(updateEvent event.UpdateEvent) bool {
136154
newEtcd, ok := updateEvent.ObjectNew.(*druidv1alpha1.Etcd)
137155
// return false if either the object is not an etcd resource or it has not been reconciled yet.

internal/controller/etcd/register_test.go

+69-10
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ import (
2020
)
2121

2222
type predicateTestCase struct {
23-
name string
24-
etcdSpecChanged bool
25-
etcdStatusChanged bool
26-
lastOperationState *druidv1alpha1.LastOperationState
23+
name string
24+
etcdSpecChanged bool
25+
etcdStatusChanged bool
26+
previouslyReconciled bool
27+
lastOperationState *druidv1alpha1.LastOperationState
2728
// expected behavior for different event types
2829
shouldAllowCreateEvent bool
2930
shouldAllowDeleteEvent bool
@@ -45,6 +46,7 @@ func TestBuildPredicateWithOnlyAutoReconcileEnabled(t *testing.T) {
4546
{
4647
name: "only spec has changed and previous reconciliation has completed",
4748
etcdSpecChanged: true,
49+
previouslyReconciled: true,
4850
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateSucceeded),
4951
shouldAllowCreateEvent: true,
5052
shouldAllowDeleteEvent: true,
@@ -54,6 +56,7 @@ func TestBuildPredicateWithOnlyAutoReconcileEnabled(t *testing.T) {
5456
{
5557
name: "only spec has changed and previous reconciliation has errored",
5658
etcdSpecChanged: true,
59+
previouslyReconciled: true,
5760
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateError),
5861
shouldAllowCreateEvent: true,
5962
shouldAllowDeleteEvent: true,
@@ -63,6 +66,7 @@ func TestBuildPredicateWithOnlyAutoReconcileEnabled(t *testing.T) {
6366
{
6467
name: "only status has changed",
6568
etcdStatusChanged: true,
69+
previouslyReconciled: true,
6670
shouldAllowCreateEvent: true,
6771
shouldAllowDeleteEvent: true,
6872
shouldAllowGenericEvent: false,
@@ -72,6 +76,7 @@ func TestBuildPredicateWithOnlyAutoReconcileEnabled(t *testing.T) {
7276
name: "both spec and status have changed and previous reconciliation is in progress",
7377
etcdSpecChanged: true,
7478
etcdStatusChanged: true,
79+
previouslyReconciled: true,
7580
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateProcessing),
7681
shouldAllowCreateEvent: true,
7782
shouldAllowDeleteEvent: true,
@@ -80,11 +85,21 @@ func TestBuildPredicateWithOnlyAutoReconcileEnabled(t *testing.T) {
8085
},
8186
{
8287
name: "neither spec nor status has changed",
88+
previouslyReconciled: true,
8389
shouldAllowCreateEvent: true,
8490
shouldAllowDeleteEvent: true,
8591
shouldAllowGenericEvent: false,
8692
shouldAllowUpdateEvent: false,
8793
},
94+
{
95+
// This case is described in https://github.com/gardener/etcd-druid/issues/898
96+
name: "for an existing Etcd resource, neither spec nor status has changed and it has never been reconciled before",
97+
previouslyReconciled: false,
98+
shouldAllowCreateEvent: true,
99+
shouldAllowDeleteEvent: true,
100+
shouldAllowGenericEvent: false,
101+
shouldAllowUpdateEvent: true,
102+
},
88103
}
89104
g := NewWithT(t)
90105
etcd := createEtcd()
@@ -93,7 +108,7 @@ func TestBuildPredicateWithOnlyAutoReconcileEnabled(t *testing.T) {
93108
for _, tc := range testCases {
94109
t.Run(tc.name, func(t *testing.T) {
95110
t.Parallel()
96-
updatedEtcd := updateEtcd(etcd, tc.etcdSpecChanged, tc.etcdStatusChanged, tc.lastOperationState, false)
111+
updatedEtcd := updateEtcd(etcd, tc.etcdSpecChanged, tc.etcdStatusChanged, tc.previouslyReconciled, tc.lastOperationState, false)
97112
g.Expect(predicate.Create(event.CreateEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowCreateEvent))
98113
g.Expect(predicate.Delete(event.DeleteEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowDeleteEvent))
99114
g.Expect(predicate.Generic(event.GenericEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowGenericEvent))
@@ -107,6 +122,7 @@ func TestBuildPredicateWithNoAutoReconcileAndNoReconcileAnnot(t *testing.T) {
107122
{
108123
name: "only spec has changed",
109124
etcdSpecChanged: true,
125+
previouslyReconciled: true,
110126
shouldAllowCreateEvent: true,
111127
shouldAllowDeleteEvent: true,
112128
shouldAllowGenericEvent: false,
@@ -115,6 +131,7 @@ func TestBuildPredicateWithNoAutoReconcileAndNoReconcileAnnot(t *testing.T) {
115131
{
116132
name: "only status has changed",
117133
etcdStatusChanged: true,
134+
previouslyReconciled: true,
118135
shouldAllowCreateEvent: true,
119136
shouldAllowDeleteEvent: true,
120137
shouldAllowGenericEvent: false,
@@ -123,6 +140,7 @@ func TestBuildPredicateWithNoAutoReconcileAndNoReconcileAnnot(t *testing.T) {
123140
{
124141
name: "both spec and status have changed",
125142
etcdSpecChanged: true,
143+
previouslyReconciled: true,
126144
etcdStatusChanged: true,
127145
shouldAllowCreateEvent: true,
128146
shouldAllowDeleteEvent: true,
@@ -131,6 +149,16 @@ func TestBuildPredicateWithNoAutoReconcileAndNoReconcileAnnot(t *testing.T) {
131149
},
132150
{
133151
name: "neither spec nor status has changed",
152+
previouslyReconciled: true,
153+
shouldAllowCreateEvent: true,
154+
shouldAllowDeleteEvent: true,
155+
shouldAllowGenericEvent: false,
156+
shouldAllowUpdateEvent: false,
157+
},
158+
{
159+
// This case is described in https://github.com/gardener/etcd-druid/issues/898
160+
name: "for an existing Etcd resource, neither spec nor status has changed and it has never been reconciled before",
161+
previouslyReconciled: false,
134162
shouldAllowCreateEvent: true,
135163
shouldAllowDeleteEvent: true,
136164
shouldAllowGenericEvent: false,
@@ -144,7 +172,7 @@ func TestBuildPredicateWithNoAutoReconcileAndNoReconcileAnnot(t *testing.T) {
144172
for _, tc := range testCases {
145173
t.Run(tc.name, func(t *testing.T) {
146174
t.Parallel()
147-
updatedEtcd := updateEtcd(etcd, tc.etcdSpecChanged, tc.etcdStatusChanged, tc.lastOperationState, false)
175+
updatedEtcd := updateEtcd(etcd, tc.etcdSpecChanged, tc.etcdStatusChanged, tc.previouslyReconciled, tc.lastOperationState, false)
148176
g.Expect(predicate.Create(event.CreateEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowCreateEvent))
149177
g.Expect(predicate.Delete(event.DeleteEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowDeleteEvent))
150178
g.Expect(predicate.Generic(event.GenericEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowGenericEvent))
@@ -168,6 +196,7 @@ func TestBuildPredicateWithNoAutoReconcileButReconcileAnnotPresent(t *testing.T)
168196
name: "only spec has changed and previous reconciliation is completed",
169197
etcdSpecChanged: true,
170198
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateSucceeded),
199+
previouslyReconciled: true,
171200
shouldAllowCreateEvent: true,
172201
shouldAllowDeleteEvent: true,
173202
shouldAllowGenericEvent: false,
@@ -176,6 +205,7 @@ func TestBuildPredicateWithNoAutoReconcileButReconcileAnnotPresent(t *testing.T)
176205
{
177206
name: "only status has changed and previous reconciliation is in progress",
178207
etcdStatusChanged: true,
208+
previouslyReconciled: true,
179209
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateProcessing),
180210
shouldAllowCreateEvent: true,
181211
shouldAllowDeleteEvent: true,
@@ -185,6 +215,7 @@ func TestBuildPredicateWithNoAutoReconcileButReconcileAnnotPresent(t *testing.T)
185215
{
186216
name: "only status has changed and previous reconciliation is completed",
187217
etcdStatusChanged: true,
218+
previouslyReconciled: true,
188219
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateSucceeded),
189220
shouldAllowCreateEvent: true,
190221
shouldAllowDeleteEvent: true,
@@ -195,6 +226,7 @@ func TestBuildPredicateWithNoAutoReconcileButReconcileAnnotPresent(t *testing.T)
195226
name: "both spec and status have changed",
196227
etcdSpecChanged: true,
197228
etcdStatusChanged: true,
229+
previouslyReconciled: true,
198230
shouldAllowCreateEvent: true,
199231
shouldAllowDeleteEvent: true,
200232
shouldAllowGenericEvent: false,
@@ -203,6 +235,7 @@ func TestBuildPredicateWithNoAutoReconcileButReconcileAnnotPresent(t *testing.T)
203235
{
204236
name: "neither spec nor status has changed and previous reconciliation is in error",
205237
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateError),
238+
previouslyReconciled: true,
206239
shouldAllowCreateEvent: true,
207240
shouldAllowDeleteEvent: true,
208241
shouldAllowGenericEvent: false,
@@ -211,6 +244,16 @@ func TestBuildPredicateWithNoAutoReconcileButReconcileAnnotPresent(t *testing.T)
211244
{
212245
name: "neither spec nor status has changed and previous reconciliation is completed",
213246
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateSucceeded),
247+
previouslyReconciled: true,
248+
shouldAllowCreateEvent: true,
249+
shouldAllowDeleteEvent: true,
250+
shouldAllowGenericEvent: false,
251+
shouldAllowUpdateEvent: true,
252+
},
253+
{
254+
// This case is described in https://github.com/gardener/etcd-druid/issues/898
255+
name: "for an existing Etcd resource, neither spec nor status has changed and it has never been reconciled before",
256+
previouslyReconciled: false,
214257
shouldAllowCreateEvent: true,
215258
shouldAllowDeleteEvent: true,
216259
shouldAllowGenericEvent: false,
@@ -224,7 +267,7 @@ func TestBuildPredicateWithNoAutoReconcileButReconcileAnnotPresent(t *testing.T)
224267
for _, tc := range testCases {
225268
t.Run(tc.name, func(t *testing.T) {
226269
t.Parallel()
227-
updatedEtcd := updateEtcd(etcd, tc.etcdSpecChanged, tc.etcdStatusChanged, tc.lastOperationState, true)
270+
updatedEtcd := updateEtcd(etcd, tc.etcdSpecChanged, tc.etcdStatusChanged, tc.previouslyReconciled, tc.lastOperationState, true)
228271
g.Expect(predicate.Create(event.CreateEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowCreateEvent))
229272
g.Expect(predicate.Delete(event.DeleteEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowDeleteEvent))
230273
g.Expect(predicate.Generic(event.GenericEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowGenericEvent))
@@ -256,6 +299,7 @@ func TestBuildPredicateWithAutoReconcileAndReconcileAnnotSet(t *testing.T) {
256299
name: "only status has changed and previous reconciliation is completed",
257300
etcdStatusChanged: true,
258301
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateSucceeded),
302+
previouslyReconciled: true,
259303
shouldAllowCreateEvent: true,
260304
shouldAllowDeleteEvent: true,
261305
shouldAllowGenericEvent: false,
@@ -265,6 +309,7 @@ func TestBuildPredicateWithAutoReconcileAndReconcileAnnotSet(t *testing.T) {
265309
name: "both spec and status have changed",
266310
etcdSpecChanged: true,
267311
etcdStatusChanged: true,
312+
previouslyReconciled: true,
268313
shouldAllowCreateEvent: true,
269314
shouldAllowDeleteEvent: true,
270315
shouldAllowGenericEvent: false,
@@ -273,6 +318,7 @@ func TestBuildPredicateWithAutoReconcileAndReconcileAnnotSet(t *testing.T) {
273318
{
274319
name: "neither spec nor status has changed and previous reconciliation is in error",
275320
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateError),
321+
previouslyReconciled: true,
276322
shouldAllowCreateEvent: true,
277323
shouldAllowDeleteEvent: true,
278324
shouldAllowGenericEvent: false,
@@ -289,6 +335,16 @@ func TestBuildPredicateWithAutoReconcileAndReconcileAnnotSet(t *testing.T) {
289335
{
290336
name: "neither spec nor status has changed and previous reconciliation is completed",
291337
lastOperationState: ptr.To(druidv1alpha1.LastOperationStateSucceeded),
338+
previouslyReconciled: true,
339+
shouldAllowCreateEvent: true,
340+
shouldAllowDeleteEvent: true,
341+
shouldAllowGenericEvent: false,
342+
shouldAllowUpdateEvent: true,
343+
},
344+
{
345+
// This case is described in https://github.com/gardener/etcd-druid/issues/898
346+
name: "for an existing Etcd resource, neither spec nor status has changed and it has never been reconciled before",
347+
previouslyReconciled: false,
292348
shouldAllowCreateEvent: true,
293349
shouldAllowDeleteEvent: true,
294350
shouldAllowGenericEvent: false,
@@ -302,7 +358,7 @@ func TestBuildPredicateWithAutoReconcileAndReconcileAnnotSet(t *testing.T) {
302358
for _, tc := range testCases {
303359
t.Run(tc.name, func(t *testing.T) {
304360
t.Parallel()
305-
updatedEtcd := updateEtcd(etcd, tc.etcdSpecChanged, tc.etcdStatusChanged, tc.lastOperationState, true)
361+
updatedEtcd := updateEtcd(etcd, tc.etcdSpecChanged, tc.etcdStatusChanged, tc.previouslyReconciled, tc.lastOperationState, true)
306362
g.Expect(predicate.Create(event.CreateEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowCreateEvent))
307363
g.Expect(predicate.Delete(event.DeleteEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowDeleteEvent))
308364
g.Expect(predicate.Generic(event.GenericEvent{Object: updatedEtcd})).To(Equal(tc.shouldAllowGenericEvent))
@@ -314,7 +370,7 @@ func TestBuildPredicateWithAutoReconcileAndReconcileAnnotSet(t *testing.T) {
314370
func createEtcd() *druidv1alpha1.Etcd {
315371
etcd := testutils.EtcdBuilderWithDefaults(testutils.TestEtcdName, testutils.TestNamespace).WithReplicas(3).Build()
316372
etcd.Status = druidv1alpha1.EtcdStatus{
317-
ObservedGeneration: ptr.To[int64](0),
373+
ObservedGeneration: nil,
318374
Etcd: &druidv1alpha1.CrossVersionObjectReference{
319375
Kind: "StatefulSet",
320376
Name: testutils.TestEtcdName,
@@ -328,7 +384,7 @@ func createEtcd() *druidv1alpha1.Etcd {
328384
return etcd
329385
}
330386

331-
func updateEtcd(originalEtcd *druidv1alpha1.Etcd, specChanged, statusChanged bool, lastOpState *druidv1alpha1.LastOperationState, reconcileAnnotPresent bool) *druidv1alpha1.Etcd {
387+
func updateEtcd(originalEtcd *druidv1alpha1.Etcd, specChanged, statusChanged bool, previouslyReconciled bool, lastOpState *druidv1alpha1.LastOperationState, reconcileAnnotPresent bool) *druidv1alpha1.Etcd {
332388
newEtcd := originalEtcd.DeepCopy()
333389
annotations := make(map[string]string)
334390
if reconcileAnnotPresent {
@@ -345,6 +401,9 @@ func updateEtcd(originalEtcd *druidv1alpha1.Etcd, specChanged, statusChanged boo
345401
newEtcd.Status.ReadyReplicas = 2
346402
newEtcd.Status.Ready = ptr.To(false)
347403
}
404+
if previouslyReconciled && originalEtcd.Status.ObservedGeneration == nil {
405+
newEtcd.Status.ObservedGeneration = ptr.To[int64](1)
406+
}
348407
if lastOpState != nil {
349408
newEtcd.Status.LastOperation = &druidv1alpha1.LastOperation{
350409
Type: druidv1alpha1.LastOperationTypeReconcile,

0 commit comments

Comments
 (0)