Skip to content

Commit 9551542

Browse files
committed
CER: centralize status updates into big-R Reconcile method
Signed-off-by: Joe Lanford <[email protected]>
1 parent 01cf0d3 commit 9551542

File tree

2 files changed

+81
-23
lines changed

2 files changed

+81
-23
lines changed

internal/operator-controller/controllers/clusterextension_controller.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,14 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
104104
l := log.FromContext(ctx).WithName("cluster-extension")
105105
ctx = log.IntoContext(ctx, l)
106106

107-
l.Info("reconcile starting")
108-
defer l.Info("reconcile ending")
109-
110107
existingExt := &ocv1.ClusterExtension{}
111108
if err := r.Get(ctx, req.NamespacedName, existingExt); err != nil {
112109
return ctrl.Result{}, client.IgnoreNotFound(err)
113110
}
114111

112+
l.Info("reconcile starting")
113+
defer l.Info("reconcile ending")
114+
115115
reconciledExt := existingExt.DeepCopy()
116116
res, reconcileErr := r.reconcile(ctx, reconciledExt)
117117

@@ -120,7 +120,7 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
120120
updateFinalizers := !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers)
121121

122122
// If any unexpected fields have changed, panic before updating the resource
123-
unexpectedFieldsChanged := checkForUnexpectedFieldChange(*existingExt, *reconciledExt)
123+
unexpectedFieldsChanged := checkForUnexpectedClusterExtensionFieldChange(*existingExt, *reconciledExt)
124124
if unexpectedFieldsChanged {
125125
panic("spec or metadata changed by reconciler")
126126
}
@@ -167,7 +167,7 @@ func ensureAllConditionsWithReason(ext *ocv1.ClusterExtension, reason v1alpha1.C
167167
}
168168

