Skip to content

Commit 6e3e2dd

Browse files
committed
Force reinstall of operator resources on release version upgrade
Fixes upgrade failures from 0.4 to main caused by incompatible webhook configuration changes that trigger index out of range panics during manifest merging. When OPENSTACK_RELEASE_VERSION is bumped, the controller now: - Detects the version change by comparing against status.ReleaseVersion - Deletes all owned resources (deployments, services, serviceaccounts, configmaps) - Removes managed webhooks (validating and mutating configurations) - Requeues to recreate resources with new manifests This one-time cleanup ensures a clean slate for incompatible upgrades where the structure of resources (especially webhooks) has changed between versions. Adds ReleaseVersion field to OpenStackStatus to track the deployed version. Jira: OSPRH-23865
1 parent 68d2b07 commit 6e3e2dd

File tree

5 files changed

+146
-0
lines changed

5 files changed

+146
-0
lines changed

api/bases/operator.openstack.org_openstacks.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ spec:
164164
observedGeneration:
165165
format: int64
166166
type: integer
167+
releaseVersion:
168+
type: string
167169
totalOperatorCount:
168170
type: integer
169171
type: object

api/operator/v1beta1/openstack_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ type OpenStackStatus struct {
256256

257257
// ContainerImage - the container image that has been successfully deployed
258258
ContainerImage *string `json:"containerImage,omitempty"`
259+
260+
// ReleaseVersion - the OpenStack release version that has been successfully deployed
261+
ReleaseVersion *string `json:"releaseVersion,omitempty"`
259262
}
260263

261264
// +kubebuilder:object:root=true

api/operator/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/operator.openstack.org_openstacks.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ spec:
164164
observedGeneration:
165165
format: int64
166166
type: integer
167+
releaseVersion:
168+
type: string
167169
totalOperatorCount:
168170
type: integer
169171
type: object

internal/controller/operator/openstack_controller.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,39 @@ func (r *OpenStackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
250250
return ctrl.Result{}, err
251251
}
252252

