Skip to content

Commit 225ab4f

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

File tree

2 files changed

+86
-23
lines changed

2 files changed

+86
-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: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/google/go-cmp/cmp"
1314
appsv1 "k8s.io/api/apps/v1"
1415
corev1 "k8s.io/api/core/v1"
16+
"k8s.io/apimachinery/pkg/api/equality"
1517
"k8s.io/apimachinery/pkg/api/meta"
1618
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1719
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -67,16 +69,52 @@ func (c *ClusterExtensionRevisionReconciler) Reconcile(ctx context.Context, req
6769
l := log.FromContext(ctx).WithName("cluster-extension-revision")
6870
ctx = log.IntoContext(ctx, l)
6971

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

75-
l = l.WithValues("key", req.String())
7677
l.Info("reconcile starting")
7778
defer l.Info("reconcile ending")
7879

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

82120
func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev *ocv1.ClusterExtensionRevision) (ctrl.Result, error) {
@@ -98,11 +136,14 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
98136
Message: err.Error(),
99137
ObservedGeneration: rev.Generation,
100138
})
101-
return ctrl.Result{}, fmt.Errorf("revision teardown: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
139+
return ctrl.Result{}, fmt.Errorf("revision teardown: %v", err)
102140
}
103141

104142
l.Info("teardown report", "report", tres.String())
105143
if !tres.IsComplete() {
144+
// TODO: If it is not complete, it seems like it would be good to update
145+
// the status in some way to tell the user that the teardown is still
146+
// in progress.
106147
return ctrl.Result{}, nil
107148
}
108149

@@ -114,9 +155,19 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
114155
Message: err.Error(),
115156
ObservedGeneration: rev.Generation,
116157
})
117-
return ctrl.Result{}, fmt.Errorf("free cache informers: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
158+
return ctrl.Result{}, fmt.Errorf("error stopping informers: %v", err)
159+
}
160+
if err := c.removeFinalizer(ctx, rev, clusterExtensionRevisionTeardownFinalizer); err != nil {
161+
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
162+
Type: "Available",
163+
Status: metav1.ConditionFalse,
164+
Reason: "ReconcileFailure",
165+
Message: err.Error(),
166+
ObservedGeneration: rev.Generation,
167+
})
168+
return ctrl.Result{}, fmt.Errorf("error removing teardown finalizer: %v", err)
118169
}
119-
return ctrl.Result{}, c.removeFinalizer(ctx, rev, clusterExtensionRevisionTeardownFinalizer)
170+
return ctrl.Result{}, nil
120171
}
121172

122173
//
@@ -130,8 +181,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
130181
Message: err.Error(),
131182
ObservedGeneration: rev.Generation,
132183
})
133-
return ctrl.Result{}, fmt.Errorf("ensure finalizer: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
184+
return ctrl.Result{}, fmt.Errorf("error ensuring teardown finalizer: %v", err)
134185
}
186+
135187
if err := c.establishWatch(ctx, rev, revision); err != nil {
136188
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
137189
Type: "Available",
@@ -140,8 +192,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
140192
Message: err.Error(),
141193
ObservedGeneration: rev.Generation,
142194
})
143-
return ctrl.Result{}, fmt.Errorf("establish watch: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
195+
return ctrl.Result{}, fmt.Errorf("establish watch: %v", err)
144196
}
197+
145198
rres, err := c.RevisionEngine.Reconcile(ctx, *revision, opts...)
146199
if err != nil {
147200
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
@@ -151,60 +204,69 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
151204
Message: err.Error(),
152205
ObservedGeneration: rev.Generation,
153206
})
154-
return ctrl.Result{}, fmt.Errorf("revision reconcile: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
207+
return ctrl.Result{}, fmt.Errorf("revision reconcile: %v", err)
155208
}
156209
l.Info("reconcile report", "report", rres.String())
157210