169169
// Compare resources - ignoring status & metadata.finalizers
170-
func checkForUnexpectedFieldChange(a, b ocv1.ClusterExtension) bool {
170+
func checkForUnexpectedClusterExtensionFieldChange(a, b ocv1.ClusterExtension) bool {
171171
a.Status, b.Status = ocv1.ClusterExtensionStatus{}, ocv1.ClusterExtensionStatus{}
172172
a.Finalizers, b.Finalizers = []string{}, []string{}
173173
return !equality.Semantic.DeepEqual(a, b)

internal/operator-controller/controllers/clusterextensionrevision_controller.go

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
appsv1 "k8s.io/api/apps/v1"
1414
corev1 "k8s.io/api/core/v1"
15+
"k8s.io/apimachinery/pkg/api/equality"
1516
"k8s.io/apimachinery/pkg/api/meta"
1617
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1718
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -67,16 +68,48 @@ func (c *ClusterExtensionRevisionReconciler) Reconcile(ctx context.Context, req
6768
l := log.FromContext(ctx).WithName("cluster-extension-revision")
6869
ctx = log.IntoContext(ctx, l)
6970

70-
rev := &ocv1.ClusterExtensionRevision{}
71-
if err := c.Client.Get(ctx, req.NamespacedName, rev); err != nil {
71+
existingRev := &ocv1.ClusterExtensionRevision{}
72+
if err := c.Client.Get(ctx, req.NamespacedName, existingRev); err != nil {
7273
return ctrl.Result{}, client.IgnoreNotFound(err)
7374
}
7475

75-
l = l.WithValues("key", req.String())
7676
l.Info("reconcile starting")
7777
defer l.Info("reconcile ending")
7878

79-
return c.reconcile(ctx, rev)
79+
reconciledRev := existingRev.DeepCopy()
80+
res, reconcileErr := c.reconcile(ctx, reconciledRev)
81+
82+
// Do checks before any Update()s, as Update() may modify the resource structure!
83+
updateStatus := !equality.Semantic.DeepEqual(existingRev.Status, reconciledRev.Status)
84+
85+
unexpectedFieldsChanged := checkForUnexpectedClusterExtensionRevisionFieldChange(*existingRev, *reconciledRev)
86+
if unexpectedFieldsChanged {
87+
panic("spec or metadata changed by reconciler")
88+
}
89+
90+
// NOTE: finalizer updates are performed during c.reconcile as patches, so that reconcile can
91+
// continue performing logic after successfully setting the finalizer. therefore we only need
92+
// to set status here.
93+
94+
if updateStatus {
95+
if err := c.Client.Status().Update(ctx, reconciledRev); err != nil {
96+
reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating status: %v", err))
97+
}
98+
}
99+
100+
return res, reconcileErr
101+
}
102+
103+
// Compare resources - ignoring status & metadata.finalizers
104+
func checkForUnexpectedClusterExtensionRevisionFieldChange(a, b ocv1.ClusterExtensionRevision) bool {
105+
a.Status, b.Status = ocv1.ClusterExtensionRevisionStatus{}, ocv1.ClusterExtensionRevisionStatus{}
106+
107+
// when finalizers are updated during reconcile, we expect finalizers, managedFields, and resourceVersion
108+
// to be updated, so we ignore changes in these fields.
109+
a.Finalizers, b.Finalizers = []string{}, []string{}
110+
a.ManagedFields, b.ManagedFields = nil, nil
111+
a.ResourceVersion, b.ResourceVersion = "", ""
112+
return !equality.Semantic.DeepEqual(a.Spec, b.Spec)
80113
}
81114

82115
func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev *ocv1.ClusterExtensionRevision) (ctrl.Result, error) {
@@ -98,11 +131,14 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
98131
Message: err.Error(),
99132
ObservedGeneration: rev.Generation,
100133
})
101-
return ctrl.Result{}, fmt.Errorf("revision teardown: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
134+
return ctrl.Result{}, fmt.Errorf("revision teardown: %v", err)
102135
}
103136

104137
l.Info("teardown report", "report", tres.String())
105138
if !tres.IsComplete() {
139+
// TODO: If it is not complete, it seems like it would be good to update
140+
// the status in some way to tell the user that the teardown is still
141+
// in progress.
106142
return ctrl.Result{}, nil
107143
}
108144

@@ -114,9 +150,19 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
114150
Message: err.Error(),
115151
ObservedGeneration: rev.Generation,
116152
})
117-
return ctrl.Result{}, fmt.Errorf("free cache informers: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
153+
return ctrl.Result{}, fmt.Errorf("error stopping informers: %v", err)
154+
}
155+
if err := c.removeFinalizer(ctx, rev, clusterExtensionRevisionTeardownFinalizer); err != nil {
156+
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
157+
Type: "Available",
158+
Status: metav1.ConditionFalse,
159+
Reason: "ReconcileFailure",
160+
Message: err.Error(),
161+
ObservedGeneration: rev.Generation,
162+
})
163+
return ctrl.Result{}, fmt.Errorf("error removing teardown finalizer: %v", err)
118164
}
119-
return ctrl.Result{}, c.removeFinalizer(ctx, rev, clusterExtensionRevisionTeardownFinalizer)
165+
return ctrl.Result{}, nil
120166
}
121167

122168
//
@@ -130,8 +176,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
130176
Message: err.Error(),
131177
ObservedGeneration: rev.Generation,
132178
})
133-
return ctrl.Result{}, fmt.Errorf("ensure finalizer: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
179+
return ctrl.Result{}, fmt.Errorf("error ensuring teardown finalizer: %v", err)
134180
}
181+
135182
if err := c.establishWatch(ctx, rev, revision); err != nil {
136183
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
137184
Type: "Available",
@@ -140,8 +187,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
140187
Message: err.Error(),
141188
ObservedGeneration: rev.Generation,
142189
})
143-
return ctrl.Result{}, fmt.Errorf("establish watch: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
190+
return ctrl.Result{}, fmt.Errorf("establish watch: %v", err)
144191
}
192+
145193
rres, err := c.RevisionEngine.Reconcile(ctx, *revision, opts...)
146194
if err != nil {
147195
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
@@ -151,60 +199,69 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
151199
Message: err.Error(),
152200
ObservedGeneration: rev.Generation,
153201
})
154-
return ctrl.Result{}, fmt.Errorf("revision reconcile: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
202+
return ctrl.Result{}, fmt.Errorf("revision reconcile: %v", err)
155203
}
156204
l.Info("reconcile report", "report", rres.String())
157205

