Skip to content

Commit bfda1b6

Browse files
carboninopenshift-merge-bot[bot]
authored andcommitted
Allow ICI status to be restored
Adds the restore status annotation to ICI CRs and also waits for the status to be added to resources that are created by a restore operation. Because the spec and status need to be set by two different API calls to the API server the controller needs to know when to wait for the status to be set before initializing the conditions. Initializing the conditions in a restore scenario can lead to a conflict where the restore fails and the old status doesn't get set. https://issues.redhat.com/browse/MGMT-22276
1 parent dc3de54 commit bfda1b6

2 files changed

Lines changed: 98 additions & 22 deletions

File tree

controllers/imageclusterinstall_controller.go

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -91,28 +91,31 @@ type imagePullSecret struct {
9191
}
9292

9393
const (
94-
detachedAnnotation = "baremetalhost.metal3.io/detached"
95-
detachedAnnotationValue = "imageclusterinstall-controller"
96-
inspectAnnotation = "inspect.metal3.io"
97-
rebootAnnotation = "reboot.metal3.io"
98-
rebootAnnotationValue = ""
99-
ibioManagedBMH = "image-based-install-managed"
100-
ClusterConfigDir = "cluster-configuration"
101-
extraManifestsDir = "extra-manifests"
102-
nmstateSecretKey = "nmstate"
103-
clusterInstallFinalizerName = "imageclusterinstall." + v1alpha1.Group + "/deprovision"
104-
caBundleFileName = "tls-ca-bundle.pem"
105-
imageBasedInstallInvoker = "image-based-install"
106-
invokerCMFileName = "invoker-cm.yaml"
107-
installTimeoutAnnotation = "imageclusterinstall." + v1alpha1.Group + "/install-timeout"
108-
backupLabel = "cluster.open-cluster-management.io/backup"
109-
backupLabelValue = "true"
110-
imageBasedConfigFilename = "image-based-config.yaml"
111-
installConfigFilename = "install-config.yaml"
112-
authDir = "auth"
113-
kubeAdminFile = "kubeadmin-password"
114-
FilesDir = "files"
115-
IsoName = "imagebasedconfig.iso"
94+
detachedAnnotation = "baremetalhost.metal3.io/detached"
95+
detachedAnnotationValue = "imageclusterinstall-controller"
96+
inspectAnnotation = "inspect.metal3.io"
97+
rebootAnnotation = "reboot.metal3.io"
98+
rebootAnnotationValue = ""
99+
ibioManagedBMH = "image-based-install-managed"
100+
ClusterConfigDir = "cluster-configuration"
101+
extraManifestsDir = "extra-manifests"
102+
nmstateSecretKey = "nmstate"
103+
clusterInstallFinalizerName = "imageclusterinstall." + v1alpha1.Group + "/deprovision"
104+
caBundleFileName = "tls-ca-bundle.pem"
105+
imageBasedInstallInvoker = "image-based-install"
106+
invokerCMFileName = "invoker-cm.yaml"
107+
installTimeoutAnnotation = "imageclusterinstall." + v1alpha1.Group + "/install-timeout"
108+
backupLabel = "cluster.open-cluster-management.io/backup"
109+
backupLabelValue = "true"
110+
imageBasedConfigFilename = "image-based-config.yaml"
111+
installConfigFilename = "install-config.yaml"
112+
authDir = "auth"
113+
kubeAdminFile = "kubeadmin-password"
114+
FilesDir = "files"
115+
IsoName = "imagebasedconfig.iso"
116+
restoreStatusAnnotation = "velero.io/restore-status"
117+
restoreStatusAnnotationValue = "true"
118+
restoreSourceLabel = "velero.io/restore-name"
116119
)
117120

118121
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
@@ -164,6 +167,16 @@ func (r *ImageClusterInstallReconciler) Reconcile(ctx context.Context, req ctrl.
164167
log.Info("Running reconcile for ici with bootTime set")
165168
}
166169

