@@ -56,7 +56,7 @@ import (
5656
5757 novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1"
5858 "github.com/openstack-k8s-operators/nova-operator/internal/nova"
59- "github.com/openstack-k8s-operators/nova-operator/internal/nova/api"
59+ novaapi "github.com/openstack-k8s-operators/nova-operator/internal/nova/api"
6060
6161 memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1"
6262 rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1"
@@ -303,6 +303,24 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul
303303 }
304304 }
305305
306+ // Add consumer finalizer to the new AC secret early, before deployment.
307+ // The old secret's finalizer is removed later (after all services deploy)
308+ // so that rapid rotations don't revoke a credential still in use by pods.
309+ if instance .Spec .Auth .ApplicationCredentialSecret != "" {
310+ if err := keystonev1 .ManageACSecretFinalizer (ctx , h , instance .Namespace ,
311+ instance .Spec .Auth .ApplicationCredentialSecret ,
312+ "" ,
313+ nova .ACConsumerFinalizer ); err != nil {
314+ instance .Status .Conditions .Set (condition .FalseCondition (
315+ condition .ServiceConfigReadyCondition ,
316+ condition .ErrorReason ,
317+ condition .SeverityWarning ,
318+ condition .ServiceConfigReadyErrorMessage ,
319+ err .Error ()))
320+ return ctrl.Result {}, err
321+ }
322+ }
323+
306324 instance .Status .Conditions .MarkTrue (condition .InputReadyCondition , condition .InputReadyMessage )
307325
308326 err = r .ensureKeystoneServiceUser (ctx , h , instance )
@@ -774,6 +792,25 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul
774792 )
775793 }
776794
795+ // Manage the old AC secret's finalizer and status tracking.
796+ // On rotation (old != new), only remove the old secret's finalizer after
797+ // all sub-services are ready with the new credentials. This prevents
798+ // premature revocation during rapid rotations.
799+ isRotation := instance .Status .ApplicationCredentialSecret != "" && instance .Status .ApplicationCredentialSecret != instance .Spec .Auth .ApplicationCredentialSecret
800+
801+ if isRotation {
802+ allServicesReady := instance .Status .Conditions .AllSubConditionIsTrue ()
803+ if allServicesReady {
804+ if err := keystonev1 .RemoveACSecretConsumerFinalizer (ctx , h , instance .Namespace ,
805+ instance .Status .ApplicationCredentialSecret , nova .ACConsumerFinalizer ); err != nil {
806+ return ctrl.Result {}, err
807+ }
808+ instance .Status .ApplicationCredentialSecret = instance .Spec .Auth .ApplicationCredentialSecret
809+ }
810+ } else {
811+ instance .Status .ApplicationCredentialSecret = instance .Spec .Auth .ApplicationCredentialSecret
812+ }
813+
777814 Log .Info ("Successfully reconciled" )
778815 return ctrl.Result {}, nil
779816}
@@ -1768,6 +1805,17 @@ func (r *NovaReconciler) reconcileDelete(
17681805 return err
17691806 }
17701807
1808+ // Remove consumer finalizer from AC secrets nova was consuming.
1809+ for _ , secretName := range []string {
1810+ instance .Status .ApplicationCredentialSecret ,
1811+ instance .Spec .Auth .ApplicationCredentialSecret ,
1812+ } {
1813+ if err := keystonev1 .RemoveACSecretConsumerFinalizer (ctx , h , instance .Namespace ,
1814+ secretName , nova .ACConsumerFinalizer ); err != nil {
1815+ return err
1816+ }
1817+ }
1818+
17711819 // Successfully cleaned up everything. So as the final step let's remove the
17721820 // finalizer from ourselves to allow the deletion of Nova CR itself
17731821 updated := controllerutil .RemoveFinalizer (instance , h .GetFinalizer ())
0 commit comments