From b5594d196e33ca03e3ebf6dc0d0b702168fb0020 Mon Sep 17 00:00:00 2001 From: Luca Miccini Date: Thu, 18 Dec 2025 18:00:41 +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-22697 --- api/bases/nova.openstack.org_nova.yaml | 50 ++++++++++++++ api/go.mod | 2 +- api/go.sum | 3 + api/v1beta1/nova_types.go | 9 +++ api/v1beta1/nova_webhook.go | 53 ++++++++++++++- api/v1beta1/novacell_types.go | 5 ++ api/v1beta1/zz_generated.deepcopy.go | 8 +++ config/crd/bases/nova.openstack.org_nova.yaml | 50 ++++++++++++++ internal/controller/nova_controller.go | 27 ++++---- test/functional/nova_controller_test.go | 11 ++-- test/functional/nova_reconfiguration_test.go | 3 +- .../00-cleanup-nova.yaml | 6 ++ .../nova-notificationsbus/01-assert.yaml | 65 +++++++++++++++++++ .../nova-notificationsbus/01-deploy.yaml | 16 +++++ .../nova-same-cluster/00-cleanup-nova.yaml | 6 ++ .../default/nova-same-cluster/01-assert.yaml | 61 +++++++++++++++++ .../default/nova-same-cluster/01-deploy.yaml | 16 +++++ 17 files changed, 372 insertions(+), 19 deletions(-) create mode 100644 test/kuttl/test-suites/default/nova-notificationsbus/00-cleanup-nova.yaml create mode 100644 test/kuttl/test-suites/default/nova-notificationsbus/01-assert.yaml create mode 100644 test/kuttl/test-suites/default/nova-notificationsbus/01-deploy.yaml create mode 100644 test/kuttl/test-suites/default/nova-same-cluster/00-cleanup-nova.yaml create mode 100644 test/kuttl/test-suites/default/nova-same-cluster/01-assert.yaml create mode 100644 test/kuttl/test-suites/default/nova-same-cluster/01-deploy.yaml diff --git a/api/bases/nova.openstack.org_nova.yaml b/api/bases/nova.openstack.org_nova.yaml index 58303ff6a..1356b378f 100644 --- a/api/bases/nova.openstack.org_nova.yaml +++ b/api/bases/nova.openstack.org_nova.yaml @@ -540,6 +540,23 @@ spec: MemcachedInstance is the name of the Memcached CR that the services in the cell will use. If defined then this takes precedence over Nova.Spec.MemcachedInstance for this cel 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 metadataServiceTemplate: description: |- MetadataServiceTemplate - defines the metadata service dedicated for the @@ -1340,6 +1357,22 @@ spec: description: MemcachedInstance is the name of the Memcached CR that all nova service will use. 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 metadataContainerImageURL: description: MetadataContainerImageURL type: string @@ -1648,6 +1681,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: |- NotificationsBusInstance is the name of the RabbitMqCluster CR to select diff --git a/api/go.mod b/api/go.mod index e871ea6d7..699ddf42d 100644 --- a/api/go.mod +++ b/api/go.mod @@ -18,7 +18,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 @@ -44,6 +43,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 1e3cbb9d5..d8a1d9cdb 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= @@ -82,6 +83,8 @@ github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251223124749-e github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251223124749-eedb97238c5f/go.mod h1:ex8ou6/3ms6ovR+CMXD6XhTlNakm1GhB6UZgagVRNW8= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35 h1:pF3mJ3nwq6r4qwom+rEWZNquZpcQW/iftHlJ1KPIDsk= 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/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/nova_types.go b/api/v1beta1/nova_types.go index 2f0ad588e..3781aedc9 100644 --- a/api/v1beta1/nova_types.go +++ b/api/v1beta1/nova_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" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -49,6 +50,10 @@ type NovaSpecCore struct { // communicate. APIMessageBusInstance string `json:"apiMessageBusInstance"` + // +kubebuilder:validation:Optional + // MessagingBus configuration (username, vhost, and cluster) + MessagingBus rabbitmqv1.RabbitMqConfig `json:"messagingBus,omitempty"` + // +kubebuilder:validation:Optional // +kubebuilder:default={cell0: {cellDatabaseAccount: nova-cell0, hasAPIAccess: true}, cell1: {cellDatabaseAccount: nova-cell1, cellDatabaseInstance: openstack-cell1, cellMessageBusInstance: rabbitmq-cell1, hasAPIAccess: true}} // Cells is a mapping of cell names to NovaCellTemplate objects defining @@ -131,6 +136,10 @@ type NovaSpecCore struct { // Avoid colocating it with RabbitMqClusterName, APIMessageBusInstance or CellMessageBusInstance used for RPC. // For particular Nova cells, notifications cannot be disabled, nor configured differently. NotificationsBusInstance *string `json:"notificationsBusInstance,omitempty"` + + // +kubebuilder:validation:Optional + // NotificationsBus configuration (username, vhost, and cluster) for notifications + NotificationsBus *rabbitmqv1.RabbitMqConfig `json:"notificationsBus,omitempty"` } // NovaSpec defines the desired state of Nova diff --git a/api/v1beta1/nova_webhook.go b/api/v1beta1/nova_webhook.go index b144b1601..c29618669 100644 --- a/api/v1beta1/nova_webhook.go +++ b/api/v1beta1/nova_webhook.go @@ -26,6 +26,7 @@ import ( "fmt" "github.com/google/go-cmp/cmp" + rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" service "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/robfig/cron/v3" @@ -88,6 +89,24 @@ func (spec *NovaSpecCore) Default() { spec.APITimeout = novaDefaults.APITimeout } + // Default MessagingBus.Cluster from APIMessageBusInstance if not already set + if spec.MessagingBus.Cluster == "" { + spec.MessagingBus.Cluster = spec.APIMessageBusInstance + } + + // 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{} + } + // Default cluster name if not already set + if spec.NotificationsBus.Cluster == "" { + spec.NotificationsBus.Cluster = *spec.NotificationsBusInstance + } + } + for cellName, cellTemplate := range spec.CellTemplates { if cellTemplate.MetadataServiceTemplate.Enabled == nil { @@ -106,6 +125,11 @@ func (spec *NovaSpecCore) Default() { } } + // Default MessagingBus.Cluster from CellMessageBusInstance if not already set + if cellTemplate.MessagingBus.Cluster == "" { + cellTemplate.MessagingBus.Cluster = cellTemplate.CellMessageBusInstance + } + // "cellTemplate" is a by-value copy, so we need to re-inject the updated version of it into the map spec.CellTemplates[cellName] = cellTemplate } @@ -315,7 +339,34 @@ func (spec *NovaSpec) ValidateUpdate(old NovaSpec, basePath *field.Path, namespa // expected to be called by the validation webhook in the higher level meta // operator func (spec *NovaSpecCore) ValidateUpdate(old NovaSpecCore, basePath *field.Path, namespace string) field.ErrorList { - errors := spec.ValidateCellTemplates(basePath, namespace) + var errors field.ErrorList + + // Reject changes to deprecated messagingBusInstance fields - users should use the new messagingBus fields instead + if spec.APIMessageBusInstance != old.APIMessageBusInstance { + errors = append(errors, field.Forbidden( + basePath.Child("apiMessageBusInstance"), + "apiMessageBusInstance is deprecated and cannot be changed. Please use messagingBus.cluster instead")) + } + + if spec.NotificationsBusInstance != nil && old.NotificationsBusInstance != nil && + *spec.NotificationsBusInstance != *old.NotificationsBusInstance { + errors = append(errors, field.Forbidden( + basePath.Child("notificationsBusInstance"), + "notificationsBusInstance is deprecated and cannot be changed. Please use notificationsBus.cluster instead")) + } + + // Check cell template changes + for cellName, cellTemplate := range spec.CellTemplates { + if oldCell, exists := old.CellTemplates[cellName]; exists { + if cellTemplate.CellMessageBusInstance != oldCell.CellMessageBusInstance { + errors = append(errors, field.Forbidden( + basePath.Child("cellTemplates").Key(cellName).Child("cellMessageBusInstance"), + "cellMessageBusInstance is deprecated and cannot be changed. Please use messagingBus.cluster instead")) + } + } + } + + errors = append(errors, spec.ValidateCellTemplates(basePath, namespace)...) // Validate top-level TopologyRef errors = append(errors, topologyv1.ValidateTopologyRef( spec.TopologyRef, *basePath.Child("topologyRef"), namespace)...) diff --git a/api/v1beta1/novacell_types.go b/api/v1beta1/novacell_types.go index 091e60e00..72c128bd7 100644 --- a/api/v1beta1/novacell_types.go +++ b/api/v1beta1/novacell_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" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/tls" @@ -51,6 +52,10 @@ type NovaCellTemplate struct { // communicate in this cell. For cell0 it is unused. CellMessageBusInstance string `json:"cellMessageBusInstance"` + // +kubebuilder:validation:Optional + // MessagingBus configuration (username, vhost, and cluster) + MessagingBus rabbitmqv1.RabbitMqConfig `json:"messagingBus,omitempty"` + // +kubebuilder:validation:Required // HasAPIAccess defines if this Cell is configured to have access to the // API DB and message bus. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index e493aca03..026f24def 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" @@ -505,6 +506,7 @@ func (in *NovaCellStatus) DeepCopy() *NovaCellStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NovaCellTemplate) DeepCopyInto(out *NovaCellTemplate) { *out = *in + out.MessagingBus = in.MessagingBus if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = new(map[string]string) @@ -1659,6 +1661,7 @@ func (in *NovaSpec) DeepCopy() *NovaSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NovaSpecCore) DeepCopyInto(out *NovaSpecCore) { *out = *in + out.MessagingBus = in.MessagingBus if in.CellTemplates != nil { in, out := &in.CellTemplates, &out.CellTemplates *out = make(map[string]NovaCellTemplate, len(*in)) @@ -1691,6 +1694,11 @@ func (in *NovaSpecCore) DeepCopyInto(out *NovaSpecCore) { *out = new(string) **out = **in } + if in.NotificationsBus != nil { + in, out := &in.NotificationsBus, &out.NotificationsBus + *out = new(rabbitmqv1beta1.RabbitMqConfig) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaSpecCore. diff --git a/config/crd/bases/nova.openstack.org_nova.yaml b/config/crd/bases/nova.openstack.org_nova.yaml index 58303ff6a..1356b378f 100644 --- a/config/crd/bases/nova.openstack.org_nova.yaml +++ b/config/crd/bases/nova.openstack.org_nova.yaml @@ -540,6 +540,23 @@ spec: MemcachedInstance is the name of the Memcached CR that the services in the cell will use. If defined then this takes precedence over Nova.Spec.MemcachedInstance for this cel 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 metadataServiceTemplate: description: |- MetadataServiceTemplate - defines the metadata service dedicated for the @@ -1340,6 +1357,22 @@ spec: description: MemcachedInstance is the name of the Memcached CR that all nova service will use. 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 metadataContainerImageURL: description: MetadataContainerImageURL type: string @@ -1648,6 +1681,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: |- NotificationsBusInstance is the name of the RabbitMqCluster CR to select diff --git a/internal/controller/nova_controller.go b/internal/controller/nova_controller.go index f59d01c6e..cd3bd7793 100644 --- a/internal/controller/nova_controller.go +++ b/internal/controller/nova_controller.go @@ -368,7 +368,7 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul // message bus is always the same as the top level API message bus so // we create API MQ separately first apiTransportURL, apiQuorumQueues, apiMQStatus, apiMQError := r.ensureMQ( - ctx, h, instance, instance.Name+"-api-transport", instance.Spec.APIMessageBusInstance) + ctx, h, instance, instance.Name+"-api-transport", instance.Spec.MessagingBus) switch apiMQStatus { case nova.MQFailed: instance.Status.Conditions.Set(condition.FalseCondition( @@ -392,20 +392,18 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul return ctrl.Result{}, fmt.Errorf("%w from for the API MQ: %d", util.ErrInvalidStatus, apiMQStatus) } - // nova broadcaster rabbit - notificationBusName := "" - if instance.Spec.NotificationsBusInstance != nil { - notificationBusName = *instance.Spec.NotificationsBusInstance - } - + // Determine if notifications are enabled by checking NotificationsBus.Cluster + // (the webhook defaults this from the deprecated NotificationsBusInstance field) var notificationTransportURL string var notificationMQStatus nova.MessageBusStatus var notificationMQError error notificationTransportURLName := instance.Name + "-notification-transport" - if notificationBusName != "" { + if instance.Spec.NotificationsBus != nil && instance.Spec.NotificationsBus.Cluster != "" { + // Use NotificationsBus config (never fall back to MessagingBus to ensure separation) + notificationsRabbitMqConfig := *instance.Spec.NotificationsBus notificationTransportURL, _, notificationMQStatus, notificationMQError = r.ensureMQ( - ctx, h, instance, notificationTransportURLName, notificationBusName) + ctx, h, instance, notificationTransportURLName, notificationsRabbitMqConfig) switch notificationMQStatus { case nova.MQFailed: @@ -470,7 +468,7 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul err = apiMQError } else { cellTransportURL, cellQuorumQueues, status, err = r.ensureMQ( - ctx, h, instance, instance.Name+"-"+cellName+"-transport", cellTemplate.CellMessageBusInstance) + ctx, h, instance, instance.Name+"-"+cellName+"-transport", cellTemplate.MessagingBus) } switch status { case nova.MQFailed: @@ -1711,7 +1709,7 @@ func (r *NovaReconciler) ensureMQ( h *helper.Helper, instance *novav1.Nova, transportName string, - messageBusInstanceName string, + rabbitMqConfig rabbitmqv1.RabbitMqConfig, ) (string, bool, nova.MessageBusStatus, error) { Log := r.GetLogger(ctx) transportURL := &rabbitmqv1.TransportURL{ @@ -1722,7 +1720,12 @@ func (r *NovaReconciler) ensureMQ( } op, err := controllerutil.CreateOrPatch(ctx, r.Client, transportURL, func() error { - transportURL.Spec.RabbitmqClusterName = messageBusInstanceName + transportURL.Spec.RabbitmqClusterName = rabbitMqConfig.Cluster + if rabbitMqConfig.User != "" { + transportURL.Spec.Username = rabbitMqConfig.User + } + // Always set Vhost - empty string means use default "/" vhost + transportURL.Spec.Vhost = rabbitMqConfig.Vhost err := controllerutil.SetControllerReference(instance, transportURL, r.Scheme) return err diff --git a/test/functional/nova_controller_test.go b/test/functional/nova_controller_test.go index 59bd0a67f..fe6e455ce 100644 --- a/test/functional/nova_controller_test.go +++ b/test/functional/nova_controller_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" + rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" @@ -77,13 +78,15 @@ var _ = Describe("Nova controller - notifications", func() { It("notification transport url is set with new rabbit", func() { - // add new-rabbit in Nova CR + // add new-rabbit in Nova CR using the new notificationsBus API notificationsBus := GetNotificationsBusNames(novaNames.NovaName) DeferCleanup(k8sClient.Delete, ctx, CreateNotificationTransportURLSecret(notificationsBus)) Eventually(func(g Gomega) { nova := GetNova(novaNames.NovaName) - nova.Spec.NotificationsBusInstance = ¬ificationsBus.BusName + nova.Spec.NotificationsBus = &rabbitmqv1.RabbitMqConfig{ + Cluster: notificationsBus.BusName, + } g.Expect(k8sClient.Update(ctx, nova)).Should(Succeed()) }, timeout, interval).Should(Succeed()) @@ -123,10 +126,10 @@ var _ = Describe("Nova controller - notifications", func() { configData = string(configDataMap.Data["01-nova.conf"]) AssertHaveNotificationTransportURL(notificationsBus.TransportURLName.Name, configData) - // cleanup notifications transporturl + // cleanup notifications transporturl by clearing the notificationsBus Eventually(func(g Gomega) { nova := GetNova(novaNames.NovaName) - nova.Spec.NotificationsBusInstance = nil + nova.Spec.NotificationsBus = nil g.Expect(k8sClient.Update(ctx, nova)).Should(Succeed()) }, timeout, interval).Should(Succeed()) diff --git a/test/functional/nova_reconfiguration_test.go b/test/functional/nova_reconfiguration_test.go index 29be006a1..442054c62 100644 --- a/test/functional/nova_reconfiguration_test.go +++ b/test/functional/nova_reconfiguration_test.go @@ -689,7 +689,8 @@ var _ = Describe("Nova reconfiguration", func() { nova := GetNova(novaNames.NovaName) cell1 := nova.Spec.CellTemplates["cell1"] - cell1.CellMessageBusInstance = "alternate-mq-for-cell1" + // Use the new messagingBus.cluster field instead of the deprecated cellMessageBusInstance + cell1.MessagingBus.Cluster = "alternate-mq-for-cell1" nova.Spec.CellTemplates["cell1"] = cell1 g.Expect(k8sClient.Update(ctx, nova)).To(Succeed()) diff --git a/test/kuttl/test-suites/default/nova-notificationsbus/00-cleanup-nova.yaml b/test/kuttl/test-suites/default/nova-notificationsbus/00-cleanup-nova.yaml new file mode 100644 index 000000000..b96266521 --- /dev/null +++ b/test/kuttl/test-suites/default/nova-notificationsbus/00-cleanup-nova.yaml @@ -0,0 +1,6 @@ +apiVersion: nova.openstack.org/v1beta1 +kind: Nova +metadata: + name: nova-kuttl + namespace: nova-kuttl-default +$patch: delete diff --git a/test/kuttl/test-suites/default/nova-notificationsbus/01-assert.yaml b/test/kuttl/test-suites/default/nova-notificationsbus/01-assert.yaml new file mode 100644 index 000000000..7b25bfbce --- /dev/null +++ b/test/kuttl/test-suites/default/nova-notificationsbus/01-assert.yaml @@ -0,0 +1,65 @@ +apiVersion: nova.openstack.org/v1beta1 +kind: Nova +metadata: + name: nova-kuttl +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Notification message bus created successfully + reason: Ready + status: "True" + type: NovaNotificationMQReady +--- +apiVersion: rabbitmq.openstack.org/v1beta1 +kind: TransportURL +metadata: + name: nova-kuttl-nova-transport +spec: + rabbitmqClusterName: rabbitmq +status: + conditions: + - message: TransportURL successfully created + reason: Ready + status: "True" + type: Ready +--- +apiVersion: rabbitmq.openstack.org/v1beta1 +kind: TransportURL +metadata: + name: nova-kuttl-nova-notification-rabbitmq-notifications +spec: + rabbitmqClusterName: rabbitmq-notifications +status: + conditions: + - message: TransportURL successfully created + reason: Ready + status: "True" + type: Ready +--- +# Verify that 2 TransportURL CRs were created (separate clusters) +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + set -euxo pipefail + # Count TransportURL CRs - should be at least 2 (one for messaging, one for notifications) + count=$(kubectl get transporturl -n $NAMESPACE -o name | grep "nova-kuttl-nova" | wc -l) + if [ "$count" -lt "2" ]; then + echo "Expected at least 2 TransportURLs for nova, found $count" + exit 1 + fi + echo "Correctly found $count TransportURLs (separate clusters)" + + # Verify that nova.conf contains the notifications transport_url + NOVA_API_POD=$(kubectl get pods -n $NAMESPACE -l "service=nova-api" -o custom-columns=:metadata.name --no-headers | grep -v ^$ | head -1) + if [ -z "${NOVA_API_POD}" ]; then + echo "No nova-api pod found" + exit 1 + fi + # Verify oslo_messaging_notifications section has transport_url configured + kubectl exec -n $NAMESPACE ${NOVA_API_POD} -c nova-kuttl-api-api -- cat /etc/nova/nova.conf.d/01-default.conf | grep -A 2 '\[oslo_messaging_notifications\]' | grep -q 'transport_url' + echo "Successfully verified notifications transport_url in nova.conf" + exit 0 diff --git a/test/kuttl/test-suites/default/nova-notificationsbus/01-deploy.yaml b/test/kuttl/test-suites/default/nova-notificationsbus/01-deploy.yaml new file mode 100644 index 000000000..0e5bd9de0 --- /dev/null +++ b/test/kuttl/test-suites/default/nova-notificationsbus/01-deploy.yaml @@ -0,0 +1,16 @@ +apiVersion: nova.openstack.org/v1beta1 +kind: Nova +metadata: + name: nova-kuttl +spec: + secret: osp-secret + messagingBus: + cluster: rabbitmq + notificationsBus: + cluster: rabbitmq-notifications + cellTemplates: + cell0: + cellDatabaseInstance: openstack + cellDatabaseAccount: nova-cell0 + hasAPIAccess: true + memcachedInstance: memcached diff --git a/test/kuttl/test-suites/default/nova-same-cluster/00-cleanup-nova.yaml b/test/kuttl/test-suites/default/nova-same-cluster/00-cleanup-nova.yaml new file mode 100644 index 000000000..b96266521 --- /dev/null +++ b/test/kuttl/test-suites/default/nova-same-cluster/00-cleanup-nova.yaml @@ -0,0 +1,6 @@ +apiVersion: nova.openstack.org/v1beta1 +kind: Nova +metadata: + name: nova-kuttl + namespace: nova-kuttl-default +$patch: delete diff --git a/test/kuttl/test-suites/default/nova-same-cluster/01-assert.yaml b/test/kuttl/test-suites/default/nova-same-cluster/01-assert.yaml new file mode 100644 index 000000000..78c4fcc9c --- /dev/null +++ b/test/kuttl/test-suites/default/nova-same-cluster/01-assert.yaml @@ -0,0 +1,61 @@ +apiVersion: nova.openstack.org/v1beta1 +kind: Nova +metadata: + name: nova-kuttl +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Notification message bus created successfully + reason: Ready + status: "True" + type: NovaNotificationMQReady +--- +apiVersion: rabbitmq.openstack.org/v1beta1 +kind: TransportURL +metadata: + name: nova-kuttl-nova-transport +spec: + rabbitmqClusterName: rabbitmq +status: + conditions: + - message: TransportURL successfully created + reason: Ready + status: "True" + type: Ready +--- +# Verify that NO second TransportURL was created (same cluster optimization) +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + set -euxo pipefail + # Count TransportURL CRs for nova - should be exactly 1 (API transport) + # Note: Nova may have cell TransportURLs too, so we only count the main nova transport + nova_transport_count=$(kubectl get transporturl -n $NAMESPACE -o name | grep "nova-kuttl-nova-transport" | wc -l) + notification_count=$(kubectl get transporturl -n $NAMESPACE -o name | grep "nova-kuttl-nova-notification" | wc -l) + + if [ "$nova_transport_count" -ne "1" ]; then + echo "Expected 1 nova-transport TransportURL, found $nova_transport_count" + exit 1 + fi + + if [ "$notification_count" -ne "0" ]; then + echo "Expected 0 notification TransportURL (same-cluster optimization), found $notification_count" + exit 1 + fi + + echo "Correctly found 1 TransportURL (same-cluster optimization working)" + + # Verify that nova.conf still has notifications transport_url configured (reusing main transport) + NOVA_API_POD=$(kubectl get pods -n $NAMESPACE -l "service=nova-api" -o custom-columns=:metadata.name --no-headers | grep -v ^$ | head -1) + if [ -z "${NOVA_API_POD}" ]; then + echo "No nova-api pod found" + exit 1 + fi + # Verify oslo_messaging_notifications section has transport_url configured + kubectl exec -n $NAMESPACE ${NOVA_API_POD} -c nova-kuttl-api-api -- cat /etc/nova/nova.conf.d/01-default.conf | grep -A 2 '\[oslo_messaging_notifications\]' | grep -q 'transport_url' + echo "Successfully verified notifications transport_url in nova.conf (using same cluster)" + exit 0 diff --git a/test/kuttl/test-suites/default/nova-same-cluster/01-deploy.yaml b/test/kuttl/test-suites/default/nova-same-cluster/01-deploy.yaml new file mode 100644 index 000000000..d271b0ec5 --- /dev/null +++ b/test/kuttl/test-suites/default/nova-same-cluster/01-deploy.yaml @@ -0,0 +1,16 @@ +apiVersion: nova.openstack.org/v1beta1 +kind: Nova +metadata: + name: nova-kuttl +spec: + secret: osp-secret + messagingBus: + cluster: rabbitmq + notificationsBus: + cluster: rabbitmq + cellTemplates: + cell0: + cellDatabaseInstance: openstack + cellDatabaseAccount: nova-cell0 + hasAPIAccess: true + memcachedInstance: memcached