170+
if iciIsBeingRestored(ici) {
171+
log.Info("Waiting for status restore")
172+
return ctrl.Result{}, nil
173+
}
174+
175+
if err := r.ensureRestoreStatusAnnotation(ctx, ici); err != nil {
176+
log.Errorf("Failed to ensure restore status annotation: %s", err)
177+
return ctrl.Result{}, err
178+
}
179+
167180
if err := r.initializeConditions(ctx, ici); err != nil {
168181
log.Errorf("Failed to initialize conditions: %s", err)
169182
return ctrl.Result{}, err
@@ -1457,3 +1470,21 @@ func verifyIsoAndAuthExists(clusterConfigPath string) bool {
14571470

14581471
return true
14591472
}
1473+
1474+
// iciIsBeingRestored returns true if the status is completely empty and the velero restore source label is present
1475+
func iciIsBeingRestored(ici *v1alpha1.ImageClusterInstall) bool {
1476+
_, hasLabel := ici.ObjectMeta.Labels[restoreSourceLabel]
1477+
return hasLabel &&
1478+
len(ici.Status.Conditions) == 0 &&
1479+
ici.Status.InstallRestarts == 0 &&
1480+
ici.Status.BareMetalHostRef == nil &&
1481+
ici.Status.BootTime.IsZero()
1482+
}
1483+
1484+
func (r *ImageClusterInstallReconciler) ensureRestoreStatusAnnotation(ctx context.Context, ici *v1alpha1.ImageClusterInstall) error {
1485+
patch := client.MergeFrom(ici.DeepCopy())
1486+
if !setAnnotationIfNotExists(&ici.ObjectMeta, restoreStatusAnnotation, restoreStatusAnnotationValue) {
1487+
return nil
1488+
}
1489+
return r.Patch(ctx, ici, patch)
1490+
}

controllers/imageclusterinstall_controller_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,51 @@ var _ = Describe("Reconcile", func() {
566566
Expect(res).To(Equal(ctrl.Result{}))
567567
})
568568

569+
It("sets restore status annotation on image cluster install", func() {
570+
Expect(c.Create(ctx, clusterInstall)).To(Succeed())
571+
Expect(c.Create(ctx, clusterDeployment)).To(Succeed())
572+
573+
key := types.NamespacedName{
574+
Namespace: clusterInstallNamespace,
575+
Name: clusterInstallName,
576+
}
577+
installerSuccess()
578+
res, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key})
579+
Expect(err).NotTo(HaveOccurred())
580+
Expect(res).To(Equal(ctrl.Result{}))
581+
582+
// Verify the restore status annotation was set
583+
updatedICI := &v1alpha1.ImageClusterInstall{}
584+
Expect(c.Get(ctx, key, updatedICI)).To(Succeed())
585+
Expect(updatedICI.Annotations).To(HaveKey(restoreStatusAnnotation))
586+
Expect(updatedICI.Annotations[restoreStatusAnnotation]).To(Equal(restoreStatusAnnotationValue))
587+
})
588+
589+
It("stops reconcile early when status is empty and restore name label is set", func() {
590+
clusterInstall.ObjectMeta.Labels = map[string]string{
591+
restoreSourceLabel: "test-restore",
592+
}
593+
Expect(c.Create(ctx, clusterInstall)).To(Succeed())
594+
595+
key := types.NamespacedName{
596+
Namespace: clusterInstallNamespace,
597+
Name: clusterInstallName,
598+
}
599+
600+
// Reconcile should stop early - no installer methods should be called
601+
res, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key})
602+
Expect(err).NotTo(HaveOccurred())
603+
Expect(res).To(Equal(ctrl.Result{}))
604+
605+
// Verify the status remains empty (reconcile stopped early)
606+
updatedICI := &v1alpha1.ImageClusterInstall{}
607+
Expect(c.Get(ctx, key, updatedICI)).To(Succeed())
608+
Expect(len(updatedICI.Status.Conditions)).To(Equal(0))
609+
Expect(updatedICI.Status.InstallRestarts).To(Equal(0))
610+
Expect(updatedICI.Status.BareMetalHostRef).To(BeNil())
611+
Expect(updatedICI.Status.BootTime.IsZero()).To(BeTrue())
612+
})
613+
569614
It("creates the ca bundle", func() {
570615
caData := map[string]string{caBundleFileName: "mycabundle"}
571616
cm := &corev1.ConfigMap{

0 commit comments

Comments
 (0)