From d3641d53086a78fd09aaa651b9e88d0136dc7826 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Wed, 30 Apr 2025 00:28:52 -0400 Subject: [PATCH 1/2] Adds notification transporturl 1 - human operator will update rabbitmq in openstackcontrolplane CR a) under rabbitmq: add new rabbitmq, ex: rabbitmq-broadcaster, something like rabbitmq-broadcaster: replicas: 1 2 - human operator will update Nova in openstackcontrolplane CR a) under nova.template.spec: register new rabbit (as we do for new cell but not inside celltemplate, as this one is top-level) nova: template: spec: apiContainerImageURL: image_url apiMessageBusInstance: rabbitmq // new var notificationsBusInstance: rabbitmq-broadcaster in Nova CR instance.Spec.NotificationsBusInstance value nil : means not mentioned in CR, so disable it. "" : disable notification rabbitmq-broadcaster : notification to a bus rabbitmq-broadcaster 3 - nova-operator will recognize notificationsBusInstance and retrieve transport_url of new rabbit (rabbitmq-broadcaster) 4 - nova-operator will set this transport_url in nova.conf Closes: OSPRH-15392 --- api/v1beta1/conditions.go | 16 ++++++ controllers/common.go | 4 ++ controllers/nova_controller.go | 76 ++++++++++++++++++++++--- controllers/novaapi_controller.go | 62 ++++++++++---------- controllers/novacell_controller.go | 30 +++++----- controllers/novacompute_controller.go | 30 +++++----- controllers/novaconductor_controller.go | 34 +++++------ controllers/novascheduler_controller.go | 60 ++++++++++--------- templates/nova.conf | 21 ++++--- test/functional/base_test.go | 12 ++-- test/functional/nova_controller_test.go | 4 +- 11 files changed, 226 insertions(+), 123 deletions(-) diff --git a/api/v1beta1/conditions.go b/api/v1beta1/conditions.go index 015902df8..388043e66 100644 --- a/api/v1beta1/conditions.go +++ b/api/v1beta1/conditions.go @@ -54,6 +54,10 @@ const ( NovaAllControlPlaneComputesReadyCondition condition.Type = "NovaAllControlPlaneComputesReady" //NovaCellsDeletionCondition indicates that the NovaCells deletion is in progress NovaCellsDeletionCondition condition.Type = "NovaCellsDeletion" + + // notifications + // NovaNotificationMQReadyCondition indicated that the top level notification message bus is ready + NovaNotificationMQReadyCondition condition.Type = "NovaNotificationMQReady" ) // Common Messages used by API objects. @@ -183,4 +187,16 @@ const ( // NovaCellsDeletionConditionReadyMessage NovaCellsDeletionConditionReadyMessage = "There is no more NovaCells to delete" + // notifications + // NovaNotificationMQReadyInitMessage + NovaNotificationMQReadyInitMessage = "Notification message bus not started" + + // NovaNotificationMQReadyErrorMessage + NovaNotificationMQReadyErrorMessage = "Notification message bus creation failed: %s" + + // NovaNotificationMQReadyCreatingMessage + NovaNotificationMQReadyCreatingMessage = "Notification message bus creation ongoing" + + // NovaNotificationMQReadyMessage + NovaNotificationMQReadyMessage = "Notification message bus created successfully" ) diff --git a/controllers/common.go b/controllers/common.go index f4efae449..7c2f3f1c9 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -96,6 +96,10 @@ const ( // Secret for the cell message bus transport URL TransportURLSelector = "transport_url" + // NotificationTransportURLSelector is the name of + // top level notification message bus transport URL + NotificationTransportURLSelector = "notification_transport_url" + // fields to index to reconcile when change passwordSecretField = ".spec.secret" caBundleSecretNameField = ".spec.tls.caBundleSecretName" // #nosec G101 diff --git a/controllers/nova_controller.go b/controllers/nova_controller.go index 8b3f7ce08..f1dab8f98 100644 --- a/controllers/nova_controller.go +++ b/controllers/nova_controller.go @@ -391,6 +391,47 @@ 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 + } + + var notificationTransportURL string + var notificationMQStatus nova.MessageBusStatus + var notificationMQError error + + if notificationBusName != "" { + notificationTransportURL, notificationMQStatus, notificationMQError = r.ensureMQ( + ctx, h, instance, instance.Name+"-notification-transport", notificationBusName) + + switch notificationMQStatus { + case nova.MQFailed: + instance.Status.Conditions.Set(condition.FalseCondition( + novav1.NovaNotificationMQReadyCondition, + condition.ErrorReason, + condition.SeverityError, + novav1.NovaNotificationMQReadyErrorMessage, + notificationMQError.Error(), + )) + case nova.MQCreating: + instance.Status.Conditions.Set(condition.FalseCondition( + novav1.NovaNotificationMQReadyCondition, + condition.ErrorReason, + condition.SeverityError, + novav1.NovaNotificationMQReadyCreatingMessage, + )) + case nova.MQCompleted: + instance.Status.Conditions.MarkTrue( + novav1.NovaNotificationMQReadyCondition, novav1.NovaNotificationMQReadyMessage) + default: + return ctrl.Result{}, fmt.Errorf("%w from for the Notification MQ: %d", + util.ErrInvalidStatus, notificationMQStatus) + } + } else { + instance.Status.Conditions.Remove(novav1.NovaNotificationMQReadyCondition) + } + cellMQs := map[string]*nova.MessageBus{} var failedMQs []string var creatingMQs []string @@ -481,7 +522,7 @@ 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, + cellDB.Database, apiDB, cellMQ.TransportURL, notificationTransportURL, keystoneInternalAuthURL, secret, ) cells[cellName] = cell @@ -546,7 +587,11 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul return ctrl.Result{}, nil } - topLevelSecretName, err := r.ensureTopLevelSecret(ctx, h, instance, apiTransportURL, secret) + topLevelSecretName, err := r.ensureTopLevelSecret( + ctx, h, instance, + apiTransportURL, + notificationTransportURL, + secret) if err != nil { return ctrl.Result{}, err } @@ -936,6 +981,11 @@ func (r *NovaReconciler) initConditions( condition.InitReason, condition.MemcachedReadyInitMessage, ), + condition.UnknownCondition( + novav1.NovaNotificationMQReadyCondition, + condition.InitReason, + novav1.NovaNotificationMQReadyInitMessage, + ), ) instance.Status.Conditions.Init(&cl) return nil @@ -1133,12 +1183,16 @@ func (r *NovaReconciler) ensureCell( cellDB *mariadbv1.Database, apiDB *mariadbv1.Database, cellTransportURL string, + notificationTransportURL string, keystoneAuthURL string, secret corev1.Secret, ) (*novav1.NovaCell, nova.CellDeploymentStatus, error) { Log := r.GetLogger(ctx) - cellSecretName, err := r.ensureCellSecret(ctx, h, instance, cellName, cellTemplate, cellTransportURL, secret) + cellSecretName, err := r.ensureCellSecret( + ctx, h, instance, cellName, cellTemplate, + cellTransportURL, notificationTransportURL, + secret) if err != nil { return nil, nova.CellDeploying, err } @@ -1684,6 +1738,7 @@ func (r *NovaReconciler) ensureMQ( } secretName := types.NamespacedName{Namespace: instance.Namespace, Name: transportURL.Status.SecretName} + secret := &corev1.Secret{} err = h.GetClient().Get(ctx, secretName, secret) if err != nil { @@ -1698,7 +1753,6 @@ func (r *NovaReconciler) ensureMQ( return "", nova.MQFailed, fmt.Errorf( "%w: the TransportURL secret %s does not have 'transport_url' field", util.ErrFieldNotFound, transportURL.Status.SecretName) } - return string(url), nova.MQCompleted, nil } @@ -1902,13 +1956,15 @@ func (r *NovaReconciler) ensureCellSecret( cellName string, cellTemplate novav1.NovaCellTemplate, cellTransportURL string, + notificationTransportURL string, externalSecret corev1.Secret, ) (string, error) { // NOTE(gibi): We can move other sensitive data to the internal Secret from // the NovaCellSpec fields, possibly hostnames or usernames. data := map[string]string{ - ServicePasswordSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.Service]), - TransportURLSelector: cellTransportURL, + ServicePasswordSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.Service]), + TransportURLSelector: cellTransportURL, + NotificationTransportURLSelector: notificationTransportURL, } // If metadata is enabled in the cell then the cell secret needs the @@ -1952,14 +2008,16 @@ func (r *NovaReconciler) ensureTopLevelSecret( h *helper.Helper, instance *novav1.Nova, apiTransportURL string, + notificationTransportURL string, externalSecret corev1.Secret, ) (string, error) { // NOTE(gibi): We can move other sensitive data to the internal Secret from // the subCR fields, possibly hostnames or usernames. data := map[string]string{ - ServicePasswordSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.Service]), - MetadataSecretSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.MetadataSecret]), - TransportURLSelector: apiTransportURL, + ServicePasswordSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.Service]), + MetadataSecretSelector: string(externalSecret.Data[instance.Spec.PasswordSelectors.MetadataSecret]), + TransportURLSelector: apiTransportURL, + NotificationTransportURLSelector: notificationTransportURL, } // NOTE(gibi): When we switch to immutable secrets then we need to include diff --git a/controllers/novaapi_controller.go b/controllers/novaapi_controller.go index 64214fb4e..614465425 100644 --- a/controllers/novaapi_controller.go +++ b/controllers/novaapi_controller.go @@ -183,16 +183,19 @@ func (r *NovaAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re // detect if something is changed. hashes := make(map[string]env.Setter) - secretHash, result, secret, err := ensureSecret( - ctx, - types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, + requiredSecretFields := []string{ // TODO(gibi): add keystoneAuthURL here is that is also passed via // the Secret. Also add DB and MQ user name here too if those are // passed via the Secret - []string{ - ServicePasswordSelector, - TransportURLSelector, - }, + ServicePasswordSelector, + TransportURLSelector, + NotificationTransportURLSelector, + } + + secretHash, result, secret, err := ensureSecret( + ctx, + types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, + requiredSecretFields, h.GetClient(), &instance.Status.Conditions, r.RequeueTimeout, @@ -473,28 +476,29 @@ func (r *NovaAPIReconciler) generateConfigs( "keystone_internal_url": instance.Spec.KeystoneAuthURL, // NOTE(gibi): As per the definition of www_authenticate_uri this // always needs to point to the public keystone endpoint. - "www_authenticate_uri": instance.Spec.KeystonePublicAuthURL, - "nova_keystone_user": instance.Spec.ServiceUser, - "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), - "api_db_name": NovaAPIDatabaseName, - "api_db_user": apiDatabaseAccount.Spec.UserName, - "api_db_password": string(apiDbSecret.Data[mariadbv1.DatabasePasswordSelector]), - "api_db_address": instance.Spec.APIDatabaseHostname, - "api_db_port": 3306, - "cell_db_name": NovaCell0DatabaseName, - "cell_db_user": cellDatabaseAccount.Spec.UserName, - "cell_db_password": string(cellDbSecret.Data[mariadbv1.DatabasePasswordSelector]), - "cell_db_address": instance.Spec.Cell0DatabaseHostname, - "cell_db_port": 3306, - "openstack_region_name": "regionOne", // fixme - "default_project_domain": "Default", // fixme - "default_user_domain": "Default", // fixme - "transport_url": string(secret.Data[TransportURLSelector]), - "log_file": "/var/log/nova/nova-api.log", - "tls": false, - "MemcachedServers": memcachedInstance.GetMemcachedServerListString(), - "MemcachedServersWithInet": memcachedInstance.GetMemcachedServerListWithInetString(), - "MemcachedTLS": memcachedInstance.GetMemcachedTLSSupport(), + "www_authenticate_uri": instance.Spec.KeystonePublicAuthURL, + "nova_keystone_user": instance.Spec.ServiceUser, + "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), + "api_db_name": NovaAPIDatabaseName, + "api_db_user": apiDatabaseAccount.Spec.UserName, + "api_db_password": string(apiDbSecret.Data[mariadbv1.DatabasePasswordSelector]), + "api_db_address": instance.Spec.APIDatabaseHostname, + "api_db_port": 3306, + "cell_db_name": NovaCell0DatabaseName, + "cell_db_user": cellDatabaseAccount.Spec.UserName, + "cell_db_password": string(cellDbSecret.Data[mariadbv1.DatabasePasswordSelector]), + "cell_db_address": instance.Spec.Cell0DatabaseHostname, + "cell_db_port": 3306, + "openstack_region_name": "regionOne", // fixme + "default_project_domain": "Default", // fixme + "default_user_domain": "Default", // fixme + "transport_url": string(secret.Data[TransportURLSelector]), + "notification_transport_url": string(secret.Data[NotificationTransportURLSelector]), + "log_file": "/var/log/nova/nova-api.log", + "tls": false, + "MemcachedServers": memcachedInstance.GetMemcachedServerListString(), + "MemcachedServersWithInet": memcachedInstance.GetMemcachedServerListWithInetString(), + "MemcachedTLS": memcachedInstance.GetMemcachedTLSSupport(), } // create httpd vhost template parameters httpdVhostConfig := map[string]interface{}{} diff --git a/controllers/novacell_controller.go b/controllers/novacell_controller.go index 0fef31837..4db0fc53c 100644 --- a/controllers/novacell_controller.go +++ b/controllers/novacell_controller.go @@ -144,14 +144,17 @@ func (r *NovaCellReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r } }() + requiredSecretFields := []string{ + ServicePasswordSelector, + TransportURLSelector, + NotificationTransportURLSelector, + } + // For the compute config generation we need to read the input secrets _, result, secret, err := ensureSecret( ctx, types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, - []string{ - ServicePasswordSelector, - TransportURLSelector, - }, + requiredSecretFields, h.GetClient(), &instance.Status.Conditions, r.RequeueTimeout, @@ -767,15 +770,16 @@ func (r *NovaCellReconciler) generateComputeConfigs( secret corev1.Secret, vncProxyURL *string, ) error { templateParameters := map[string]interface{}{ - "service_name": "nova-compute", - "keystone_internal_url": instance.Spec.KeystoneAuthURL, - "nova_keystone_user": instance.Spec.ServiceUser, - "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), - "openstack_region_name": "regionOne", // fixme - "default_project_domain": "Default", // fixme - "default_user_domain": "Default", // fixme - "compute_driver": "libvirt.LibvirtDriver", - "transport_url": string(secret.Data[TransportURLSelector]), + "service_name": "nova-compute", + "keystone_internal_url": instance.Spec.KeystoneAuthURL, + "nova_keystone_user": instance.Spec.ServiceUser, + "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), + "openstack_region_name": "regionOne", // fixme + "default_project_domain": "Default", // fixme + "default_user_domain": "Default", // fixme + "compute_driver": "libvirt.LibvirtDriver", + "transport_url": string(secret.Data[TransportURLSelector]), + "notification_transport_url": string(secret.Data[NotificationTransportURLSelector]), } // vnc is optional so we only need to configure it for the compute // if the proxy service is deployed in the cell diff --git a/controllers/novacompute_controller.go b/controllers/novacompute_controller.go index 23cdabf9a..e789a22bb 100644 --- a/controllers/novacompute_controller.go +++ b/controllers/novacompute_controller.go @@ -158,13 +158,16 @@ func (r *NovaComputeReconciler) Reconcile(ctx context.Context, req ctrl.Request) hashes := make(map[string]env.Setter) + requiredSecretFields := []string{ + ServicePasswordSelector, + TransportURLSelector, + NotificationTransportURLSelector, + } + secretHash, result, secret, err := ensureSecret( ctx, types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, - []string{ - ServicePasswordSelector, - TransportURLSelector, - }, + requiredSecretFields, h.GetClient(), &instance.Status.Conditions, r.RequeueTimeout, @@ -336,15 +339,16 @@ func (r *NovaComputeReconciler) generateConfigs( ctx context.Context, h *helper.Helper, instance *novav1.NovaCompute, hashes *map[string]env.Setter, secret corev1.Secret, ) error { templateParameters := map[string]interface{}{ - "service_name": NovaComputeLabelPrefix, - "keystone_internal_url": instance.Spec.KeystoneAuthURL, - "nova_keystone_user": instance.Spec.ServiceUser, - "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), - "openstack_region_name": "regionOne", // fixme - "default_project_domain": "Default", // fixme - "default_user_domain": "Default", // fixme - "transport_url": string(secret.Data[TransportURLSelector]), - "compute_driver": instance.Spec.ComputeDriver, + "service_name": NovaComputeLabelPrefix, + "keystone_internal_url": instance.Spec.KeystoneAuthURL, + "nova_keystone_user": instance.Spec.ServiceUser, + "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), + "openstack_region_name": "regionOne", // fixme + "default_project_domain": "Default", // fixme + "default_user_domain": "Default", // fixme + "transport_url": string(secret.Data[TransportURLSelector]), + "notification_transport_url": string(secret.Data[NotificationTransportURLSelector]), + "compute_driver": instance.Spec.ComputeDriver, // Neither the ironic driver nor the fake driver support VNC "vnc_enabled": false, } diff --git a/controllers/novaconductor_controller.go b/controllers/novaconductor_controller.go index 013d3fd83..8d5699854 100644 --- a/controllers/novaconductor_controller.go +++ b/controllers/novaconductor_controller.go @@ -182,6 +182,7 @@ func (r *NovaConductorReconciler) Reconcile(ctx context.Context, req ctrl.Reques requiredSecretFields := []string{ ServicePasswordSelector, TransportURLSelector, + NotificationTransportURLSelector, } secretHash, result, secret, err := ensureSecret( @@ -431,22 +432,23 @@ func (r *NovaConductorReconciler) generateConfigs( cellDbSecret := cellDB.GetSecret() templateParameters := map[string]interface{}{ - "service_name": "nova-conductor", - "keystone_internal_url": instance.Spec.KeystoneAuthURL, - "nova_keystone_user": instance.Spec.ServiceUser, - "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), - "cell_db_name": getCellDatabaseName(instance.Spec.CellName), - "cell_db_user": cellDatabaseAccount.Spec.UserName, - "cell_db_password": string(cellDbSecret.Data[mariadbv1.DatabasePasswordSelector]), - "cell_db_address": instance.Spec.CellDatabaseHostname, - "cell_db_port": 3306, - "openstack_region_name": "regionOne", // fixme - "default_project_domain": "Default", // fixme - "default_user_domain": "Default", // fixme - "transport_url": string(secret.Data[TransportURLSelector]), - "MemcachedServers": memcachedInstance.GetMemcachedServerListString(), - "MemcachedServersWithInet": memcachedInstance.GetMemcachedServerListWithInetString(), - "MemcachedTLS": memcachedInstance.GetMemcachedTLSSupport(), + "service_name": "nova-conductor", + "keystone_internal_url": instance.Spec.KeystoneAuthURL, + "nova_keystone_user": instance.Spec.ServiceUser, + "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), + "cell_db_name": getCellDatabaseName(instance.Spec.CellName), + "cell_db_user": cellDatabaseAccount.Spec.UserName, + "cell_db_password": string(cellDbSecret.Data[mariadbv1.DatabasePasswordSelector]), + "cell_db_address": instance.Spec.CellDatabaseHostname, + "cell_db_port": 3306, + "openstack_region_name": "regionOne", // fixme + "default_project_domain": "Default", // fixme + "default_user_domain": "Default", // fixme + "transport_url": string(secret.Data[TransportURLSelector]), + "notification_transport_url": string(secret.Data[NotificationTransportURLSelector]), + "MemcachedServers": memcachedInstance.GetMemcachedServerListString(), + "MemcachedServersWithInet": memcachedInstance.GetMemcachedServerListWithInetString(), + "MemcachedTLS": memcachedInstance.GetMemcachedTLSSupport(), } if len(instance.Spec.APIDatabaseHostname) > 0 { apiDatabaseAccount, apiDbSecret, err := mariadbv1.GetAccountAndSecret(ctx, h, instance.Spec.APIDatabaseAccount, instance.Namespace) diff --git a/controllers/novascheduler_controller.go b/controllers/novascheduler_controller.go index cdf7b4f8d..46a3daf9e 100644 --- a/controllers/novascheduler_controller.go +++ b/controllers/novascheduler_controller.go @@ -177,16 +177,19 @@ func (r *NovaSchedulerReconciler) Reconcile(ctx context.Context, req ctrl.Reques // detect if something is changed. hashes := make(map[string]env.Setter) - secretHash, result, secret, err := ensureSecret( - ctx, - types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, + requiredSecretFields := []string{ // TODO(gibi): add keystoneAuthURL here is that is also passed via // the Secret. Also add DB and MQ user name here too if those are // passed via the Secret - []string{ - ServicePasswordSelector, - TransportURLSelector, - }, + ServicePasswordSelector, + TransportURLSelector, + NotificationTransportURLSelector, + } + + secretHash, result, secret, err := ensureSecret( + ctx, + types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, + requiredSecretFields, h.GetClient(), &instance.Status.Conditions, r.RequeueTimeout, @@ -518,27 +521,28 @@ func (r *NovaSchedulerReconciler) generateConfigs( } templateParameters := map[string]interface{}{ - "service_name": "nova-scheduler", - "keystone_internal_url": instance.Spec.KeystoneAuthURL, - "nova_keystone_user": instance.Spec.ServiceUser, - "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), - "api_db_name": NovaAPIDatabaseName, - "api_db_user": apiDatabaseAccount.Spec.UserName, - "api_db_password": string(apiDbSecret.Data[mariadbv1.DatabasePasswordSelector]), - "api_db_address": instance.Spec.APIDatabaseHostname, - "api_db_port": 3306, - "cell_db_name": NovaCell0DatabaseName, - "cell_db_user": cellDatabaseAccount.Spec.UserName, - "cell_db_password": string(cellDbSecret.Data[mariadbv1.DatabasePasswordSelector]), - "cell_db_address": instance.Spec.Cell0DatabaseHostname, - "cell_db_port": 3306, - "openstack_region_name": "regionOne", // fixme - "default_project_domain": "Default", // fixme - "default_user_domain": "Default", // fixme - "transport_url": string(secret.Data[TransportURLSelector]), - "MemcachedServers": memcachedInstance.GetMemcachedServerListString(), - "MemcachedServersWithInet": memcachedInstance.GetMemcachedServerListWithInetString(), - "MemcachedTLS": memcachedInstance.GetMemcachedTLSSupport(), + "service_name": "nova-scheduler", + "keystone_internal_url": instance.Spec.KeystoneAuthURL, + "nova_keystone_user": instance.Spec.ServiceUser, + "nova_keystone_password": string(secret.Data[ServicePasswordSelector]), + "api_db_name": NovaAPIDatabaseName, + "api_db_user": apiDatabaseAccount.Spec.UserName, + "api_db_password": string(apiDbSecret.Data[mariadbv1.DatabasePasswordSelector]), + "api_db_address": instance.Spec.APIDatabaseHostname, + "api_db_port": 3306, + "cell_db_name": NovaCell0DatabaseName, + "cell_db_user": cellDatabaseAccount.Spec.UserName, + "cell_db_password": string(cellDbSecret.Data[mariadbv1.DatabasePasswordSelector]), + "cell_db_address": instance.Spec.Cell0DatabaseHostname, + "cell_db_port": 3306, + "openstack_region_name": "regionOne", // fixme + "default_project_domain": "Default", // fixme + "default_user_domain": "Default", // fixme + "transport_url": string(secret.Data[TransportURLSelector]), + "notification_transport_url": string(secret.Data[NotificationTransportURLSelector]), + "MemcachedServers": memcachedInstance.GetMemcachedServerListString(), + "MemcachedServersWithInet": memcachedInstance.GetMemcachedServerListWithInetString(), + "MemcachedTLS": memcachedInstance.GetMemcachedTLSSupport(), } var tlsCfg *tls.Service diff --git a/templates/nova.conf b/templates/nova.conf index 19c614ff1..d010e89c4 100644 --- a/templates/nova.conf +++ b/templates/nova.conf @@ -126,20 +126,25 @@ enable_proxy_headers_parsing = True api_paste_config = /etc/nova/api-paste.ini {{end}} +{{ if (index . "notification_transport_url")}} +[notifications] +notify_on_state_change = vm_and_task_state +notification_format=both +# notification_format=both specific to ceilometer as it depends on unversioned notifications +# bug https://bugs.launchpad.net/ceilometer/+bug/1665449 +# while other services support versioned +# so we emit both verioned and unversioned if notifications are enabled. +{{ end }} + [oslo_messaging_notifications] -{{ if (index . "nova_enabled_notification") }} -transport_url = {{ .nova_cell_notify_transport_url }} +{{ if (index . "notification_transport_url")}} +transport_url = {{.notification_transport_url}} driver = messagingv2 -notification_format=versioned {{ else }} driver = noop -{{end}} - -{{if (index . "enable_ceilometer") }} -[notifications] -notify_on_state_change = vm_and_task_state {{ end }} + {{ if eq .service_name "nova-novncproxy"}} [vnc] enabled = True diff --git a/test/functional/base_test.go b/test/functional/base_test.go index c29ad34d5..166aae650 100644 --- a/test/functional/base_test.go +++ b/test/functional/base_test.go @@ -679,9 +679,10 @@ func CreateInternalTopLevelSecret(novaNames NovaNames) *corev1.Secret { return th.CreateSecret( novaNames.InternalTopLevelSecretName, map[string][]byte{ - "ServicePassword": []byte("service-password"), - "MetadataSecret": []byte("metadata-secret"), - "transport_url": []byte("rabbit://api/fake"), + "ServicePassword": []byte("service-password"), + "MetadataSecret": []byte("metadata-secret"), + "transport_url": []byte("rabbit://api/fake"), + "notification_transport_url": []byte("rabbit://notifications/fake"), }, ) } @@ -751,8 +752,9 @@ func GetDefaultNovaNoVNCProxySpec(cell CellNames) map[string]interface{} { func CreateCellInternalSecret(cell CellNames, additionalValues map[string][]byte) *corev1.Secret { secretMap := map[string][]byte{ - "ServicePassword": []byte("service-password"), - "transport_url": []byte(fmt.Sprintf("rabbit://%s/fake", cell.CellName)), + "ServicePassword": []byte("service-password"), + "transport_url": []byte(fmt.Sprintf("rabbit://%s/fake", cell.CellName)), + "notification_transport_url": []byte("rabbit://notifications/fake"), } // (ksambor) this can be replaced with maps.Copy directly from maps // not experimental package when we move to go 1.21 diff --git a/test/functional/nova_controller_test.go b/test/functional/nova_controller_test.go index 6ac1e6da7..e59ef7cb2 100644 --- a/test/functional/nova_controller_test.go +++ b/test/functional/nova_controller_test.go @@ -260,7 +260,7 @@ 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(2)) + Expect(internalCellSecret.Data).To(HaveLen(3)) Expect(internalCellSecret.Data).To( HaveKeyWithValue(controllers.ServicePasswordSelector, []byte("service-password"))) Expect(internalCellSecret.Data).To( @@ -374,7 +374,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(3)) + Expect(internalTopLevelSecret.Data).To(HaveLen(4)) Expect(internalTopLevelSecret.Data).To( HaveKeyWithValue(controllers.ServicePasswordSelector, []byte("service-password"))) Expect(internalTopLevelSecret.Data).To( From 3a6534a56854e5e41287a504f6a5342a634fe830 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Wed, 16 Apr 2025 04:39:51 -0400 Subject: [PATCH 2/2] Adds tests for notification bus --- test/functional/base_test.go | 57 +++++++- .../nova_compute_ironic_controller_test.go | 41 ++++++ test/functional/nova_controller_test.go | 123 ++++++++++++++++++ test/functional/nova_scheduler_test.go | 40 ++++++ test/functional/novaapi_controller_test.go | 40 ++++++ test/functional/novacell_controller_test.go | 2 + .../novaconductor_controller_test.go | 40 ++++++ 7 files changed, 342 insertions(+), 1 deletion(-) diff --git a/test/functional/base_test.go b/test/functional/base_test.go index 166aae650..c8761d0b4 100644 --- a/test/functional/base_test.go +++ b/test/functional/base_test.go @@ -218,7 +218,9 @@ func NovaConductorConditionGetter(name types.NamespacedName) condition.Condition func CreateNovaMessageBusSecret(cell CellNames) *corev1.Secret { s := th.CreateSecret( - types.NamespacedName{Namespace: cell.CellCRName.Namespace, Name: fmt.Sprintf("%s-secret", cell.TransportURLName.Name)}, + types.NamespacedName{ + Namespace: cell.CellCRName.Namespace, + Name: fmt.Sprintf("%s-secret", cell.TransportURLName.Name)}, map[string][]byte{ "transport_url": []byte(fmt.Sprintf("rabbit://%s/fake", cell.CellName)), }, @@ -227,6 +229,19 @@ func CreateNovaMessageBusSecret(cell CellNames) *corev1.Secret { return s } +func CreateNotificiationTransportURLSecret(notificationsBus NotificationsBusNames) *corev1.Secret { + s := th.CreateSecret( + types.NamespacedName{ + Namespace: novaNames.NovaName.Namespace, + Name: fmt.Sprintf("%s-secret", notificationsBus.BusName)}, + map[string][]byte{ + "transport_url": []byte(fmt.Sprintf("rabbit://%s/fake", notificationsBus.TransportURLName.Name)), + }, + ) + logger.Info("Secret created", "name", s.Name) + return s +} + func GetDefaultNovaCellSpec(cell CellNames) map[string]interface{} { return map[string]interface{}{ "cellName": cell.CellName, @@ -474,6 +489,20 @@ func GetCellNames(novaName types.NamespacedName, cell string) CellNames { return c } +type NotificationsBusNames struct { + BusName string + TransportURLName types.NamespacedName +} + +func GetNotificationsBusNames(novaName types.NamespacedName) NotificationsBusNames { + return NotificationsBusNames{ + BusName: "rabbitmq-broadcaster", + TransportURLName: types.NamespacedName{ + Namespace: novaName.Namespace, + Name: novaName.Name + "-notification-transport"}, + } +} + type NovaNames struct { Namespace string NovaName types.NamespacedName @@ -916,3 +945,29 @@ func GetSampleTopologySpec(label string) (map[string]interface{}, []corev1.Topol } return topologySpec, topologySpecObj } + +func AssertNotHaveNotificationTransportURL(configData string) { + + Expect(configData).ToNot( + ContainSubstring("[notifications]")) + + Expect(configData).To( + ContainSubstring("[oslo_messaging_notifications]\ndriver = noop")) + +} + +func AssertHaveNotificationTransportURL(notificationsTransportURLName string, configData string) { + + expectedConf1 := "[notifications]\nnotify_on_state_change = vm_and_task_state\nnotification_format=both" + + expectedConf2 := fmt.Sprintf( + "[oslo_messaging_notifications]\ntransport_url = rabbit://%s/fake\ndriver = messagingv2", + notificationsTransportURLName) + + Expect(configData).To( + ContainSubstring(expectedConf1)) + + Expect(configData).To( + ContainSubstring(expectedConf2)) + +} diff --git a/test/functional/nova_compute_ironic_controller_test.go b/test/functional/nova_compute_ironic_controller_test.go index 0a6e2e20e..527747466 100644 --- a/test/functional/nova_compute_ironic_controller_test.go +++ b/test/functional/nova_compute_ironic_controller_test.go @@ -139,6 +139,45 @@ var _ = Describe("NovaCompute controller", func() { }) + When("the Secret is created but notification fields is missing", func() { + BeforeEach(func() { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cell1.InternalCellSecretName.Name, + Namespace: cell1.InternalCellSecretName.Namespace, + }, + Data: map[string][]byte{ + "ServicePassword": []byte("12345678"), + "transport_url": []byte("rabbit://cell1/fake"), + // notification_transport_url is missing + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, secret) + }) + + It("is not Ready", func() { + th.ExpectCondition( + cell1.NovaComputeName, + ConditionGetterFunc(NovaComputeConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reports that the inputs are not ready", func() { + th.ExpectConditionWithDetails( + cell1.NovaComputeName, + ConditionGetterFunc(NovaComputeConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("Input data error occurred field not found in Secret: 'notification_transport_url' not found in secret/%s", cell1.InternalCellSecretName.Name), + ) + }) + + }) + When("the Secret is created with all the expected fields", func() { BeforeEach(func() { DeferCleanup( @@ -165,6 +204,8 @@ var _ = Describe("NovaCompute controller", func() { Expect(configDataMap).ShouldNot(BeNil()) Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) configData := string(configDataMap.Data["01-nova.conf"]) + AssertHaveNotificationTransportURL("notifications", configData) + Expect(configData).Should( ContainSubstring("transport_url=rabbit://cell1/fake")) Expect(configData).Should( diff --git a/test/functional/nova_controller_test.go b/test/functional/nova_controller_test.go index e59ef7cb2..ccb65cdfa 100644 --- a/test/functional/nova_controller_test.go +++ b/test/functional/nova_controller_test.go @@ -38,6 +38,125 @@ import ( "github.com/openstack-k8s-operators/nova-operator/controllers" ) +var _ = Describe("Nova controller - notifications", func() { + + When("Nova CR instance is created", func() { + BeforeEach(func() { + DeferCleanup( + k8sClient.Delete, ctx, CreateNovaSecret(novaNames.NovaName.Namespace, SecretName)) + DeferCleanup( + k8sClient.Delete, ctx, CreateNovaMessageBusSecret(cell0)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + novaNames.NovaName.Namespace, + "openstack", + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + memcachedSpec := infra.GetDefaultMemcachedSpec() + + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(novaNames.MemcachedNamespace) + + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)) + + DeferCleanup(th.DeleteInstance, CreateNovaWithCell0(novaNames.NovaName)) + + keystone.SimulateKeystoneServiceReady(novaNames.KeystoneServiceName) + mariadb.SimulateMariaDBDatabaseCompleted(novaNames.APIMariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(novaNames.APIMariaDBDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(cell0.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(cell0.MariaDBAccountName) + infra.SimulateTransportURLReady(cell0.TransportURLName) + SimulateReadyOfNovaTopServices() + }) + It("notification transport url is not set", func() { + + // assert that a the top level internal internal secret is created + // with the proper data + internalTopLevelSecret := th.GetSecret(novaNames.InternalTopLevelSecretName) + // verify if nova secret has notification-transport-url + Expect(internalTopLevelSecret.Data).To(HaveKey("notification_transport_url")) + Expect(internalTopLevelSecret.Data).To( + HaveKeyWithValue("notification_transport_url", []byte(""))) + + // verify if confs are updated with notification-transport-url under oslo_messaging_notifications + // assert in nova-api conf + configDataMap := th.GetSecret(novaNames.APIConfigDataName) + Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData := string(configDataMap.Data["01-nova.conf"]) + AssertNotHaveNotificationTransportURL(configData) + + // assert in sch conf + configDataMap = th.GetSecret(novaNames.SchedulerConfigDataName) + Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData = string(configDataMap.Data["01-nova.conf"]) + AssertNotHaveNotificationTransportURL(configData) + + // assert in cell0-conductor conf + configDataMap = th.GetSecret(cell0.ConductorConfigDataName) + Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData = string(configDataMap.Data["01-nova.conf"]) + AssertNotHaveNotificationTransportURL(configData) + + }) + + It("notification transport url is set with new rabbit", func() { + + // add new-rabbit in Nova CR + notificationsBus := GetNotificationsBusNames(novaNames.NovaName) + DeferCleanup(k8sClient.Delete, ctx, CreateNotificiationTransportURLSecret(notificationsBus)) + + Eventually(func(g Gomega) { + nova := GetNova(novaNames.NovaName) + nova.Spec.NotificationsBusInstance = ¬ificationsBus.BusName + g.Expect(k8sClient.Update(ctx, nova)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + // as new-rabbit already exists in cluster, infra operator will create transporturl for it + // simulating same + infra.SimulateTransportURLReady(notificationsBus.TransportURLName) + transportURLName := infra.GetTransportURL(notificationsBus.TransportURLName) + Expect(transportURLName.Spec.RabbitmqClusterName).To(Equal(notificationsBus.BusName)) + + th.ExpectCondition( + novaNames.NovaName, + ConditionGetterFunc(NovaConditionGetter), + novav1.NovaNotificationMQReadyCondition, + corev1.ConditionTrue, + ) + + // the nova secret(i.e top level secret) should have notification_transport_url value set + internalTopLevelSecret := th.GetSecret(novaNames.InternalTopLevelSecretName) + Expect(internalTopLevelSecret.Data).To(HaveKey("notification_transport_url")) + Expect(internalTopLevelSecret.Data["notification_transport_url"]).ShouldNot(BeEmpty()) + + // assert in nova-api conf + configDataMap := th.GetSecret(novaNames.APIConfigDataName) + Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData := string(configDataMap.Data["01-nova.conf"]) + AssertHaveNotificationTransportURL(notificationsBus.TransportURLName.Name, configData) + + // assert in sch conf + configDataMap = th.GetSecret(novaNames.SchedulerConfigDataName) + Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData = string(configDataMap.Data["01-nova.conf"]) + AssertHaveNotificationTransportURL(notificationsBus.TransportURLName.Name, configData) + + // assert in cell0-conductor conf + configDataMap = th.GetSecret(cell0.ConductorConfigDataName) + Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData = string(configDataMap.Data["01-nova.conf"]) + AssertHaveNotificationTransportURL(notificationsBus.TransportURLName.Name, configData) + + }) + }) + +}) + var _ = Describe("Nova controller", func() { When("Nova CR instance is created without a proper secret", func() { BeforeEach(func() { @@ -265,6 +384,8 @@ var _ = Describe("Nova controller", func() { 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(cell.Spec.Secret).To(Equal(cell0.InternalCellSecretName.Name)) Expect(conductor.Spec.Secret).To(Equal(cell0.InternalCellSecretName.Name)) @@ -381,6 +502,8 @@ var _ = Describe("Nova controller", func() { HaveKeyWithValue(controllers.MetadataSecretSelector, []byte("metadata-secret"))) Expect(internalTopLevelSecret.Data).To( HaveKeyWithValue("transport_url", []byte("rabbit://cell0/fake"))) + Expect(internalTopLevelSecret.Data).To( + HaveKeyWithValue("notification_transport_url", []byte(""))) }) It("creates NovaAPI", func() { diff --git a/test/functional/nova_scheduler_test.go b/test/functional/nova_scheduler_test.go index 27b3de445..97834bb16 100644 --- a/test/functional/nova_scheduler_test.go +++ b/test/functional/nova_scheduler_test.go @@ -163,6 +163,44 @@ var _ = Describe("NovaScheduler controller", func() { }) }) + When("the Secret is created but notification fields is missing", func() { + BeforeEach(func() { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: novaNames.InternalTopLevelSecretName.Name, + Namespace: novaNames.InternalTopLevelSecretName.Namespace, + }, + Data: map[string][]byte{ + "ServicePassword": []byte("12345678"), + "transport_url": []byte("rabbit://api/fake"), + // notification_transport_url is missing + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, secret) + }) + + It("is not Ready", func() { + th.ExpectCondition( + novaNames.SchedulerName, + ConditionGetterFunc(NovaSchedulerConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reports that the inputs are not ready", func() { + th.ExpectConditionWithDetails( + novaNames.SchedulerName, + ConditionGetterFunc(NovaSchedulerConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("Input data error occurred field not found in Secret: 'notification_transport_url' not found in secret/%s", novaNames.InternalTopLevelSecretName.Name), + ) + }) + }) + When("the Secret is created with all the expected fields", func() { BeforeEach(func() { DeferCleanup( @@ -192,6 +230,8 @@ var _ = Describe("NovaScheduler controller", func() { Expect(configDataMap).ShouldNot(BeNil()) Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) configData := string(configDataMap.Data["01-nova.conf"]) + AssertHaveNotificationTransportURL("notifications", configData) + Expect(configData).To(ContainSubstring("transport_url=rabbit://api/fake")) Expect(configData).To(ContainSubstring("password = service-password")) memcacheInstance := infra.GetMemcached(novaNames.MemcachedNamespace) diff --git a/test/functional/novaapi_controller_test.go b/test/functional/novaapi_controller_test.go index 5d1f93970..428fc97a0 100644 --- a/test/functional/novaapi_controller_test.go +++ b/test/functional/novaapi_controller_test.go @@ -162,6 +162,44 @@ var _ = Describe("NovaAPI controller", func() { }) }) + When("the Secret is created but notification_transport_url field is missing", func() { + BeforeEach(func() { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: novaNames.InternalTopLevelSecretName.Name, + Namespace: novaNames.InternalTopLevelSecretName.Namespace, + }, + Data: map[string][]byte{ + "ServicePassword": []byte("12345678"), + "transport_url": []byte("rabbit://api/fake"), + // notification_transport_url is missing + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, secret) + }) + + It("is not Ready", func() { + th.ExpectCondition( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reports that the inputs are not ready", func() { + th.ExpectConditionWithDetails( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("Input data error occurred field not found in Secret: 'notification_transport_url' not found in secret/%s", novaNames.InternalTopLevelSecretName.Name), + ) + }) + }) + When("the Secret is created with all the expected fields", func() { BeforeEach(func() { DeferCleanup( @@ -192,6 +230,8 @@ var _ = Describe("NovaAPI controller", func() { Expect(configDataMap).ShouldNot(BeNil()) Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) configData := string(configDataMap.Data["01-nova.conf"]) + AssertHaveNotificationTransportURL("notifications", configData) + Expect(configData).Should(ContainSubstring("transport_url=rabbit://api/fake")) // as of I3629b84d3255a8fe9d8a7cea8c6131d7c40899e8 nova now requires // service_user configuration to work to address Bug: #2004555 diff --git a/test/functional/novacell_controller_test.go b/test/functional/novacell_controller_test.go index 1a52b3db0..0e1a18868 100644 --- a/test/functional/novacell_controller_test.go +++ b/test/functional/novacell_controller_test.go @@ -307,6 +307,8 @@ var _ = Describe("NovaCell controller", func() { Expect(computeConfigData).ShouldNot(BeNil()) Expect(computeConfigData.Data).Should(HaveKey("01-nova.conf")) configData := string(computeConfigData.Data["01-nova.conf"]) + + AssertHaveNotificationTransportURL("notifications", configData) // ensure we maintain the tripleo default for backwards compatibility Expect(configData).Should(ContainSubstring("dhcp_domain = ''")) Expect(configData).To(ContainSubstring("transport_url=rabbit://cell1/fake")) diff --git a/test/functional/novaconductor_controller_test.go b/test/functional/novaconductor_controller_test.go index bc8d3c49b..56688ad9b 100644 --- a/test/functional/novaconductor_controller_test.go +++ b/test/functional/novaconductor_controller_test.go @@ -162,6 +162,44 @@ var _ = Describe("NovaConductor controller", func() { }) }) + When("the Secret is created but notification fields is missing", func() { + BeforeEach(func() { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cell0.InternalCellSecretName.Name, + Namespace: cell0.InternalCellSecretName.Namespace, + }, + Data: map[string][]byte{ + "ServicePassword": []byte("12345678"), + "transport_url": []byte("rabbit://cell0/fake"), + // notification_transport_url is missing + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, secret) + }) + + It("is not Ready", func() { + th.ExpectCondition( + cell0.ConductorName, + ConditionGetterFunc(NovaConductorConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reports that the inputs are not ready", func() { + th.ExpectConditionWithDetails( + cell0.ConductorName, + ConditionGetterFunc(NovaConductorConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("Input data error occurred field not found in Secret: 'notification_transport_url' not found in secret/%s", cell0.InternalCellSecretName.Name), + ) + }) + }) + When("the Secret is created with all the expected fields", func() { BeforeEach(func() { DeferCleanup( @@ -196,6 +234,8 @@ var _ = Describe("NovaConductor controller", func() { Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) configData := string(configDataMap.Data["01-nova.conf"]) + AssertHaveNotificationTransportURL("notifications", configData) + Expect(configData).Should(ContainSubstring("password = service-password")) Expect(configData).Should(ContainSubstring("transport_url=rabbit://cell0/fake")) Expect(configData).Should(