Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/bases/glance.openstack.org_glanceapis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,10 @@ spec:
type: string
description: API endpoint
type: object
applicationCredentialSecret:
description: ApplicationCredentialSecret - Secret that GlanceAPI is
actively consuming (AC consumer finalizer present)
type: string
conditions:
description: Conditions
items:
Expand Down
2 changes: 2 additions & 0 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging
replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging

replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging

replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20
3 changes: 3 additions & 0 deletions api/v1beta1/glanceapi_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ type GlanceAPIStatus struct {
// NotificationBusSecret - Secret containing RabbitMQ transportURL for
// notification purposes
NotificationBusSecret string `json:"notificationBusSecret,omitempty"`

// ApplicationCredentialSecret - Secret that GlanceAPI is actively consuming (AC consumer finalizer present)
ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/glance.openstack.org_glanceapis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,10 @@ spec:
type: string
description: API endpoint
type: object
applicationCredentialSecret:
description: ApplicationCredentialSecret - Secret that GlanceAPI is
actively consuming (AC consumer finalizer present)
type: string
conditions:
description: Conditions
items:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging
replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging

replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging

replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20 h1:iyxfh2SDvQrOrsHItYAE3A3+8Ku9UnzWAq9jnLJDLjg=
github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20/go.mod h1:SpO4CL7c5/1HG+61fP6kWhL2+3aqR+5SNatdZueKrz8=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
Expand Down Expand Up @@ -124,8 +126,6 @@ github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20260418053129-
github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20260418053129-fb096ad89dce/go.mod h1:ZMH+2206hZgGFjEhC+hhPvU+v6haNaeh5FR1mHylfqw=
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6 h1:117Gu9HCSu2tAp579WnCJ9QtnslH2qnPB8UFvn8ZpqE=
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6/go.mod h1:i7l8cihvFktd/LSuyvL2z6OcwauarQGoVhDMePL4VyI=
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260422175310-d957b8482944 h1:C0qDfnVa//1NwYyO6o5EK5RBKohYjldnmbGvj7RTQ2E=
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260422175310-d957b8482944/go.mod h1:SpO4CL7c5/1HG+61fP6kWhL2+3aqR+5SNatdZueKrz8=
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 h1:v1viH0gmNb+AXMg/0GxDcj8VUTdjVLotfOIGrNyMxHk=
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:I/VBXZLdjk8DUGsEbB+Ha72JBFYYntP7Pm2FpEto9K8=
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260417092244-81c71b39e981 h1:jN3Kvt+RYUTaL9EXeeeIqRXVjqeNF74SuLTDXmi4X2Y=
Expand Down
30 changes: 30 additions & 0 deletions internal/controller/glanceapi_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,19 @@ func (r *GlanceAPIReconciler) reconcileDelete(ctx context.Context, instance *gla
return ctrlResult, err
}

// Remove consumer finalizer from AC secrets GlanceAPI was consuming.
// Check both status and spec to handle the edge case where the reconciler
// crashed after adding the finalizer but before updating the status.
for _, secretName := range []string{
instance.Status.ApplicationCredentialSecret,
instance.Spec.Auth.ApplicationCredentialSecret,
} {
if err := keystonev1.RemoveACSecretConsumerFinalizer(ctx, helper, instance.Namespace,
secretName, glance.ACConsumerFinalizerName(instance.APIName())); err != nil {
return ctrl.Result{}, err
}
}

// Remove finalizer on the Topology CR
if ctrlResult, err := topologyv1.EnsureDeletedTopologyRef(
ctx,
Expand Down Expand Up @@ -987,6 +1000,23 @@ func (r *GlanceAPIReconciler) reconcileNormal(
return ctrl.Result{}, err
}

// Manage AC consumer finalizer, the AC data was already read and rendered to the service config
if instance.Spec.Auth.ApplicationCredentialSecret != "" || instance.Status.ApplicationCredentialSecret != "" {
if err := keystonev1.ManageACSecretFinalizer(ctx, helper, instance.Namespace,
instance.Spec.Auth.ApplicationCredentialSecret,
instance.Status.ApplicationCredentialSecret,
glance.ACConsumerFinalizerName(instance.APIName())); err != nil {
instance.Status.Conditions.Set(condition.FalseCondition(
condition.ServiceConfigReadyCondition,
condition.ErrorReason,
condition.SeverityWarning,
condition.ServiceConfigReadyErrorMessage,
err.Error()))
return ctrl.Result{}, err
}
}
instance.Status.ApplicationCredentialSecret = instance.Spec.Auth.ApplicationCredentialSecret

// At this point the config is generated and the inputHash is computed
// we can mark the ServiceConfigReady as True and rollout the new pods
instance.Status.Conditions.MarkTrue(condition.ServiceConfigReadyCondition, condition.ServiceConfigReadyMessage)
Expand Down
2 changes: 2 additions & 0 deletions internal/glance/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ const (
GlanceCacheCleaner = "/usr/bin/glance-cache-cleaner"
// GlanceCachePruner -
GlanceCachePruner = "/usr/bin/glance-cache-pruner"
// ACConsumerFinalizerPrefix is the base prefix for the per-GlanceAPI AC consumer finalizer
ACConsumerFinalizerPrefix = "openstack.org/glanceapi-"
// ShortDuration -
ShortDuration = time.Duration(5) * time.Second
// NormalDuration -
Expand Down
9 changes: 9 additions & 0 deletions internal/glance/funcs.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package glance

import (
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// ACConsumerFinalizerName returns a per-GlanceAPI finalizer name so that
// multiple GlanceAPI instances (e.g. internal, external) sharing the same
// AC secret each get their own distinct finalizer on the secret.
func ACConsumerFinalizerName(apiName string) string {
return fmt.Sprintf("%s%s-ac-consumer", ACConsumerFinalizerPrefix, apiName)
}

// GetOwningGlanceName - Given a GlanceAPI (both internal and external)
// object, return the parent Glance object that created it (if any)
func GetOwningGlanceName(instance client.Object) string {
Expand Down
134 changes: 134 additions & 0 deletions test/functional/glanceapi_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1669,4 +1669,138 @@ var _ = Describe("Glanceapi controller", func() {
}, timeout, interval).Should(Succeed())
})
})

When("ApplicationCredential consumer finalizer is managed", func() {
var acSecretName string

BeforeEach(func() {
DeferCleanup(k8sClient.Delete, ctx, CreateGlanceSecret(glanceTest.Instance.Namespace, ACTestServicePasswordSecret))
DeferCleanup(k8sClient.Delete, ctx, CreateGlanceMessageBusSecret(glanceTest.Instance.Namespace, glanceTest.RabbitmqSecretName))
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(glanceTest.Instance.Namespace, MemcachedInstance, memcachedv1.MemcachedSpec{}))
infra.SimulateMemcachedReady(glanceTest.GlanceMemcached)
DeferCleanup(
mariadb.DeleteDBService,
mariadb.CreateDBService(
glanceTest.Instance.Namespace,
glanceTest.GlanceDatabaseName.Name,
corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Port: 3306}}}))
mariadb.CreateMariaDBDatabase(glanceTest.GlanceDatabaseName.Namespace, glanceTest.GlanceDatabaseName.Name, mariadbv1.MariaDBDatabaseSpec{})
DeferCleanup(k8sClient.Delete, ctx, mariadb.GetMariaDBDatabase(glanceTest.GlanceDatabaseName))
mariadb.SimulateMariaDBAccountCompleted(glanceTest.GlanceDatabaseAccount)
mariadb.SimulateMariaDBDatabaseCompleted(glanceTest.GlanceDatabaseName)
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(glanceTest.Instance.Namespace))

