Skip to content

Commit 9ad2a4b

Browse files
map Operator status according to that of bundle deployment
1 parent 583d4b9 commit 9ad2a4b

File tree

3 files changed

+247
-57
lines changed

3 files changed

+247
-57
lines changed

config/samples/operators_v1alpha1_operator.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ metadata:
1010
name: operator-sample
1111
spec:
1212
# TODO(user): Add fields here
13-
packageName: prometheus
13+
packageName: lightbend-console-operator

controllers/operator_controller.go

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,21 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha
162162
return ctrl.Result{}, err
163163
}
164164

165-
// update operator status
166-
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
167-
Type: operatorsv1alpha1.TypeReady,
168-
Status: metav1.ConditionTrue,
169-
Reason: operatorsv1alpha1.ReasonResolutionSucceeded,
170-
Message: "resolution was successful",
171-
ObservedGeneration: op.GetGeneration(),
172-
})
165+
// convert existing unstructured object into bundleDeployment for easier mapping of status.
166+
existingTypedBundleDeployment := &rukpakv1alpha1.BundleDeployment{}
167+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(dep.UnstructuredContent(), existingTypedBundleDeployment); err != nil {
168+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
169+
Type: operatorsv1alpha1.TypeReady,
170+
Status: metav1.ConditionUnknown,
171+
Reason: operatorsv1alpha1.ReasonResolutionSucceeded,
172+
Message: err.Error(),
173+
ObservedGeneration: op.GetGeneration(),
174+
})
175+
return ctrl.Result{}, err
176+
}
177+
178+
// set the status of the operator based on the respective bundle deployment status conditions.
179+
apimeta.SetStatusCondition(&op.Status.Conditions, mapBDStatusToReadyCondition(existingTypedBundleDeployment, op.GetGeneration()))
173180
return ctrl.Result{}, nil
174181
}
175182

@@ -259,14 +266,11 @@ func (r *OperatorReconciler) ensureBundleDeployment(ctx context.Context, desired
259266
// If the existing BD already has everything that the desired BD has, no need to contact the API server.
260267
// Make sure the status of the existingBD from the server is as expected.
261268
if equality.Semantic.DeepDerivative(desiredBundleDeployment, existingBundleDeployment) {
262-
return verifySuccessfulBDStatus(existingBundleDeployment)
263-
}
264-
265-
if err = r.Client.Patch(ctx, desiredBundleDeployment, client.Apply, client.ForceOwnership, client.FieldOwner("operator-controller")); err != nil {
266-
return err
269+
*desiredBundleDeployment = *existingBundleDeployment
270+
return nil
267271
}
268272

269-
return verifySuccessfulBDStatus(desiredBundleDeployment)
273+
return r.Client.Patch(ctx, desiredBundleDeployment, client.Apply, client.ForceOwnership, client.FieldOwner("operator-controller"))
270274
}
271275

