Skip to content

Commit 9fc9ecc

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

File tree

2 files changed

+77
-23
lines changed

2 files changed

+77
-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: 72 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,44 @@ 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+
a.Finalizers, b.Finalizers = []string{}, []string{}
107+
a.ResourceVersion, b.ResourceVersion = "", ""
108+
return !equality.Semantic.DeepEqual(a, b)
80109
}
81110

82111
func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev *ocv1.ClusterExtensionRevision) (ctrl.Result, error) {
@@ -98,11 +127,14 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
98127
Message: err.Error(),
99128
ObservedGeneration: rev.Generation,
100129
})
101-
return ctrl.Result{}, fmt.Errorf("revision teardown: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
130+
return ctrl.Result{}, fmt.Errorf("revision teardown: %v", err)
102131
}
103132

104133
l.Info("teardown report", "report", tres.String())
105134
if !tres.IsComplete() {
135+
// TODO: If it is not complete, it seems like it would be good to update
136+
// the status in some way to tell the user that the teardown is still
137+
// in progress.
106138
return ctrl.Result{}, nil
107139
}
108140

@@ -114,9 +146,19 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
114146
Message: err.Error(),
115147
ObservedGeneration: rev.Generation,
116148
})
117-
return ctrl.Result{}, fmt.Errorf("free cache informers: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
149+
return ctrl.Result{}, fmt.Errorf("error stopping informers: %v", err)
118150
}
119-
return ctrl.Result{}, c.removeFinalizer(ctx, rev, clusterExtensionRevisionTeardownFinalizer)
151+
if err := c.removeFinalizer(ctx, rev, clusterExtensionRevisionTeardownFinalizer); err != nil {
152+
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
153+
Type: "Available",
154+
Status: metav1.ConditionFalse,
155+
Reason: "ReconcileFailure",
156+
Message: err.Error(),
157+
ObservedGeneration: rev.Generation,
158+
})
159+
return ctrl.Result{}, fmt.Errorf("error removing teardown finalizer: %v", err)
160+
}
161+
return ctrl.Result{}, nil
120162
}
121163

122164
//
@@ -130,8 +172,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
130172
Message: err.Error(),
131173
ObservedGeneration: rev.Generation,
132174
})
133-
return ctrl.Result{}, fmt.Errorf("ensure finalizer: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
175+
return ctrl.Result{}, fmt.Errorf("error ensuring teardown finalizer: %v", err)
134176
}
177+
135178
if err := c.establishWatch(ctx, rev, revision); err != nil {
136179
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
137180
Type: "Available",
@@ -140,8 +183,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
140183
Message: err.Error(),
141184
ObservedGeneration: rev.Generation,
142185
})
143-
return ctrl.Result{}, fmt.Errorf("establish watch: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
186+
return ctrl.Result{}, fmt.Errorf("establish watch: %v", err)
144187
}
188+
145189
rres, err := c.RevisionEngine.Reconcile(ctx, *revision, opts...)
146190
if err != nil {
147191
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
@@ -151,60 +195,69 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
151195
Message: err.Error(),
152196
ObservedGeneration: rev.Generation,
153197
})
154-
return ctrl.Result{}, fmt.Errorf("revision reconcile: %w", errors.Join(err, c.Client.Status().Update(ctx, rev)))
198+
return ctrl.Result{}, fmt.Errorf("revision reconcile: %v", err)
155199
}
156200
l.Info("reconcile report", "report", rres.String())
157201

158202
// Retry failing preflight checks with a flat 10s retry.
159203
// TODO: report status, backoff?
160204
if verr := rres.GetValidationError(); verr != nil {
161205
l.Info("preflight error, retrying after 10s", "err", verr.String())
206+
162207
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
163208
Type: "Available",
164209
Status: metav1.ConditionFalse,
165210
Reason: "RevisionValidationFailure",
166211
Message: fmt.Sprintf("revision validation error: %s", verr),
167212
ObservedGeneration: rev.Generation,
168213
})
169-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
214+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
170215
}
216+
171217
for i, pres := range rres.GetPhases() {
172218
if verr := pres.GetValidationError(); verr != nil {
173219
l.Info("preflight error, retrying after 10s", "err", verr.String())
220+
174221
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
175222
Type: "Available",
176223
Status: metav1.ConditionFalse,
177224
Reason: "PhaseValidationError",
178225
Message: fmt.Sprintf("phase %d validation error: %s", i, verr),
179226
ObservedGeneration: rev.Generation,
180227
})
181-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
228+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
182229
}
230+
183231
var collidingObjs []string
184232
for _, ores := range pres.GetObjects() {
185233
if ores.Action() == machinery.ActionCollision {
186234
collidingObjs = append(collidingObjs, ores.String())
187235
}
188236
}
237+
189238
if len(collidingObjs) > 0 {
190239
l.Info("object collision error, retrying after 10s", "collisions", collidingObjs)
240+
191241
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
192242
Type: "Available",
193243
Status: metav1.ConditionFalse,
194244
Reason: "ObjectCollisions",
195245
Message: fmt.Sprintf("revision object collisions in phase %d\n%s", i, strings.Join(collidingObjs, "\n\n")),
196246
ObservedGeneration: rev.Generation,
197247
})
198-
return ctrl.Result{RequeueAfter: 10 * time.Second}, c.Client.Status().Update(ctx, rev)
248+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
199249
}
200250
}
201251

202252
//nolint:nestif
203253
if rres.IsComplete() {
204254
// Archive other revisions.
205255
for _, a := range previous {
206-
if err := c.Client.Patch(ctx, a, client.RawPatch(
207-
types.MergePatchType, []byte(`{"spec":{"lifecycleState":"Archived"}}`))); err != nil {
256+
patch := []byte(`{"spec":{"lifecycleState":"Archived"}}`)
257+
if err := c.Client.Patch(ctx, a, client.RawPatch(types.MergePatchType, patch)); err != nil {
258+
// TODO: It feels like an error here needs to propagate to a status _somewhere_.
259+
// Not sure the current CER makes sense? But it also feels off to set the CE
260+
// status from outside the CE reconciler.
208261
return ctrl.Result{}, fmt.Errorf("archive previous Revision: %w", err)
209262
}
210263
}
@@ -217,6 +270,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
217270
Message: "Object is available and passes all probes.",
218271
ObservedGeneration: rev.Generation,
219272
})
273+
220274
if !meta.IsStatusConditionTrue(rev.Status.Conditions, "Succeeded") {
221275
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
222276
Type: "Succeeded",
@@ -278,7 +332,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
278332
meta.RemoveStatusCondition(&rev.Status.Conditions, "Progressing")
279333
}
280334

281-
return ctrl.Result{}, c.Client.Status().Update(ctx, rev)
335+
return ctrl.Result{}, nil
282336
}
283337

284338
type Sourcerer interface {
@@ -370,7 +424,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
370424
for _, specPhase := range rev.Spec.Phases {
371425
phase := boxcutter.Phase{Name: specPhase.Name}
372426
for _, specObj := range specPhase.Objects {
373-
obj := specObj.Object
427+
obj := specObj.Object.DeepCopy()
374428

375429
labels := obj.GetLabels()
376430
if labels == nil {
@@ -379,7 +433,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
379433
labels[ClusterExtensionRevisionOwnerLabel] = rev.Labels[ClusterExtensionRevisionOwnerLabel]
380434
obj.SetLabels(labels)
381435

382-
phase.Objects = append(phase.Objects, obj)
436+
phase.Objects = append(phase.Objects, *obj)
383437
}
384438
r.Phases = append(r.Phases, phase)
385439
}

0 commit comments

Comments
 (0)