158211
// Retry failing preflight checks with a flat 10s retry.
159212
// TODO: report status, backoff?
160213
if verr := rres.GetValidationError(); verr != nil {
161214
l.Info("preflight error, retrying after 10s", "err", verr.String())
215+
162216
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
163217
Type: "Available",
164218
Status: metav1.ConditionFalse,
165219
Reason: "RevisionValidationFailure",
166220
Message: fmt.Sprintf("revision validation error: %s", verr),
167221
ObservedGeneration: rev.Generation,
168222
})
169-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
223+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
170224
}
225+
171226
for i, pres := range rres.GetPhases() {
172227
if verr := pres.GetValidationError(); verr != nil {
173228
l.Info("preflight error, retrying after 10s", "err", verr.String())
229+
174230
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
175231
Type: "Available",
176232
Status: metav1.ConditionFalse,
177233
Reason: "PhaseValidationError",
178234
Message: fmt.Sprintf("phase %d validation error: %s", i, verr),
179235
ObservedGeneration: rev.Generation,
180236
})
181-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
237+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
182238
}
239+
183240
var collidingObjs []string
184241
for _, ores := range pres.GetObjects() {
185242
if ores.Action() == machinery.ActionCollision {
186243
collidingObjs = append(collidingObjs, ores.String())
187244
}
188245
}
246+
189247
if len(collidingObjs) > 0 {
190248
l.Info("object collision error, retrying after 10s", "collisions", collidingObjs)
249+
191250
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
192251
Type: "Available",
193252
Status: metav1.ConditionFalse,
194253
Reason: "ObjectCollisions",
195254
Message: fmt.Sprintf("revision object collisions in phase %d\n%s", i, strings.Join(collidingObjs, "\n\n")),
196255
ObservedGeneration: rev.Generation,
197256
})
198-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
257+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
199258
}
200259
}
201260

202261
//nolint:nestif
203262
if rres.IsComplete() {
204263
// Archive other revisions.
205264
for _, a := range previous {
206-
if err := c.Client.Patch(ctx, a, client.RawPatch(
207-
types.MergePatchType, []byte(`{"spec":{"lifecycleState":"Archived"}}`))); err != nil {
265+
patch := []byte(`{"spec":{"lifecycleState":"Archived"}}`)
266+
if err := c.Client.Patch(ctx, a, client.RawPatch(types.MergePatchType, patch)); err != nil {
267+
// TODO: It feels like an error here needs to propagate to a status _somewhere_.
268+
// Not sure the current CER makes sense? But it also feels off to set the CE
269+
// status from outside the CE reconciler.
208270
return ctrl.Result{}, fmt.Errorf("archive previous Revision: %w", err)
209271
}
210272
}
@@ -217,6 +279,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
217279
Message: "Object is available and passes all probes.",
218280
ObservedGeneration: rev.Generation,
219281
})
282+
220283
if !meta.IsStatusConditionTrue(rev.Status.Conditions, "Succeeded") {
221284
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
222285
Type: "Succeeded",
@@ -278,7 +341,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
278341
meta.RemoveStatusCondition(&rev.Status.Conditions, "Progressing")
279342
}
280343

281-
return ctrl.Result{}, c.Client.Status().Update(ctx, rev)
344+
return ctrl.Result{}, nil
282345
}
283346

284347
type Sourcerer interface {
@@ -370,7 +433,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
370433
for _, specPhase := range rev.Spec.Phases {
371434
phase := boxcutter.Phase{Name: specPhase.Name}
372435
for _, specObj := range specPhase.Objects {
373-
obj := specObj.Object
436+
obj := specObj.Object.DeepCopy()
374437

375438
labels := obj.GetLabels()
376439
if labels == nil {
@@ -379,7 +442,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
379442
labels[ClusterExtensionRevisionOwnerLabel] = rev.Labels[ClusterExtensionRevisionOwnerLabel]
380443
obj.SetLabels(labels)
381444

382-
phase.Objects = append(phase.Objects, obj)
445+
phase.Objects = append(phase.Objects, *obj)
383446
}
384447
r.Phases = append(r.Phases, phase)
385448
}

0 commit comments

Comments
 (0)