Skip to content

Commit 1f9ae2f

Browse files
committed
Add AC finalizer management
Signed-off-by: Veronika Fisarova <vfisarov@redhat.com>
1 parent 123b74b commit 1f9ae2f

9 files changed

Lines changed: 220 additions & 3 deletions

File tree

api/bases/nova.openstack.org_nova.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,6 +1914,13 @@ spec:
19141914
from nova-api
19151915
format: int32
19161916
type: integer
1917+
applicationCredentialSecret:
1918+
description: |-
1919+
ApplicationCredentialSecret - the AC secret nova is currently
1920+
consuming and protecting with the openstack.org/nova-ac-consumer
1921+
finalizer. Tracked so the controller can remove its finalizer from the
1922+
old secret when the openstack-operator rotates the reference.
1923+
type: string
19171924
conditions:
19181925
description: Conditions
19191926
items:

api/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging
9393
replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging
9494

9595
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
96+
97+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20

api/nova/v1beta1/nova_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ type NovaStatus struct {
185185

186186
//ObservedGeneration - the most recent generation observed for this service. If the observed generation is less than the spec generation, then the controller has not processed the latest changes.
187187
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
188+
189+
// ApplicationCredentialSecret - the AC secret nova is currently
190+
// consuming and protecting with the openstack.org/nova-ac-consumer
191+
// finalizer. Tracked so the controller can remove its finalizer from the
192+
// old secret when the openstack-operator rotates the reference.
193+
ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"`
188194
}
189195

190196
//+kubebuilder:object:root=true

config/crd/bases/nova.openstack.org_nova.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,6 +1914,13 @@ spec:
19141914
from nova-api
19151915
format: int32
19161916
type: integer
1917+
applicationCredentialSecret:
1918+
description: |-
1919+
ApplicationCredentialSecret - the AC secret nova is currently
1920+
consuming and protecting with the openstack.org/nova-ac-consumer
1921+
finalizer. Tracked so the controller can remove its finalizer from the
1922+
old secret when the openstack-operator rotates the reference.
1923+
type: string
19171924
conditions:
19181925
description: Conditions
19191926
items:

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging
143143
replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging
144144

145145
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
146+
147+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20 h1:iyxfh2SDvQrOrsHItYAE3A3+8Ku9UnzWAq9jnLJDLjg=
2+
github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20/go.mod h1:SpO4CL7c5/1HG+61fP6kWhL2+3aqR+5SNatdZueKrz8=
13
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
24
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
35
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
@@ -120,8 +122,6 @@ github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyU
120122
github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo=
121123
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6 h1:117Gu9HCSu2tAp579WnCJ9QtnslH2qnPB8UFvn8ZpqE=
122124
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6/go.mod h1:i7l8cihvFktd/LSuyvL2z6OcwauarQGoVhDMePL4VyI=
123-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260420052838-77f94aef5af2 h1:h7pTz90cHqX6nTYjYDphuitIfD4UpM9yGnI3AbLdHrY=
124-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260420052838-77f94aef5af2/go.mod h1:SpO4CL7c5/1HG+61fP6kWhL2+3aqR+5SNatdZueKrz8=
125125
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 h1:v1viH0gmNb+AXMg/0GxDcj8VUTdjVLotfOIGrNyMxHk=
126126
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:I/VBXZLdjk8DUGsEbB+Ha72JBFYYntP7Pm2FpEto9K8=
127127
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260417092244-81c71b39e981 h1:jN3Kvt+RYUTaL9EXeeeIqRXVjqeNF74SuLTDXmi4X2Y=

internal/controller/nova/nova_controller.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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,22 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul
303303
}
304304
}
305305

306+
if instance.Spec.Auth.ApplicationCredentialSecret != "" || instance.Status.ApplicationCredentialSecret != "" {
307+
if err := keystonev1.ManageACSecretFinalizer(ctx, h, instance.Namespace,
308+
instance.Spec.Auth.ApplicationCredentialSecret,
309+
instance.Status.ApplicationCredentialSecret,
310+
nova.ACConsumerFinalizer); err != nil {
311+
instance.Status.Conditions.Set(condition.FalseCondition(
312+
condition.ServiceConfigReadyCondition,
313+
condition.ErrorReason,
314+
condition.SeverityWarning,
315+
condition.ServiceConfigReadyErrorMessage,
316+
err.Error()))
317+
return ctrl.Result{}, err
318+
}
319+
}
320+
instance.Status.ApplicationCredentialSecret = instance.Spec.Auth.ApplicationCredentialSecret
321+
306322
instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage)
307323