acSecretName = "ac-glance-a1b2c-secret" //nolint:gosec // G101
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: glanceTest.Instance.Namespace,
Name: acSecretName,
},
Data: map[string][]byte{
keystonev1.ACIDSecretKey: []byte("a1b2ctest-ac-id"),
keystonev1.ACSecretSecretKey: []byte("test-ac-secret"),
},
}
DeferCleanup(k8sClient.Delete, ctx, secret)
Expect(k8sClient.Create(ctx, secret)).To(Succeed())

spec := GetGlanceAPISpecWithAC(GlanceAPITypeInternal, acSecretName)
DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceInternal, spec))
th.SimulateStatefulSetReplicaReady(glanceTest.GlanceInternalStatefulSet)
keystone.SimulateKeystoneEndpointReady(glanceTest.GlanceInternal)
})

It("should add the consumer finalizer to the AC secret", func() {
Eventually(func(g Gomega) {
secret := th.GetSecret(types.NamespacedName{
Namespace: glanceTest.Instance.Namespace,
Name: acSecretName,
})
g.Expect(secret.Finalizers).To(
ContainElement(glance.ACConsumerFinalizerName("default")))
}, timeout, interval).Should(Succeed())
})

It("should track the consumed AC secret in status", func() {
Eventually(func(g Gomega) {
api := GetGlanceAPI(glanceTest.GlanceInternal)
g.Expect(api.Status.ApplicationCredentialSecret).To(Equal(acSecretName))
}, timeout, interval).Should(Succeed())
})

