Skip to content

Commit 3213179

Browse files
committed
reconcile: Set observed gen when conditions exist
The observed generation must be set only when actual observation is made. When an actual observation is made, some conditions are set on the object. Introduce a helper function addPatchOptionWithStatusObservedGeneration() to set the patcher option WithStatusObservedGeneration only when there's any condition in the status. Updates the existing tests that depended on this behavior. This fixes the issue where the observed generation is set by the patcher when a reconciler does an early return for setting the finalizers only. With this, the observed generation will be updated only when some observations are made on the object based on the usual rules of success result, no error, ignore error and stalled condition. Signed-off-by: Sunny <darkowlzz@protonmail.com>
1 parent 7aa0814 commit 3213179

File tree

2 files changed

+85
-6
lines changed

2 files changed

+85
-6
lines changed

internal/reconcile/reconcile.go

+18-4
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ func ComputeReconcileResult(obj conditions.Setter, res Result, recErr error, rb
128128
switch t := recErr.(type) {
129129
case *serror.Stalling:
130130
if res == ResultEmpty {
131+
conditions.MarkStalled(obj, t.Reason, t.Error())
131132
// The current generation has been reconciled successfully and it
132133
// has resulted in a stalled state. Return no error to stop further
133134
// requeuing.
134-
pOpts = append(pOpts, patch.WithStatusObservedGeneration{})
135-
conditions.MarkStalled(obj, t.Reason, t.Error())
135+
pOpts = addPatchOptionWithStatusObservedGeneration(obj, pOpts)
136136
return pOpts, result, nil
137137
}
138138
// NOTE: Non-empty result with stalling error indicates that the
@@ -150,7 +150,7 @@ func ComputeReconcileResult(obj conditions.Setter, res Result, recErr error, rb
150150
if t.Ignore {
151151
// The current generation has been reconciled successfully with
152152
// no-op result. Update status observed generation.
153-
pOpts = append(pOpts, patch.WithStatusObservedGeneration{})
153+
pOpts = addPatchOptionWithStatusObservedGeneration(obj, pOpts)
154154
conditions.Delete(obj, meta.ReconcilingCondition)
155155
return pOpts, result, nil
156156
}
@@ -159,7 +159,7 @@ func ComputeReconcileResult(obj conditions.Setter, res Result, recErr error, rb
159159
// state. If a requeue is requested, the current generation has not been
160160
// reconciled successfully.
161161
if res != ResultRequeue {
162-
pOpts = append(pOpts, patch.WithStatusObservedGeneration{})
162+
pOpts = addPatchOptionWithStatusObservedGeneration(obj, pOpts)
163163
}
164164
conditions.Delete(obj, meta.StalledCondition)
165165
default:
@@ -207,3 +207,17 @@ func FailureRecovery(oldObj, newObj conditions.Getter, failConditions []string)
207207
}
208208
return failuresBefore > 0
209209
}
210+
211+
// addPatchOptionWithStatusObservedGeneration adds patch option
212+
// WithStatusObservedGeneration to the provided patch option slice only if there
213+
// is any condition present on the object, and returns it. This is necessary to
214+
// prevent setting status observed generation without any effectual observation.
215+
// An object must have some condition in the status if it has been observed.
216+
// TODO: Move this to fluxcd/pkg/runtime/patch package after it has proven its
217+
// need.
218+
func addPatchOptionWithStatusObservedGeneration(obj conditions.Setter, opts []patch.Option) []patch.Option {
219+
if len(obj.GetConditions()) > 0 {
220+
opts = append(opts, patch.WithStatusObservedGeneration{})
221+
}
222+
return opts
223+
}

internal/reconcile/reconcile_test.go

+67-2
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,17 @@ func TestComputeReconcileResult(t *testing.T) {
7171
afterFunc func(t *WithT, obj conditions.Setter, patchOpts *patch.HelperOptions)
7272
}{
7373
{
74-
name: "successful result",
75-
result: ResultSuccess,
74+
name: "successful result",
75+
result: ResultSuccess,
76+
beforeFunc: func(obj conditions.Setter) {
77+
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "foo")
78+
},
7679
recErr: nil,
7780
wantResult: ctrl.Result{RequeueAfter: testSuccessInterval},
7881
wantErr: false,
82+
assertConditions: []metav1.Condition{
83+
*conditions.TrueCondition(meta.ReadyCondition, meta.SucceededReason, "foo"),
84+
},
7985
afterFunc: func(t *WithT, obj conditions.Setter, patchOpts *patch.HelperOptions) {
8086
t.Expect(patchOpts.IncludeStatusObservedGeneration).To(BeTrue())
8187
},
@@ -85,10 +91,14 @@ func TestComputeReconcileResult(t *testing.T) {
8591
result: ResultSuccess,
8692
beforeFunc: func(obj conditions.Setter) {
8793
conditions.MarkReconciling(obj, "NewRevision", "new revision")
94+
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "foo")
8895
},
8996
recErr: nil,
9097
wantResult: ctrl.Result{RequeueAfter: testSuccessInterval},
9198
wantErr: false,
99+
assertConditions: []metav1.Condition{
100+
*conditions.TrueCondition(meta.ReadyCondition, meta.SucceededReason, "foo"),
101+
},
92102
afterFunc: func(t *WithT, obj conditions.Setter, patchOpts *patch.HelperOptions) {
93103
t.Expect(patchOpts.IncludeStatusObservedGeneration).To(BeTrue())
94104
t.Expect(conditions.IsUnknown(obj, meta.ReconcilingCondition)).To(BeTrue())
@@ -367,3 +377,58 @@ func TestFailureRecovery(t *testing.T) {
367377
})
368378
}
369379
}
380+
381+
func TestAddOptionWithStatusObservedGeneration(t *testing.T) {
382+
tests := []struct {
383+
name string
384+
beforeFunc func(obj conditions.Setter)
385+
patchOpts []patch.Option
386+
want bool
387+
}{
388+
{
389+
name: "no conditions",
390+
want: false,
391+
},
392+
{
393+
name: "some condition",
394+
beforeFunc: func(obj conditions.Setter) {
395+
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "foo")
396+
},
397+
want: true,
398+
},
399+
{
400+
name: "existing option with conditions",
401+
beforeFunc: func(obj conditions.Setter) {
402+
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "foo")
403+
},
404+
patchOpts: []patch.Option{patch.WithForceOverwriteConditions{}, patch.WithStatusObservedGeneration{}},
405+
want: true,
406+
},
407+
{
408+
name: "existing option, no conditions, can't remove",
409+
patchOpts: []patch.Option{patch.WithForceOverwriteConditions{}, patch.WithStatusObservedGeneration{}},
410+
want: true,
411+
},
412+
}
413+
414+
for _, tt := range tests {
415+
t.Run(tt.name, func(t *testing.T) {
416+
g := NewWithT(t)
417+
418+
obj := &sourcev1.GitRepository{}
419+
420+
if tt.beforeFunc != nil {
421+
tt.beforeFunc(obj)
422+
}
423+
424+
tt.patchOpts = addPatchOptionWithStatusObservedGeneration(obj, tt.patchOpts)
425+
426+
// Apply the options and evaluate the result.
427+
options := &patch.HelperOptions{}
428+
for _, opt := range tt.patchOpts {
429+
opt.ApplyToHelper(options)
430+
}
431+
g.Expect(options.IncludeStatusObservedGeneration).To(Equal(tt.want))
432+
})
433+
}
434+
}

0 commit comments

Comments
 (0)