272276
func (r *OperatorReconciler) existingBundleDeploymentUnstructured(ctx context.Context, name string) (*unstructured.Unstructured, error) {
@@ -284,29 +288,49 @@ func (r *OperatorReconciler) existingBundleDeploymentUnstructured(ctx context.Co
284288
return &unstructured.Unstructured{Object: unstrExistingBundleDeploymentObj}, nil
285289
}
286290

287-
func verifySuccessfulBDStatus(dep *unstructured.Unstructured) error {
288-
// convert the unstructured object from patch call to typed bundledeployment to make checking of Status easier.
289-
existingTypedBundleDeployment := &rukpakv1alpha1.BundleDeployment{}
291+
// verifyBDStatus reads the various possibilities of status in bundle deployment and translates
292+
// into corresponding operator condition status and message.
293+
func verifyBDStatus(dep *rukpakv1alpha1.BundleDeployment) (metav1.ConditionStatus, string) {
294+
isValidBundleCond := apimeta.FindStatusCondition(dep.Status.Conditions, rukpakv1alpha1.TypeHasValidBundle)
295+
isInstalledCond := apimeta.FindStatusCondition(dep.Status.Conditions, rukpakv1alpha1.TypeInstalled)
290296

291-
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(dep.UnstructuredContent(), existingTypedBundleDeployment); err != nil {
292-
return err
297+
if isValidBundleCond == nil && isInstalledCond == nil {
298+
return metav1.ConditionUnknown, fmt.Sprintf("waiting for bundleDeployment %q status to be updated", dep.Name)
293299
}
294300

295-
ers := []error{}
296-
conditions := existingTypedBundleDeployment.Status.Conditions
301+
if isValidBundleCond != nil && isValidBundleCond.Status == metav1.ConditionFalse {
302+
return metav1.ConditionFalse, isValidBundleCond.Message
303+
}
297304

298-
// Do we error here?
299-
if conditions == nil || len(conditions) == 0 {
300-
return nil
305+
if isInstalledCond != nil && isInstalledCond.Status == metav1.ConditionFalse {
306+
return metav1.ConditionFalse, isInstalledCond.Message
301307
}
302308

303-
for _, condition := range conditions {
304-
// Currently we are checking for only these two types, if any other condition is added to Rukpak, validate accordingly.
305-
if condition.Type == rukpakv1alpha1.TypeHasValidBundle || condition.Type == rukpakv1alpha1.TypeInstalled {
306-
if condition.Status == metav1.ConditionFalse {
307-
ers = append(ers, fmt.Errorf("error observed by Rukpak: %v", condition.Message))
308-
}
309-
}
309+
if isInstalledCond != nil && isInstalledCond.Status == metav1.ConditionTrue {
310+
return metav1.ConditionTrue, "resolution was successful"
311+
}
312+
return metav1.ConditionUnknown, fmt.Sprintf("waiting for rukpak to install bundleDeployment successfully %s", dep.Name)
313+
}
314+
315+
// mapBDStatusToReadyCondition returns the operator object's "TypeReady" condition based on the bundle deployment statuses.
316+
func mapBDStatusToReadyCondition(existingBD *rukpakv1alpha1.BundleDeployment, observedGeneration int64) metav1.Condition {
317+
// update operator status:
318+
// 1. If the Operator "Ready" status is "Unknown": The status of successful bundleDeployment is unknown, wait till Rukpak updates the BD status.
319+
// 2. If the Operator "Ready" status is "True": Update the "successful resolution" status and return the result.
320+
// 3. If the Operator "Ready" status is "False": There is error observed from Rukpak. Update the status accordingly.
321+
status, message := verifyBDStatus(existingBD)
322+
var reason string
323+
if status == metav1.ConditionTrue || status == metav1.ConditionUnknown {
324+
reason = operatorsv1alpha1.ReasonResolutionSucceeded
325+
} else {
326+
reason = operatorsv1alpha1.ReasonBundleDeploymentFailed
327+
}
328+
329+
return metav1.Condition{
330+
Type: operatorsv1alpha1.TypeReady,
331+
Status: status,
332+
Reason: reason,
333+
Message: message,
334+
ObservedGeneration: observedGeneration,
310335
}
311-
return utilerrors.NewAggregate(ers)
312336
}

controllers/operator_controller_test.go

Lines changed: 190 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,13 @@ var _ = Describe("Reconcile Test", func() {
112112
Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed"))
113113
})
114114
It("sets resolution success status", func() {
115-
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
116-
Expect(cond).NotTo(BeNil())
117-
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
118-
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
119-
Expect(cond.Message).To(Equal("resolution was successful"))
115+
Eventually(func() {
116+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
117+
Expect(cond).NotTo(BeNil())
118+
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
119+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
120+
Expect(cond.Message).To(Equal("resolution was successful"))
121+
})
120122
})
121123
})
122124
When("the expected BundleDeployment already exists", func() {
@@ -175,11 +177,13 @@ var _ = Describe("Reconcile Test", func() {
175177
Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed"))
176178
})
177179
It("sets resolution success status", func() {
178-
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
179-
Expect(cond).NotTo(BeNil())
180-
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
181-
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
182-
Expect(cond.Message).To(Equal("resolution was successful"))
180+
Eventually(func() {
181+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
182+
Expect(cond).NotTo(BeNil())
183+
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
184+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
185+
Expect(cond.Message).To(Equal("resolution was successful"))
186+
})
183187
})
184188
})
185189
When("an out-of-date BundleDeployment exists", func() {
@@ -223,11 +227,14 @@ var _ = Describe("Reconcile Test", func() {
223227
Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed"))
224228
})
225229
It("sets resolution success status", func() {
226-
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
227-
Expect(cond).NotTo(BeNil())
228-
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
229-
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
230-
Expect(cond.Message).To(Equal("resolution was successful"))
230+
Eventually(func() {
231+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
232+
Expect(cond).NotTo(BeNil())
233+
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
234+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
235+
Expect(cond.Message).To(Equal("resolution was successful"))
236+
})
237+
231238
})
232239
})
233240
})
@@ -331,24 +338,77 @@ var _ = Describe("Reconcile Test", func() {
331338
Expect(err).NotTo(HaveOccurred())
332339

333340
})
334-
It("verify if operator status is updated according to bundleDeployment", func() {
341+
It("verify if operator status when bundle deployment is waiting to be created", func() {
342+
By("running reconcile")
343+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
344+
Expect(res).To(Equal(ctrl.Result{}))
345+
Expect(err).NotTo(HaveOccurred())
346+
347+
By("fetching the updated operator after reconcile")
348+
op := &operatorsv1alpha1.Operator{}
349+
err = cl.Get(ctx, opKey, op)
350+
Expect(err).NotTo(HaveOccurred())
351+
352+
By("checking the expected conditions")
353+
cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady)
354+
Expect(cond).NotTo(BeNil())
355+
Expect(cond.Status).To(Equal(metav1.ConditionUnknown))
356+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
357+
Expect(cond.Message).To(ContainSubstring(`waiting for bundleDeployment`))
358+
})
359+
360+
It("verify operator status when `HasValidBundle` condition of rukpak is false", func() {
361+
err := cl.Get(ctx, opKey, &bd)
362+
Expect(err).NotTo(HaveOccurred())
363+
364+
apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{
365+
Type: rukpakv1alpha1.TypeHasValidBundle,
366+
Status: metav1.ConditionFalse,
367+
Message: "failed to unpack",
368+
Reason: rukpakv1alpha1.ReasonUnpackFailed,
369+
})
370+
371+
By("updating the status of bundleDeployment")
372+
err = cl.Status().Update(ctx, &bd)
373+
Expect(err).NotTo(HaveOccurred())
374+
375+
By("running reconcile")
376+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
377+
Expect(res).To(Equal(ctrl.Result{}))
378+
Expect(err).NotTo(HaveOccurred())
379+
380+
By("fetching the updated operator after reconcile")
381+
op := &operatorsv1alpha1.Operator{}
382+
err = cl.Get(ctx, opKey, op)
383+
Expect(err).NotTo(HaveOccurred())
384+
385+
By("checking the expected conditions")
386+
cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady)
387+
Expect(cond).NotTo(BeNil())
388+
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
389+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonBundleDeploymentFailed))
390+
Expect(cond.Message).To(ContainSubstring(`failed to unpack`))
391+
})
392+
393+
It("verify operator status when `InstallReady` condition of rukpak is false", func() {
335394
err := cl.Get(ctx, opKey, &bd)
336395
Expect(err).NotTo(HaveOccurred())
396+
337397
apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{
338-
Type: rukpakv1alpha1.TypeInstalled,
339-
LastTransitionTime: metav1.Now(),
340-
Status: metav1.ConditionFalse,
341-
Message: "fail to unpack",
342-
Reason: rukpakv1alpha1.ReasonInstallFailed,
398+
Type: rukpakv1alpha1.TypeInstalled,
399+
Status: metav1.ConditionFalse,
400+
Message: "failed to install",
401+
Reason: rukpakv1alpha1.ReasonInstallFailed,
343402
})
403+
404+
By("updating the status of bundleDeployment")
344405
err = cl.Status().Update(ctx, &bd)
345406
Expect(err).NotTo(HaveOccurred())
346407

347408
By("running reconcile")
348409
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
349410
Expect(res).To(Equal(ctrl.Result{}))
350-
Expect(err).To(HaveOccurred())
351-
Expect(err.Error()).To(ContainSubstring(`error observed by Rukpak`))
411+
Expect(err).NotTo(HaveOccurred())
352412

353413
By("fetching the updated operator after reconcile")
354414
op := &operatorsv1alpha1.Operator{}
@@ -360,7 +420,113 @@ var _ = Describe("Reconcile Test", func() {
360420
Expect(cond).NotTo(BeNil())
361421
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
362422
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonBundleDeploymentFailed))
363-
Expect(cond.Message).To(ContainSubstring(`error observed by Rukpak`))
423+
Expect(cond.Message).To(ContainSubstring(`failed to install`))
424+
})
425+
426+
It("verify operator status when `InstallReady` condition of rukpak is true", func() {
427+
err := cl.Get(ctx, opKey, &bd)
428+
Expect(err).NotTo(HaveOccurred())
429+
430+
apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{
431+
Type: rukpakv1alpha1.TypeInstalled,
432+
Status: metav1.ConditionTrue,
433+
Message: "operator installed successfully",
434+
Reason: rukpakv1alpha1.ReasonInstallationSucceeded,
435+
})
436+
437+
By("updating the status of bundleDeployment")
438+
err = cl.Status().Update(ctx, &bd)
439+
Expect(err).NotTo(HaveOccurred())
440+
441+
By("running reconcile")
442+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
443+
Expect(res).To(Equal(ctrl.Result{}))
444+
Expect(err).NotTo(HaveOccurred())
445+
446+
By("fetching the updated operator after reconcile")
447+
op := &operatorsv1alpha1.Operator{}
448+
err = cl.Get(ctx, opKey, op)
449+
Expect(err).NotTo(HaveOccurred())
450+
451+
By("checking the expected conditions")
452+
cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady)
453+
Expect(cond).NotTo(BeNil())
454+
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
455+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
456+
Expect(cond.Message).To(ContainSubstring(`resolution was successful`))
457+
})
458+
459+
It("verify any other unknown status of bundledeployment", func() {
460+
err := cl.Get(ctx, opKey, &bd)
461+
Expect(err).NotTo(HaveOccurred())
462+
463+
apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{
464+
Type: rukpakv1alpha1.TypeHasValidBundle,
465+
Status: metav1.ConditionUnknown,
466+
Message: "unpacking",
467+
Reason: rukpakv1alpha1.ReasonUnpackSuccessful,
468+
})
469+
470+
apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{
471+
Type: rukpakv1alpha1.TypeInstalled,
472+
Status: metav1.ConditionUnknown,
473+
Message: "installing",
474+
Reason: rukpakv1alpha1.ReasonInstallationSucceeded,
475+
})
476+
477+
By("updating the status of bundleDeployment")
478+
err = cl.Status().Update(ctx, &bd)
479+
Expect(err).NotTo(HaveOccurred())
480+
481+
By("running reconcile")
482+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
483+
Expect(res).To(Equal(ctrl.Result{}))
484+
Expect(err).NotTo(HaveOccurred())
485+
486+
By("fetching the updated operator after reconcile")
487+
op := &operatorsv1alpha1.Operator{}
488+
err = cl.Get(ctx, opKey, op)
489+
Expect(err).NotTo(HaveOccurred())
490+
491+
By("checking the expected conditions")
492+
cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady)
493+
Expect(cond).NotTo(BeNil())
494+
Expect(cond.Status).To(Equal(metav1.ConditionUnknown))
495+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
496+
Expect(cond.Message).To(ContainSubstring(`waiting for rukpak to install bundleDeployment successfully`))
497+
})
498+
499+
It("verify operator status when bundleDeployment installation status is unknown", func() {
500+
err := cl.Get(ctx, opKey, &bd)
501+
Expect(err).NotTo(HaveOccurred())
502+
503+
apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{
504+
Type: rukpakv1alpha1.TypeInstalled,
505+
Status: metav1.ConditionUnknown,
506+
Message: "installing",
507+
Reason: rukpakv1alpha1.ReasonInstallationSucceeded,
508+
})
509+
510+
By("updating the status of bundleDeployment")
511+
err = cl.Status().Update(ctx, &bd)
512+
Expect(err).NotTo(HaveOccurred())
513+
514+
By("running reconcile")
515+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
516+
Expect(res).To(Equal(ctrl.Result{}))
517+
Expect(err).NotTo(HaveOccurred())
518+
519+
By("fetching the updated operator after reconcile")
520+
op := &operatorsv1alpha1.Operator{}
521+
err = cl.Get(ctx, opKey, op)
522+
Expect(err).NotTo(HaveOccurred())
523+
524+
By("checking the expected conditions")
525+
cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady)
526+
Expect(cond).NotTo(BeNil())
527+
Expect(cond.Status).To(Equal(metav1.ConditionUnknown))
528+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionSucceeded))
529+
Expect(cond.Message).To(ContainSubstring(`waiting for rukpak to install bundleDeployment successfully`))
364530
})
365531
})
366532
AfterEach(func() {

0 commit comments

Comments
 (0)