Skip to content

Commit 37a2e21

Browse files
authored
fix: ResourceGroup unnecessary timestamp updates (#1570)
The ResourceGroup controller has been updating status conditions every time it reconciles, even when nothing changed but the LastTransitionTime. This change fixes that problem by only updating the conditions if another condition field changed. This prevents the pre-existing semantic DeepEquals from always failing.
1 parent 616e9fa commit 37a2e21

File tree

3 files changed

+54
-23
lines changed

3 files changed

+54
-23
lines changed

pkg/resourcegroup/controllers/resourcegroup/condition.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,32 @@ func newStalledCondition(status v1alpha1.ConditionStatus, reason, message string
4242
}
4343
}
4444

45+
// updateCondition modifies and returns a list of conditions to update the
46+
// specified condition. This avoids updating the LastTransitionTime when there's
47+
// no other change.
48+
func updateCondition(conditions []v1alpha1.Condition, newCondition v1alpha1.Condition) []v1alpha1.Condition {
49+
for i, condition := range conditions {
50+
if condition.Type == newCondition.Type {
51+
if !isConditionEqual(condition, newCondition) {
52+
conditions[i] = newCondition
53+
}
54+
// assume no duplicate condition types
55+
return conditions
56+
}
57+
}
58+
// if not found, add it
59+
conditions = append(conditions, newCondition)
60+
return conditions
61+
}
62+
63+
// isConditionEqual returns true if a == b, ignoring the LastTransitionTime.
64+
func isConditionEqual(a, b v1alpha1.Condition) bool {
65+
return a.Type == b.Type &&
66+
a.Status == b.Status &&
67+
a.Reason == b.Reason &&
68+
a.Message == b.Message
69+
}
70+
4571
// adjustConditionOrder adjusts the order of the conditions to make sure that
4672
// the first condition in the slice is Reconciling;
4773
// the second condition in the slice is Stalled;

pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller.go

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,15 @@ func (r *reconciler) updateStatusKptGroup(ctx context.Context, resgroup *v1alpha
166166
}
167167

168168
func (r *reconciler) startReconcilingStatus(status v1alpha1.ResourceGroupStatus) v1alpha1.ResourceGroupStatus {
169-
newStatus := v1alpha1.ResourceGroupStatus{
170-
ObservedGeneration: status.ObservedGeneration,
171-
ResourceStatuses: status.ResourceStatuses,
172-
SubgroupStatuses: status.SubgroupStatuses,
173-
Conditions: []v1alpha1.Condition{
174-
newReconcilingCondition(v1alpha1.TrueConditionStatus, StartReconciling, startReconcilingMsg),
175-
newStalledCondition(v1alpha1.FalseConditionStatus, "", ""),
176-
},
177-
}
169+
// Preserve existing status and only update fields that need to change.
170+
// This avoids repeatedly updating the status just to update a timestamp.
171+
// It also prevents fighting with other controllers updating the status.
172+
newStatus := *status.DeepCopy()
173+
// Update conditions, if they've changed
174+
newStatus.Conditions = updateCondition(newStatus.Conditions,
175+
newReconcilingCondition(v1alpha1.TrueConditionStatus, StartReconciling, startReconcilingMsg))
176+
newStatus.Conditions = updateCondition(newStatus.Conditions,
177+
newStalledCondition(v1alpha1.FalseConditionStatus, "", ""))
178178
return newStatus
179179
}
180180

