From d876cea9b3c8e25e5f7f5c230d70e4bf2db77416 Mon Sep 17 00:00:00 2001 From: Luca Miccini Date: Thu, 18 Dec 2025 18:00:41 +0100 Subject: [PATCH 1/2] 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 Finally, we add the rabbitmquser crs to the secret so they can be stored for dataplane finalizers management and do auto cleanup of orphaned users after credential rotations. Jira: https://issues.redhat.com/browse/OSPRH-22697 --- api/bases/nova.openstack.org_nova.yaml | 50 +++++++ api/go.mod | 6 +- api/go.sum | 11 +- api/v1beta1/common_webhook.go | 126 ++++++++++++++++++ api/v1beta1/nova_types.go | 13 +- api/v1beta1/nova_webhook.go | 92 ++++++++++--- api/v1beta1/novacell_types.go | 7 +- api/v1beta1/zz_generated.deepcopy.go | 8 ++ .../control_plane_hook.yaml | 18 ++- config/crd/bases/nova.openstack.org_nova.yaml | 50 +++++++ go.mod | 6 +- go.sum | 16 +-- internal/controller/common.go | 8 ++ internal/controller/nova_controller.go | 105 +++++++++------ test/functional/nova_controller_test.go | 23 +++- test/functional/nova_reconfiguration_test.go | 5 +- .../config-tests/02-enable-notifications.yaml | 4 +- .../kuttl/test-suites/default/deps/infra.yaml | 2 + .../rmquser-vhost/00-cleanup-nova.yaml | 7 + .../default/rmquser-vhost/01-assert.yaml | 126 ++++++++++++++++++ .../default/rmquser-vhost/01-deploy.yaml | 16 +++ 21 files changed, 610 insertions(+), 89 deletions(-) create mode 100644 test/kuttl/test-suites/default/rmquser-vhost/00-cleanup-nova.yaml create mode 100644 test/kuttl/test-suites/default/rmquser-vhost/01-assert.yaml create mode 100644 test/kuttl/test-suites/default/rmquser-vhost/01-deploy.yaml diff --git a/api/bases/nova.openstack.org_nova.yaml b/api/bases/nova.openstack.org_nova.yaml index c3f3b5f8b..1f53f695f 100644 --- a/api/bases/nova.openstack.org_nova.yaml +++ b/api/bases/nova.openstack.org_nova.yaml @@ -550,6 +550,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 @@ -1350,6 +1367,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 @@ -1658,6 +1691,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 9e4d78fc1..ab5cfa259 100644 --- a/api/go.mod +++ b/api/go.mod @@ -4,8 +4,8 @@ go 1.24.4 require ( github.com/google/go-cmp v0.7.0 - github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260123105816-865d02e287a9 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35 + github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09 + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260126081203-efc2df9207eb github.com/robfig/cron/v3 v3.0.1 k8s.io/api v0.31.14 k8s.io/apimachinery v0.31.14 @@ -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 @@ -46,6 +45,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 c741f5c9f..7f5b7d66a 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= @@ -78,10 +79,12 @@ github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260123105816-865d02e287a9 h1:tD6nnTRcyUCXdVMWPHLApk12tzQlQni5eoxvQ8XdbP8= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260123105816-865d02e287a9/go.mod h1:ZXwFlspJCdZEUjMbmaf61t5AMB4u2vMyAMMoe/vJroE= -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/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09 h1:vhAGLKZitJIffj7ONiPpKmOX7Tmt/LGJpaY0Z2LeyfQ= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09/go.mod h1:ZXwFlspJCdZEUjMbmaf61t5AMB4u2vMyAMMoe/vJroE= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260126081203-efc2df9207eb h1:S7tnYO/E1f1KQfcp7N5bam8+ax/ExDTOhZ1WqG4Bfu0= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260126081203-efc2df9207eb/go.mod h1:ndqfy1KbVorHH6+zlUFPIrCRhMSxO3ImYJUGaooE0x0= +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/common_webhook.go b/api/v1beta1/common_webhook.go index 6f3397dc1..7bbc52681 100644 --- a/api/v1beta1/common_webhook.go +++ b/api/v1beta1/common_webhook.go @@ -21,6 +21,7 @@ import ( "path/filepath" "strings" + common_webhook "github.com/openstack-k8s-operators/lib-common/modules/common/webhook" "k8s.io/apimachinery/pkg/util/validation/field" ) @@ -58,3 +59,128 @@ func matchAny(requested string, allowed []string) bool { } return false } + +// getDeprecatedFields returns the centralized list of deprecated fields for NovaSpecCore +func (spec *NovaSpecCore) getDeprecatedFields(old *NovaSpecCore) []common_webhook.DeprecatedFieldUpdate { + // Get new field value (handle nil NotificationsBus) + var newNotifBusCluster *string + if spec.NotificationsBus != nil { + newNotifBusCluster = &spec.NotificationsBus.Cluster + } + + deprecatedFields := []common_webhook.DeprecatedFieldUpdate{ + { + DeprecatedFieldName: "apiMessageBusInstance", + NewFieldPath: []string{"messagingBus", "cluster"}, + NewDeprecatedValue: &spec.APIMessageBusInstance, + NewValue: &spec.MessagingBus.Cluster, + }, + { + DeprecatedFieldName: "notificationsBusInstance", + NewFieldPath: []string{"notificationsBus", "cluster"}, + NewDeprecatedValue: spec.NotificationsBusInstance, + NewValue: newNotifBusCluster, + }, + } + + // If old spec is provided (UPDATE operation), add old values + if old != nil { + deprecatedFields[0].OldDeprecatedValue = &old.APIMessageBusInstance + deprecatedFields[1].OldDeprecatedValue = old.NotificationsBusInstance + } + + return deprecatedFields +} + +// validateDeprecatedFieldsCreate validates deprecated fields during CREATE operations +func (spec *NovaSpecCore) validateDeprecatedFieldsCreate(basePath *field.Path) ([]string, field.ErrorList) { + // Get deprecated fields list (without old values for CREATE) + deprecatedFieldsUpdate := spec.getDeprecatedFields(nil) + + // Convert to DeprecatedField list for CREATE validation + deprecatedFields := make([]common_webhook.DeprecatedField, len(deprecatedFieldsUpdate)) + for i, df := range deprecatedFieldsUpdate { + deprecatedFields[i] = common_webhook.DeprecatedField{ + DeprecatedFieldName: df.DeprecatedFieldName, + NewFieldPath: df.NewFieldPath, + DeprecatedValue: df.NewDeprecatedValue, + NewValue: df.NewValue, + } + } + + // Validate top-level NovaSpecCore fields + warnings := common_webhook.ValidateDeprecatedFieldsCreate(deprecatedFields, basePath) + + // Validate deprecated fields in cell templates + for cellName, cellTemplate := range spec.CellTemplates { + cellPath := basePath.Child("cellTemplates").Key(cellName) + cellWarnings := cellTemplate.validateDeprecatedFieldsCreate(cellPath) + warnings = append(warnings, cellWarnings...) + } + + return warnings, nil +} + +// validateDeprecatedFieldsUpdate validates deprecated fields during UPDATE operations +func (spec *NovaSpecCore) validateDeprecatedFieldsUpdate(old NovaSpecCore, basePath *field.Path) ([]string, field.ErrorList) { + // Get deprecated fields list with old values + deprecatedFields := spec.getDeprecatedFields(&old) + warnings, errors := common_webhook.ValidateDeprecatedFieldsUpdate(deprecatedFields, basePath) + + // Validate deprecated fields in cell templates + for cellName, cellTemplate := range spec.CellTemplates { + if oldCell, exists := old.CellTemplates[cellName]; exists { + cellPath := basePath.Child("cellTemplates").Key(cellName) + cellWarnings, cellErrors := cellTemplate.validateDeprecatedFieldsUpdate(oldCell, cellPath) + warnings = append(warnings, cellWarnings...) + errors = append(errors, cellErrors...) + } + } + + return warnings, errors +} + +// getDeprecatedFields returns the centralized list of deprecated fields for NovaCellTemplate +func (spec *NovaCellTemplate) getDeprecatedFields(old *NovaCellTemplate) []common_webhook.DeprecatedFieldUpdate { + deprecatedFields := []common_webhook.DeprecatedFieldUpdate{ + { + DeprecatedFieldName: "cellMessageBusInstance", + NewFieldPath: []string{"messagingBus", "cluster"}, + NewDeprecatedValue: &spec.CellMessageBusInstance, + NewValue: &spec.MessagingBus.Cluster, + }, + } + + // If old spec is provided (UPDATE operation), add old values + if old != nil { + deprecatedFields[0].OldDeprecatedValue = &old.CellMessageBusInstance + } + + return deprecatedFields +} + +// validateDeprecatedFieldsCreate validates deprecated fields during CREATE operations for NovaCellTemplate +func (spec *NovaCellTemplate) validateDeprecatedFieldsCreate(basePath *field.Path) []string { + // Get deprecated fields list (without old values for CREATE) + deprecatedFieldsUpdate := spec.getDeprecatedFields(nil) + + // Convert to DeprecatedField list for CREATE validation + deprecatedFields := make([]common_webhook.DeprecatedField, len(deprecatedFieldsUpdate)) + for i, df := range deprecatedFieldsUpdate { + deprecatedFields[i] = common_webhook.DeprecatedField{ + DeprecatedFieldName: df.DeprecatedFieldName, + NewFieldPath: df.NewFieldPath, + DeprecatedValue: df.NewDeprecatedValue, + NewValue: df.NewValue, + } + } + + return common_webhook.ValidateDeprecatedFieldsCreate(deprecatedFields, basePath) +} + +// validateDeprecatedFieldsUpdate validates deprecated fields during UPDATE operations for NovaCellTemplate +func (spec *NovaCellTemplate) validateDeprecatedFieldsUpdate(old NovaCellTemplate, basePath *field.Path) ([]string, field.ErrorList) { + // Get deprecated fields list with old values + deprecatedFields := spec.getDeprecatedFields(&old) + return common_webhook.ValidateDeprecatedFieldsUpdate(deprecatedFields, basePath) +} diff --git a/api/v1beta1/nova_types.go b/api/v1beta1/nova_types.go index 7b22d24b7..2802c4b1d 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" @@ -47,7 +48,11 @@ type NovaSpecCore struct { // APIMessageBusInstance is the name of the RabbitMqCluster CR to select // the Message Bus Service instance used by the Nova top level services to // communicate. - APIMessageBusInstance string `json:"apiMessageBusInstance"` + APIMessageBusInstance string `json:"apiMessageBusInstance" deprecated:"true" deprecatedNew:"messagingBus.cluster"` + + // +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}} @@ -130,7 +135,11 @@ type NovaSpecCore struct { // An empty value "" leaves the notification drivers unconfigured and emitting no notifications at all. // 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"` + NotificationsBusInstance *string `json:"notificationsBusInstance,omitempty" deprecated:"true" deprecatedNew:"notificationsBus.cluster"` + + // +kubebuilder:validation:Optional + // NotificationsBus configuration (username, vhost, and cluster) for notifications + NotificationsBus *rabbitmqv1.RabbitMqConfig `json:"notificationsBus,omitempty"` // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec diff --git a/api/v1beta1/nova_webhook.go b/api/v1beta1/nova_webhook.go index c4c28ebc3..096aaaf56 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" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/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 } @@ -138,10 +162,26 @@ func (spec *NovaSpecCore) ValidateCellTemplates(basePath *field.Path, namespace cell.TopologyRef, *basePath.Child("topologyRef"), namespace)...) if name != Cell0Name { - if dupName, ok := cellMessageBusNames[cell.CellMessageBusInstance]; ok { + // Determine which rabbit cluster this cell is using + // Prefer the new MessagingBus.Cluster field, fall back to deprecated CellMessageBusInstance + var cellCluster string + if cell.MessagingBus.Cluster != "" { + cellCluster = cell.MessagingBus.Cluster + } else { + cellCluster = cell.CellMessageBusInstance + } + + // Check if this rabbit cluster is already used by another cell + if dupName, ok := cellMessageBusNames[cellCluster]; ok { + // Determine which field to report the error on + fieldPath := cellPath.Child("messagingBus").Child("cluster") + if cell.MessagingBus.Cluster == "" { + fieldPath = cellPath.Child("cellMessageBusInstance") + } + errors = append(errors, field.Invalid( - cellPath.Child("cellMessageBusInstance"), - cell.CellMessageBusInstance, + fieldPath, + cellCluster, fmt.Sprintf( "RabbitMqCluster CR need to be uniq per cell. It's duplicated with cell: %s", dupName), @@ -149,7 +189,7 @@ func (spec *NovaSpecCore) ValidateCellTemplates(basePath *field.Path, namespace ) } - cellMessageBusNames[cell.CellMessageBusInstance] = name + cellMessageBusNames[cellCluster] = name } if *cell.MetadataServiceTemplate.Enabled && *spec.MetadataServiceTemplate.Enabled { errors = append( @@ -260,16 +300,24 @@ func (spec *NovaSpecCore) ValidateSchedulerServiceTemplate(basePath *field.Path, return errors } + // ValidateCreate validates the NovaSpec during the webhook invocation. -func (spec *NovaSpec) ValidateCreate(basePath *field.Path, namespace string) field.ErrorList { +func (spec *NovaSpec) ValidateCreate(basePath *field.Path, namespace string) (admission.Warnings, field.ErrorList) { return spec.NovaSpecCore.ValidateCreate(basePath, namespace) } // ValidateCreate validates the NovaSpecCore during the webhook invocation. It is // expected to be called by the validation webhook in the higher level meta // operator -func (spec *NovaSpecCore) ValidateCreate(basePath *field.Path, namespace string) field.ErrorList { - errors := spec.ValidateCellTemplates(basePath, namespace) +func (spec *NovaSpecCore) ValidateCreate(basePath *field.Path, namespace string) (admission.Warnings, field.ErrorList) { + var warnings admission.Warnings + + // Validate deprecated fields + deprecatedWarnings, deprecatedErrors := spec.validateDeprecatedFieldsCreate(basePath) + warnings = append(warnings, deprecatedWarnings...) + + errors := deprecatedErrors + errors = append(errors, spec.ValidateCellTemplates(basePath, namespace)...) errors = append(errors, spec.ValidateAPIServiceTemplate(basePath, namespace)...) errors = append(errors, spec.ValidateSchedulerServiceTemplate(basePath, namespace)...) @@ -289,33 +337,41 @@ func (spec *NovaSpecCore) ValidateCreate(basePath *field.Path, namespace string) topologyv1.ValidateTopologyRef( spec.TopologyRef, *basePath.Child("topologyRef"), namespace)...) - return errors + return warnings, errors } // ValidateCreate implements webhook.Validator so a webhook will be registered for the type func (r *Nova) ValidateCreate() (admission.Warnings, error) { novalog.Info("validate create", "name", r.Name) - errors := r.Spec.ValidateCreate(field.NewPath("spec"), r.Namespace) + warnings, errors := r.Spec.ValidateCreate(field.NewPath("spec"), r.Namespace) if len(errors) != 0 { novalog.Info("validation failed", "name", r.Name) - return nil, apierrors.NewInvalid( + return warnings, apierrors.NewInvalid( schema.GroupKind{Group: "nova.openstack.org", Kind: "Nova"}, r.Name, errors) } - return nil, nil + return warnings, nil } // ValidateUpdate validates the NovaSpec during the webhook invocation. -func (spec *NovaSpec) ValidateUpdate(old NovaSpec, basePath *field.Path, namespace string) field.ErrorList { +func (spec *NovaSpec) ValidateUpdate(old NovaSpec, basePath *field.Path, namespace string) (admission.Warnings, field.ErrorList) { return spec.NovaSpecCore.ValidateUpdate(old.NovaSpecCore, basePath, namespace) } // ValidateUpdate validates the NovaSpecCore during the webhook invocation. It is // 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) +func (spec *NovaSpecCore) ValidateUpdate(old NovaSpecCore, basePath *field.Path, namespace string) (admission.Warnings, field.ErrorList) { + var errors field.ErrorList + var warnings admission.Warnings + + // Validate deprecated fields + deprecatedWarnings, deprecatedErrors := spec.validateDeprecatedFieldsUpdate(old, basePath) + warnings = append(warnings, deprecatedWarnings...) + errors = append(errors, deprecatedErrors...) + + errors = append(errors, spec.ValidateCellTemplates(basePath, namespace)...) // Validate top-level TopologyRef errors = append(errors, topologyv1.ValidateTopologyRef( spec.TopologyRef, *basePath.Child("topologyRef"), namespace)...) @@ -334,7 +390,7 @@ func (spec *NovaSpecCore) ValidateUpdate(old NovaSpecCore, basePath *field.Path, spec.MetadataServiceTemplate.ValidateDefaultConfigOverwrite( basePath.Child("metadataServiceTemplate"))...) - return errors + return warnings, errors } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type @@ -347,14 +403,14 @@ func (r *Nova) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { novalog.Info("validate update", "diff", cmp.Diff(oldNova, r)) - errors := r.Spec.ValidateUpdate(oldNova.Spec, field.NewPath("spec"), r.Namespace) + warnings, errors := r.Spec.ValidateUpdate(oldNova.Spec, field.NewPath("spec"), r.Namespace) if len(errors) != 0 { novalog.Info("validation failed", "name", r.Name) - return nil, apierrors.NewInvalid( + return warnings, apierrors.NewInvalid( schema.GroupKind{Group: "nova.openstack.org", Kind: "Nova"}, r.Name, errors) } - return nil, nil + return warnings, nil } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type diff --git a/api/v1beta1/novacell_types.go b/api/v1beta1/novacell_types.go index 775bd473f..4dda05ae5 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" @@ -49,7 +50,11 @@ type NovaCellTemplate struct { // CellMessageBusInstance is the name of the RabbitMqCluster CR to select // the Message Bus Service instance used by the nova services to // communicate in this cell. For cell0 it is unused. - CellMessageBusInstance string `json:"cellMessageBusInstance"` + CellMessageBusInstance string `json:"cellMessageBusInstance" deprecated:"true" deprecatedNew:"messagingBus.cluster"` + + // +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 diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 3869b1c84..0da528695 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" @@ -520,6 +521,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) @@ -1674,6 +1676,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)) @@ -1706,6 +1709,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 + } out.Auth = in.Auth } diff --git a/ci/nova-operator-tempest-multinode/control_plane_hook.yaml b/ci/nova-operator-tempest-multinode/control_plane_hook.yaml index f685e2899..ad83a4e0f 100644 --- a/ci/nova-operator-tempest-multinode/control_plane_hook.yaml +++ b/ci/nova-operator-tempest-multinode/control_plane_hook.yaml @@ -24,9 +24,18 @@ path: /spec/nova/template/apiServiceTemplate/replicas value: 2 - - op: replace + - op: add + path: /spec/nova/template/apiMessageBusInstance + value: "" + + - op: add + path: /spec/nova/template/notificationsBus + value: + cluster: rabbitmq + + - op: add path: /spec/nova/template/notificationsBusInstance - value: rabbitmq + value: null - op: replace path: /spec/neutron/template/replicas @@ -47,13 +56,16 @@ value: cell0: cellDatabaseAccount: nova-cell0 + cellMessageBusInstance: "" hasAPIAccess: true metadataServiceTemplate: enabled: false cell1: cellDatabaseAccount: nova-cell1 + cellMessageBusInstance: "" hasAPIAccess: true - cellMessageBusInstance: rabbitmq-cell1 + messagingBus: + cluster: rabbitmq-cell1 cellDatabaseInstance: openstack-cell1 metadataServiceTemplate: enabled: true diff --git a/config/crd/bases/nova.openstack.org_nova.yaml b/config/crd/bases/nova.openstack.org_nova.yaml index c3f3b5f8b..1f53f695f 100644 --- a/config/crd/bases/nova.openstack.org_nova.yaml +++ b/config/crd/bases/nova.openstack.org_nova.yaml @@ -550,6 +550,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 @@ -1350,6 +1367,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 @@ -1658,6 +1691,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/go.mod b/go.mod index 845b1753a..49d1cc228 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,11 @@ require ( github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 github.com/onsi/ginkgo/v2 v2.27.5 github.com/onsi/gomega v1.39.0 - github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260123105816-865d02e287a9 + github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09 github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260126175636-114b4c65a959 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35 + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260128142552-e2c25eccae5a - github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20251230215914-6ba873b49a35 + github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260128142552-e2c25eccae5a github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260120155328-e04f52e73f01 github.com/openstack-k8s-operators/nova-operator/api v0.0.0-20221209164002-f9e6b9363961 go.uber.org/zap v1.27.1 diff --git a/go.sum b/go.sum index a2475798b..e2f720bc9 100644 --- a/go.sum +++ b/go.sum @@ -118,20 +118,20 @@ github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyUt0GEdoAE+r5TXy7YS21yNEo+2U= github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260123105816-865d02e287a9 h1:tD6nnTRcyUCXdVMWPHLApk12tzQlQni5eoxvQ8XdbP8= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260123105816-865d02e287a9/go.mod h1:ZXwFlspJCdZEUjMbmaf61t5AMB4u2vMyAMMoe/vJroE= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09 h1:vhAGLKZitJIffj7ONiPpKmOX7Tmt/LGJpaY0Z2LeyfQ= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09/go.mod h1:ZXwFlspJCdZEUjMbmaf61t5AMB4u2vMyAMMoe/vJroE= github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260126175636-114b4c65a959 h1:8FSpTYAoLq27ElDGe3igPl2QUq9IYD6RJGu2Xu+Ymus= github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260126175636-114b4c65a959/go.mod h1:pN/s+czXvApiE9nxeTtDeRTXWcaaCLZSrtoyOSUb37k= -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/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a h1:pF3mJ3nwq6r4qwom+rEWZNquZpcQW/iftHlJ1KPIDsk= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a/go.mod h1:kycZyoe7OZdW1HUghr2nI3N7wSJtNahXf6b/ypD14f4= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260128142552-e2c25eccae5a h1:rMrtMsHAfkEqodLUD4Yu5NYtjGW8U3f7zxJTJpwjvPs= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260128142552-e2c25eccae5a/go.mod h1:zOX7Y05keiSppIvLabuyh42QHBMhCcoskAtxFRbwXKo= 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/lib-common/modules/test v0.6.1-0.20251230215914-6ba873b49a35 h1:8rQc4Fsfe6yqRU5Xjt9lWXqUqfBjRubr0utnUpUBKTE= -github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20251230215914-6ba873b49a35/go.mod h1:QWzyC+tTBB2OGuYyIiLLo1oA0+I/0NUMXD+dj4Quv4M= -github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260120155328-e04f52e73f01 h1:93NxJ/fFx41HcFXk4nJk4PPz4lrqzNMviTmKyWwa+vg= -github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260120155328-e04f52e73f01/go.mod h1:X6W8pIULiWUc6smaTqiNocjxoXaRLgXediwpI/dxD9s= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260126081203-efc2df9207eb h1:Fh9yjyogiR9P4oV3a2pSlSUyYzfbWbvlU6RFIjZoxsg= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260126081203-efc2df9207eb/go.mod h1:sqKTKvYhSzu4Opnjx/J+zzetXKRqYrhxsfvrST/NjoU= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260124124804-c82210f7a636 h1:Mtjy0cc2pBdyP44/5K6TB/VxIDvtjU2EHer17JKUaLg= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260124124804-c82210f7a636/go.mod h1:X6W8pIULiWUc6smaTqiNocjxoXaRLgXediwpI/dxD9s= 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= diff --git a/internal/controller/common.go b/internal/controller/common.go index b5da03e03..dfe5bfa12 100644 --- a/internal/controller/common.go +++ b/internal/controller/common.go @@ -111,6 +111,14 @@ const ( // the message bus quorum queues configuration QuorumQueuesTemplateKey = "quorum_queues" + // RabbitmqUserNameSelector is the name of key in the internal Secret for + // the RabbitMQUser CR name for the RPC/messaging bus + RabbitmqUserNameSelector = "rabbitmq_user_name" + + // NotificationRabbitmqUserNameSelector is the name of key in the internal + // Secret for the RabbitMQUser CR name for the notifications bus + NotificationRabbitmqUserNameSelector = "notification_rabbitmq_user_name" + // fields to index to reconcile when change passwordSecretField = ".spec.secret" authAppCredSecretField = ".spec.auth.applicationCredentialSecret" // #nosec G101 diff --git a/internal/controller/nova_controller.go b/internal/controller/nova_controller.go index b9b96f69a..b6c751792 100644 --- a/internal/controller/nova_controller.go +++ b/internal/controller/nova_controller.go @@ -410,8 +410,8 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul // Create TransportURLs to access the message buses of each cell. Cell0 // 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) + apiTransportURL, apiRabbitmqUserName, apiQuorumQueues, apiMQStatus, apiMQError := r.ensureMQ( + ctx, h, instance, instance.Name+"-api-transport", instance.Spec.MessagingBus) switch apiMQStatus { case nova.MQFailed: instance.Status.Conditions.Set(condition.FalseCondition( @@ -435,20 +435,19 @@ 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 notificationRabbitmqUserName string var notificationMQStatus nova.MessageBusStatus var notificationMQError error - notificationTransportURLName := instance.Name + "-notification-transport" - if notificationBusName != "" { - notificationTransportURL, _, notificationMQStatus, notificationMQError = r.ensureMQ( - ctx, h, instance, notificationTransportURLName, notificationBusName) + notificationTransportName := instance.Name + "-notification-transport" + 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, notificationRabbitmqUserName, _, notificationMQStatus, notificationMQError = r.ensureMQ( + ctx, h, instance, notificationTransportName, notificationsRabbitMqConfig) switch notificationMQStatus { case nova.MQFailed: @@ -486,7 +485,7 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul } for _, url := range transportURLList.Items { - if strings.Contains(url.Name, notificationTransportURLName) { + if strings.Contains(url.Name, notificationTransportName) { err = r.ensureMQDeleted(ctx, instance, url.Name) if err != nil { return ctrl.Result{}, err @@ -496,10 +495,12 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul } cellMQs := map[string]*nova.MessageBus{} + cellRabbitmqUserNames := map[string]string{} var failedMQs []string var creatingMQs []string for _, cellName := range orderedCellNames { var cellTransportURL string + var cellRabbitmqUserName string var status nova.MessageBusStatus var err error cellTemplate := instance.Spec.CellTemplates[cellName] @@ -508,12 +509,13 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul // API message bus instead if cellName == novav1.Cell0Name { cellTransportURL = apiTransportURL + cellRabbitmqUserName = apiRabbitmqUserName cellQuorumQueues = apiQuorumQueues status = apiMQStatus err = apiMQError } else { - cellTransportURL, cellQuorumQueues, status, err = r.ensureMQ( - ctx, h, instance, instance.Name+"-"+cellName+"-transport", cellTemplate.CellMessageBusInstance) + cellTransportURL, cellRabbitmqUserName, cellQuorumQueues, status, err = r.ensureMQ( + ctx, h, instance, instance.Name+"-"+cellName+"-transport", cellTemplate.MessagingBus) } switch status { case nova.MQFailed: @@ -525,6 +527,7 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul return ctrl.Result{}, fmt.Errorf("%w from ensureMQ: %d for cell %s", util.ErrInvalidStatus, status, cellName) } cellMQs[cellName] = &nova.MessageBus{TransportURL: cellTransportURL, QuorumQueues: cellQuorumQueues, Status: status} + cellRabbitmqUserNames[cellName] = cellRabbitmqUserName } if len(failedMQs) > 0 { instance.Status.Conditions.Set(condition.FalseCondition( @@ -587,7 +590,8 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul } cell, status, err := r.ensureCell( ctx, h, instance, cellName, cellTemplate, - cellDB.Database, apiDB, cellMQ.TransportURL, cellMQ.QuorumQueues, notificationTransportURL, + cellDB.Database, apiDB, cellMQ.TransportURL, cellRabbitmqUserNames[cellName], cellMQ.QuorumQueues, + notificationTransportURL, notificationRabbitmqUserName, keystoneInternalAuthURL, region, ospSecret, acData, ) cells[cellName] = cell @@ -654,8 +658,8 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul topLevelSecretName, err := r.ensureTopLevelSecret( ctx, h, instance, - apiTransportURL, apiQuorumQueues, - notificationTransportURL, + apiTransportURL, apiRabbitmqUserName, apiQuorumQueues, + notificationTransportURL, notificationRabbitmqUserName, ospSecret, acData) if err != nil { return ctrl.Result{}, err @@ -1243,8 +1247,10 @@ func (r *NovaReconciler) ensureCell( cellDB *mariadbv1.Database, apiDB *mariadbv1.Database, cellTransportURL string, + cellRabbitmqUserName string, cellQuorumQueues bool, notificationTransportURL string, + notificationRabbitmqUserName string, keystoneAuthURL string, region string, secret corev1.Secret, @@ -1254,7 +1260,8 @@ func (r *NovaReconciler) ensureCell( cellSecretName, err := r.ensureCellSecret( ctx, h, instance, cellName, cellTemplate, - cellTransportURL, cellQuorumQueues, notificationTransportURL, + cellTransportURL, cellRabbitmqUserName, cellQuorumQueues, + notificationTransportURL, notificationRabbitmqUserName, secret, acData) if err != nil { return nil, nova.CellDeploying, err @@ -1771,8 +1778,8 @@ func (r *NovaReconciler) ensureMQ( h *helper.Helper, instance *novav1.Nova, transportName string, - messageBusInstanceName string, -) (string, bool, nova.MessageBusStatus, error) { + rabbitMqConfig rabbitmqv1.RabbitMqConfig, +) (string, string, bool, nova.MessageBusStatus, error) { Log := r.GetLogger(ctx) transportURL := &rabbitmqv1.TransportURL{ ObjectMeta: metav1.ObjectMeta{ @@ -1782,14 +1789,20 @@ func (r *NovaReconciler) ensureMQ( } op, err := controllerutil.CreateOrPatch(ctx, r.Client, transportURL, func() error { - transportURL.Spec.RabbitmqClusterName = messageBusInstanceName + transportURL.Spec.RabbitmqClusterName = rabbitMqConfig.Cluster + // Always set Username and Vhost to allow clearing/resetting them + // The infra-operator TransportURL controller handles empty values: + // - Empty Username: uses default cluster admin credentials + // - Empty Vhost: defaults to "/" vhost + transportURL.Spec.Username = rabbitMqConfig.User + transportURL.Spec.Vhost = rabbitMqConfig.Vhost err := controllerutil.SetControllerReference(instance, transportURL, r.Scheme) return err }) if err != nil && !k8s_errors.IsNotFound(err) { - return "", false, nova.MQFailed, util.WrapErrorForObject( + return "", "", false, nova.MQFailed, util.WrapErrorForObject( fmt.Sprintf("Error create or update TransportURL object %s", transportName), transportURL, err, @@ -1798,12 +1811,12 @@ func (r *NovaReconciler) ensureMQ( if op != controllerutil.OperationResultNone { Log.Info(fmt.Sprintf("TransportURL object %s created or patched", transportName)) - return "", false, nova.MQCreating, nil + return "", "", false, nova.MQCreating, nil } err = r.Client.Get(ctx, types.NamespacedName{Namespace: instance.Namespace, Name: transportName}, transportURL) if err != nil && !k8s_errors.IsNotFound(err) { - return "", false, nova.MQFailed, util.WrapErrorForObject( + return "", "", false, nova.MQFailed, util.WrapErrorForObject( fmt.Sprintf("Error reading TransportURL object %s", transportName), transportURL, err, @@ -1811,7 +1824,7 @@ func (r *NovaReconciler) ensureMQ( } if k8s_errors.IsNotFound(err) || !transportURL.IsReady() || transportURL.Status.SecretName == "" { - return "", false, nova.MQCreating, nil + return "", "", false, nova.MQCreating, nil } secretName := types.NamespacedName{Namespace: instance.Namespace, Name: transportURL.Status.SecretName} @@ -1820,14 +1833,14 @@ func (r *NovaReconciler) ensureMQ( err = h.GetClient().Get(ctx, secretName, secret) if err != nil { if k8s_errors.IsNotFound(err) { - return "", false, nova.MQCreating, nil + return "", "", false, nova.MQCreating, nil } - return "", false, nova.MQFailed, err + return "", "", false, nova.MQFailed, err } url, ok := secret.Data[TransportURLSelector] if !ok { - return "", false, nova.MQFailed, fmt.Errorf( + return "", "", false, nova.MQFailed, fmt.Errorf( "%w: the TransportURL secret %s does not have 'transport_url' field", util.ErrFieldNotFound, transportURL.Status.SecretName) } @@ -1837,7 +1850,13 @@ func (r *NovaReconciler) ensureMQ( quorumQueues = string(val) == "true" } - return string(url), quorumQueues, nova.MQCompleted, nil + // Get the RabbitMQUser CR name from the TransportURL status + // The infra-operator populates status.RabbitmqUserRef with the name of the RabbitMQUser CR + // that was created or referenced by this TransportURL. + // Empty string means using default RabbitMQ user (no dedicated RabbitMQUser CR) + rabbitmqUserName := transportURL.Status.RabbitmqUserRef + + return string(url), rabbitmqUserName, quorumQueues, nova.MQCompleted, nil } func (r *NovaReconciler) ensureMQDeleted( @@ -2066,8 +2085,10 @@ func (r *NovaReconciler) ensureCellSecret( cellName string, cellTemplate novav1.NovaCellTemplate, cellTransportURL string, + cellRabbitmqUserName string, cellQuorumQueues bool, notificationTransportURL string, + notificationRabbitmqUserName string, externalSecret corev1.Secret, acData *keystonev1.ApplicationCredentialData, ) (string, error) { @@ -2080,10 +2101,12 @@ func (r *NovaReconciler) ensureCellSecret( } data := map[string]string{ - ServicePasswordSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.Service]), - TransportURLSelector: cellTransportURL, - NotificationTransportURLSelector: notificationTransportURL, - QuorumQueuesTemplateKey: quorumQueuesValue, + ServicePasswordSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.Service]), + TransportURLSelector: cellTransportURL, + RabbitmqUserNameSelector: cellRabbitmqUserName, + NotificationTransportURLSelector: notificationTransportURL, + NotificationRabbitmqUserNameSelector: notificationRabbitmqUserName, + QuorumQueuesTemplateKey: quorumQueuesValue, } // Add Application Credential data @@ -2133,8 +2156,10 @@ func (r *NovaReconciler) ensureTopLevelSecret( h *helper.Helper, instance *novav1.Nova, apiTransportURL string, + apiRabbitmqUserName string, apiQuorumQueues bool, notificationTransportURL string, + notificationRabbitmqUserName string, externalSecret corev1.Secret, acData *keystonev1.ApplicationCredentialData, ) (string, error) { @@ -2146,11 +2171,13 @@ func (r *NovaReconciler) ensureTopLevelSecret( } data := map[string]string{ - ServicePasswordSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.Service]), - MetadataSecretSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.MetadataSecret]), - TransportURLSelector: apiTransportURL, - NotificationTransportURLSelector: notificationTransportURL, - QuorumQueuesTemplateKey: quorumQueuesValue, + ServicePasswordSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.Service]), + MetadataSecretSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.MetadataSecret]), + TransportURLSelector: apiTransportURL, + RabbitmqUserNameSelector: apiRabbitmqUserName, + NotificationTransportURLSelector: notificationTransportURL, + NotificationRabbitmqUserNameSelector: notificationRabbitmqUserName, + QuorumQueuesTemplateKey: quorumQueuesValue, } // Add Application Credential data if provided diff --git a/test/functional/nova_controller_test.go b/test/functional/nova_controller_test.go index f89118821..e0bc76335 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" @@ -81,13 +82,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()) @@ -127,10 +130,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()) @@ -538,13 +541,17 @@ var _ = Describe("Nova controller", func() { // proper content and the cell subCRs are configured to use the // internal secret internalCellSecret := th.GetSecret(cell0.InternalCellSecretName) - Expect(internalCellSecret.Data).To(HaveLen(4)) + Expect(internalCellSecret.Data).To(HaveLen(6)) Expect(internalCellSecret.Data).To( HaveKeyWithValue(controllers.ServicePasswordSelector, []byte("service-password"))) Expect(internalCellSecret.Data).To( HaveKeyWithValue("transport_url", []byte("rabbit://cell0/fake"))) Expect(internalCellSecret.Data).To( HaveKeyWithValue("notification_transport_url", []byte(""))) + Expect(internalCellSecret.Data).To( + HaveKey(controllers.RabbitmqUserNameSelector)) + Expect(internalCellSecret.Data).To( + HaveKey(controllers.NotificationRabbitmqUserNameSelector)) Expect(cell.Spec.Secret).To(Equal(cell0.InternalCellSecretName.Name)) Expect(conductor.Spec.Secret).To(Equal(cell0.InternalCellSecretName.Name)) @@ -654,7 +661,7 @@ var _ = Describe("Nova controller", func() { // assert that a the top level internal internal secret is created // with the proper data internalTopLevelSecret := th.GetSecret(novaNames.InternalTopLevelSecretName) - Expect(internalTopLevelSecret.Data).To(HaveLen(5)) + Expect(internalTopLevelSecret.Data).To(HaveLen(7)) Expect(internalTopLevelSecret.Data).To( HaveKeyWithValue(controllers.ServicePasswordSelector, []byte("service-password"))) Expect(internalTopLevelSecret.Data).To( @@ -663,6 +670,10 @@ var _ = Describe("Nova controller", func() { HaveKeyWithValue("transport_url", []byte("rabbit://cell0/fake"))) Expect(internalTopLevelSecret.Data).To( HaveKeyWithValue("notification_transport_url", []byte(""))) + Expect(internalTopLevelSecret.Data).To( + HaveKey(controllers.RabbitmqUserNameSelector)) + Expect(internalTopLevelSecret.Data).To( + HaveKey(controllers.NotificationRabbitmqUserNameSelector)) }) It("creates NovaAPI", func() { diff --git a/test/functional/nova_reconfiguration_test.go b/test/functional/nova_reconfiguration_test.go index 29be006a1..664312cc9 100644 --- a/test/functional/nova_reconfiguration_test.go +++ b/test/functional/nova_reconfiguration_test.go @@ -689,7 +689,10 @@ var _ = Describe("Nova reconfiguration", func() { nova := GetNova(novaNames.NovaName) cell1 := nova.Spec.CellTemplates["cell1"] - cell1.CellMessageBusInstance = "alternate-mq-for-cell1" + // Migrate from deprecated cellMessageBusInstance to new messagingBus.cluster field + // Must null out the old field when setting the new one to avoid validation error + cell1.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/config-tests/02-enable-notifications.yaml b/test/kuttl/test-suites/default/config-tests/02-enable-notifications.yaml index 15ecdd8f4..e36be5a98 100644 --- a/test/kuttl/test-suites/default/config-tests/02-enable-notifications.yaml +++ b/test/kuttl/test-suites/default/config-tests/02-enable-notifications.yaml @@ -4,4 +4,6 @@ metadata: name: nova-kuttl spec: secret: osp-secret - notificationsBusInstance: rabbitmq-broadcaster + notificationsBusInstance: null + notificationsBus: + cluster: rabbitmq-broadcaster diff --git a/test/kuttl/test-suites/default/deps/infra.yaml b/test/kuttl/test-suites/default/deps/infra.yaml index 428fb21d2..7b87da888 100644 --- a/test/kuttl/test-suites/default/deps/infra.yaml +++ b/test/kuttl/test-suites/default/deps/infra.yaml @@ -20,6 +20,8 @@ spec: replicas: 1 rabbitmq-broadcaster: replicas: 1 + rabbitmq-notifications: + replicas: 1 memcached: templates: memcached: diff --git a/test/kuttl/test-suites/default/rmquser-vhost/00-cleanup-nova.yaml b/test/kuttl/test-suites/default/rmquser-vhost/00-cleanup-nova.yaml new file mode 100644 index 000000000..b8d66a4b0 --- /dev/null +++ b/test/kuttl/test-suites/default/rmquser-vhost/00-cleanup-nova.yaml @@ -0,0 +1,7 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: nova.openstack.org/v1beta1 + kind: Nova + name: nova-kuttl + namespace: nova-kuttl-default diff --git a/test/kuttl/test-suites/default/rmquser-vhost/01-assert.yaml b/test/kuttl/test-suites/default/rmquser-vhost/01-assert.yaml new file mode 100644 index 000000000..72c8fa64a --- /dev/null +++ b/test/kuttl/test-suites/default/rmquser-vhost/01-assert.yaml @@ -0,0 +1,126 @@ +# Verify the TransportURLs have correct cluster, user, and vhost configured +apiVersion: rabbitmq.openstack.org/v1beta1 +kind: TransportURL +metadata: + name: nova-kuttl-api-transport +spec: + rabbitmqClusterName: rabbitmq + username: nova-rpc + vhost: nova-rpc +--- +apiVersion: rabbitmq.openstack.org/v1beta1 +kind: TransportURL +metadata: + name: nova-kuttl-notification-transport +spec: + rabbitmqClusterName: rabbitmq-notifications + username: nova-notifications + vhost: nova-notifications +--- +# Verify that 2 TransportURL CRs were created (separate clusters) +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + set -euxo pipefail + + # Wait for Nova to be Ready + kubectl wait --for=condition=Ready nova/nova-kuttl -n $NAMESPACE --timeout=300s + + # Verify NovaNotificationMQReady condition exists and is True + kubectl get nova nova-kuttl -n $NAMESPACE -o jsonpath='{.status.conditions[?(@.type=="NovaNotificationMQReady")].status}' | grep -q "True" + echo "NovaNotificationMQReady condition is True" + + # Count TransportURL CRs - should be exactly 2 (one for messaging, one for notifications) + api_transport_count=$(kubectl get transporturl -n $NAMESPACE -o name | grep "nova-kuttl-api-transport" | wc -l) + notification_transport_count=$(kubectl get transporturl -n $NAMESPACE -o name | grep "nova-kuttl-notification-transport" | wc -l) + + if [ "$api_transport_count" -ne "1" ]; then + echo "Expected 1 api-transport TransportURL, found $api_transport_count" + exit 1 + fi + + if [ "$notification_transport_count" -ne "1" ]; then + echo "Expected 1 notification-transport TransportURL, found $notification_transport_count" + exit 1 + fi + + echo "Correctly found 2 TransportURLs (separate clusters: api and notification)" + + # Verify api-transport has correct user and vhost + api_user=$(kubectl get transporturl nova-kuttl-api-transport -n $NAMESPACE -o jsonpath='{.spec.username}') + api_vhost=$(kubectl get transporturl nova-kuttl-api-transport -n $NAMESPACE -o jsonpath='{.spec.vhost}') + if [ "$api_user" != "nova-rpc" ]; then + echo "Expected api-transport username 'nova-rpc', found '$api_user'" + exit 1 + fi + if [ "$api_vhost" != "nova-rpc" ]; then + echo "Expected api-transport vhost 'nova-rpc', found '$api_vhost'" + exit 1 + fi + echo "API transport has correct user (nova-rpc) and vhost (nova-rpc)" + + # Verify notification-transport has correct user and vhost + notif_user=$(kubectl get transporturl nova-kuttl-notification-transport -n $NAMESPACE -o jsonpath='{.spec.username}') + notif_vhost=$(kubectl get transporturl nova-kuttl-notification-transport -n $NAMESPACE -o jsonpath='{.spec.vhost}') + if [ "$notif_user" != "nova-notifications" ]; then + echo "Expected notification-transport username 'nova-notifications', found '$notif_user'" + exit 1 + fi + if [ "$notif_vhost" != "nova-notifications" ]; then + echo "Expected notification-transport vhost 'nova-notifications', found '$notif_vhost'" + exit 1 + fi + echo "Notification transport has correct user (nova-notifications) and vhost (nova-notifications)" + + # 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 RPC transport_url in DEFAULT section + rpc_transport_url=$(kubectl exec -n $NAMESPACE ${NOVA_API_POD} -c nova-kuttl-api-api -- cat /etc/nova/nova.conf.d/01-nova.conf | grep -E '^\[DEFAULT\]' -A 50 | grep 'transport_url' | head -1 || true) + if [ -z "$rpc_transport_url" ]; then + echo "transport_url not found in DEFAULT section" + exit 1 + fi + echo "Found RPC transport_url: $rpc_transport_url" + + # Verify the RPC transport_url contains the correct vhost (nova-rpc) + if ! echo "$rpc_transport_url" | grep -q '/nova-rpc'; then + echo "RPC transport_url does not contain expected vhost '/nova-rpc'" + exit 1 + fi + echo "Successfully verified vhost 'nova-rpc' in RPC transport_url" + + # Verify the RPC transport_url contains the correct username (nova-rpc) + if ! echo "$rpc_transport_url" | grep -q 'nova-rpc:'; then + echo "RPC transport_url does not contain expected username 'nova-rpc:'" + exit 1 + fi + echo "Successfully verified username 'nova-rpc' in RPC transport_url" + + # Verify oslo_messaging_notifications section has transport_url configured + notif_transport_url=$(kubectl exec -n $NAMESPACE ${NOVA_API_POD} -c nova-kuttl-api-api -- cat /etc/nova/nova.conf.d/01-nova.conf | grep -A 2 '\[oslo_messaging_notifications\]' | grep 'transport_url' || true) + if [ -z "$notif_transport_url" ]; then + echo "transport_url not found in oslo_messaging_notifications section" + exit 1 + fi + echo "Found notifications transport_url: $notif_transport_url" + + # Verify the notifications transport_url contains the correct vhost (nova-notifications) + if ! echo "$notif_transport_url" | grep -q '/nova-notifications'; then + echo "Notifications transport_url does not contain expected vhost '/nova-notifications'" + exit 1 + fi + echo "Successfully verified vhost 'nova-notifications' in notifications transport_url" + + # Verify the notifications transport_url contains the correct username (nova-notifications) + if ! echo "$notif_transport_url" | grep -q 'nova-notifications:'; then + echo "Notifications transport_url does not contain expected username 'nova-notifications:'" + exit 1 + fi + echo "Successfully verified username 'nova-notifications' in notifications transport_url" + + exit 0 diff --git a/test/kuttl/test-suites/default/rmquser-vhost/01-deploy.yaml b/test/kuttl/test-suites/default/rmquser-vhost/01-deploy.yaml new file mode 100644 index 000000000..c991dc61f --- /dev/null +++ b/test/kuttl/test-suites/default/rmquser-vhost/01-deploy.yaml @@ -0,0 +1,16 @@ +apiVersion: nova.openstack.org/v1beta1 +kind: Nova +metadata: + name: nova-kuttl +spec: + secret: osp-secret + apiMessageBusInstance: "" + messagingBus: + cluster: rabbitmq + user: nova-rpc + vhost: nova-rpc + notificationsBusInstance: null + notificationsBus: + cluster: rabbitmq-notifications + user: nova-notifications + vhost: nova-notifications From ebc58fbfbae3e761d4e8afd55b6aa0049ea5eb9b Mon Sep 17 00:00:00 2001 From: Luca Miccini Date: Thu, 29 Jan 2026 11:55:49 +0100 Subject: [PATCH 2/2] Make deprecated parameter optional --- api/bases/nova.openstack.org_nova.yaml | 7 ++-- api/go.mod | 3 +- api/go.sum | 4 +-- api/v1beta1/nova_types.go | 6 ++-- api/v1beta1/nova_webhook.go | 29 +++++++-------- api/v1beta1/novacell_types.go | 4 +-- config/crd/bases/nova.openstack.org_nova.yaml | 7 ++-- .../samples/nova_v1beta1_nova-multi-cell.yaml | 12 ++++--- go.sum | 12 +++---- test/functional/base_test.go | 22 ++++++++---- test/functional/nova_controller_test.go | 16 ++++++--- test/functional/nova_multicell_test.go | 36 ++++++++++++++----- test/functional/validation_webhook_test.go | 10 ++++-- .../default/cell-tests/01-assert.yaml | 9 +++-- ...-deploy-with-default-config-overwrite.yaml | 4 +-- .../config-tests/02-enable-notifications.yaml | 1 - .../default/rmquser-vhost/01-deploy.yaml | 2 -- .../default/scale-tests/01-assert.yaml | 9 +++-- .../default/scale-tests/04-assert.yaml | 9 +++-- 19 files changed, 122 insertions(+), 80 deletions(-) diff --git a/api/bases/nova.openstack.org_nova.yaml b/api/bases/nova.openstack.org_nova.yaml index 1f53f695f..322c6ee19 100644 --- a/api/bases/nova.openstack.org_nova.yaml +++ b/api/bases/nova.openstack.org_nova.yaml @@ -54,11 +54,11 @@ spec: Service instance used for the Nova API DB. type: string apiMessageBusInstance: - default: rabbitmq description: |- APIMessageBusInstance is the name of the RabbitMqCluster CR to select the Message Bus Service instance used by the Nova top level services to communicate. + Deprecated: Use MessagingBus.Cluster instead type: string apiServiceTemplate: default: @@ -398,11 +398,11 @@ spec: Service instance used as the DB of this cell. type: string cellMessageBusInstance: - default: rabbitmq description: |- CellMessageBusInstance is the name of the RabbitMqCluster CR to select the Message Bus Service instance used by the nova services to communicate in this cell. For cell0 it is unused. + Deprecated: Use MessagingBus.Cluster instead type: string conductorServiceTemplate: description: ConductorServiceTemplate - defines the cell conductor @@ -1342,8 +1342,9 @@ spec: cell1: cellDatabaseAccount: nova-cell1 cellDatabaseInstance: openstack-cell1 - cellMessageBusInstance: rabbitmq-cell1 hasAPIAccess: true + messagingBus: + cluster: rabbitmq-cell1 description: |- Cells is a mapping of cell names to NovaCellTemplate objects defining the cells in the deployment. The "cell0" cell is a mandatory cell in diff --git a/api/go.mod b/api/go.mod index ab5cfa259..22d1a9626 100644 --- a/api/go.mod +++ b/api/go.mod @@ -5,7 +5,7 @@ go 1.24.4 require ( github.com/google/go-cmp v0.7.0 github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260126081203-efc2df9207eb + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a github.com/robfig/cron/v3 v3.0.1 k8s.io/api v0.31.14 k8s.io/apimachinery v0.31.14 @@ -39,7 +39,6 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/ginkgo/v2 v2.27.5 // indirect - github.com/onsi/gomega v1.39.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect diff --git a/api/go.sum b/api/go.sum index 7f5b7d66a..5cbe305ee 100644 --- a/api/go.sum +++ b/api/go.sum @@ -81,8 +81,8 @@ github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09 h1:vhAGLKZitJIffj7ONiPpKmOX7Tmt/LGJpaY0Z2LeyfQ= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09/go.mod h1:ZXwFlspJCdZEUjMbmaf61t5AMB4u2vMyAMMoe/vJroE= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260126081203-efc2df9207eb h1:S7tnYO/E1f1KQfcp7N5bam8+ax/ExDTOhZ1WqG4Bfu0= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260126081203-efc2df9207eb/go.mod h1:ndqfy1KbVorHH6+zlUFPIrCRhMSxO3ImYJUGaooE0x0= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a h1:97OfmmJgoIKTfbED2SfyjoPkivoiMHg4jfbrTuwSGQw= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a/go.mod h1:ndqfy1KbVorHH6+zlUFPIrCRhMSxO3ImYJUGaooE0x0= 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= diff --git a/api/v1beta1/nova_types.go b/api/v1beta1/nova_types.go index 2802c4b1d..39d4a5c11 100644 --- a/api/v1beta1/nova_types.go +++ b/api/v1beta1/nova_types.go @@ -44,18 +44,18 @@ type NovaSpecCore struct { APIDatabaseInstance string `json:"apiDatabaseInstance"` // +kubebuilder:validation:Optional - // +kubebuilder:default=rabbitmq // APIMessageBusInstance is the name of the RabbitMqCluster CR to select // the Message Bus Service instance used by the Nova top level services to // communicate. - APIMessageBusInstance string `json:"apiMessageBusInstance" deprecated:"true" deprecatedNew:"messagingBus.cluster"` + // Deprecated: Use MessagingBus.Cluster instead + APIMessageBusInstance string `json:"apiMessageBusInstance,omitempty"` // +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}} + // +kubebuilder:default={cell0: {cellDatabaseAccount: nova-cell0, hasAPIAccess: true}, cell1: {cellDatabaseAccount: nova-cell1, cellDatabaseInstance: openstack-cell1, messagingBus: {cluster: rabbitmq-cell1}, hasAPIAccess: true}} // Cells is a mapping of cell names to NovaCellTemplate objects defining // the cells in the deployment. The "cell0" cell is a mandatory cell in // every deployment. Moreover any real deployment needs at least one diff --git a/api/v1beta1/nova_webhook.go b/api/v1beta1/nova_webhook.go index 096aaaf56..1e5762c79 100644 --- a/api/v1beta1/nova_webhook.go +++ b/api/v1beta1/nova_webhook.go @@ -26,7 +26,6 @@ import ( "fmt" "github.com/google/go-cmp/cmp" - rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" service "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/robfig/cron/v3" @@ -89,23 +88,14 @@ func (spec *NovaSpecCore) Default() { spec.APITimeout = novaDefaults.APITimeout } - // Default MessagingBus.Cluster from APIMessageBusInstance if not already set + // Default MessagingBus.Cluster if not set + // Migration from deprecated fields is handled by openstack-operator if spec.MessagingBus.Cluster == "" { - spec.MessagingBus.Cluster = spec.APIMessageBusInstance + spec.MessagingBus.Cluster = "rabbitmq" } - // 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 - } - } + // NotificationsBus.Cluster is not defaulted - it must be explicitly set if NotificationsBus is configured + // This ensures users make a conscious choice about which cluster to use for notifications for cellName, cellTemplate := range spec.CellTemplates { @@ -125,9 +115,14 @@ func (spec *NovaSpecCore) Default() { } } - // Default MessagingBus.Cluster from CellMessageBusInstance if not already set + // Default MessagingBus.Cluster if not set + // Migration from deprecated fields is handled by openstack-operator if cellTemplate.MessagingBus.Cluster == "" { - cellTemplate.MessagingBus.Cluster = cellTemplate.CellMessageBusInstance + if cellName == Cell0Name { + cellTemplate.MessagingBus.Cluster = "rabbitmq" + } else { + cellTemplate.MessagingBus.Cluster = "rabbitmq-" + cellName + } } // "cellTemplate" is a by-value copy, so we need to re-inject the updated version of it into the map diff --git a/api/v1beta1/novacell_types.go b/api/v1beta1/novacell_types.go index 4dda05ae5..7b35e9ddf 100644 --- a/api/v1beta1/novacell_types.go +++ b/api/v1beta1/novacell_types.go @@ -46,11 +46,11 @@ type NovaCellTemplate struct { CellDatabaseAccount string `json:"cellDatabaseAccount"` // +kubebuilder:validation:Optional - // +kubebuilder:default=rabbitmq // CellMessageBusInstance is the name of the RabbitMqCluster CR to select // the Message Bus Service instance used by the nova services to // communicate in this cell. For cell0 it is unused. - CellMessageBusInstance string `json:"cellMessageBusInstance" deprecated:"true" deprecatedNew:"messagingBus.cluster"` + // Deprecated: Use MessagingBus.Cluster instead + CellMessageBusInstance string `json:"cellMessageBusInstance,omitempty"` // +kubebuilder:validation:Optional // MessagingBus configuration (username, vhost, and cluster) diff --git a/config/crd/bases/nova.openstack.org_nova.yaml b/config/crd/bases/nova.openstack.org_nova.yaml index 1f53f695f..322c6ee19 100644 --- a/config/crd/bases/nova.openstack.org_nova.yaml +++ b/config/crd/bases/nova.openstack.org_nova.yaml @@ -54,11 +54,11 @@ spec: Service instance used for the Nova API DB. type: string apiMessageBusInstance: - default: rabbitmq description: |- APIMessageBusInstance is the name of the RabbitMqCluster CR to select the Message Bus Service instance used by the Nova top level services to communicate. + Deprecated: Use MessagingBus.Cluster instead type: string apiServiceTemplate: default: @@ -398,11 +398,11 @@ spec: Service instance used as the DB of this cell. type: string cellMessageBusInstance: - default: rabbitmq description: |- CellMessageBusInstance is the name of the RabbitMqCluster CR to select the Message Bus Service instance used by the nova services to communicate in this cell. For cell0 it is unused. + Deprecated: Use MessagingBus.Cluster instead type: string conductorServiceTemplate: description: ConductorServiceTemplate - defines the cell conductor @@ -1342,8 +1342,9 @@ spec: cell1: cellDatabaseAccount: nova-cell1 cellDatabaseInstance: openstack-cell1 - cellMessageBusInstance: rabbitmq-cell1 hasAPIAccess: true + messagingBus: + cluster: rabbitmq-cell1 description: |- Cells is a mapping of cell names to NovaCellTemplate objects defining the cells in the deployment. The "cell0" cell is a mandatory cell in diff --git a/config/samples/nova_v1beta1_nova-multi-cell.yaml b/config/samples/nova_v1beta1_nova-multi-cell.yaml index 37e86f633..d50179145 100644 --- a/config/samples/nova_v1beta1_nova-multi-cell.yaml +++ b/config/samples/nova_v1beta1_nova-multi-cell.yaml @@ -10,7 +10,8 @@ spec: # This is the name of the single RabbitMqCluster CR we deploy today # The Service is labelled with # app.kubernetes.io/component=rabbitmq, app.kubernetes.io/name= - apiMessageBusInstance: rabbitmq + messagingBus: + cluster: rabbitmq # This is the name of the KeystoneAPI CR we deploy today # The Service is labelled with service=keystone,internal=true keystoneInstance: keystone @@ -49,7 +50,8 @@ spec: cell0: cellDatabaseInstance: openstack cellDatabaseAccount: nova-cell0 - cellMessageBusInstance: rabbitmq + messagingBus: + cluster: rabbitmq # cell0 always needs access to the API DB and MQ as it hosts the super # conductor. It will inherit the API DB and MQ access from the Nova CR # that creates it. @@ -63,7 +65,8 @@ spec: cell1: cellDatabaseInstance: mariadb-cell1 cellDatabaseAccount: nova-cell1 - cellMessageBusInstance: rabbitmq-cell1 + messagingBus: + cluster: rabbitmq-cell1 # cell1 will have upcalls support. It will inherit the API DB and MQ # access from the Nova CR that creates it. hasAPIAccess: true @@ -82,7 +85,8 @@ spec: cell2: cellDatabaseInstance: mariadb-cell2 cellDatabaseAccount: nova-cell2 - cellMessageBusInstance: rabbitmq-cell2 + messagingBus: + cluster: rabbitmq-cell2 # cell2 will not get the API DB and MQ connection info from the Nova CR hasAPIAccess: false conductorServiceTemplate: diff --git a/go.sum b/go.sum index e2f720bc9..e3460bf43 100644 --- a/go.sum +++ b/go.sum @@ -122,16 +122,16 @@ github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7 github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260126091827-7758173fbb09/go.mod h1:ZXwFlspJCdZEUjMbmaf61t5AMB4u2vMyAMMoe/vJroE= github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260126175636-114b4c65a959 h1:8FSpTYAoLq27ElDGe3igPl2QUq9IYD6RJGu2Xu+Ymus= github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260126175636-114b4c65a959/go.mod h1:pN/s+czXvApiE9nxeTtDeRTXWcaaCLZSrtoyOSUb37k= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a h1:pF3mJ3nwq6r4qwom+rEWZNquZpcQW/iftHlJ1KPIDsk= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a/go.mod h1:kycZyoe7OZdW1HUghr2nI3N7wSJtNahXf6b/ypD14f4= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a h1:97OfmmJgoIKTfbED2SfyjoPkivoiMHg4jfbrTuwSGQw= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260128142552-e2c25eccae5a/go.mod h1:ndqfy1KbVorHH6+zlUFPIrCRhMSxO3ImYJUGaooE0x0= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260128142552-e2c25eccae5a h1:rMrtMsHAfkEqodLUD4Yu5NYtjGW8U3f7zxJTJpwjvPs= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260128142552-e2c25eccae5a/go.mod h1:zOX7Y05keiSppIvLabuyh42QHBMhCcoskAtxFRbwXKo= 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/lib-common/modules/test v0.6.1-0.20260126081203-efc2df9207eb h1:Fh9yjyogiR9P4oV3a2pSlSUyYzfbWbvlU6RFIjZoxsg= -github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260126081203-efc2df9207eb/go.mod h1:sqKTKvYhSzu4Opnjx/J+zzetXKRqYrhxsfvrST/NjoU= -github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260124124804-c82210f7a636 h1:Mtjy0cc2pBdyP44/5K6TB/VxIDvtjU2EHer17JKUaLg= -github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260124124804-c82210f7a636/go.mod h1:X6W8pIULiWUc6smaTqiNocjxoXaRLgXediwpI/dxD9s= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260128142552-e2c25eccae5a h1:EIjfIa5m79FIVfEU9zgLJmkMqNN3PhXGKaS6CpaXyfw= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260128142552-e2c25eccae5a/go.mod h1:sqKTKvYhSzu4Opnjx/J+zzetXKRqYrhxsfvrST/NjoU= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260120155328-e04f52e73f01 h1:93NxJ/fFx41HcFXk4nJk4PPz4lrqzNMviTmKyWwa+vg= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260120155328-e04f52e73f01/go.mod h1:X6W8pIULiWUc6smaTqiNocjxoXaRLgXediwpI/dxD9s= 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= diff --git a/test/functional/base_test.go b/test/functional/base_test.go index 9cfdd0bc8..889d65a78 100644 --- a/test/functional/base_test.go +++ b/test/functional/base_test.go @@ -112,10 +112,12 @@ func NovaSchedulerConditionGetter(name types.NamespacedName) condition.Condition func GetDefaultNovaSpec() map[string]any { return map[string]any{ - "secret": SecretName, - "cellTemplates": map[string]any{}, - "apiMessageBusInstance": cell0.TransportURLName.Name, - "apiDatabaseAccount": novaNames.APIMariaDBDatabaseAccount.Name, + "secret": SecretName, + "cellTemplates": map[string]any{}, + "apiDatabaseAccount": novaNames.APIMariaDBDatabaseAccount.Name, + "messagingBus": map[string]any{ + "cluster": cell0.TransportURLName.Name, + }, } } @@ -161,7 +163,9 @@ func CreateNovaWithCell0(name types.NamespacedName) client.Object { }, }, }, - "apiMessageBusInstance": cell0.TransportURLName.Name, + "messagingBus": map[string]any{ + "cluster": cell0.TransportURLName.Name, + }, }, } @@ -1025,7 +1029,9 @@ func CreateNovaWithNCellsAndEnsureReady(cellNumber int, novaNames *NovaNames) { template["cellDatabaseAccount"] = account.Name if i != 0 { // cell0 - template["cellMessageBusInstance"] = cell.TransportURLName.Name + template["messagingBus"] = map[string]any{ + "cluster": cell.TransportURLName.Name, + } } if i == 1 { @@ -1046,7 +1052,9 @@ func CreateNovaWithNCellsAndEnsureReady(cellNumber int, novaNames *NovaNames) { spec := GetDefaultNovaSpec() spec["cellTemplates"] = cellTemplates spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name - spec["apiMessageBusInstance"] = novaNames.Cells["cell0"].TransportURLName.Name + spec["messagingBus"] = map[string]any{ + "cluster": novaNames.Cells["cell0"].TransportURLName.Name, + } // Deploy Nova and simulate its dependencies DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec)) diff --git a/test/functional/nova_controller_test.go b/test/functional/nova_controller_test.go index e0bc76335..44b9c7153 100644 --- a/test/functional/nova_controller_test.go +++ b/test/functional/nova_controller_test.go @@ -1188,9 +1188,11 @@ var _ = Describe("Nova controller", func() { }, ) rawSpec := map[string]any{ - "secret": SecretName, - "apiDatabaseAccount": novaNames.APIMariaDBDatabaseAccount.Name, - "apiMessageBusInstance": cell0.TransportURLName.Name, + "secret": SecretName, + "apiDatabaseAccount": novaNames.APIMariaDBDatabaseAccount.Name, + "messagingBus": map[string]any{ + "cluster": cell0.TransportURLName.Name, + }, "cellTemplates": map[string]any{ "cell0": map[string]any{ "apiDatabaseAccount": novaNames.APIMariaDBDatabaseAccount.Name, @@ -1484,7 +1486,9 @@ var _ = Describe("Nova controller", func() { cell1Template := GetDefaultNovaCellTemplate() cell1Template["cellDatabaseInstance"] = cell1.MariaDBDatabaseName.Name cell1Template["cellDatabaseAccount"] = cell1.MariaDBAccountName.Name - cell1Template["cellMessageBusInstance"] = cell1.TransportURLName.Name + cell1Template["messagingBus"] = map[string]any{ + "cluster": cell1.TransportURLName.Name, + } // We reference the cell1 topology that is inherited by the cell1 conductor, // metadata, and novncproxy cell1Template["topologyRef"] = map[string]any{"name": topologyRefCell.Name} @@ -1500,7 +1504,9 @@ var _ = Describe("Nova controller", func() { "enabled": false, } spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name - spec["apiMessageBusInstance"] = cell0.TransportURLName.Name + spec["messagingBus"] = map[string]any{ + "cluster": cell0.TransportURLName.Name, + } // We reference the global topology and is inherited by the sub components // except cell1 that has an override spec["topologyRef"] = map[string]any{"name": topologyRefTopLevel.Name} diff --git a/test/functional/nova_multicell_test.go b/test/functional/nova_multicell_test.go index 7b46d59ea..7596204d0 100644 --- a/test/functional/nova_multicell_test.go +++ b/test/functional/nova_multicell_test.go @@ -79,7 +79,9 @@ var _ = Describe("Nova multi cell", func() { cell1Template := GetDefaultNovaCellTemplate() cell1Template["cellDatabaseInstance"] = cell1.MariaDBDatabaseName.Name cell1Template["cellDatabaseAccount"] = cell1.MariaDBAccountName.Name - cell1Template["cellMessageBusInstance"] = cell1.TransportURLName.Name + cell1Template["messagingBus"] = map[string]any{ + "cluster": cell1.TransportURLName.Name, + } cell1Template["passwordSelectors"] = map[string]any{ "database": "NovaCell1DatabasePassword", } @@ -92,7 +94,9 @@ var _ = Describe("Nova multi cell", func() { cell2Template := GetDefaultNovaCellTemplate() cell2Template["cellDatabaseInstance"] = cell2.MariaDBDatabaseName.Name cell2Template["cellDatabaseAccount"] = cell2.MariaDBAccountName.Name - cell2Template["cellMessageBusInstance"] = cell2.TransportURLName.Name + cell2Template["messagingBus"] = map[string]any{ + "cluster": cell2.TransportURLName.Name, + } cell2Template["hasAPIAccess"] = false cell2Template["passwordSelectors"] = map[string]any{ "database": "NovaCell2DatabasePassword", @@ -104,7 +108,9 @@ var _ = Describe("Nova multi cell", func() { "cell2": cell2Template, } spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name - spec["apiMessageBusInstance"] = cell0.TransportURLName.Name + spec["messagingBus"] = map[string]any{ + "cluster": cell0.TransportURLName.Name, + } DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec)) DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)) @@ -675,7 +681,9 @@ var _ = Describe("Nova multi cell", func() { // will act both as a super conductor and as cell1 conductor cell1Template["cellDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name cell1Template["cellDatabaseAccount"] = cell1.MariaDBAccountName.Name - cell1Template["cellMessageBusInstance"] = cell0.TransportURLName.Name + cell1Template["messagingBus"] = map[string]any{ + "cluster": cell0.TransportURLName.Name, + } cell1Template["hasAPIAccess"] = true spec["cellTemplates"] = map[string]any{ @@ -683,7 +691,9 @@ var _ = Describe("Nova multi cell", func() { "cell1": cell1Template, } spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name - spec["apiMessageBusInstance"] = cell0.TransportURLName.Name + spec["messagingBus"] = map[string]any{ + "cluster": cell0.TransportURLName.Name, + } DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec)) memcachedSpec := infra.GetDefaultMemcachedSpec() @@ -790,14 +800,18 @@ var _ = Describe("Nova multi cell", func() { cell1Template := GetDefaultNovaCellTemplate() cell1Template["cellDatabaseInstance"] = cell1.MariaDBDatabaseName.Name cell1Template["cellDatabaseAccount"] = cell1.MariaDBAccountName.Name - cell1Template["cellMessageBusInstance"] = cell1.TransportURLName.Name + cell1Template["messagingBus"] = map[string]any{ + "cluster": cell1.TransportURLName.Name, + } spec["cellTemplates"] = map[string]any{ "cell0": cell0Template, "cell1": cell1Template, } spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name - spec["apiMessageBusInstance"] = cell0.TransportURLName.Name + spec["messagingBus"] = map[string]any{ + "cluster": cell0.TransportURLName.Name, + } DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec)) memcachedSpec := infra.GetDefaultMemcachedSpec() @@ -849,7 +863,9 @@ var _ = Describe("Nova multi cell", func() { cell1Template := GetDefaultNovaCellTemplate() cell1Template["cellDatabaseInstance"] = cell1.MariaDBDatabaseName.Name cell1Template["cellDatabaseAccount"] = cell1.MariaDBAccountName.Name - cell1Template["cellMessageBusInstance"] = cell1.TransportURLName.Name + cell1Template["messagingBus"] = map[string]any{ + "cluster": cell1.TransportURLName.Name, + } cell1Template["metadataServiceTemplate"] = map[string]any{ "enabled": true, } @@ -862,7 +878,9 @@ var _ = Describe("Nova multi cell", func() { "enabled": false, } spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name - spec["apiMessageBusInstance"] = cell0.TransportURLName.Name + spec["messagingBus"] = map[string]any{ + "cluster": cell0.TransportURLName.Name, + } DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec)) memcachedSpec := infra.GetDefaultMemcachedSpec() diff --git a/test/functional/validation_webhook_test.go b/test/functional/validation_webhook_test.go index 453b19f6d..ff6ee4ea2 100644 --- a/test/functional/validation_webhook_test.go +++ b/test/functional/validation_webhook_test.go @@ -662,13 +662,17 @@ var _ = Describe("Nova validation", func() { ), ) }) - It("check Cell validation with duplicate cellMessageBusInstance", func() { + It("check Cell validation with duplicate messagingBus.cluster", func() { spec := GetDefaultNovaSpec() cell0 := GetDefaultNovaCellTemplate() cell1 := GetDefaultNovaCellTemplate() cell2 := GetDefaultNovaCellTemplate() - cell1["cellMessageBusInstance"] = "rabbitmq-of-caerbannog" - cell2["cellMessageBusInstance"] = "rabbitmq-of-caerbannog" + cell1["messagingBus"] = map[string]any{ + "cluster": "rabbitmq-of-caerbannog", + } + cell2["messagingBus"] = map[string]any{ + "cluster": "rabbitmq-of-caerbannog", + } spec["cellTemplates"] = map[string]any{"cell0": cell0, "cell1": cell1, "cell2": cell2} raw := map[string]any{ "apiVersion": "nova.openstack.org/v1beta1", diff --git a/test/kuttl/test-suites/default/cell-tests/01-assert.yaml b/test/kuttl/test-suites/default/cell-tests/01-assert.yaml index 63dab5d87..2f4e88d6b 100644 --- a/test/kuttl/test-suites/default/cell-tests/01-assert.yaml +++ b/test/kuttl/test-suites/default/cell-tests/01-assert.yaml @@ -8,7 +8,8 @@ metadata: spec: apiDatabaseInstance: openstack apiDatabaseAccount: nova-api - apiMessageBusInstance: rabbitmq + messagingBus: + cluster: rabbitmq apiServiceTemplate: customServiceConfig: "" replicas: 1 @@ -17,7 +18,8 @@ spec: cell0: cellDatabaseInstance: openstack cellDatabaseAccount: nova-cell0 - cellMessageBusInstance: rabbitmq + messagingBus: + cluster: rabbitmq conductorServiceTemplate: customServiceConfig: "" replicas: 1 @@ -30,7 +32,8 @@ spec: cell1: cellDatabaseInstance: openstack-cell1 cellDatabaseAccount: nova-cell1 - cellMessageBusInstance: rabbitmq-cell1 + messagingBus: + cluster: rabbitmq-cell1 conductorServiceTemplate: customServiceConfig: "" replicas: 1 diff --git a/test/kuttl/test-suites/default/config-tests/01-deploy-with-default-config-overwrite.yaml b/test/kuttl/test-suites/default/config-tests/01-deploy-with-default-config-overwrite.yaml index 563fd7daf..31545f0bf 100644 --- a/test/kuttl/test-suites/default/config-tests/01-deploy-with-default-config-overwrite.yaml +++ b/test/kuttl/test-suites/default/config-tests/01-deploy-with-default-config-overwrite.yaml @@ -12,13 +12,13 @@ spec: cell0: cellDatabaseInstance: openstack cellDatabaseAccount: nova-cell0 - cellMessageBusInstance: rabbitmq hasAPIAccess: true memcachedInstance: memcached cell1: cellDatabaseInstance: openstack-cell1 cellDatabaseAccount: nova-cell1 - cellMessageBusInstance: rabbitmq-cell1 + messagingBus: + cluster: rabbitmq-cell1 memcachedInstance: memcached novaComputeTemplates: compute-fake1: diff --git a/test/kuttl/test-suites/default/config-tests/02-enable-notifications.yaml b/test/kuttl/test-suites/default/config-tests/02-enable-notifications.yaml index e36be5a98..25bdbe24d 100644 --- a/test/kuttl/test-suites/default/config-tests/02-enable-notifications.yaml +++ b/test/kuttl/test-suites/default/config-tests/02-enable-notifications.yaml @@ -4,6 +4,5 @@ metadata: name: nova-kuttl spec: secret: osp-secret - notificationsBusInstance: null notificationsBus: cluster: rabbitmq-broadcaster diff --git a/test/kuttl/test-suites/default/rmquser-vhost/01-deploy.yaml b/test/kuttl/test-suites/default/rmquser-vhost/01-deploy.yaml index c991dc61f..d31053621 100644 --- a/test/kuttl/test-suites/default/rmquser-vhost/01-deploy.yaml +++ b/test/kuttl/test-suites/default/rmquser-vhost/01-deploy.yaml @@ -4,12 +4,10 @@ metadata: name: nova-kuttl spec: secret: osp-secret - apiMessageBusInstance: "" messagingBus: cluster: rabbitmq user: nova-rpc vhost: nova-rpc - notificationsBusInstance: null notificationsBus: cluster: rabbitmq-notifications user: nova-notifications diff --git a/test/kuttl/test-suites/default/scale-tests/01-assert.yaml b/test/kuttl/test-suites/default/scale-tests/01-assert.yaml index 63dab5d87..2f4e88d6b 100644 --- a/test/kuttl/test-suites/default/scale-tests/01-assert.yaml +++ b/test/kuttl/test-suites/default/scale-tests/01-assert.yaml @@ -8,7 +8,8 @@ metadata: spec: apiDatabaseInstance: openstack apiDatabaseAccount: nova-api - apiMessageBusInstance: rabbitmq + messagingBus: + cluster: rabbitmq apiServiceTemplate: customServiceConfig: "" replicas: 1 @@ -17,7 +18,8 @@ spec: cell0: cellDatabaseInstance: openstack cellDatabaseAccount: nova-cell0 - cellMessageBusInstance: rabbitmq + messagingBus: + cluster: rabbitmq conductorServiceTemplate: customServiceConfig: "" replicas: 1 @@ -30,7 +32,8 @@ spec: cell1: cellDatabaseInstance: openstack-cell1 cellDatabaseAccount: nova-cell1 - cellMessageBusInstance: rabbitmq-cell1 + messagingBus: + cluster: rabbitmq-cell1 conductorServiceTemplate: customServiceConfig: "" replicas: 1 diff --git a/test/kuttl/test-suites/default/scale-tests/04-assert.yaml b/test/kuttl/test-suites/default/scale-tests/04-assert.yaml index 9df6c6196..d95994f75 100644 --- a/test/kuttl/test-suites/default/scale-tests/04-assert.yaml +++ b/test/kuttl/test-suites/default/scale-tests/04-assert.yaml @@ -15,7 +15,8 @@ metadata: spec: apiDatabaseInstance: openstack apiDatabaseAccount: nova-api - apiMessageBusInstance: rabbitmq + messagingBus: + cluster: rabbitmq apiServiceTemplate: replicas: 0 metadataServiceTemplate: @@ -25,7 +26,8 @@ spec: cell0: cellDatabaseInstance: openstack cellDatabaseAccount: nova-cell0 - cellMessageBusInstance: rabbitmq + messagingBus: + cluster: rabbitmq conductorServiceTemplate: customServiceConfig: "" replicas: 0 @@ -37,7 +39,8 @@ spec: cell1: cellDatabaseInstance: openstack-cell1 cellDatabaseAccount: nova-cell1 - cellMessageBusInstance: rabbitmq-cell1 + messagingBus: + cluster: rabbitmq-cell1 conductorServiceTemplate: customServiceConfig: "" replicas: 0