It("should move the finalizer from the old to the new secret on rotation", func() {
Eventually(func(g Gomega) {
secret := th.GetSecret(types.NamespacedName{
Namespace: glanceTest.Instance.Namespace,
Name: acSecretName,
})
g.Expect(secret.Finalizers).To(
ContainElement(glance.ACConsumerFinalizerName("default")))
}, timeout, interval).Should(Succeed())

newACSecretName := "ac-glance-x9y8z-secret" //nolint:gosec // G101
newSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: glanceTest.Instance.Namespace,
Name: newACSecretName,
},
Data: map[string][]byte{
keystonev1.ACIDSecretKey: []byte("x9y8zrotated-ac-id"),
keystonev1.ACSecretSecretKey: []byte("rotated-ac-secret"),
},
}
DeferCleanup(k8sClient.Delete, ctx, newSecret)
Expect(k8sClient.Create(ctx, newSecret)).To(Succeed())

Eventually(func(g Gomega) {
api := GetGlanceAPI(glanceTest.GlanceInternal)
api.Spec.Auth.ApplicationCredentialSecret = newACSecretName
g.Expect(k8sClient.Update(ctx, api)).Should(Succeed())
}, timeout, interval).Should(Succeed())

Eventually(func(g Gomega) {
secret := th.GetSecret(types.NamespacedName{
Namespace: glanceTest.Instance.Namespace,
Name: newACSecretName,
})
g.Expect(secret.Finalizers).To(
ContainElement(glance.ACConsumerFinalizerName("default")))
}, timeout, interval).Should(Succeed())

Eventually(func(g Gomega) {
secret := th.GetSecret(types.NamespacedName{
Namespace: glanceTest.Instance.Namespace,
Name: acSecretName,
})
g.Expect(secret.Finalizers).NotTo(
ContainElement(glance.ACConsumerFinalizerName("default")))
}, timeout, interval).Should(Succeed())

Eventually(func(g Gomega) {
api := GetGlanceAPI(glanceTest.GlanceInternal)
g.Expect(api.Status.ApplicationCredentialSecret).To(Equal(newACSecretName))
}, timeout, interval).Should(Succeed())
})

It("should remove the consumer finalizer from AC secret on CR deletion", func() {
Eventually(func(g Gomega) {
secret := th.GetSecret(types.NamespacedName{
Namespace: glanceTest.Instance.Namespace,
Name: acSecretName,
})
g.Expect(secret.Finalizers).To(
ContainElement(glance.ACConsumerFinalizerName("default")))
}, timeout, interval).Should(Succeed())

th.DeleteInstance(GetGlanceAPI(glanceTest.GlanceInternal))

secret := th.GetSecret(types.NamespacedName{
Namespace: glanceTest.Instance.Namespace,
Name: acSecretName,
})
Expect(secret.Finalizers).NotTo(
ContainElement(glance.ACConsumerFinalizerName("default")))
})
})
})
Loading