253+
// Check if OPENSTACK_RELEASE_VERSION has changed - if so, delete all owned resources
254+
// This is a one-time fix to handle incompatible upgrades
255+
if instance.Status.ReleaseVersion != nil && *instance.Status.ReleaseVersion != openstackReleaseVersion {
256+
Log.Info("OpenStack release version changed, deleting all owned resources",
257+
"old", *instance.Status.ReleaseVersion,
258+
"new", openstackReleaseVersion)
259+
260+
if err := r.deleteAllOwnedResources(ctx, instance); err != nil {
261+
instance.Status.Conditions.Set(condition.FalseCondition(
262+
operatorv1beta1.OpenStackOperatorReadyCondition,
263+
condition.ErrorReason,
264+
condition.SeverityWarning,
265+
operatorv1beta1.OpenStackOperatorErrorMessage,
266+
err))
267+
return ctrl.Result{}, err
268+
}
269+
270+
// Reset the container image status to force re-application of CRDs and RBAC
271+
instance.Status.ContainerImage = nil
272+
273+
// Update the release version in status
274+
instance.Status.ReleaseVersion = &openstackReleaseVersion
275+
276+
// Requeue to allow resources to be deleted before recreating
277+
Log.Info("Resources deleted, requeuing to recreate with new version")
278+
return ctrl.Result{RequeueAfter: time.Duration(5) * time.Second}, nil
279+
}
280+
281+
// Set the release version if not set
282+
if instance.Status.ReleaseVersion == nil {
283+
instance.Status.ReleaseVersion = &openstackReleaseVersion
284+
}
285+
253286
if err := r.applyManifests(ctx, instance); err != nil {
254287
instance.Status.Conditions.Set(condition.FalseCondition(
255288
operatorv1beta1.OpenStackOperatorReadyCondition,
@@ -316,6 +349,107 @@ func (r *OpenStackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
316349

317350
}
318351

352+
func (r *OpenStackReconciler) deleteAllOwnedResources(ctx context.Context, instance *operatorv1beta1.OpenStack) error {
353+
Log := r.GetLogger(ctx)
354+
Log.Info("Deleting all owned resources for release version upgrade")
355+
356+
// Delete all owned deployments
357+
deployments := &appsv1.DeploymentList{}
358+
err := r.List(ctx, deployments, &client.ListOptions{Namespace: instance.Namespace})
359+
if err != nil {
360+
return errors.Wrap(err, "failed to list deployments")
361+
}
362+
for _, deployment := range deployments.Items {
363+
if metav1.IsControlledBy(&deployment, instance) {
364+
Log.Info("Deleting deployment", "name", deployment.Name)
365+
err := r.Delete(ctx, &deployment)
366+
if err != nil && !apierrors.IsNotFound(err) {
367+
return errors.Wrapf(err, "failed to delete deployment %s", deployment.Name)
368+
}
369+
}
370+
}
371+
372+
// Delete all owned service accounts
373+
serviceAccounts := &corev1.ServiceAccountList{}
374+
err = r.List(ctx, serviceAccounts, &client.ListOptions{Namespace: instance.Namespace})
375+
if err != nil {
376+
return errors.Wrap(err, "failed to list service accounts")
377+
}
378+
for _, sa := range serviceAccounts.Items {
379+
if metav1.IsControlledBy(&sa, instance) {
380+
Log.Info("Deleting service account", "name", sa.Name)
381+
err := r.Delete(ctx, &sa)
382+
if err != nil && !apierrors.IsNotFound(err) {
383+
return errors.Wrapf(err, "failed to delete service account %s", sa.Name)
384+
}
385+
}
386+
}
387+
388+
// Delete all owned services
389+
services := &corev1.ServiceList{}
390+
err = r.List(ctx, services, &client.ListOptions{Namespace: instance.Namespace})
391+
if err != nil {
392+
return errors.Wrap(err, "failed to list services")
393+
}
394+
for _, svc := range services.Items {
395+
if metav1.IsControlledBy(&svc, instance) {
396+
Log.Info("Deleting service", "name", svc.Name)
397+
err := r.Delete(ctx, &svc)
398+
if err != nil && !apierrors.IsNotFound(err) {
399+
return errors.Wrapf(err, "failed to delete service %s", svc.Name)
400+
}
401+
}
402+
}
403+
404+
// Delete all owned configmaps
405+
configMaps := &corev1.ConfigMapList{}
406+
err = r.List(ctx, configMaps, &client.ListOptions{Namespace: instance.Namespace})
407+
if err != nil {
408+
return errors.Wrap(err, "failed to list configmaps")
409+
}
410+
for _, cm := range configMaps.Items {
411+
if metav1.IsControlledBy(&cm, instance) {
412+
Log.Info("Deleting configmap", "name", cm.Name)
413+
err := r.Delete(ctx, &cm)
414+
if err != nil && !apierrors.IsNotFound(err) {
415+
return errors.Wrapf(err, "failed to delete configmap %s", cm.Name)
416+
}
417+
}
418+
}
419+
420+
// Delete webhooks (these are cluster-scoped and not owned, but managed by label)
421+
valWebhooks, err := r.Kclient.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, metav1.ListOptions{
422+
LabelSelector: "openstack.openstack.org/managed=true",
423+
})
424+
if err != nil {
425+
return errors.Wrap(err, "failed listing validating webhook configurations")
426+
}
427+
for _, webhook := range valWebhooks.Items {
428+
Log.Info("Deleting validating webhook", "name", webhook.Name)
429+
err := r.Kclient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, webhook.Name, metav1.DeleteOptions{})
430+
if err != nil && !apierrors.IsNotFound(err) {
431+
return errors.Wrapf(err, "failed to delete validating webhook %s", webhook.Name)
432+
}
433+
}
434+
435+
mutWebhooks, err := r.Kclient.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, metav1.ListOptions{
436+
LabelSelector: "openstack.openstack.org/managed=true",
437+
})
438+
if err != nil {
439+
return errors.Wrap(err, "failed listing mutating webhook configurations")
440+
}
441+
for _, webhook := range mutWebhooks.Items {
442+
Log.Info("Deleting mutating webhook", "name", webhook.Name)
443+
err := r.Kclient.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(ctx, webhook.Name, metav1.DeleteOptions{})
444+
if err != nil && !apierrors.IsNotFound(err) {
445+
return errors.Wrapf(err, "failed to delete mutating webhook %s", webhook.Name)
446+
}
447+
}
448+
449+
Log.Info("All owned resources deleted successfully")
450+
return nil
451+
}
452+
319453
func (r *OpenStackReconciler) reconcileDelete(ctx context.Context, instance *operatorv1beta1.OpenStack, helper *helper.Helper) (ctrl.Result, error) {
320454
Log := r.GetLogger(ctx)
321455
Log.Info("Reconciling OpenStack initialization resource delete")

0 commit comments

Comments
 (0)