308324
err = r.ensureKeystoneServiceUser(ctx, h, instance)
@@ -1768,6 +1784,17 @@ func (r *NovaReconciler) reconcileDelete(
17681784
return err
17691785
}
17701786

1787+
// Remove consumer finalizer from AC secrets nova was consuming.
1788+
for _, secretName := range []string{
1789+
instance.Status.ApplicationCredentialSecret,
1790+
instance.Spec.Auth.ApplicationCredentialSecret,
1791+
} {
1792+
if err := keystonev1.RemoveACSecretConsumerFinalizer(ctx, h, instance.Namespace,
1793+
secretName, nova.ACConsumerFinalizer); err != nil {
1794+
return err
1795+
}
1796+
}
1797+
17711798
// Successfully cleaned up everything. So as the final step let's remove the
17721799
// finalizer from ourselves to allow the deletion of Nova CR itself
17731800
updated := controllerutil.RemoveFinalizer(instance, h.GetFinalizer())

internal/nova/common.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ const (
3030
NovaUserID int64 = 42436
3131
)
3232

33+
const (
34+
// ACConsumerFinalizer is added to AC secrets that nova is actively consuming
35+
ACConsumerFinalizer = "openstack.org/nova-ac-consumer"
36+
)
37+
3338
// GetScriptSecretName returns the name of the Secret used for the
3439
// db sync scripts
3540
func GetScriptSecretName(crName string) string {

test/functional/nova/nova_controller_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
3838
novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1"
3939
controllers "github.com/openstack-k8s-operators/nova-operator/internal/controller/nova"
40+
nova "github.com/openstack-k8s-operators/nova-operator/internal/nova"
4041

4142
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4243

@@ -2157,4 +2158,164 @@ var _ = Describe("application credentials", func() {
21572158
}, timeout, interval).Should(Succeed())
21582159
})
21592160
})
2161+
2162+
When("ApplicationCredential consumer finalizer is managed", func() {
2163+
var acSecretName string
2164+
2165+
BeforeEach(func() {
2166+
acSecretName = "ac-nova-a1b2c-secret" //nolint:gosec // G101
2167+
ac := &corev1.Secret{
2168+
ObjectMeta: metav1.ObjectMeta{
2169+
Namespace: novaNames.NovaName.Namespace,
2170+
Name: acSecretName,
2171+
},
2172+
Data: map[string][]byte{
2173+
keystonev1.ACIDSecretKey: []byte("a1b2ctest-ac-id"),
2174+
keystonev1.ACSecretSecretKey: []byte("test-ac-secret"),
2175+
},
2176+
}
2177+
DeferCleanup(k8sClient.Delete, ctx, ac)
2178+
Expect(k8sClient.Create(ctx, ac)).To(Succeed())
2179+
2180+
DeferCleanup(k8sClient.Delete, ctx, CreateNovaSecret(novaNames.NovaName.Namespace, SecretName))
2181+
DeferCleanup(k8sClient.Delete, ctx, CreateNovaMessageBusSecret(cell0))
2182+
DeferCleanup(
2183+
mariadb.DeleteDBService,
2184+
mariadb.CreateDBService(
2185+
novaNames.NovaName.Namespace,
2186+
"openstack",
2187+
corev1.ServiceSpec{
2188+
Ports: []corev1.ServicePort{{Port: 3306}},
2189+
},
2190+
),
2191+
)
2192+
memcachedSpec := infra.GetDefaultMemcachedSpec()
2193+
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
2194+
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
2195+
2196+
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
2197+
2198+
rawNova := map[string]any{
2199+
"apiVersion": "nova.openstack.org/v1beta1",
2200+
"kind": "Nova",
2201+
"metadata": map[string]any{
2202+
"name": novaNames.NovaName.Name,
2203+
"namespace": novaNames.NovaName.Namespace,
2204+
},
2205+
"spec": map[string]any{
2206+
"secret": SecretName,
2207+
"apiDatabaseAccount": novaNames.APIMariaDBDatabaseAccount.Name,
2208+
"cellTemplates": map[string]any{
2209+
"cell0": map[string]any{
2210+
"cellDatabaseAccount": cell0.MariaDBAccountName.Name,
2211+
"apiDatabaseAccount": novaNames.APIMariaDBDatabaseAccount.Name,
2212+
"hasAPIAccess": true,
2213+
"dbPurge": map[string]any{
2214+
"schedule": "1 0 * * *",
2215+
},
2216+
},
2217+
},
2218+
"messagingBus": map[string]any{
2219+
"cluster": cell0.TransportURLName.Name,
2220+
},
2221+
"auth": map[string]any{
2222+
"applicationCredentialSecret": acSecretName,
2223+
},
2224+
},
2225+
}
2226+
DeferCleanup(th.DeleteInstance, th.CreateUnstructured(rawNova))
2227+
})
2228+
2229+
It("should add the consumer finalizer to the AC secret", func() {
2230+
Eventually(func(g Gomega) {
2231+
secret := th.GetSecret(types.NamespacedName{
2232+
Namespace: novaNames.NovaName.Namespace,
2233+
Name: acSecretName,
2234+
})
2235+
g.Expect(secret.Finalizers).To(
2236+
ContainElement(nova.ACConsumerFinalizer))
2237+
}, timeout, interval).Should(Succeed())
2238+
})
2239+
2240+
It("should track the consumed AC secret in status", func() {
2241+
Eventually(func(g Gomega) {
2242+
n := GetNova(novaNames.NovaName)
2243+
g.Expect(n.Status.ApplicationCredentialSecret).To(Equal(acSecretName))
2244+
}, timeout, interval).Should(Succeed())
2245+
})
2246+
2247+
It("should move the finalizer from the old to the new secret on rotation", func() {
2248+
Eventually(func(g Gomega) {
2249+
secret := th.GetSecret(types.NamespacedName{
2250+
Namespace: novaNames.NovaName.Namespace,
2251+
Name: acSecretName,
2252+
})
2253+
g.Expect(secret.Finalizers).To(
2254+
ContainElement(nova.ACConsumerFinalizer))
2255+
}, timeout, interval).Should(Succeed())
2256+
2257+
newACSecretName := "ac-nova-x9y8z-secret" //nolint:gosec // G101
2258+
newSecret := &corev1.Secret{
2259+
ObjectMeta: metav1.ObjectMeta{
2260+
Namespace: novaNames.NovaName.Namespace,
2261+
Name: newACSecretName,
2262+
},
2263+
Data: map[string][]byte{
2264+
keystonev1.ACIDSecretKey: []byte("x9y8zrotated-ac-id"),
2265+
keystonev1.ACSecretSecretKey: []byte("rotated-ac-secret"),
2266+
},
2267+
}
2268+
DeferCleanup(k8sClient.Delete, ctx, newSecret)
2269+
Expect(k8sClient.Create(ctx, newSecret)).To(Succeed())
2270+
2271+
Eventually(func(g Gomega) {
2272+
n := GetNova(novaNames.NovaName)
2273+
n.Spec.Auth.ApplicationCredentialSecret = newACSecretName
2274+
g.Expect(k8sClient.Update(ctx, n)).Should(Succeed())
2275+
}, timeout, interval).Should(Succeed())
2276+
2277+
Eventually(func(g Gomega) {
2278+
secret := th.GetSecret(types.NamespacedName{
2279+
Namespace: novaNames.NovaName.Namespace,
2280+
Name: newACSecretName,
2281+
})
2282+
g.Expect(secret.Finalizers).To(
2283+
ContainElement(nova.ACConsumerFinalizer))
2284+
}, timeout, interval).Should(Succeed())
2285+
2286+
Eventually(func(g Gomega) {
2287+
secret := th.GetSecret(types.NamespacedName{
2288+
Namespace: novaNames.NovaName.Namespace,
2289+
Name: acSecretName,
2290+
})
2291+
g.Expect(secret.Finalizers).NotTo(
2292+
ContainElement(nova.ACConsumerFinalizer))
2293+
}, timeout, interval).Should(Succeed())
2294+
2295+
Eventually(func(g Gomega) {
2296+
n := GetNova(novaNames.NovaName)
2297+
g.Expect(n.Status.ApplicationCredentialSecret).To(Equal(newACSecretName))
2298+
}, timeout, interval).Should(Succeed())
2299+
})
2300+
2301+
It("should remove the consumer finalizer from AC secret on CR deletion", func() {
2302+
Eventually(func(g Gomega) {
2303+
secret := th.GetSecret(types.NamespacedName{
2304+
Namespace: novaNames.NovaName.Namespace,
2305+
Name: acSecretName,
2306+
})
2307+
g.Expect(secret.Finalizers).To(
2308+
ContainElement(nova.ACConsumerFinalizer))
2309+
}, timeout, interval).Should(Succeed())
2310+
2311+
th.DeleteInstance(GetNova(novaNames.NovaName))
2312+
2313+
secret := th.GetSecret(types.NamespacedName{
2314+
Namespace: novaNames.NovaName.Namespace,
2315+
Name: acSecretName,
2316+
})
2317+
Expect(secret.Finalizers).NotTo(
2318+
ContainElement(nova.ACConsumerFinalizer))
2319+
})
2320+
})
21602321
})

0 commit comments

Comments
 (0)