Skip to content

Commit 28fe0a6

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

9 files changed

Lines changed: 242 additions & 2 deletions

File tree

api/bases/neutron.openstack.org_neutronapis.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,28 @@ spec:
14551455
from the Secret
14561456
type: string
14571457
type: object
1458+
podAnnotations:
1459+
description: PodAnnotations are added to the Neutron API Deployment
1460+
pod template.
1461+
items:
1462+
description: |-
1463+
PodAnnotation is a single key/value pair that will be applied to the Neutron API
1464+
Deployment PodTemplate as an annotation.
1465+
properties:
1466+
name:
1467+
description: Name is the annotation key.
1468+
type: string
1469+
value:
1470+
description: Value is the annotation value.
1471+
type: string
1472+
required:
1473+
- name
1474+
- value
1475+
type: object
1476+
type: array
1477+
x-kubernetes-list-map-keys:
1478+
- name
1479+
x-kubernetes-list-type: map
14581480
preserveJobs:
14591481
default: false
14601482
description: PreserveJobs - do not delete jobs after they finished
@@ -1607,6 +1629,13 @@ spec:
16071629
status:
16081630
description: NeutronAPIStatus defines the observed state of NeutronAPI
16091631
properties:
1632+
applicationCredentialSecret:
1633+
description: |-
1634+
ApplicationCredentialSecret - the AC secret NeutronAPI is currently
1635+
consuming and protecting with the openstack.org/neutronapi-ac-consumer
1636+
finalizer. Tracked so the controller can remove its finalizer from the
1637+
old secret when the openstack-operator rotates the reference.
1638+
type: string
16101639
conditions:
16111640
description: Conditions
16121641
items:

api/v1beta1/neutronapi_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ type NeutronAPISpecCore struct {
177177
// An empty value "" leaves the notification drivers unconfigured and emitting no notifications at all.
178178
// Avoid colocating it with RabbitMqClusterName used for RPC.
179179
NotificationsBusInstance *string `json:"notificationsBusInstance,omitempty" deprecated:"notificationsBus.cluster"`
180+
180181
}
181182