@@ -186,32 +186,33 @@ func (r *reconciler) endReconcilingStatus(
186186
status v1alpha1.ResourceGroupStatus,
187187
generation int64,
188188
) v1alpha1.ResourceGroupStatus {
189-
// reset newStatus to make sure the former setting of newStatus does not carry over
190-
newStatus := v1alpha1.ResourceGroupStatus{}
189+
// Preserve existing status and only update fields that need to change.
190+
// This avoids repeatedly updating the status just to update a timestamp.
191+
// It also prevents fighting with other controllers updating the status.
192+
newStatus := *status.DeepCopy()
191193
startTime := time.Now()
192194
reconcileTimeout := getReconcileTimeOut(len(spec.Subgroups) + len(spec.Resources))
193195

194196
finish := make(chan struct{})
195197
go func() {
198+
defer close(finish)
196199
newStatus.ResourceStatuses = r.computeResourceStatuses(ctx, id, status, spec.Resources, namespacedName)
197200
newStatus.SubgroupStatuses = r.computeSubGroupStatuses(ctx, id, status, spec.Subgroups, namespacedName)
198-
close(finish)
199201
}()
200202
select {
201203
case <-finish:
202204
newStatus.ObservedGeneration = generation
203-
newStatus.Conditions = []v1alpha1.Condition{
204-
newReconcilingCondition(v1alpha1.FalseConditionStatus, FinishReconciling, finishReconcilingMsg),
205-
aggregateResourceStatuses(newStatus.ResourceStatuses),
206-
}
205+
// Update conditions, if they've changed
206+
newStatus.Conditions = updateCondition(newStatus.Conditions,
207+
newReconcilingCondition(v1alpha1.FalseConditionStatus, FinishReconciling, finishReconcilingMsg))
208+
newStatus.Conditions = updateCondition(newStatus.Conditions,
209+
aggregateResourceStatuses(newStatus.ResourceStatuses))
207210
case <-time.After(reconcileTimeout):
208-
newStatus.ObservedGeneration = status.ObservedGeneration
209-
newStatus.ResourceStatuses = status.ResourceStatuses
210-
newStatus.SubgroupStatuses = status.SubgroupStatuses
211-
newStatus.Conditions = []v1alpha1.Condition{
212-
newReconcilingCondition(v1alpha1.FalseConditionStatus, ExceedTimeout, exceedTimeoutMsg),
213-
newStalledCondition(v1alpha1.TrueConditionStatus, ExceedTimeout, exceedTimeoutMsg),
214-
}
211+
// Update conditions, if they've changed
212+
newStatus.Conditions = updateCondition(newStatus.Conditions,
213+
newReconcilingCondition(v1alpha1.FalseConditionStatus, ExceedTimeout, exceedTimeoutMsg))
214+
newStatus.Conditions = updateCondition(newStatus.Conditions,
215+
newStalledCondition(v1alpha1.TrueConditionStatus, ExceedTimeout, exceedTimeoutMsg))
215216
}
216217

217218
metrics.RecordReconcileDuration(ctx, newStatus.Conditions[1].Reason, startTime)

pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ func TestReconcile(t *testing.T) {
128128
resgroupKpt = waitForResourceGroupStatus(t, ctx, c, rgKey, 1, 0, expectedStatus)
129129

130130
// Push an event to the channel, which will cause trigger a reconciliation for resgroup
131+
t.Log("Sending event to controller")
131132
channelKpt <- event.GenericEvent{Object: resgroupKpt}
132133

133134
// Verify that the reconciliation modifies the ResourceGroupStatus field correctly
@@ -171,6 +172,7 @@ func TestReconcile(t *testing.T) {
171172
expectedStatus.ObservedGeneration = 2
172173
resgroupKpt = waitForResourceGroupStatus(t, ctx, c, rgKey, 2, 2, expectedStatus)
173174

175+
t.Log("Sending event to controller")
174176
channelKpt <- event.GenericEvent{Object: resgroupKpt}
175177

176178
// Verify that the reconciliation modifies the ResourceGroupStatus field correctly
@@ -235,6 +237,7 @@ func TestReconcile(t *testing.T) {
235237
require.NoError(t, err)
236238
require.Equal(t, corev1.NamespaceActive, updatedNS.Status.Phase)
237239

240+
t.Log("Sending event to controller")
238241
channelKpt <- event.GenericEvent{Object: resgroupKpt}
239242

240243
// Verify that the reconciliation modifies the ResourceGroupStatus field correctly
@@ -282,6 +285,7 @@ func TestReconcile(t *testing.T) {
282285
expectedStatus.ObservedGeneration = 3
283286
resgroupKpt = waitForResourceGroupStatus(t, ctx, c, rgKey, 3, 1, expectedStatus)
284287

288+
t.Log("Sending event to controller")
285289
channelKpt <- event.GenericEvent{Object: resgroupKpt}
286290

287291
// Verify that the reconciliation modifies the ResourceGroupStatus field correctly

0 commit comments

Comments
 (0)