From 06384555fddbafc959abef45c382a788a2b70855 Mon Sep 17 00:00:00 2001 From: Luca Miccini Date: Tue, 16 Dec 2025 16:39:49 +0100 Subject: [PATCH] Rabbitmq vhost and user support Add new messagingBus and notificationsBus interfaces to hold cluster, user and vhost names for optional usage. The controller adds these values to the TransportURL create request when present. Additionally, we migrate RabbitMQ cluster name to RabbitMq config struct using DefaultRabbitMqConfig from infra-operator to automatically populate the new Cluster field from legacy RabbitMqClusterName. Example usage: spec: messagingBus: cluster: rpc-rabbitmq user: rpc-user vhost: rpc-vhost notificationsBus: cluster: notifications-rabbitmq user: notifications-user vhost: notifications-vhost Jira: https://issues.redhat.com/browse/OSPRH-23748 --- api/bases/manila.openstack.org_manilas.yaml | 33 ++ api/go.mod | 2 +- api/go.sum | 3 + api/v1beta1/conditions.go | 18 + api/v1beta1/manila_types.go | 9 + api/v1beta1/manila_webhook.go | 43 ++ api/v1beta1/zz_generated.deepcopy.go | 7 + .../bases/manila.openstack.org_manilas.yaml | 33 ++ ...manila_v1beta1_manila_rabbitmq_custom.yaml | 34 ++ internal/controller/manila_controller.go | 62 ++- test/functional/manila_controller_test.go | 512 +++++++++++++++++- test/functional/manila_webhook_test.go | 118 ++++ 12 files changed, 848 insertions(+), 26 deletions(-) create mode 100644 config/samples/manila_v1beta1_manila_rabbitmq_custom.yaml create mode 100644 test/functional/manila_webhook_test.go diff --git a/api/bases/manila.openstack.org_manilas.yaml b/api/bases/manila.openstack.org_manilas.yaml index 4bef4571..308d25aa 100644 --- a/api/bases/manila.openstack.org_manilas.yaml +++ b/api/bases/manila.openstack.org_manilas.yaml @@ -1788,6 +1788,22 @@ spec: default: memcached description: Memcached instance name. type: string + messagingBus: + description: MessagingBus configuration (username, vhost, and cluster) + properties: + cluster: + description: Name of the cluster + minLength: 1 + type: string + user: + description: User - RabbitMQ username + type: string + vhost: + description: Vhost - RabbitMQ vhost name + type: string + required: + - cluster + type: object nodeSelector: additionalProperties: type: string @@ -1796,6 +1812,23 @@ spec: NodeSelector here acts as a default value and can be overridden by service specific NodeSelector Settings. type: object + notificationsBus: + description: NotificationsBus configuration (username, vhost, and + cluster) for notifications + properties: + cluster: + description: Name of the cluster + minLength: 1 + type: string + user: + description: User - RabbitMQ username + type: string + vhost: + description: Vhost - RabbitMQ vhost name + type: string + required: + - cluster + type: object notificationsBusInstance: description: |- RabbitMQ instance name used to request a transportURL that is used for diff --git a/api/go.mod b/api/go.mod index f7a728a3..d7b10c4a 100644 --- a/api/go.mod +++ b/api/go.mod @@ -16,7 +16,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect - github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -43,6 +42,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect + github.com/rabbitmq/cluster-operator/v2 v2.16.0 // indirect github.com/spf13/pflag v1.0.7 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/api/go.sum b/api/go.sum index a27413e2..082291e5 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,3 +1,4 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 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= @@ -84,6 +85,8 @@ github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.2025123021 github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35/go.mod h1:kycZyoe7OZdW1HUghr2nI3N7wSJtNahXf6b/ypD14f4= github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20251230215914-6ba873b49a35 h1:8WZYfCt1VJHa5sJRX0UhpmoXud/fn8LHQhXsakdYXuQ= github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20251230215914-6ba873b49a35/go.mod h1:H0aQANk8iJPRhS2Bg9n6cYb/IHF0Cks9g7+uZG04Rhk= +github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec h1:saovr368HPAKHN0aRPh8h8n9s9dn3d8Frmfua0UYRlc= +github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec/go.mod h1:Nh2NEePLjovUQof2krTAg4JaAoLacqtPTZQXK6izNfg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/api/v1beta1/conditions.go b/api/v1beta1/conditions.go index 0ed93cd1..136f26ba 100644 --- a/api/v1beta1/conditions.go +++ b/api/v1beta1/conditions.go @@ -27,6 +27,9 @@ const ( // ManilaShareReadyCondition Status=True condition which indicates if the ManilaShare is configured and operational ManilaShareReadyCondition condition.Type = "ManilaShareReady" + + // ManilaNotificationBusReadyCondition Status=True condition which indicates if the NotificationBus is configured + ManilaNotificationBusReadyCondition condition.Type = "ManilaNotificationBusReady" ) // Common Messages used by API objects. @@ -60,4 +63,19 @@ const ( // ManilaShareReadyRunningMessage ManilaShareReadyRunningMessage = "ManilaShare deployments in progress" + + // + // ManilaNotificationBusReady condition messages + // + // ManilaNotificationBusReadyInitMessage + ManilaNotificationBusReadyInitMessage = "ManilaNotificationBus not started" + + // ManilaNotificationBusReadyRunningMessage + ManilaNotificationBusReadyRunningMessage = "ManilaNotificationBus creation in progress" + + // ManilaNotificationBusReadyMessage + ManilaNotificationBusReadyMessage = "ManilaNotificationBus successfully created" + + // ManilaNotificationBusReadyErrorMessage + ManilaNotificationBusReadyErrorMessage = "ManilaNotificationBus error occured %s" ) diff --git a/api/v1beta1/manila_types.go b/api/v1beta1/manila_types.go index ef4c2f08..05a23584 100644 --- a/api/v1beta1/manila_types.go +++ b/api/v1beta1/manila_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1beta1 import ( + rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/storage" @@ -82,6 +83,14 @@ type ManilaSpecBase struct { // Needed to request a transportURL that is created and used in Manila RabbitMqClusterName string `json:"rabbitMqClusterName"` + // +kubebuilder:validation:Optional + // MessagingBus configuration (username, vhost, and cluster) + MessagingBus rabbitmqv1.RabbitMqConfig `json:"messagingBus,omitempty"` + + // +kubebuilder:validation:Optional + // NotificationsBus configuration (username, vhost, and cluster) for notifications + NotificationsBus *rabbitmqv1.RabbitMqConfig `json:"notificationsBus,omitempty"` + // +kubebuilder:validation:Required // +kubebuilder:default=memcached // Memcached instance name. diff --git a/api/v1beta1/manila_webhook.go b/api/v1beta1/manila_webhook.go index 6d8b1234..89720d3f 100644 --- a/api/v1beta1/manila_webhook.go +++ b/api/v1beta1/manila_webhook.go @@ -25,6 +25,7 @@ package v1beta1 import ( "fmt" + rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/util" @@ -101,6 +102,18 @@ func (spec *ManilaSpec) Default() { // Default - set defaults for this Manila spec base func (spec *ManilaSpecBase) Default() { + // Default MessagingBus with cluster name from RabbitMqClusterName + rabbitmqv1.DefaultRabbitMqConfig(&spec.MessagingBus, spec.RabbitMqClusterName) + + // Default NotificationsBus if NotificationsBusInstance is specified + if spec.NotificationsBusInstance != nil && *spec.NotificationsBusInstance != "" { + if spec.NotificationsBus == nil { + // Initialize empty NotificationsBus - credentials will be created dynamically + // to ensure separation from MessagingBus (RPC and notifications should never share credentials) + spec.NotificationsBus = &rabbitmqv1.RabbitMqConfig{} + } + rabbitmqv1.DefaultRabbitMqConfig(spec.NotificationsBus, *spec.NotificationsBusInstance) + } if spec.APITimeout == 0 { spec.APITimeout = manilaDefaults.APITimeout @@ -200,6 +213,21 @@ func (r *Manila) ValidateUpdate(old runtime.Object) (admission.Warnings, error) func (spec *ManilaSpec) ValidateUpdate(old ManilaSpec, basePath *field.Path, namespace string) field.ErrorList { var allErrs field.ErrorList + // Reject changes to deprecated RabbitMqClusterName field - users should use the new messagingBus.cluster field instead + if spec.RabbitMqClusterName != old.RabbitMqClusterName { + allErrs = append(allErrs, field.Forbidden( + basePath.Child("rabbitMqClusterName"), + "rabbitMqClusterName is deprecated and cannot be changed. Please use messagingBus.cluster instead")) + } + + // Reject changes to deprecated NotificationsBusInstance field + if spec.NotificationsBusInstance != nil && old.NotificationsBusInstance != nil && + *spec.NotificationsBusInstance != *old.NotificationsBusInstance { + allErrs = append(allErrs, field.Forbidden( + basePath.Child("notificationsBusInstance"), + "notificationsBusInstance is deprecated and cannot be changed. Please use notificationsBus.cluster instead")) + } + // validate the service base parameters allErrs = append(allErrs, spec.ValidateBaseParams(basePath)...) @@ -217,6 +245,21 @@ func (spec *ManilaSpec) ValidateUpdate(old ManilaSpec, basePath *field.Path, nam func (spec *ManilaSpecCore) ValidateUpdate(old ManilaSpecCore, basePath *field.Path, namespace string) field.ErrorList { var allErrs field.ErrorList + // Reject changes to deprecated RabbitMqClusterName field - users should use the new messagingBus.cluster field instead + if spec.RabbitMqClusterName != old.RabbitMqClusterName { + allErrs = append(allErrs, field.Forbidden( + basePath.Child("rabbitMqClusterName"), + "rabbitMqClusterName is deprecated and cannot be changed. Please use messagingBus.cluster instead")) + } + + // Reject changes to deprecated NotificationsBusInstance field + if spec.NotificationsBusInstance != nil && old.NotificationsBusInstance != nil && + *spec.NotificationsBusInstance != *old.NotificationsBusInstance { + allErrs = append(allErrs, field.Forbidden( + basePath.Child("notificationsBusInstance"), + "notificationsBusInstance is deprecated and cannot be changed. Please use notificationsBus.cluster instead")) + } + // validate the service base parameters allErrs = append(allErrs, spec.ValidateBaseParams(basePath)...) diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 3888dd46..865f4c6b 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package v1beta1 import ( + rabbitmqv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" topologyv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/service" @@ -776,6 +777,12 @@ func (in *ManilaSpec) DeepCopy() *ManilaSpec { func (in *ManilaSpecBase) DeepCopyInto(out *ManilaSpecBase) { *out = *in out.ManilaTemplate = in.ManilaTemplate + out.MessagingBus = in.MessagingBus + if in.NotificationsBus != nil { + in, out := &in.NotificationsBus, &out.NotificationsBus + *out = new(rabbitmqv1beta1.RabbitMqConfig) + **out = **in + } out.Debug = in.Debug if in.ExtraMounts != nil { in, out := &in.ExtraMounts, &out.ExtraMounts diff --git a/config/crd/bases/manila.openstack.org_manilas.yaml b/config/crd/bases/manila.openstack.org_manilas.yaml index 4bef4571..308d25aa 100644 --- a/config/crd/bases/manila.openstack.org_manilas.yaml +++ b/config/crd/bases/manila.openstack.org_manilas.yaml @@ -1788,6 +1788,22 @@ spec: default: memcached description: Memcached instance name. type: string + messagingBus: + description: MessagingBus configuration (username, vhost, and cluster) + properties: + cluster: + description: Name of the cluster + minLength: 1 + type: string + user: + description: User - RabbitMQ username + type: string + vhost: + description: Vhost - RabbitMQ vhost name + type: string + required: + - cluster + type: object nodeSelector: additionalProperties: type: string @@ -1796,6 +1812,23 @@ spec: NodeSelector here acts as a default value and can be overridden by service specific NodeSelector Settings. type: object + notificationsBus: + description: NotificationsBus configuration (username, vhost, and + cluster) for notifications + properties: + cluster: + description: Name of the cluster + minLength: 1 + type: string + user: + description: User - RabbitMQ username + type: string + vhost: + description: Vhost - RabbitMQ vhost name + type: string + required: + - cluster + type: object notificationsBusInstance: description: |- RabbitMQ instance name used to request a transportURL that is used for diff --git a/config/samples/manila_v1beta1_manila_rabbitmq_custom.yaml b/config/samples/manila_v1beta1_manila_rabbitmq_custom.yaml new file mode 100644 index 00000000..ef87f25a --- /dev/null +++ b/config/samples/manila_v1beta1_manila_rabbitmq_custom.yaml @@ -0,0 +1,34 @@ +apiVersion: manila.openstack.org/v1beta1 +kind: Manila +metadata: + name: manila-rabbitmq-custom + namespace: openstack +spec: + secret: osp-secret + databaseInstance: openstack + databaseAccount: manila + rabbitMqClusterName: rabbitmq + # Custom RabbitMQ configuration for the main messaging bus + rabbitmq: + user: main-user + vhost: main-vhost + # Optional: Separate RabbitMQ configuration for notifications + notificationsBusInstance: rabbitmq-notification + notificationsRabbitmq: + user: notifications-user + vhost: notifications-vhost + memcachedInstance: memcached + serviceUser: manila + customServiceConfig: | + [DEFAULT] + debug = true + manilaAPI: + replicas: 1 + containerImage: quay.io/podified-antelope-centos9/openstack-manila-api:current-podified + manilaScheduler: + replicas: 1 + containerImage: quay.io/podified-antelope-centos9/openstack-manila-scheduler:current-podified + manilaShares: + share0: + replicas: 1 + containerImage: quay.io/podified-antelope-centos9/openstack-manila-share:current-podified diff --git a/internal/controller/manila_controller.go b/internal/controller/manila_controller.go index a3d24f8c..e2595055 100644 --- a/internal/controller/manila_controller.go +++ b/internal/controller/manila_controller.go @@ -194,7 +194,9 @@ func (r *ManilaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res condition.UnknownCondition(condition.CronJobReadyCondition, condition.InitReason, condition.CronJobReadyInitMessage), ) - if instance.Spec.NotificationsBusInstance != nil { + // Initialize notification bus condition if configured via either the new or deprecated field + if instance.Spec.NotificationsBusInstance != nil || + (instance.Spec.NotificationsBus != nil && instance.Spec.NotificationsBus.Cluster != "") { c := condition.UnknownCondition( condition.NotificationBusInstanceReadyCondition, condition.InitReason, @@ -506,7 +508,7 @@ func (r *ManilaReconciler) reconcileNormal(ctx context.Context, instance *manila // create RabbitMQ transportURL CR and get the actual URL from the associated secret that is created // - transportURL, op, err := r.transportURLCreateOrUpdate(ctx, instance, serviceLabels, "") + transportURL, op, err := r.transportURLCreateOrUpdate(ctx, instance, serviceLabels, "", instance.Spec.MessagingBus) if err != nil { instance.Status.Conditions.Set(condition.FalseCondition( condition.RabbitMqTransportURLReadyCondition, @@ -542,19 +544,26 @@ func (r *ManilaReconciler) reconcileNormal(ctx context.Context, instance *manila // associated secret that is created // - // Request TransportURL when the parameter is provided in the CR - // and it does not match with the existing RabbitMqClusterName - if instance.Spec.NotificationsBusInstance != nil { - // init .Status.NotificationsURLSecret - instance.Status.NotificationsURLSecret = ptr.To("") - + // Request TransportURL when notifications are configured either via: + // Determine if notifications are enabled by checking NotificationsBus.Cluster + // (the webhook defaults this from the deprecated NotificationsBusInstance field) + var notificationBusName string + notificationsEnabled := instance.Spec.NotificationsBus != nil && instance.Spec.NotificationsBus.Cluster != "" + if notificationsEnabled { // setting notificationBusName to an empty string ensures that we do not // request a new transportURL unless the two spec fields do not match - var notificationBusName string - if *instance.Spec.NotificationsBusInstance != instance.Spec.RabbitMqClusterName { - notificationBusName = *instance.Spec.NotificationsBusInstance + if instance.Spec.NotificationsBus.Cluster != instance.Spec.RabbitMqClusterName { + notificationBusName = instance.Spec.NotificationsBus.Cluster } - notificationBusInstanceURL, op, err := r.transportURLCreateOrUpdate(ctx, instance, serviceLabels, notificationBusName) + } + + if notificationsEnabled { + // init .Status.NotificationsURLSecret + instance.Status.NotificationsURLSecret = ptr.To("") + + // Use NotificationsBus config (never fall back to MessagingBus to ensure separation) + notificationsRabbitMqConfig := *instance.Spec.NotificationsBus + notificationBusInstanceURL, op, err := r.transportURLCreateOrUpdate(ctx, instance, serviceLabels, notificationBusName, notificationsRabbitMqConfig) if err != nil { instance.Status.Conditions.Set(condition.FalseCondition( condition.NotificationBusInstanceReadyCondition, @@ -1044,7 +1053,15 @@ func (r *ManilaReconciler) generateServiceConfig( if instance.Status.NotificationsURLSecret != nil { // Get a notificationInstanceURLSecret only if rabbitMQ referenced in // the spec is different, otherwise inherits the existing transport_url - if instance.Spec.RabbitMqClusterName != *instance.Spec.NotificationsBusInstance { + // Check both the new NotificationsBus.Cluster field and deprecated NotificationsBusInstance + notificationCluster := instance.Spec.RabbitMqClusterName + if instance.Spec.NotificationsBus != nil && instance.Spec.NotificationsBus.Cluster != "" { + notificationCluster = instance.Spec.NotificationsBus.Cluster + } else if instance.Spec.NotificationsBusInstance != nil { + notificationCluster = *instance.Spec.NotificationsBusInstance + } + + if instance.Spec.RabbitMqClusterName != notificationCluster { notificationInstanceURLSecret, _, err = secret.GetSecret(ctx, h, *instance.Status.NotificationsURLSecret, instance.Namespace) if err != nil { return err @@ -1134,7 +1151,8 @@ func (r *ManilaReconciler) apiDeploymentCreateOrUpdate(ctx context.Context, inst op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deployment, func() error { deployment.Spec = apiSpec - if instance.Spec.NotificationsBusInstance != nil { + if instance.Spec.NotificationsBusInstance != nil || + (instance.Spec.NotificationsBus != nil && instance.Spec.NotificationsBus.Cluster != "") { deployment.Spec.NotificationsURLSecret = *instance.Status.NotificationsURLSecret } @@ -1181,7 +1199,8 @@ func (r *ManilaReconciler) schedulerDeploymentCreateOrUpdate(ctx context.Context op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deployment, func() error { deployment.Spec = schedulerSpec - if instance.Spec.NotificationsBusInstance != nil { + if instance.Spec.NotificationsBusInstance != nil || + (instance.Spec.NotificationsBus != nil && instance.Spec.NotificationsBus.Cluster != "") { deployment.Spec.NotificationsURLSecret = *instance.Status.NotificationsURLSecret } @@ -1238,7 +1257,8 @@ func (r *ManilaReconciler) shareDeploymentCreateOrUpdate( op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deployment, func() error { deployment.Spec = shareSpec - if instance.Spec.NotificationsBusInstance != nil { + if instance.Spec.NotificationsBusInstance != nil || + (instance.Spec.NotificationsBus != nil && instance.Spec.NotificationsBus.Cluster != "") { deployment.Spec.NotificationsURLSecret = *instance.Status.NotificationsURLSecret } @@ -1263,6 +1283,7 @@ func (r *ManilaReconciler) transportURLCreateOrUpdate( instance *manilav1beta1.Manila, serviceLabels map[string]string, rabbitMqClusterName string, + rabbitMqConfig rabbitmqv1.RabbitMqConfig, ) (*rabbitmqv1.TransportURL, controllerutil.OperationResult, error) { // Default values used for regular messagingBus transportURL and explicitly @@ -1285,9 +1306,12 @@ func (r *ManilaReconciler) transportURLCreateOrUpdate( } op, err := controllerutil.CreateOrUpdate(ctx, r.Client, transportURL, func() error { transportURL.Spec.RabbitmqClusterName = transportURLName - - err := controllerutil.SetControllerReference(instance, transportURL, r.Scheme) - return err + if rabbitMqConfig.User != "" { + transportURL.Spec.Username = rabbitMqConfig.User + } + // Always set Vhost - empty string means use default "/" vhost + transportURL.Spec.Vhost = rabbitMqConfig.Vhost + return controllerutil.SetControllerReference(instance, transportURL, r.Scheme) }) return transportURL, op, err } diff --git a/test/functional/manila_controller_test.go b/test/functional/manila_controller_test.go index 7d1410ca..0b795910 100644 --- a/test/functional/manila_controller_test.go +++ b/test/functional/manila_controller_test.go @@ -1198,10 +1198,12 @@ var _ = Describe("Manila controller", func() { When("Manila CR instance has notifications enabled", func() { BeforeEach(func() { rawSpec := map[string]any{ - "secret": SecretName, - "databaseInstance": "openstack", - "rabbitMqClusterName": "rabbitmq", - "notificationsBusInstance": "rabbitmq", + "secret": SecretName, + "databaseInstance": "openstack", + "rabbitMqClusterName": "rabbitmq", + "notificationsBus": map[string]any{ + "cluster": "rabbitmq", + }, "manilaAPI": map[string]any{ "containerImage": manilav1.ManilaAPIContainerImage, }, @@ -1267,7 +1269,7 @@ var _ = Describe("Manila controller", func() { // update Manila CR to point to the new (dedicated) rabbit instance Eventually(func(g Gomega) { manila := GetManila(manilaTest.Instance) - *manila.Spec.NotificationsBusInstance = "rabbitmq-notification" + manila.Spec.NotificationsBus.Cluster = "rabbitmq-notification" g.Expect(k8sClient.Update(ctx, manila)).To(Succeed()) }, timeout, interval).Should(Succeed()) @@ -1288,7 +1290,7 @@ var _ = Describe("Manila controller", func() { It("updates manila CR and disable notifications", func() { Eventually(func(g Gomega) { manila := GetManila(manilaTest.Instance) - manila.Spec.NotificationsBusInstance = nil + manila.Spec.NotificationsBus = nil g.Expect(k8sClient.Update(ctx, manila)).To(Succeed()) }, timeout, interval).Should(Succeed()) @@ -1380,6 +1382,147 @@ var _ = Describe("Manila controller", func() { }) }) + When("Manila is created with custom RabbitMQ user and vhost", func() { + BeforeEach(func() { + rawSpec := map[string]any{ + "secret": SecretName, + "databaseInstance": "openstack", + "rabbitMqClusterName": "rabbitmq", + "messagingBus": map[string]any{ + "user": "main-user", + "vhost": "main-vhost", + }, + "manilaAPI": map[string]any{ + "containerImage": manilav1.ManilaAPIContainerImage, + }, + "manilaScheduler": map[string]any{ + "containerImage": manilav1.ManilaSchedulerContainerImage, + }, + "manilaShares": map[string]any{ + "share0": map[string]any{ + "containerImage": manilav1.ManilaShareContainerImage, + }, + }, + } + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, rawSpec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(manilaTest.Instance.Namespace)) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + }) + + It("creates TransportURL with custom user and vhost", func() { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + Expect(transportURL.Spec.Username).To(Equal("main-user")) + Expect(transportURL.Spec.Vhost).To(Equal("main-vhost")) + }) + }) + + When("Manila is created with separate notifications RabbitMQ config", func() { + BeforeEach(func() { + rawSpec := map[string]any{ + "secret": SecretName, + "databaseInstance": "openstack", + "rabbitMqClusterName": "rabbitmq", + "notificationsBusInstance": "rabbitmq-notification", + "messagingBus": map[string]any{ + "user": "main-user", + "vhost": "main-vhost", + }, + "notificationsBus": map[string]any{ + "user": "notifications-user", + "vhost": "notifications-vhost", + }, + "manilaAPI": map[string]any{ + "containerImage": manilav1.ManilaAPIContainerImage, + }, + "manilaScheduler": map[string]any{ + "containerImage": manilav1.ManilaSchedulerContainerImage, + }, + "manilaShares": map[string]any{ + "share0": map[string]any{ + "containerImage": manilav1.ManilaShareContainerImage, + }, + }, + } + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, rawSpec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.NotificationSecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + }) + + It("creates separate TransportURLs with different configs", func() { + // Check main transport URL + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + Expect(transportURL.Spec.Username).To(Equal("main-user")) + Expect(transportURL.Spec.Vhost).To(Equal("main-vhost")) + + // Check notifications transport URL + notificationTransportURL := infra.GetTransportURL(types.NamespacedName{ + Namespace: manilaTest.Instance.Namespace, + Name: fmt.Sprintf("%s-manila-transport-rabbitmq-notification", manilaTest.Instance.Name), + }) + Expect(notificationTransportURL.Spec.Username).To(Equal("notifications-user")) + Expect(notificationTransportURL.Spec.Vhost).To(Equal("notifications-vhost")) + }) + }) + + When("Manila is created with default RabbitMQ config", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, GetDefaultManilaSpec())) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(manilaTest.Instance.Namespace)) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + }) + + It("creates TransportURL without custom user and vhost", func() { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + Expect(transportURL.Spec.Username).To(Equal("")) + Expect(transportURL.Spec.Vhost).To(Equal("")) + }) + }) + // Run MariaDBAccount suite tests. these are pre-packaged ginkgo tests // that exercise standard account create / update patterns that should be // common to all controllers that ensure MariaDBAccount CRs. @@ -1568,3 +1711,360 @@ var _ = Describe("Manila Webhook", func() { }), ) }) + +var _ = Describe("Manila with RabbitMQ custom vhost and user", func() { + var memcachedSpec memcachedv1.MemcachedSpec + + BeforeEach(func() { + memcachedSpec = infra.GetDefaultMemcachedSpec() + }) + + When("Manila is created with custom RabbitMQ vhost and user", func() { + BeforeEach(func() { + spec := GetDefaultManilaSpec() + spec["messagingBus"] = map[string]any{ + "user": "custom-user", + "vhost": "custom-vhost", + } + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should create TransportURL with custom vhost and user", func() { + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("custom-user")) + g.Expect(transportURL.Spec.Vhost).To(Equal("custom-vhost")) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("Manila is created with default RabbitMQ configuration", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, GetDefaultManilaSpec())) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should create TransportURL with default vhost and user", func() { + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("")) + g.Expect(transportURL.Spec.Vhost).To(Equal("")) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("Manila is created with only custom RabbitMQ user", func() { + BeforeEach(func() { + spec := GetDefaultManilaSpec() + spec["messagingBus"] = map[string]any{ + "user": "custom-user-only", + } + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should create TransportURL with custom user and default vhost", func() { + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("custom-user-only")) + g.Expect(transportURL.Spec.Vhost).To(Equal("")) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("Manila is created with only custom RabbitMQ vhost", func() { + BeforeEach(func() { + spec := GetDefaultManilaSpec() + spec["messagingBus"] = map[string]any{ + "vhost": "custom-vhost-only", + } + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should create TransportURL with custom vhost and default user", func() { + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("")) + g.Expect(transportURL.Spec.Vhost).To(Equal("custom-vhost-only")) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("Manila RabbitMQ configuration is updated", func() { + BeforeEach(func() { + spec := GetDefaultManilaSpec() + spec["messagingBus"] = map[string]any{ + "user": "initial-user", + "vhost": "initial-vhost", + } + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should update TransportURL when RabbitMQ configuration changes", func() { + // Verify initial configuration + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("initial-user")) + g.Expect(transportURL.Spec.Vhost).To(Equal("initial-vhost")) + }, timeout, interval).Should(Succeed()) + + // Update the Manila CR with new RabbitMQ configuration + Eventually(func(g Gomega) { + manila := GetManila(manilaTest.Instance) + manila.Spec.MessagingBus.User = "updated-user" + manila.Spec.MessagingBus.Vhost = "updated-vhost" + g.Expect(k8sClient.Update(ctx, manila)).To(Succeed()) + }, timeout, interval).Should(Succeed()) + + // Verify the TransportURL is updated + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("updated-user")) + g.Expect(transportURL.Spec.Vhost).To(Equal("updated-vhost")) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("Manila is created with custom RabbitMQ config and notifications bus", func() { + BeforeEach(func() { + spec := GetDefaultManilaSpec() + spec["messagingBus"] = map[string]any{ + "user": "custom-user", + "vhost": "custom-vhost", + } + notificationsBusInstance := "rabbitmq-notifications" + spec["notificationsBusInstance"] = ¬ificationsBusInstance + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, "rabbitmq-notifications-secret")) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should apply custom RabbitMQ config to main but NOT inherit to notifications TransportURL", func() { + // Verify main TransportURL has custom config + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("custom-user")) + g.Expect(transportURL.Spec.Vhost).To(Equal("custom-vhost")) + }, timeout, interval).Should(Succeed()) + + // Verify notifications TransportURL does NOT inherit custom config (for separation) + notificationsTransportURLName := types.NamespacedName{ + Namespace: manilaTest.Instance.Namespace, + Name: fmt.Sprintf("%s-manila-transport-rabbitmq-notifications", manilaTest.Instance.Name), + } + infra.SimulateTransportURLReady(notificationsTransportURLName) + + Eventually(func(g Gomega) { + notificationsTransportURL := infra.GetTransportURL(notificationsTransportURLName) + // User and vhost should be empty (dynamically generated) to ensure separation + g.Expect(notificationsTransportURL.Spec.Username).To(Equal("")) + g.Expect(notificationsTransportURL.Spec.Vhost).To(Equal("")) + g.Expect(notificationsTransportURL.Spec.RabbitmqClusterName).To(Equal("rabbitmq-notifications")) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("Manila is created with different RabbitMQ configs for main and notifications", func() { + BeforeEach(func() { + spec := GetDefaultManilaSpec() + spec["messagingBus"] = map[string]any{ + "user": "main-user", + "vhost": "main-vhost", + } + spec["notificationsBus"] = map[string]any{ + "user": "notifications-user", + "vhost": "notifications-vhost", + } + notificationsBusInstance := "rabbitmq-notifications" + spec["notificationsBusInstance"] = ¬ificationsBusInstance + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, "rabbitmq-notifications-secret")) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should use different credentials for main and notifications TransportURLs", func() { + // Verify main TransportURL has main-specific config + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("main-user")) + g.Expect(transportURL.Spec.Vhost).To(Equal("main-vhost")) + g.Expect(transportURL.Spec.RabbitmqClusterName).To(Equal(manilaTest.RabbitmqClusterName)) + }, timeout, interval).Should(Succeed()) + + // Verify notifications TransportURL has notifications-specific config + notificationsTransportURLName := types.NamespacedName{ + Namespace: manilaTest.Instance.Namespace, + Name: fmt.Sprintf("%s-manila-transport-rabbitmq-notifications", manilaTest.Instance.Name), + } + infra.SimulateTransportURLReady(notificationsTransportURLName) + + Eventually(func(g Gomega) { + notificationsTransportURL := infra.GetTransportURL(notificationsTransportURLName) + g.Expect(notificationsTransportURL.Spec.Username).To(Equal("notifications-user")) + g.Expect(notificationsTransportURL.Spec.Vhost).To(Equal("notifications-vhost")) + g.Expect(notificationsTransportURL.Spec.RabbitmqClusterName).To(Equal("rabbitmq-notifications")) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("Manila has notifications bus but only main RabbitMQ config", func() { + BeforeEach(func() { + spec := GetDefaultManilaSpec() + spec["messagingBus"] = map[string]any{ + "user": "shared-user", + "vhost": "shared-vhost", + } + notificationsBusInstance := "rabbitmq-notifications" + spec["notificationsBusInstance"] = ¬ificationsBusInstance + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup(k8sClient.Delete, ctx, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, "rabbitmq-notifications-secret")) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.Instance.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should NOT fall back to main RabbitMQ config for notifications (ensure separation)", func() { + // Verify main TransportURL has custom config + Eventually(func(g Gomega) { + transportURL := infra.GetTransportURL(manilaTest.ManilaTransportURL) + g.Expect(transportURL.Spec.Username).To(Equal("shared-user")) + g.Expect(transportURL.Spec.Vhost).To(Equal("shared-vhost")) + }, timeout, interval).Should(Succeed()) + + notificationsTransportURLName := types.NamespacedName{ + Namespace: manilaTest.Instance.Namespace, + Name: fmt.Sprintf("%s-manila-transport-rabbitmq-notifications", manilaTest.Instance.Name), + } + infra.SimulateTransportURLReady(notificationsTransportURLName) + + Eventually(func(g Gomega) { + notificationsTransportURL := infra.GetTransportURL(notificationsTransportURLName) + // Notifications should NOT fall back to main config - ensure separation + g.Expect(notificationsTransportURL.Spec.Username).To(Equal("")) + g.Expect(notificationsTransportURL.Spec.Vhost).To(Equal("")) + g.Expect(notificationsTransportURL.Spec.RabbitmqClusterName).To(Equal("rabbitmq-notifications")) + }, timeout, interval).Should(Succeed()) + }) + }) +}) diff --git a/test/functional/manila_webhook_test.go b/test/functional/manila_webhook_test.go new file mode 100644 index 00000000..b4a0f453 --- /dev/null +++ b/test/functional/manila_webhook_test.go @@ -0,0 +1,118 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package functional + +import ( + "errors" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +var _ = Describe("Manila webhook", func() { + It("rejects update to deprecated rabbitMqClusterName field", func() { + spec := GetDefaultManilaSpec() + spec["rabbitMqClusterName"] = "rabbitmq" + + manilaName := types.NamespacedName{ + Namespace: namespace, + Name: "manila-webhook-test", + } + + raw := map[string]any{ + "apiVersion": "manila.openstack.org/v1beta1", + "kind": "Manila", + "metadata": map[string]any{ + "name": manilaName.Name, + "namespace": manilaName.Namespace, + }, + "spec": spec, + } + + // Create the Manila instance + unstructuredObj := &unstructured.Unstructured{Object: raw} + _, err := controllerutil.CreateOrPatch( + ctx, k8sClient, unstructuredObj, func() error { return nil }) + Expect(err).ShouldNot(HaveOccurred()) + + // Try to update rabbitMqClusterName + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, manilaName, unstructuredObj)).Should(Succeed()) + specMap := unstructuredObj.Object["spec"].(map[string]any) + specMap["rabbitMqClusterName"] = "rabbitmq2" + err := k8sClient.Update(ctx, unstructuredObj) + g.Expect(err).Should(HaveOccurred()) + + var statusError *k8s_errors.StatusError + g.Expect(errors.As(err, &statusError)).To(BeTrue()) + g.Expect(statusError.ErrStatus.Details.Kind).To(Equal("Manila")) + g.Expect(statusError.ErrStatus.Message).To( + ContainSubstring("rabbitMqClusterName is deprecated and cannot be changed")) + g.Expect(statusError.ErrStatus.Message).To( + ContainSubstring("Please use messagingBus.cluster instead")) + }, timeout, interval).Should(Succeed()) + }) + + It("rejects update to deprecated notificationsBusInstance field", func() { + spec := GetDefaultManilaSpec() + notificationsBusInstance := "notifications-rabbitmq" + spec["notificationsBusInstance"] = notificationsBusInstance + + manilaName := types.NamespacedName{ + Namespace: namespace, + Name: "manila-webhook-notif-test", + } + + raw := map[string]any{ + "apiVersion": "manila.openstack.org/v1beta1", + "kind": "Manila", + "metadata": map[string]any{ + "name": manilaName.Name, + "namespace": manilaName.Namespace, + }, + "spec": spec, + } + + // Create the Manila instance + unstructuredObj := &unstructured.Unstructured{Object: raw} + _, err := controllerutil.CreateOrPatch( + ctx, k8sClient, unstructuredObj, func() error { return nil }) + Expect(err).ShouldNot(HaveOccurred()) + + // Try to update notificationsBusInstance + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, manilaName, unstructuredObj)).Should(Succeed()) + specMap := unstructuredObj.Object["spec"].(map[string]any) + specMap["notificationsBusInstance"] = "notifications-rabbitmq2" + err := k8sClient.Update(ctx, unstructuredObj) + g.Expect(err).Should(HaveOccurred()) + + var statusError *k8s_errors.StatusError + g.Expect(errors.As(err, &statusError)).To(BeTrue()) + g.Expect(statusError.ErrStatus.Details.Kind).To(Equal("Manila")) + g.Expect(statusError.ErrStatus.Message).To( + ContainSubstring("notificationsBusInstance is deprecated and cannot be changed")) + g.Expect(statusError.ErrStatus.Message).To( + ContainSubstring("Please use notificationsBus.cluster instead")) + }, timeout, interval).Should(Succeed()) + }) +})