182183
type NeutronApiTLS struct {
@@ -237,6 +238,12 @@ type NeutronAPIStatus struct {
237238
// NetworkAttachments status of the deployment pods
238239
NetworkAttachments map[string][]string `json:"networkAttachments,omitempty"`
239240

241+
// ApplicationCredentialSecret - the AC secret NeutronAPI is currently
242+
// consuming and protecting with the openstack.org/neutronapi-ac-consumer
243+
// finalizer. Tracked so the controller can remove its finalizer from the
244+
// old secret when the openstack-operator rotates the reference.
245+
ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"`
246+
240247
// ObservedGeneration - the most recent generation observed for this
241248
// service. If the observed generation is less than the spec generation,
242249
// then the controller has not processed the latest changes injected by

api/v1beta1/zz_generated.deepcopy.go

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

config/crd/bases/neutron.openstack.org_neutronapis.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,28 @@ spec:
14551455
from the Secret
14561456
type: string
14571457
type: object
1458+
podAnnotations:
1459+
description: PodAnnotations are added to the Neutron API Deployment
1460+
pod template.
1461+
items:
1462+
description: |-
1463+
PodAnnotation is a single key/value pair that will be applied to the Neutron API
1464+
Deployment PodTemplate as an annotation.
1465+
properties:
1466+
name:
1467+
description: Name is the annotation key.
1468+
type: string
1469+
value:
1470+
description: Value is the annotation value.
1471+
type: string
1472+
required:
1473+
- name
1474+
- value
1475+
type: object
1476+
type: array
1477+
x-kubernetes-list-map-keys:
1478+
- name
1479+
x-kubernetes-list-type: map
14581480
preserveJobs:
14591481
default: false
14601482
description: PreserveJobs - do not delete jobs after they finished
@@ -1607,6 +1629,13 @@ spec:
16071629
status:
16081630
description: NeutronAPIStatus defines the observed state of NeutronAPI
16091631
properties:
1632+
applicationCredentialSecret:
1633+
description: |-
1634+
ApplicationCredentialSecret - the AC secret NeutronAPI is currently
1635+
consuming and protecting with the openstack.org/neutronapi-ac-consumer
1636+
finalizer. Tracked so the controller can remove its finalizer from the
1637+
old secret when the openstack-operator rotates the reference.
1638+
type: string
16101639
conditions:
16111640
description: Conditions
16121641
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.20260422175310-d957b8482944 h1:C0qDfnVa//1NwYyO6o5EK5RBKohYjldnmbGvj7RTQ2E=
124-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260422175310-d957b8482944/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/neutronapi_controller.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,19 @@ func (r *NeutronAPIReconciler) reconcileDelete(ctx context.Context, instance *ne
492492
); err != nil {
493493
return ctrlResult, err
494494
}
495+
// Remove consumer finalizer from AC secrets NeutronAPI was consuming.
496+
// Check both status and spec to handle the edge case where the reconciler
497+
// crashed after adding the finalizer but before updating the status.
498+
for _, secretName := range []string{
499+
instance.Status.ApplicationCredentialSecret,
500+
instance.Spec.Auth.ApplicationCredentialSecret,
501+
} {
502+
if err := keystonev1.RemoveACSecretConsumerFinalizer(ctx, helper, instance.Namespace,
503+
secretName, neutronapi.ACConsumerFinalizer); err != nil {
504+
return ctrl.Result{}, err
505+
}
506+
}
507+
495508
// Service is deleted so remove the finalizer.
496509
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())
497510
Log.Info("Reconciled Service delete successfully")
@@ -648,6 +661,23 @@ func (r *NeutronAPIReconciler) reconcileInit(
648661

649662
// Create Secrets - end
650663

664+
// Manage consumer finalizer, the AC data was already read and rendered to the service config secret
665+
if instance.Spec.Auth.ApplicationCredentialSecret != "" || instance.Status.ApplicationCredentialSecret != "" {
666+
if err := keystonev1.ManageACSecretFinalizer(ctx, helper, instance.Namespace,
667+
instance.Spec.Auth.ApplicationCredentialSecret,
668+
instance.Status.ApplicationCredentialSecret,
669+
neutronapi.ACConsumerFinalizer); err != nil {
670+
instance.Status.Conditions.Set(condition.FalseCondition(
671+
condition.ServiceConfigReadyCondition,
672+
condition.ErrorReason,
673+
condition.SeverityWarning,
674+
condition.ServiceConfigReadyErrorMessage,
675+
err.Error()))
676+
return ctrl.Result{}, err
677+
}
678+
}
679+
instance.Status.ApplicationCredentialSecret = instance.Spec.Auth.ApplicationCredentialSecret
680+
651681
instance.Status.Conditions.MarkTrue(condition.ServiceConfigReadyCondition, condition.ServiceConfigReadyMessage)
652682

653683
//

internal/neutronapi/const.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ const (
5050

5151
// NeutronDhcpAgentSecretKey is the key in external Secret for Neutron DHCP Agent with agent config
5252
NeutronDhcpAgentSecretKey = "10-neutron-dhcp.conf"
53+
54+
// ACConsumerFinalizer is added to AC secrets that neutron is actively consuming
55+
ACConsumerFinalizer = "openstack.org/neutronapi-ac-consumer"
5356
)
5457

5558
// DbsyncPropagation keeps track of the DBSync Service Propagation Type

test/functional/neutronapi_controller_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,145 @@ func getNeutronAPIControllerSuite(ml2MechanismDrivers []string) func() {
22022202
}, timeout, interval).Should(Succeed())
22032203
})
22042204
})
2205+
2206+
When("ApplicationCredential consumer finalizer is managed", func() {
2207+
var acSecretName string
2208+
2209+
BeforeEach(func() {
2210+
acSecretName = "ac-neutron-a1b2c-secret" //nolint:gosec // G101
2211+
secret := &corev1.Secret{
2212+
ObjectMeta: metav1.ObjectMeta{
2213+
Namespace: namespace,
2214+
Name: acSecretName,
2215+
},
2216+
Data: map[string][]byte{
2217+
keystonev1.ACIDSecretKey: []byte("a1b2ctest-ac-id"),
2218+
keystonev1.ACSecretSecretKey: []byte("test-ac-secret"),
2219+
},
2220+
}
2221+
DeferCleanup(k8sClient.Delete, ctx, secret)
2222+
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
2223+
2224+
spec["auth"] = map[string]any{
2225+
"applicationCredentialSecret": acSecretName,
2226+
}
2227+
2228+
DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec))
2229+
DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName))
2230+
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec))
2231+
infra.SimulateMemcachedReady(memcachedName)
2232+
DeferCleanup(
2233+
mariadb.DeleteDBService,
2234+
mariadb.CreateDBService(
2235+
namespace,
2236+
GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance,
2237+
corev1.ServiceSpec{
2238+
Ports: []corev1.ServicePort{{Port: 3306}},
2239+
},
2240+
),
2241+
)
2242+
SimulateTransportURLReady(apiTransportURLName)
2243+
mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount})
2244+
mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName})
2245+
2246+
if isOVNEnabled {
2247+
DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace))
2248+
}
2249+
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace))
2250+
})
2251+
2252+
It("should add the consumer finalizer to the AC secret", func() {
2253+
Eventually(func(g Gomega) {
2254+
secret := th.GetSecret(types.NamespacedName{
2255+
Namespace: namespace,
2256+
Name: acSecretName,
2257+
})
2258+
g.Expect(secret.Finalizers).To(
2259+
ContainElement(neutronapi.ACConsumerFinalizer))
2260+
}, timeout, interval).Should(Succeed())
2261+
})
2262+
2263+
It("should track the consumed AC secret in status", func() {
2264+
Eventually(func(g Gomega) {
2265+
n := GetNeutronAPI(neutronAPIName)
2266+
g.Expect(n.Status.ApplicationCredentialSecret).To(Equal(acSecretName))
2267+
}, timeout, interval).Should(Succeed())
2268+
})
2269+
2270+
It("should move the finalizer from the old to the new secret on rotation", func() {
2271+
Eventually(func(g Gomega) {
2272+
secret := th.GetSecret(types.NamespacedName{
2273+
Namespace: namespace,
2274+
Name: acSecretName,
2275+
})
2276+
g.Expect(secret.Finalizers).To(
2277+
ContainElement(neutronapi.ACConsumerFinalizer))
2278+
}, timeout, interval).Should(Succeed())
2279+
2280+
newACSecretName := "ac-neutron-x9y8z-secret" //nolint:gosec // G101
2281+
newSecret := &corev1.Secret{
2282+
ObjectMeta: metav1.ObjectMeta{
2283+
Namespace: namespace,
2284+
Name: newACSecretName,
2285+
},
2286+
Data: map[string][]byte{
2287+
keystonev1.ACIDSecretKey: []byte("x9y8zrotated-ac-id"),
2288+
keystonev1.ACSecretSecretKey: []byte("rotated-ac-secret"),
2289+
},
2290+
}
2291+
DeferCleanup(k8sClient.Delete, ctx, newSecret)
2292+
Expect(k8sClient.Create(ctx, newSecret)).To(Succeed())
2293+
2294+
Eventually(func(g Gomega) {
2295+
n := GetNeutronAPI(neutronAPIName)
2296+
n.Spec.Auth.ApplicationCredentialSecret = newACSecretName
2297+
g.Expect(k8sClient.Update(ctx, n)).Should(Succeed())
2298+
}, timeout, interval).Should(Succeed())
2299+
2300+
Eventually(func(g Gomega) {
2301+
secret := th.GetSecret(types.NamespacedName{
2302+
Namespace: namespace,
2303+
Name: newACSecretName,
2304+
})
2305+
g.Expect(secret.Finalizers).To(
2306+
ContainElement(neutronapi.ACConsumerFinalizer))
2307+
}, timeout, interval).Should(Succeed())
2308+
2309+
Eventually(func(g Gomega) {
2310+
secret := th.GetSecret(types.NamespacedName{
2311+
Namespace: namespace,
2312+
Name: acSecretName,
2313+
})
2314+
g.Expect(secret.Finalizers).NotTo(
2315+
ContainElement(neutronapi.ACConsumerFinalizer))
2316+
}, timeout, interval).Should(Succeed())
2317+
2318+
Eventually(func(g Gomega) {
2319+
n := GetNeutronAPI(neutronAPIName)
2320+
g.Expect(n.Status.ApplicationCredentialSecret).To(Equal(newACSecretName))
2321+
}, timeout, interval).Should(Succeed())
2322+
})
2323+
2324+
It("should remove the consumer finalizer from AC secret on CR deletion", func() {
2325+
Eventually(func(g Gomega) {
2326+
secret := th.GetSecret(types.NamespacedName{
2327+
Namespace: namespace,
2328+
Name: acSecretName,
2329+
})
2330+
g.Expect(secret.Finalizers).To(
2331+
ContainElement(neutronapi.ACConsumerFinalizer))
2332+
}, timeout, interval).Should(Succeed())
2333+
2334+
th.DeleteInstance(GetNeutronAPI(neutronAPIName))
2335+
2336+
secret := th.GetSecret(types.NamespacedName{
2337+
Namespace: namespace,
2338+
Name: acSecretName,
2339+
})
2340+
Expect(secret.Finalizers).NotTo(
2341+
ContainElement(neutronapi.ACConsumerFinalizer))
2342+
})
2343+
})
22052344
}
22062345
}
22072346

0 commit comments

Comments
 (0)