158206
// Retry failing preflight checks with a flat 10s retry.
159207
// TODO: report status, backoff?
160208
if verr := rres.GetValidationError(); verr != nil {
161209
l.Info("preflight error, retrying after 10s", "err", verr.String())
210+
162211
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
163212
Type: "Available",
164213
Status: metav1.ConditionFalse,
165214
Reason: "RevisionValidationFailure",
166215
Message: fmt.Sprintf("revision validation error: %s", verr),
167216
ObservedGeneration: rev.Generation,
168217
})
169-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
218+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
170219
}
220+
171221
for i, pres := range rres.GetPhases() {
172222
if verr := pres.GetValidationError(); verr != nil {
173223
l.Info("preflight error, retrying after 10s", "err", verr.String())
224+
174225
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
175226
Type: "Available",
176227
Status: metav1.ConditionFalse,
177228
Reason: "PhaseValidationError",
178229
Message: fmt.Sprintf("phase %d validation error: %s", i, verr),
179230
ObservedGeneration: rev.Generation,
180231
})
181-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
232+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
182233
}
234+
183235
var collidingObjs []string
184236
for _, ores := range pres.GetObjects() {
185237
if ores.Action() == machinery.ActionCollision {
186238
collidingObjs = append(collidingObjs, ores.String())
187239
}
188240
}
241+
189242
if len(collidingObjs) > 0 {
190243
l.Info("object collision error, retrying after 10s", "collisions", collidingObjs)
244+
191245
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
192246
Type: "Available",
193247
Status: metav1.ConditionFalse,
194248
Reason: "ObjectCollisions",
195249
Message: fmt.Sprintf("revision object collisions in phase %d\n%s", i, strings.Join(collidingObjs, "\n\n")),
196250
ObservedGeneration: rev.Generation,
197251
})
198-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
252+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
199253
}
200254
}
201255

202256
//nolint:nestif
203257
if rres.IsComplete() {
204258
// Archive other revisions.
205259
for _, a := range previous {
206-
if err := c.Client.Patch(ctx, a, client.RawPatch(
207-
types.MergePatchType, []byte(`{"spec":{"lifecycleState":"Archived"}}`))); err != nil {
260+
patch := []byte(`{"spec":{"lifecycleState":"Archived"}}`)
261+
if err := c.Client.Patch(ctx, a, client.RawPatch(types.MergePatchType, patch)); err != nil {
262+
// TODO: It feels like an error here needs to propagate to a status _somewhere_.
263+
// Not sure the current CER makes sense? But it also feels off to set the CE
264+
// status from outside the CE reconciler.
208265
return ctrl.Result{}, fmt.Errorf("archive previous Revision: %w", err)
209266
}
210267
}
@@ -217,6 +274,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
217274
Message: "Object is available and passes all probes.",
218275
ObservedGeneration: rev.Generation,
219276
})
277+
220278
if !meta.IsStatusConditionTrue(rev.Status.Conditions, "Succeeded") {
221279
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
222280
Type: "Succeeded",
@@ -278,7 +336,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
278336
meta.RemoveStatusCondition(&rev.Status.Conditions, "Progressing")
279337
}
280338

281-
return ctrl.Result{}, c.Client.Status().Update(ctx, rev)
339+
return ctrl.Result{}, nil
282340
}
283341

284342
type Sourcerer interface {
@@ -370,7 +428,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
370428
for _, specPhase := range rev.Spec.Phases {
371429
phase := boxcutter.Phase{Name: specPhase.Name}
372430
for _, specObj := range specPhase.Objects {
373-
obj := specObj.Object
431+
obj := specObj.Object.DeepCopy()
374432

375433
labels := obj.GetLabels()
376434
if labels == nil {
@@ -379,7 +437,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
379437
labels[ClusterExtensionRevisionOwnerLabel] = rev.Labels[ClusterExtensionRevisionOwnerLabel]
380438
obj.SetLabels(labels)
381439

382-
phase.Objects = append(phase.Objects, obj)
440+
phase.Objects = append(phase.Objects, *obj)
383441
}
384442
r.Phases = append(r.Phases, phase)
385443
}

0 commit comments

Comments
 (0)