diff --git a/controllers/operator_controller.go b/controllers/operator_controller.go index 8492bb8df..e89968497 100644 --- a/controllers/operator_controller.go +++ b/controllers/operator_controller.go @@ -294,10 +294,6 @@ func verifyBDStatus(dep *rukpakv1alpha1.BundleDeployment) (metav1.ConditionStatu isValidBundleCond := apimeta.FindStatusCondition(dep.Status.Conditions, rukpakv1alpha1.TypeHasValidBundle) isInstalledCond := apimeta.FindStatusCondition(dep.Status.Conditions, rukpakv1alpha1.TypeInstalled) - if isValidBundleCond == nil && isInstalledCond == nil { - return metav1.ConditionUnknown, fmt.Sprintf("waiting for bundleDeployment %q status to be updated", dep.Name) - } - if isValidBundleCond != nil && isValidBundleCond.Status == metav1.ConditionFalse { return metav1.ConditionFalse, isValidBundleCond.Message } @@ -309,11 +305,21 @@ func verifyBDStatus(dep *rukpakv1alpha1.BundleDeployment) (metav1.ConditionStatu if isInstalledCond != nil && isInstalledCond.Status == metav1.ConditionTrue { return metav1.ConditionTrue, "install was successful" } - return metav1.ConditionUnknown, fmt.Sprintf("could not determine the state of bundleDeployment %s", dep.Name) + return metav1.ConditionUnknown, fmt.Sprintf("could not determine the state of BundleDeployment %s", dep.Name) } // mapBDStatusToReadyCondition returns the operator object's "TypeReady" condition based on the bundle deployment statuses. func mapBDStatusToReadyCondition(existingBD *rukpakv1alpha1.BundleDeployment, observedGeneration int64) metav1.Condition { + // if BundleDeployment status is stale, return an unknown condition. + if isBundleDepStale(existingBD) { + return metav1.Condition{ + Type: operatorsv1alpha1.TypeReady, + Status: metav1.ConditionUnknown, + Reason: operatorsv1alpha1.ReasonInstallationStatusUnknown, + Message: fmt.Sprintf("waiting for BundleDeployment %q status to be updated. BundleDeployment conditions out of date.", existingBD.Name), + ObservedGeneration: observedGeneration, + } + } // update operator status: // 1. If the Operator "Ready" status is "Unknown": The status of successful bundleDeployment is unknown, wait till Rukpak updates the BD status. // 2. If the Operator "Ready" status is "True": Update the "successful resolution" status and return the result. @@ -338,3 +344,8 @@ func mapBDStatusToReadyCondition(existingBD *rukpakv1alpha1.BundleDeployment, ob ObservedGeneration: observedGeneration, } } + +// isBundleDepStale returns true if conditions are out of date. +func isBundleDepStale(bd *rukpakv1alpha1.BundleDeployment) bool { + return bd != nil && bd.Status.ObservedGeneration != bd.GetGeneration() +} diff --git a/controllers/operator_controller_test.go b/controllers/operator_controller_test.go index f1764ca19..e2c0c7b6d 100644 --- a/controllers/operator_controller_test.go +++ b/controllers/operator_controller_test.go @@ -117,7 +117,7 @@ var _ = Describe("Reconcile Test", func() { Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(ContainSubstring("waiting for bundleDeployment")) + Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment")) }) }) When("the expected BundleDeployment already exists", func() { @@ -180,7 +180,7 @@ var _ = Describe("Reconcile Test", func() { Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(ContainSubstring("waiting for bundleDeployment")) + Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment")) }) }) When("an out-of-date BundleDeployment exists", func() { @@ -223,12 +223,12 @@ var _ = Describe("Reconcile Test", func() { Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) }) - It("sets resolution success status", func() { + It("sets resolution to unknown status", func() { cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(ContainSubstring("waiting for bundleDeployment")) + Expect(cond.Message).To(ContainSubstring("waiting for BundleDeployment")) }) }) }) @@ -348,180 +348,210 @@ var _ = Describe("Reconcile Test", func() { Expect(err).NotTo(HaveOccurred()) }) - It("verify operator status when bundle deployment is waiting to be created", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) + When("The BundleDeployment status is stale", func() { + It("sets operator ready condition to unknown when bundle deployment status is stale", func() { + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + Expect(res).To(Equal(ctrl.Result{})) + Expect(err).NotTo(HaveOccurred()) - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) + By("fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + Expect(err).NotTo(HaveOccurred()) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(ContainSubstring(`waiting for bundleDeployment`)) + By("checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) + Expect(cond.Message).To(Equal(fmt.Sprintf("waiting for BundleDeployment %q status to be updated. BundleDeployment conditions out of date.", bd.Name))) + }) }) - It("verify operator status when `HasValidBundle` condition of rukpak is false", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeHasValidBundle, - Status: metav1.ConditionFalse, - Message: "failed to unpack", - Reason: rukpakv1alpha1.ReasonUnpackFailed, + When("The BundleDeployment status is up-to-date", func() { + BeforeEach(func() { + bd.Status.ObservedGeneration = bd.GetGeneration() }) - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) + It("verify operator status when bundle deployment is waiting to be created", func() { + By("updating the status of bundleDeployment") + err := cl.Status().Update(ctx, bd) + Expect(err).NotTo(HaveOccurred()) - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) + By("running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + Expect(res).To(Equal(ctrl.Result{})) + Expect(err).NotTo(HaveOccurred()) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(ContainSubstring(`failed to unpack`)) - }) + By("fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + Expect(err).NotTo(HaveOccurred()) - It("verify operator status when `InstallReady` condition of rukpak is false", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionFalse, - Message: "failed to install", - Reason: rukpakv1alpha1.ReasonInstallFailed, + By("checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) + Expect(cond.Message).To(Equal(fmt.Sprintf("could not determine the state of BundleDeployment %s", bd.Name))) }) - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) + It("verify operator status when `HasValidBundle` condition of rukpak is false", func() { + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionFalse, + Message: "failed to unpack", + Reason: rukpakv1alpha1.ReasonUnpackFailed, + }) - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) + By("updating the status of bundleDeployment") + err := cl.Status().Update(ctx, bd) + Expect(err).NotTo(HaveOccurred()) - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) + By("running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + Expect(res).To(Equal(ctrl.Result{})) + Expect(err).NotTo(HaveOccurred()) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(ContainSubstring(`failed to install`)) - }) + By("fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + Expect(err).NotTo(HaveOccurred()) - It("verify operator status when `InstallReady` condition of rukpak is true", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionTrue, - Message: "operator installed successfully", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + By("checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) + Expect(cond.Message).To(ContainSubstring(`failed to unpack`)) }) - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) + It("verify operator status when `InstallReady` condition of rukpak is false", func() { + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionFalse, + Message: "failed to install", + Reason: rukpakv1alpha1.ReasonInstallFailed, + }) - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) + By("updating the status of bundleDeployment") + err := cl.Status().Update(ctx, bd) + Expect(err).NotTo(HaveOccurred()) - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) + By("running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + Expect(res).To(Equal(ctrl.Result{})) + Expect(err).NotTo(HaveOccurred()) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationSucceeded)) - Expect(cond.Message).To(ContainSubstring(`install was successful`)) - }) + By("fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + Expect(err).NotTo(HaveOccurred()) - It("verify any other unknown status of bundledeployment", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeHasValidBundle, - Status: metav1.ConditionUnknown, - Message: "unpacking", - Reason: rukpakv1alpha1.ReasonUnpackSuccessful, + By("checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionFalse)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) + Expect(cond.Message).To(ContainSubstring(`failed to install`)) }) - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionUnknown, - Message: "installing", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + It("verify operator status when `InstallReady` condition of rukpak is true", func() { + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionTrue, + Message: "operator installed successfully", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) + + By("updating the status of bundleDeployment") + err := cl.Status().Update(ctx, bd) + Expect(err).NotTo(HaveOccurred()) + + By("running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + Expect(res).To(Equal(ctrl.Result{})) + Expect(err).NotTo(HaveOccurred()) + + By("fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + Expect(err).NotTo(HaveOccurred()) + + By("checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationSucceeded)) + Expect(cond.Message).To(ContainSubstring(`install was successful`)) }) - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) + It("verify any other unknown status of bundledeployment", func() { + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionUnknown, + Message: "unpacking", + Reason: rukpakv1alpha1.ReasonUnpackSuccessful, + }) - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionUnknown, + Message: "installing", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) + By("updating the status of bundleDeployment") + err := cl.Status().Update(ctx, bd) + Expect(err).NotTo(HaveOccurred()) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(ContainSubstring(`could not determine the state of bundleDeployment`)) - }) + By("running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + Expect(res).To(Equal(ctrl.Result{})) + Expect(err).NotTo(HaveOccurred()) + + By("fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + Expect(err).NotTo(HaveOccurred()) - It("verify operator status when bundleDeployment installation status is unknown", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionUnknown, - Message: "installing", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + By("checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) + Expect(cond.Message).To(Equal(fmt.Sprintf("could not determine the state of BundleDeployment %s", bd.Name))) }) - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) + It("verify operator status when bundleDeployment installation status is unknown", func() { + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionUnknown, + Message: "installing", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) + By("updating the status of bundleDeployment") + err := cl.Status().Update(ctx, bd) + Expect(err).NotTo(HaveOccurred()) - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) + By("running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + Expect(res).To(Equal(ctrl.Result{})) + Expect(err).NotTo(HaveOccurred()) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(ContainSubstring(`could not determine the state of bundleDeployment`)) + By("fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + Expect(err).NotTo(HaveOccurred()) + + By("checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeReady) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) + Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) + Expect(cond.Message).To(Equal(fmt.Sprintf("could not determine the state of BundleDeployment %s", bd.Name))) + }) }) }) AfterEach(func() {