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
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.13 //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-20251103091514-244e15fe5d63
11 changes: 11 additions & 0 deletions controllers/cinder_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,17 @@ func (r *CinderReconciler) generateServiceConfigs(
templateParameters["ServicePassword"] = string(ospSecret.Data[instance.Spec.PasswordSelectors.Service])
templateParameters["KeystoneInternalURL"] = keystoneInternalURL
templateParameters["KeystonePublicURL"] = keystonePublicURL

// Check for Application Credentials
Log := r.GetLogger(ctx)
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, h.GetClient(), instance.Namespace, cinder.ServiceName); err != nil {
Log.Error(err, "Failed to get ApplicationCredential for service", "service", cinder.ServiceName)
return err
} else if acData != nil {
templateParameters["ApplicationCredentialID"] = acData.ID
templateParameters["ApplicationCredentialSecret"] = acData.Secret
Log.Info("Using ApplicationCredentials auth", "service", cinder.ServiceName)
}
templateParameters["TransportURL"] = transportURLSecretData
templateParameters["DatabaseConnection"] = fmt.Sprintf("mysql+pymysql://%s:%s@%s/%s?read_default_file=/etc/my.cnf",
databaseAccount.Spec.UserName,
Expand Down
43 changes: 43 additions & 0 deletions controllers/cinderapi_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,42 @@ func (r *CinderAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
return err
}

// Application Credential secret watching function
acSecretFn := func(_ context.Context, o client.Object) []reconcile.Request {
name := o.GetName()
ns := o.GetNamespace()
result := []reconcile.Request{}

// Only handle Secret objects
if _, isSecret := o.(*corev1.Secret); !isSecret {
return nil
}

// Check if this is a cinder AC secret by name pattern (ac-cinder-secret)
expectedSecretName := keystonev1.GetACSecretName("cinder")
if name == expectedSecretName {
// get all CinderAPI CRs in this namespace
cinderAPIs := &cinderv1beta1.CinderAPIList{}
listOpts := []client.ListOption{
client.InNamespace(ns),
}
if err := r.List(context.Background(), cinderAPIs, listOpts...); err != nil {
return nil
}

// Enqueue reconcile for all cinder API instances
for _, cr := range cinderAPIs.Items {
objKey := client.ObjectKey{
Namespace: ns,
Name: cr.Name,
}
result = append(result, reconcile.Request{NamespacedName: objKey})
}
}

return result
}

return ctrl.NewControllerManagedBy(mgr).
For(&cinderv1beta1.CinderAPI{}).
Owns(&keystonev1.KeystoneService{}).
Expand All @@ -352,6 +388,8 @@ func (r *CinderAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
).
Watches(&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(acSecretFn)).
Watches(&topologyv1.Topology{},
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Expand Down Expand Up @@ -701,6 +739,11 @@ func (r *CinderAPIReconciler) reconcileNormal(ctx context.Context, instance *cin
return ctrlResult, nil
}

// Check for Application Credentials
if ctrlResult, err = keystonev1.VerifyApplicationCredentialsForService(ctx, r.Client, instance.Namespace, "cinder", &configVars, cinder.NormalDuration); err != nil || ctrlResult.RequeueAfter > 0 {
return ctrlResult, err
}

//
// check for required Transport URL and config secrets
//
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.13 //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-20251103091514-244e15fe5d63
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-20251103091514-244e15fe5d63 h1:ug2YPMQJ/+0ifOjFyaPx1YtX0zsVnL02pB2ngacYviw=
github.com/Deydra71/keystone-operator/api v0.0.0-20251103091514-244e15fe5d63/go.mod h1:FMFoO4MjEQ85JpdLtDHxYSZxvJ9KzHua+HdKhpl0KRI=
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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down Expand Up @@ -100,8 +102,6 @@ github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyU
github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo=
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251030184102-82d2cbaafd35 h1:QFFGu93A+XCvDUxZIgfBE4gB5hEdVQAIw+E8dF1kP/E=
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251030184102-82d2cbaafd35/go.mod h1:qq8BCRxTEmLRriUsQ4HeDUzqltWg32MQPDTMhgbBGK4=
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251027074845-ed8154b20ad1 h1:QohvX44nxoV2GwvvOURGXYyDuCn4SCrnwubTKJtzehY=
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251027074845-ed8154b20ad1/go.mod h1:FMFoO4MjEQ85JpdLtDHxYSZxvJ9KzHua+HdKhpl0KRI=
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251027074416-ab5c045dbe00 h1:Xih6tYYqiDVllo4fDGHqTPL+M2biO5YLOUmbiTqrW/I=
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251027074416-ab5c045dbe00/go.mod h1:PMoNILOdQ1Ij7DyrKgljN6RAiq8pFM2AGsUb6mcxe98=
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251021145236-2b84ec9fd9bb h1:wToXqX7AS1JV3Kna7RcJfkRart8rSGun2biKNfyY6Zg=
Expand Down
28 changes: 23 additions & 5 deletions templates/cinder/config/00-global-defaults.conf
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,18 @@ auth_url = {{ .KeystoneInternalURL }}
memcached_servers = {{ .MemcachedServers }}
memcache_pool_dead_retry = 10
memcache_pool_conn_get_timeout = 2
{{ if (index . "ApplicationCredentialID") -}}
auth_type = v3applicationcredential
application_credential_id = {{ .ApplicationCredentialID }}
application_credential_secret = {{ .ApplicationCredentialSecret }}
{{ else -}}
auth_type = password
username = {{ .ServiceUser }}
password = {{ .ServicePassword }}
{{ end -}}
project_domain_name = Default
user_domain_name = Default
project_name = service
username = {{ .ServiceUser }}
password = {{ .ServicePassword }}
service_token_roles_required = true
interface = internal
{{if (index . "MemcachedAuthCert")}}
Expand All @@ -93,20 +99,32 @@ memcache_tls_enabled = true

[nova]
interface = internal
{{ if (index . "ApplicationCredentialID") -}}
auth_type = v3applicationcredential
application_credential_id = {{ .ApplicationCredentialID }}
application_credential_secret = {{ .ApplicationCredentialSecret }}
{{ else -}}
auth_type = password
auth_url = {{ .KeystoneInternalURL }}
username = {{ .ServiceUser }}
password = {{ .ServicePassword }}
{{ end -}}
auth_url = {{ .KeystoneInternalURL }}
user_domain_name = Default
project_name = service
project_domain_name = Default

[service_user]
send_service_user_token = True
auth_url = {{ .KeystoneInternalURL }}
{{ if (index . "ApplicationCredentialID") -}}
auth_type = v3applicationcredential
application_credential_id = {{ .ApplicationCredentialID }}
application_credential_secret = {{ .ApplicationCredentialSecret }}
{{ else -}}
auth_type = password
username = {{ .ServiceUser }}
password = {{ .ServicePassword }}
{{ end -}}
project_domain_name = Default
user_domain_name = Default
project_name = service
username = {{ .ServiceUser }}
password = {{ .ServicePassword }}
124 changes: 124 additions & 0 deletions test/functional/cinder_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

corev1 "k8s.io/api/core/v1"
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
Expand All @@ -37,9 +38,11 @@ import (
"github.com/openstack-k8s-operators/cinder-operator/pkg/cinder"
memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1"
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
util "github.com/openstack-k8s-operators/lib-common/modules/common/util"
mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers"
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
)

var _ = Describe("Cinder controller", func() {
Expand Down Expand Up @@ -1744,4 +1747,125 @@ var _ = Describe("Cinder Webhook", func() {
return instance, fmt.Sprintf("cinderVolumes[%s].topologyRef", instance)
}),
)

When("An ApplicationCredential is created for Cinder", func() {
var (
acName string
acSecretName string
servicePasswordSecret string
passwordSelector string
)
BeforeEach(func() {
servicePasswordSecret = "ac-test-osp-secret" //nolint:gosec // G101
passwordSelector = "CinderPassword"

DeferCleanup(k8sClient.Delete, ctx,
CreateCinderMessageBusSecret(
cinderTest.Instance.Namespace,
cinderTest.RabbitmqSecretName,
),
)
DeferCleanup(k8sClient.Delete, ctx,
CreateCinderSecret(
cinderTest.Instance.Namespace, servicePasswordSecret))
// Create Cinder using the service password secret
spec := GetDefaultCinderSpec()
spec["secret"] = servicePasswordSecret
DeferCleanup(th.DeleteInstance, CreateCinder(cinderTest.Instance, spec))
DeferCleanup(
mariadb.DeleteDBService,
mariadb.CreateDBService(
cinderTest.Instance.Namespace,
GetCinder(cinderTest.Instance).Spec.DatabaseInstance,
corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Port: 3306}}}))
DeferCleanup(keystone.DeleteKeystoneAPI,
keystone.CreateKeystoneAPI(cinderTest.Instance.Namespace),
)
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(cinderTest.Instance.Namespace, MemcachedInstance, memcachedv1.MemcachedSpec{}))
infra.SimulateMemcachedReady(cinderTest.CinderMemcached)

// Create MariaDB account and database
acc, accSecret := mariadb.CreateMariaDBAccountAndSecret(cinderTest.Database, mariadbv1.MariaDBAccountSpec{})
DeferCleanup(k8sClient.Delete, ctx, acc)
DeferCleanup(k8sClient.Delete, ctx, accSecret)
mariadb.CreateMariaDBDatabase(cinderTest.Database.Namespace, cinderTest.Database.Name, mariadbv1.MariaDBDatabaseSpec{})
DeferCleanup(k8sClient.Delete, ctx, mariadb.GetMariaDBDatabase(cinderTest.Database))

acName = fmt.Sprintf("ac-%s", cinder.ServiceName)
acSecretName = acName + "-secret"
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: cinderTest.Instance.Namespace,
Name: acSecretName,
},
Data: map[string][]byte{
"AC_ID": []byte("test-ac-id"),
"AC_SECRET": []byte("test-ac-secret"),
},
}
DeferCleanup(k8sClient.Delete, ctx, secret)
Expect(k8sClient.Create(ctx, secret)).To(Succeed())

ac := &keystonev1.KeystoneApplicationCredential{
ObjectMeta: metav1.ObjectMeta{
Namespace: cinderTest.Instance.Namespace,
Name: acName,
},
Spec: keystonev1.KeystoneApplicationCredentialSpec{
UserName: cinder.ServiceName,
Secret: servicePasswordSecret,
PasswordSelector: passwordSelector,
Roles: []string{"admin", "member"},
AccessRules: []keystonev1.ACRule{{Service: "identity", Method: "POST", Path: "/auth/tokens"}},
ExpirationDays: 30,
GracePeriodDays: 5,
},
}
DeferCleanup(k8sClient.Delete, ctx, ac)
Expect(k8sClient.Create(ctx, ac)).To(Succeed())

fetched := &keystonev1.KeystoneApplicationCredential{}
key := types.NamespacedName{Namespace: ac.Namespace, Name: ac.Name}
Expect(k8sClient.Get(ctx, key, fetched)).To(Succeed())

fetched.Status.SecretName = acSecretName
now := metav1.Now()
readyCond := condition.Condition{
Type: condition.ReadyCondition,
Status: corev1.ConditionTrue,
Reason: condition.ReadyReason,
Message: condition.ReadyMessage,
LastTransitionTime: now,
}
fetched.Status.Conditions = condition.Conditions{readyCond}
Expect(k8sClient.Status().Update(ctx, fetched)).To(Succeed())

infra.SimulateTransportURLReady(cinderTest.CinderTransportURL)
mariadb.SimulateMariaDBAccountCompleted(cinderTest.Database)
mariadb.SimulateMariaDBDatabaseCompleted(cinderTest.Database)

th.SimulateJobSuccess(cinderTest.CinderDBSync)

keystone.SimulateKeystoneEndpointReady(cinderTest.CinderKeystoneEndpoint)
})

It("should render ApplicationCredential auth in 00-global-defaults.conf", func() {
keystone.SimulateKeystoneEndpointReady(cinderTest.CinderKeystoneEndpoint)

Eventually(func(g Gomega) {
cfgSecret := th.GetSecret(cinderTest.CinderConfigSecret)
g.Expect(cfgSecret).NotTo(BeNil())

conf := string(cfgSecret.Data["00-global-defaults.conf"])

g.Expect(conf).To(ContainSubstring(
"application_credential_id = test-ac-id"),
)
g.Expect(conf).To(ContainSubstring(
"application_credential_secret = test-ac-secret"),
)
}, timeout, interval).Should(Succeed())
})
})
})
Loading