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()) + }) +})