From 7e1d6a798919d7bf12e29c0a5b27875acc547dc3 Mon Sep 17 00:00:00 2001 From: Gilad Ravid Date: Wed, 24 Jun 2026 12:15:28 +0300 Subject: [PATCH] MGMT-24419: Delete DataImage after cluster is installed and remove detached annotation Having the detached annotation prevents users from performing hardware lifecycle operations, and keeping the DataImage causes the BMO to remount it after firmware updates. So we want to remove both of them. --- controllers/common.go | 126 ++++++++++++++++++ controllers/imageclusterinstall_controller.go | 110 +-------------- controllers/imageclusterinstall_monitor.go | 62 ++++++--- .../imageclusterinstall_monitor_test.go | 96 ++++++++++++- 4 files changed, 265 insertions(+), 129 deletions(-) create mode 100644 controllers/common.go diff --git a/controllers/common.go b/controllers/common.go new file mode 100644 index 000000000..eabbbafc5 --- /dev/null +++ b/controllers/common.go @@ -0,0 +1,126 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + + k8sapierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + bmh_v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" + "github.com/sirupsen/logrus" + + "github.com/openshift/image-based-install-operator/api/v1alpha1" +) + +func getDataImage(ctx context.Context, c client.Client, namespace, name string) (*bmh_v1alpha1.DataImage, error) { + dataImage := &bmh_v1alpha1.DataImage{} + key := types.NamespacedName{Name: name, Namespace: namespace} + if err := c.Get(ctx, key, dataImage); err != nil { + return nil, err + } + return dataImage, nil +} + +func deleteDataImage(ctx context.Context, c client.Client, log logrus.FieldLogger, dataImageRef types.NamespacedName) (*bmh_v1alpha1.DataImage, error) { + dataImage := &bmh_v1alpha1.DataImage{} + if err := c.Get(ctx, dataImageRef, dataImage); err != nil { + if k8sapierrors.IsNotFound(err) { + log.Infof("Can't find DataImage from BareMetalHost %s/%s, Nothing to remove", dataImageRef.Namespace, dataImageRef.Name) + return nil, nil + } + return nil, fmt.Errorf("failed to get DataImage %s/%s: %w", dataImageRef.Namespace, dataImageRef.Name, err) + } + + log.Infof("Deleting DataImage %s/%s, deletion may take some time since a BMH restart is required", dataImageRef.Namespace, dataImageRef.Name) + if err := c.Delete(ctx, dataImage); err != nil { + return dataImage, err + } + return dataImage, nil +} + +func removeBMHDataImage(ctx context.Context, c client.Client, log logrus.FieldLogger, bmhRef types.NamespacedName) (*bmh_v1alpha1.DataImage, error) { + dataImage, err := deleteDataImage(ctx, c, log, bmhRef) + if err != nil || dataImage == nil { + return dataImage, err + } + + bmh := &bmh_v1alpha1.BareMetalHost{} + if err := c.Get(ctx, bmhRef, bmh); err != nil { + if k8sapierrors.IsNotFound(err) { + log.Warnf("Referenced BareMetalHost %s/%s does not exist, not waiting for dataImage deletion", bmhRef.Namespace, bmhRef.Name) + return nil, nil + } + return dataImage, fmt.Errorf("failed to get BareMetalHost %s/%s: %w", bmhRef.Namespace, bmhRef.Name, err) + } + return dataImage, attachAndRebootBMH(ctx, c, log, bmh) +} + +func attachAndRebootBMH(ctx context.Context, c client.Client, log logrus.FieldLogger, bmh *bmh_v1alpha1.BareMetalHost) error { + patch := client.MergeFrom(bmh.DeepCopy()) + dirty := false + if annotationExists(&bmh.ObjectMeta, detachedAnnotation) { + log.Infof("Removing Detached annotation if exists on BareMetalHost %s/%s", bmh.Namespace, bmh.Name) + delete(bmh.ObjectMeta.Annotations, detachedAnnotation) + dirty = true + } + + if setAnnotationIfNotExists(&bmh.ObjectMeta, rebootAnnotation, rebootAnnotationValue) { + log.Infof("Adding reboot annotation to BareMetalHost %s/%s", bmh.Namespace, bmh.Name) + dirty = true + } + if dirty { + return c.Patch(ctx, bmh, patch) + } + return nil +} + +func getBMH(ctx context.Context, c client.Client, bmhRef *v1alpha1.BareMetalHostReference) (*bmh_v1alpha1.BareMetalHost, error) { + bmh := &bmh_v1alpha1.BareMetalHost{} + key := types.NamespacedName{ + Name: bmhRef.Name, + Namespace: bmhRef.Namespace, + } + if err := c.Get(ctx, key, bmh); err != nil { + return nil, err + } + + return bmh, nil +} + +func setAnnotationIfNotExists(meta *metav1.ObjectMeta, key string, value string) bool { + if meta.Annotations == nil { + meta.Annotations = make(map[string]string) + } + if _, ok := meta.Annotations[key]; !ok { + meta.Annotations[key] = value + return true + } + return false +} + +func annotationExists(meta *metav1.ObjectMeta, key string) bool { + if meta.Annotations == nil { + return false + } + _, ok := meta.Annotations[key] + return ok +} diff --git a/controllers/imageclusterinstall_controller.go b/controllers/imageclusterinstall_controller.go index c16358480..f84a9e279 100644 --- a/controllers/imageclusterinstall_controller.go +++ b/controllers/imageclusterinstall_controller.go @@ -292,7 +292,7 @@ func (r *ImageClusterInstallReconciler) validateConfiguration( return nil, nil } - bmh, err := r.getBMH(ctx, ici.Spec.BareMetalHostRef) + bmh, err := getBMH(ctx, r.Client, ici.Spec.BareMetalHostRef) if err != nil { cond.Message = fmt.Sprintf("failed to get BareMetalHost %s/%s", ici.Spec.BareMetalHostRef.Namespace, ici.Spec.BareMetalHostRef.Name) log.Error(err) @@ -702,16 +702,6 @@ func (r *ImageClusterInstallReconciler) addIndexforBaremetalHostRef(mgr ctrl.Man return nil } -func (r *ImageClusterInstallReconciler) getDataImage(ctx context.Context, namespace, name string) (*bmh_v1alpha1.DataImage, error) { - dataImage := bmh_v1alpha1.DataImage{} - key := client.ObjectKey{ - Name: name, - Namespace: namespace, - } - err := r.Get(ctx, key, &dataImage) - return &dataImage, err -} - func isInspectionEnabled(bmh *bmh_v1alpha1.BareMetalHost) bool { if bmh.ObjectMeta.Annotations != nil && bmh.ObjectMeta.Annotations[inspectAnnotation] != "disabled" { return true @@ -755,7 +745,7 @@ func (r *ImageClusterInstallReconciler) ensureBMHDataImage( log logrus.FieldLogger, bmh *bmh_v1alpha1.BareMetalHost, url string) (*bmh_v1alpha1.DataImage, ctrl.Result, error) { - dataImage, err := r.getDataImage(ctx, bmh.Namespace, bmh.Name) + dataImage, err := getDataImage(ctx, r.Client, bmh.Namespace, bmh.Name) if err == nil { if !dataImage.ObjectMeta.DeletionTimestamp.IsZero() { log.Errorf("dataImage %s/%s already exists but is being deleted, probably leftover from previous installation", bmh.Namespace, bmh.Name) @@ -789,7 +779,7 @@ func (r *ImageClusterInstallReconciler) ensureBMHDataImage( return dataImage, ctrl.Result{}, fmt.Errorf("failed to create dataImage due to %w", err) } - dataImage, err = r.getDataImage(ctx, bmh.Namespace, bmh.Name) + dataImage, err = getDataImage(ctx, r.Client, bmh.Namespace, bmh.Name) return dataImage, ctrl.Result{}, err } @@ -805,75 +795,6 @@ func (r *ImageClusterInstallReconciler) getCD(ctx context.Context, ici *v1alpha1 return clusterDeployment, nil } -func (r *ImageClusterInstallReconciler) getBMH(ctx context.Context, bmhRef *v1alpha1.BareMetalHostReference) (*bmh_v1alpha1.BareMetalHost, error) { - bmh := &bmh_v1alpha1.BareMetalHost{} - key := types.NamespacedName{ - Name: bmhRef.Name, - Namespace: bmhRef.Namespace, - } - if err := r.Get(ctx, key, bmh); err != nil { - return nil, err - } - - return bmh, nil -} - -func (r *ImageClusterInstallReconciler) removeBMHDataImage(ctx context.Context, log logrus.FieldLogger, bmhRef types.NamespacedName) (*bmh_v1alpha1.DataImage, error) { - dataImage, err := r.deleteDataImage(ctx, log, bmhRef) - if err != nil || dataImage == nil { - return dataImage, err - } - - bmh := &bmh_v1alpha1.BareMetalHost{} - if err := r.Get(ctx, bmhRef, bmh); err != nil { - if k8sapierrors.IsNotFound(err) { - log.Warnf("Referenced BareMetalHost %s/%s does not exist, not waiting for dataImage deletion", bmhRef.Namespace, bmhRef.Name) - return nil, nil - } else { - return dataImage, fmt.Errorf("failed to get BareMetalHost %s/%s: %w", bmhRef.Namespace, bmhRef.Name, err) - } - } - return dataImage, r.attachAndRebootBMH(ctx, log, bmh) -} - -func (r *ImageClusterInstallReconciler) attachAndRebootBMH(ctx context.Context, log logrus.FieldLogger, bmh *bmh_v1alpha1.BareMetalHost) error { - patch := client.MergeFrom(bmh.DeepCopy()) - dirty := false - if annotationExists(&bmh.ObjectMeta, detachedAnnotation) { - log.Infof("Removing Detached annotation if exists on BareMetalHost %s/%s", bmh.Namespace, bmh.Name) - delete(bmh.ObjectMeta.Annotations, detachedAnnotation) - dirty = true - } - - if setAnnotationIfNotExists(&bmh.ObjectMeta, rebootAnnotation, rebootAnnotationValue) { - log.Infof("Adding reboot annotation to BareMetalHost %s/%s", bmh.Namespace, bmh.Name) - dirty = true - } - if dirty { - return r.Patch(ctx, bmh, patch) - - } - return nil -} - -func (r *ImageClusterInstallReconciler) deleteDataImage(ctx context.Context, log logrus.FieldLogger, dataImageRef types.NamespacedName) (*bmh_v1alpha1.DataImage, error) { - dataImage := &bmh_v1alpha1.DataImage{} - - if err := r.Get(ctx, dataImageRef, dataImage); err != nil { - if k8sapierrors.IsNotFound(err) { - log.Infof("Can't find DataImage from BareMetalHost %s/%s, Nothing to remove", dataImageRef.Namespace, dataImageRef.Name) - return nil, nil - } - return nil, fmt.Errorf("failed to get DataImage %s/%s: %w", dataImageRef.Namespace, dataImageRef.Name, err) - } - - log.Infof("Deleting DataImage %s/%s, deletion may take some time since a BMH restart is required", dataImageRef.Namespace, dataImageRef.Name) - if err := r.Client.Delete(ctx, dataImage); err != nil { - return dataImage, err - } - return dataImage, nil -} - func setBackupLabel(obj client.Object) bool { labels := obj.GetLabels() if labels == nil { @@ -915,7 +836,7 @@ func (r *ImageClusterInstallReconciler) labelSecretForBackup(ctx context.Context } func (r *ImageClusterInstallReconciler) labelBMHForBackup(ctx context.Context, bmhRef *v1alpha1.BareMetalHostReference) error { - bmh, err := r.getBMH(ctx, bmhRef) + bmh, err := getBMH(ctx, r.Client, bmhRef) if err != nil { return err } @@ -928,7 +849,7 @@ func (r *ImageClusterInstallReconciler) labelBMHForBackup(ctx context.Context, b } func (r *ImageClusterInstallReconciler) labelDataImageForBackup(ctx context.Context, bmhRef *v1alpha1.BareMetalHostReference) error { - dataImage, err := r.getDataImage(ctx, bmhRef.Namespace, bmhRef.Name) + dataImage, err := getDataImage(ctx, r.Client, bmhRef.Namespace, bmhRef.Name) if err != nil { return err } @@ -1418,7 +1339,7 @@ func (r *ImageClusterInstallReconciler) handleFinalizer(ctx context.Context, log Namespace: ici.Spec.BareMetalHostRef.Namespace, } - dataImage, err := r.removeBMHDataImage(ctx, log, key) + dataImage, err := removeBMHDataImage(ctx, r.Client, log, key) if err != nil { return ctrl.Result{}, true, fmt.Errorf("failed to delete DataImage %s/%s: %w", key.Namespace, key.Name, err) } @@ -1476,25 +1397,6 @@ func (r *ImageClusterInstallReconciler) writeIBIOStartTimeCM(filePath string) er return nil } -func setAnnotationIfNotExists(meta *metav1.ObjectMeta, key string, value string) bool { - if meta.Annotations == nil { - meta.Annotations = make(map[string]string) - } - if _, ok := meta.Annotations[key]; !ok { - meta.Annotations[key] = value - return true - } - return false -} - -func annotationExists(meta *metav1.ObjectMeta, key string) bool { - if meta.Annotations == nil { - return false - } - _, ok := meta.Annotations[key] - return ok -} - func ipInCidr(ipAddr, cidr string) (bool, error) { _, ipNet, err := net.ParseCIDR(cidr) if err != nil { diff --git a/controllers/imageclusterinstall_monitor.go b/controllers/imageclusterinstall_monitor.go index 9e80a449e..79614d4a1 100644 --- a/controllers/imageclusterinstall_monitor.go +++ b/controllers/imageclusterinstall_monitor.go @@ -26,6 +26,7 @@ import ( "time" corev1 "k8s.io/api/core/v1" + k8sapierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -38,9 +39,10 @@ import ( bmh_v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" apicfgv1 "github.com/openshift/api/config/v1" + "github.com/sirupsen/logrus" + "github.com/openshift/image-based-install-operator/api/v1alpha1" "github.com/openshift/image-based-install-operator/internal/monitor" - "github.com/sirupsen/logrus" ) // ImageClusterInstallMonitor reconciles a ImageClusterInstall object @@ -57,6 +59,11 @@ type ImageClusterInstallMonitor struct { //+kubebuilder:rbac:groups=extensions.hive.openshift.io,resources=imageclusterinstalls,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=extensions.hive.openshift.io,resources=imageclusterinstalls/status,verbs=get;update;patch //+kubebuilder:rbac:groups=metal3.io,resources=baremetalhosts,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=metal3.io,resources=dataimages,verbs=get;list;watch;delete + +const ( + dataImageRemovalPendingMessage = "Waiting for DataImage to be deleted" +) func (r *ImageClusterInstallMonitor) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithFields(logrus.Fields{"name": req.Name, "namespace": req.Namespace}) @@ -85,7 +92,7 @@ func (r *ImageClusterInstallMonitor) monitorInstallationProgress( log logrus.FieldLogger, ici *v1alpha1.ImageClusterInstall) (ctrl.Result, error) { - bmh, err := r.getBMH(ctx, ici.Status.BareMetalHostRef) + bmh, err := getBMH(ctx, r.Client, ici.Status.BareMetalHostRef) if err != nil { log.WithError(err).Error("failed to get BareMetalHost") return ctrl.Result{}, err @@ -143,6 +150,13 @@ func (r *ImageClusterInstallMonitor) checkClusterStatus(ctx context.Context, log logrus.FieldLogger, ici *v1alpha1.ImageClusterInstall, bmh *bmh_v1alpha1.BareMetalHost) (ctrl.Result, error) { + bmhRef := types.NamespacedName{Name: bmh.Name, Namespace: bmh.Namespace} + + res, stop, err := r.handleDataImageDeletion(ctx, log, ici, bmhRef) + if stop || err != nil { + return res, err + } + spokeClient, err := r.spokeClient(ctx, ici) if err != nil { log.WithError(err).Error("failed to create spoke client") @@ -167,14 +181,15 @@ func (r *ImageClusterInstallMonitor) checkClusterStatus(ctx context.Context, } log.Info("cluster is installed") - // After installation ended we don't want that ironic will do any changes in the node - patch := client.MergeFrom(bmh.DeepCopy()) - if setAnnotationIfNotExists(&bmh.ObjectMeta, detachedAnnotation, detachedAnnotationValue) { - log.Infof("Adding detached annotations to BareMetalHost (%s/%s)", bmh.Name, bmh.Namespace) - if err := r.Patch(ctx, bmh, patch); err != nil { - return ctrl.Result{}, err - } + if _, err = removeBMHDataImage(ctx, r.Client, log, bmhRef); err != nil { + log.WithError(err).Error("failed to delete DataImage") + return ctrl.Result{}, err + } + res, stop, err = r.handleDataImageDeletion(ctx, log, ici, bmhRef) + if stop || err != nil { + return res, err } + if err := r.setClusterInstalledConditions(ctx, ici); err != nil { log.WithError(err).Error("failed to set installed conditions") return ctrl.Result{}, err @@ -182,6 +197,22 @@ func (r *ImageClusterInstallMonitor) checkClusterStatus(ctx context.Context, return ctrl.Result{}, nil } +func (r *ImageClusterInstallMonitor) handleDataImageDeletion(ctx context.Context, log logrus.FieldLogger, ici *v1alpha1.ImageClusterInstall, bmhRef types.NamespacedName) (ctrl.Result, bool, error) { + dataImage, err := getDataImage(ctx, r.Client, bmhRef.Namespace, bmhRef.Name) + if err != nil && !k8sapierrors.IsNotFound(err) { + log.WithError(err).Error("failed to get DataImage") + return ctrl.Result{}, true, err + } + if dataImage != nil && !dataImage.DeletionTimestamp.IsZero() { + log.Infof("Waiting for DataImage %s/%s to be deleted", bmhRef.Namespace, bmhRef.Name) + if err := r.setClusterInstallingConditions(ctx, ici, dataImageRemovalPendingMessage); err != nil { + log.WithError(err).Error("failed to set installing conditions") + } + return ctrl.Result{RequeueAfter: time.Minute}, true, nil + } + return ctrl.Result{}, false, nil +} + func (r *ImageClusterInstallMonitor) spokeClient(ctx context.Context, ici *v1alpha1.ImageClusterInstall) (client.Client, error) { if ici.Spec.ClusterMetadata == nil || ici.Spec.ClusterMetadata.AdminKubeconfigSecretRef.Name == "" { return nil, fmt.Errorf("kubeconfig secret must be set to get spoke client") @@ -254,16 +285,3 @@ func (r *ImageClusterInstallMonitor) SetupWithManager(mgr ctrl.Manager) error { WithEventFilter(bootTimeInitialized). Complete(r) } - -func (r *ImageClusterInstallMonitor) getBMH(ctx context.Context, bmhRef *v1alpha1.BareMetalHostReference) (*bmh_v1alpha1.BareMetalHost, error) { - bmh := &bmh_v1alpha1.BareMetalHost{} - key := types.NamespacedName{ - Name: bmhRef.Name, - Namespace: bmhRef.Namespace, - } - if err := r.Get(ctx, key, bmh); err != nil { - return nil, err - } - - return bmh, nil -} diff --git a/controllers/imageclusterinstall_monitor_test.go b/controllers/imageclusterinstall_monitor_test.go index c06bcba85..f0a5015ad 100644 --- a/controllers/imageclusterinstall_monitor_test.go +++ b/controllers/imageclusterinstall_monitor_test.go @@ -18,10 +18,11 @@ import ( bmh_v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/sirupsen/logrus" + "github.com/openshift/image-based-install-operator/api/v1alpha1" "github.com/openshift/image-based-install-operator/internal/credentials" "github.com/openshift/image-based-install-operator/internal/monitor" - "github.com/sirupsen/logrus" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -163,6 +164,16 @@ var _ = Describe("Monitor", func() { It("sets conditions to cluster installed when the BMH is managed and cluster is ready", func() { r.GetSpokeClusterInstallStatus = monitor.SuccessMonitor + dataImage := &bmh_v1alpha1.DataImage{ + ObjectMeta: metav1.ObjectMeta{ + Name: bmh.Name, + Namespace: bmh.Namespace, + }, + Spec: bmh_v1alpha1.DataImageSpec{ + URL: "https://example.com/config.iso", + }, + } + Expect(c.Create(ctx, dataImage)).To(Succeed()) Expect(c.Create(ctx, clusterInstall)).To(Succeed()) Expect(c.Create(ctx, clusterDeployment)).To(Succeed()) @@ -186,9 +197,12 @@ var _ = Describe("Monitor", func() { Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(corev1.ConditionTrue)) - By("Verify BMH was detached") + By("Verify DataImage was removed") + Expect(c.Get(ctx, types.NamespacedName{Namespace: bmh.Namespace, Name: bmh.Name}, &bmh_v1alpha1.DataImage{})).NotTo(Succeed()) + + By("Verify BMH was rebooted to complete DataImage removal") Expect(c.Get(ctx, types.NamespacedName{Namespace: bmh.Namespace, Name: bmh.Name}, bmh)).To(Succeed()) - Expect(bmh.Annotations[detachedAnnotation]).To(Equal(detachedAnnotationValue)) + Expect(bmh.Annotations).To(HaveKey(rebootAnnotation)) By("Verify that clusterInstall was not updated on second run") resourceVersion := clusterInstall.ResourceVersion @@ -208,6 +222,82 @@ var _ = Describe("Monitor", func() { Expect(bmh.ObjectMeta.ResourceVersion).To(Equal(resourceVersion)) }) + It("waits for DataImage deletion before reporting cluster installed", func() { + r.GetSpokeClusterInstallStatus = monitor.SuccessMonitor + dataImage := &bmh_v1alpha1.DataImage{ + ObjectMeta: metav1.ObjectMeta{ + Name: bmh.Name, + Namespace: bmh.Namespace, + Finalizers: []string{bmh_v1alpha1.DataImageFinalizer}, + }, + Spec: bmh_v1alpha1.DataImageSpec{ + URL: "https://example.com/config.iso", + }, + } + Expect(c.Create(ctx, dataImage)).To(Succeed()) + Expect(c.Delete(ctx, dataImage)).To(Succeed()) + Expect(c.Create(ctx, clusterInstall)).To(Succeed()) + Expect(c.Create(ctx, clusterDeployment)).To(Succeed()) + + key := types.NamespacedName{ + Namespace: clusterInstallNamespace, + Name: clusterInstallName, + } + res, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key}) + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(ctrl.Result{RequeueAfter: time.Minute})) + + Expect(c.Get(ctx, key, clusterInstall)).To(Succeed()) + cond := findCondition(clusterInstall.Status.Conditions, hivev1.ClusterInstallStopped) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(corev1.ConditionFalse)) + Expect(cond.Message).To(Equal(dataImageRemovalPendingMessage)) + cond = findCondition(clusterInstall.Status.Conditions, hivev1.ClusterInstallCompleted) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(corev1.ConditionFalse)) + + By("Verify DataImage still exists while deletion is in progress") + Expect(c.Get(ctx, types.NamespacedName{Namespace: bmh.Namespace, Name: bmh.Name}, dataImage)).To(Succeed()) + }) + + It("reports DataImage removal while waiting for deletion after cluster install", func() { + r.GetSpokeClusterInstallStatus = monitor.SuccessMonitor + dataImage := &bmh_v1alpha1.DataImage{ + ObjectMeta: metav1.ObjectMeta{ + Name: bmh.Name, + Namespace: bmh.Namespace, + Finalizers: []string{bmh_v1alpha1.DataImageFinalizer}, + }, + Spec: bmh_v1alpha1.DataImageSpec{ + URL: "https://example.com/config.iso", + }, + } + Expect(c.Create(ctx, dataImage)).To(Succeed()) + Expect(c.Create(ctx, clusterInstall)).To(Succeed()) + Expect(c.Create(ctx, clusterDeployment)).To(Succeed()) + + key := types.NamespacedName{ + Namespace: clusterInstallNamespace, + Name: clusterInstallName, + } + res, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key}) + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(ctrl.Result{RequeueAfter: time.Minute})) + + Expect(c.Get(ctx, key, clusterInstall)).To(Succeed()) + cond := findCondition(clusterInstall.Status.Conditions, hivev1.ClusterInstallStopped) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(corev1.ConditionFalse)) + Expect(cond.Message).To(Equal(dataImageRemovalPendingMessage)) + cond = findCondition(clusterInstall.Status.Conditions, hivev1.ClusterInstallCompleted) + Expect(cond).NotTo(BeNil()) + Expect(cond.Status).To(Equal(corev1.ConditionFalse)) + + By("Verify DataImage deletion was initiated") + Expect(c.Get(ctx, types.NamespacedName{Namespace: bmh.Namespace, Name: bmh.Name}, dataImage)).To(Succeed()) + Expect(dataImage.DeletionTimestamp).NotTo(BeNil()) + }) + It("requeues and sets conditions when spoke cluster is not ready yet", func() { r.GetSpokeClusterInstallStatus = monitor.FailureMonitor