diff --git a/api/bases/nova.openstack.org_novaapis.yaml b/api/bases/nova.openstack.org_novaapis.yaml index 0458b8ab5..316192e15 100644 --- a/api/bases/nova.openstack.org_novaapis.yaml +++ b/api/bases/nova.openstack.org_novaapis.yaml @@ -277,6 +277,10 @@ spec: The key must be the endpoint type (public, internal) type: object type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string registeredCells: additionalProperties: type: string diff --git a/api/bases/nova.openstack.org_novacells.yaml b/api/bases/nova.openstack.org_novacells.yaml index 23416db14..34e11b24f 100644 --- a/api/bases/nova.openstack.org_novacells.yaml +++ b/api/bases/nova.openstack.org_novacells.yaml @@ -967,6 +967,10 @@ spec: description: PreserveJobs - do not delete jobs after they finished e.g. to check logs type: boolean + region: + description: Region - the region name to use for service endpoint + discovery + type: string secret: description: |- Secret is the name of the Secret instance containing password diff --git a/api/bases/nova.openstack.org_novacomputes.yaml b/api/bases/nova.openstack.org_novacomputes.yaml index 032c4a773..e86374a50 100644 --- a/api/bases/nova.openstack.org_novacomputes.yaml +++ b/api/bases/nova.openstack.org_novacomputes.yaml @@ -96,6 +96,10 @@ spec: description: NodeSelector to target subset of worker nodes running this service type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string replicas: default: 1 description: Replicas of the service to run diff --git a/api/bases/nova.openstack.org_novaconductors.yaml b/api/bases/nova.openstack.org_novaconductors.yaml index b03650220..20316a2aa 100644 --- a/api/bases/nova.openstack.org_novaconductors.yaml +++ b/api/bases/nova.openstack.org_novaconductors.yaml @@ -138,6 +138,10 @@ spec: description: PreserveJobs - do not delete jobs after they finished e.g. to check logs type: boolean + region: + description: Region - the region name to use for service endpoint + discovery + type: string replicas: default: 1 description: Replicas of the service to run diff --git a/api/bases/nova.openstack.org_novametadata.yaml b/api/bases/nova.openstack.org_novametadata.yaml index 93ea4965d..6ef06aaf3 100644 --- a/api/bases/nova.openstack.org_novametadata.yaml +++ b/api/bases/nova.openstack.org_novametadata.yaml @@ -272,6 +272,10 @@ spec: type: object type: object type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string registeredCells: additionalProperties: type: string diff --git a/api/bases/nova.openstack.org_novanovncproxies.yaml b/api/bases/nova.openstack.org_novanovncproxies.yaml index 4efc5dcac..a3bbdad47 100644 --- a/api/bases/nova.openstack.org_novanovncproxies.yaml +++ b/api/bases/nova.openstack.org_novanovncproxies.yaml @@ -249,6 +249,10 @@ spec: type: object type: object type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string replicas: default: 1 description: Replicas of the service to run diff --git a/api/bases/nova.openstack.org_novaschedulers.yaml b/api/bases/nova.openstack.org_novaschedulers.yaml index e8af3bc06..87fa75150 100644 --- a/api/bases/nova.openstack.org_novaschedulers.yaml +++ b/api/bases/nova.openstack.org_novaschedulers.yaml @@ -101,6 +101,10 @@ spec: description: NodeSelector to target subset of worker nodes running this service type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string registeredCells: additionalProperties: type: string diff --git a/api/v1beta1/novaapi_types.go b/api/v1beta1/novaapi_types.go index e2d2e7c55..7fed5c796 100644 --- a/api/v1beta1/novaapi_types.go +++ b/api/v1beta1/novaapi_types.go @@ -123,6 +123,10 @@ type NovaAPISpec struct { // to redirect unauthenticated users. KeystonePublicAuthURL string `json:"keystonePublicAuthURL"` + // +kubebuilder:validation:Optional + // Region - the region name to use for service endpoint discovery + Region string `json:"region,omitempty"` + // +kubebuilder:validation:Optional // +kubebuilder:default="nova-api" // APIDatabaseAccount - MariaDBAccount to use when accessing the API DB diff --git a/api/v1beta1/novacell_types.go b/api/v1beta1/novacell_types.go index 091e60e00..49c657a5b 100644 --- a/api/v1beta1/novacell_types.go +++ b/api/v1beta1/novacell_types.go @@ -141,6 +141,10 @@ type NovaCellSpec struct { // to keystone KeystoneAuthURL string `json:"keystoneAuthURL"` + // +kubebuilder:validation:Optional + // Region - the region name to use for service endpoint discovery + Region string `json:"region,omitempty"` + // +kubebuilder:validation:Optional // +kubebuilder:default=nova // APIDatabaseAccount - MariaDBAccount to use when accessing the API DB diff --git a/api/v1beta1/novacompute_types.go b/api/v1beta1/novacompute_types.go index 35dca21bd..493541448 100644 --- a/api/v1beta1/novacompute_types.go +++ b/api/v1beta1/novacompute_types.go @@ -101,6 +101,10 @@ type NovaComputeSpec struct { // +kubebuilder:validation:Required KeystoneAuthURL string `json:"keystoneAuthURL"` + // +kubebuilder:validation:Optional + // Region - the region name to use for service endpoint discovery + Region string `json:"region,omitempty"` + // NovaServiceBase specifies the generic fields of the service NovaServiceBase `json:",inline"` diff --git a/api/v1beta1/novaconductor_types.go b/api/v1beta1/novaconductor_types.go index 9135b7ab8..6e67e1a45 100644 --- a/api/v1beta1/novaconductor_types.go +++ b/api/v1beta1/novaconductor_types.go @@ -90,6 +90,10 @@ type NovaConductorSpec struct { // talk to keystone KeystoneAuthURL string `json:"keystoneAuthURL"` + // +kubebuilder:validation:Optional + // Region - the region name to use for service endpoint discovery + Region string `json:"region,omitempty"` + // +kubebuilder:validation:Optional // +kubebuilder:default=nova // APIDatabaseAccount - MariaDBAccount to use when accessing the API DB @@ -258,6 +262,11 @@ func (n NovaConductor) GetCABundleSecretName() string { return n.Spec.TLS.CaBundleSecretName } +// GetRegion returns the Region from the Spec +func (n NovaConductor) GetRegion() string { + return n.Spec.Region +} + // GetSpecTopologyRef - Returns the LastAppliedTopology Set in the Status func (n *NovaConductor) GetSpecTopologyRef() *topologyv1.TopoRef { return n.Spec.TopologyRef diff --git a/api/v1beta1/novametadata_types.go b/api/v1beta1/novametadata_types.go index 1594e9043..f8eec48e8 100644 --- a/api/v1beta1/novametadata_types.go +++ b/api/v1beta1/novametadata_types.go @@ -134,6 +134,10 @@ type NovaMetadataSpec struct { // TODO(ksambor) Add checking if dynamic vendor data is configured KeystoneAuthURL string `json:"keystoneAuthURL"` + // +kubebuilder:validation:Optional + // Region - the region name to use for service endpoint discovery + Region string `json:"region,omitempty"` + // +kubebuilder:validation:Optional // +kubebuilder:default="nova-api" // APIDatabaseAccount - MariaDBAccount to use when accessing the API DB diff --git a/api/v1beta1/novanovncproxy_types.go b/api/v1beta1/novanovncproxy_types.go index b9e6c7ef0..521e354ae 100644 --- a/api/v1beta1/novanovncproxy_types.go +++ b/api/v1beta1/novanovncproxy_types.go @@ -138,6 +138,10 @@ type NovaNoVNCProxySpec struct { // talk to keystone KeystoneAuthURL string `json:"keystoneAuthURL"` + // +kubebuilder:validation:Optional + // Region - the region name to use for service endpoint discovery + Region string `json:"region,omitempty"` + // +kubebuilder:validation:Optional // +kubebuilder:default=nova // CellDatabaseAccount - MariaDBAccount to use when accessing the cell DB diff --git a/api/v1beta1/novascheduler_types.go b/api/v1beta1/novascheduler_types.go index d12971b00..57a2a176e 100644 --- a/api/v1beta1/novascheduler_types.go +++ b/api/v1beta1/novascheduler_types.go @@ -86,6 +86,10 @@ type NovaSchedulerSpec struct { // talk to keystone KeystoneAuthURL string `json:"keystoneAuthURL"` + // +kubebuilder:validation:Optional + // Region - the region name to use for service endpoint discovery + Region string `json:"region,omitempty"` + // +kubebuilder:validation:Optional // +kubebuilder:default=nova-api // APIDatabaseAccount - MariaDBAccount to use when accessing the API DB @@ -208,6 +212,11 @@ func (n NovaScheduler) GetCABundleSecretName() string { return n.Spec.TLS.CaBundleSecretName } +// GetRegion returns the Region from the Spec +func (n NovaScheduler) GetRegion() string { + return n.Spec.Region +} + // GetSpecTopologyRef - Returns the LastAppliedTopology Set in the Status func (n *NovaScheduler) GetSpecTopologyRef() *topologyv1.TopoRef { return n.Spec.TopologyRef diff --git a/config/crd/bases/nova.openstack.org_novaapis.yaml b/config/crd/bases/nova.openstack.org_novaapis.yaml index 0458b8ab5..316192e15 100644 --- a/config/crd/bases/nova.openstack.org_novaapis.yaml +++ b/config/crd/bases/nova.openstack.org_novaapis.yaml @@ -277,6 +277,10 @@ spec: The key must be the endpoint type (public, internal) type: object type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string registeredCells: additionalProperties: type: string diff --git a/config/crd/bases/nova.openstack.org_novacells.yaml b/config/crd/bases/nova.openstack.org_novacells.yaml index 23416db14..34e11b24f 100644 --- a/config/crd/bases/nova.openstack.org_novacells.yaml +++ b/config/crd/bases/nova.openstack.org_novacells.yaml @@ -967,6 +967,10 @@ spec: description: PreserveJobs - do not delete jobs after they finished e.g. to check logs type: boolean + region: + description: Region - the region name to use for service endpoint + discovery + type: string secret: description: |- Secret is the name of the Secret instance containing password diff --git a/config/crd/bases/nova.openstack.org_novacomputes.yaml b/config/crd/bases/nova.openstack.org_novacomputes.yaml index 032c4a773..e86374a50 100644 --- a/config/crd/bases/nova.openstack.org_novacomputes.yaml +++ b/config/crd/bases/nova.openstack.org_novacomputes.yaml @@ -96,6 +96,10 @@ spec: description: NodeSelector to target subset of worker nodes running this service type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string replicas: default: 1 description: Replicas of the service to run diff --git a/config/crd/bases/nova.openstack.org_novaconductors.yaml b/config/crd/bases/nova.openstack.org_novaconductors.yaml index b03650220..20316a2aa 100644 --- a/config/crd/bases/nova.openstack.org_novaconductors.yaml +++ b/config/crd/bases/nova.openstack.org_novaconductors.yaml @@ -138,6 +138,10 @@ spec: description: PreserveJobs - do not delete jobs after they finished e.g. to check logs type: boolean + region: + description: Region - the region name to use for service endpoint + discovery + type: string replicas: default: 1 description: Replicas of the service to run diff --git a/config/crd/bases/nova.openstack.org_novametadata.yaml b/config/crd/bases/nova.openstack.org_novametadata.yaml index 93ea4965d..6ef06aaf3 100644 --- a/config/crd/bases/nova.openstack.org_novametadata.yaml +++ b/config/crd/bases/nova.openstack.org_novametadata.yaml @@ -272,6 +272,10 @@ spec: type: object type: object type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string registeredCells: additionalProperties: type: string diff --git a/config/crd/bases/nova.openstack.org_novanovncproxies.yaml b/config/crd/bases/nova.openstack.org_novanovncproxies.yaml index 4efc5dcac..a3bbdad47 100644 --- a/config/crd/bases/nova.openstack.org_novanovncproxies.yaml +++ b/config/crd/bases/nova.openstack.org_novanovncproxies.yaml @@ -249,6 +249,10 @@ spec: type: object type: object type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string replicas: default: 1 description: Replicas of the service to run diff --git a/config/crd/bases/nova.openstack.org_novaschedulers.yaml b/config/crd/bases/nova.openstack.org_novaschedulers.yaml index e8af3bc06..87fa75150 100644 --- a/config/crd/bases/nova.openstack.org_novaschedulers.yaml +++ b/config/crd/bases/nova.openstack.org_novaschedulers.yaml @@ -101,6 +101,10 @@ spec: description: NodeSelector to target subset of worker nodes running this service type: object + region: + description: Region - the region name to use for service endpoint + discovery + type: string registeredCells: additionalProperties: type: string diff --git a/go.mod b/go.mod index 07230445e..ebcf858bb 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/openstack-k8s-operators/nova-operator/api v0.0.0-20221209164002-f9e6b9363961 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 + gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.31.13 k8s.io/apimachinery v0.31.13 diff --git a/go.sum b/go.sum index d4adef9ee..69f3f4016 100644 --- a/go.sum +++ b/go.sum @@ -272,6 +272,8 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/controller/common.go b/internal/controller/common.go index ec23010aa..6ed118cb0 100644 --- a/internal/controller/common.go +++ b/internal/controller/common.go @@ -638,6 +638,7 @@ type clientAuth interface { GetKeystoneAuthURL() string GetKeystoneUser() string GetCABundleSecretName() string + GetRegion() string } func getNovaClient( @@ -686,9 +687,9 @@ func getNovaClient( AuthURL: authURL, Username: auth.GetKeystoneUser(), Password: password, - DomainName: "Default", // fixme", - Region: "regionOne", // fixme", - TenantName: "service", // fixme", + DomainName: "Default", // fixme", + Region: auth.GetRegion(), + TenantName: "service", // fixme", TLS: tlsConfig, } endpointOpts := gophercloud.EndpointOpts{ diff --git a/internal/controller/nova_controller.go b/internal/controller/nova_controller.go index f59d01c6e..7ee076aa5 100644 --- a/internal/controller/nova_controller.go +++ b/internal/controller/nova_controller.go @@ -273,7 +273,7 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul return ctrl.Result{}, nil } - keystoneInternalAuthURL, keystonePublicAuthURL, err := r.getKeystoneAuthURL( + keystoneInternalAuthURL, keystonePublicAuthURL, region, err := r.getKeystoneAuthURL( ctx, h, instance) if err != nil { return ctrl.Result{}, err @@ -545,7 +545,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, cellMQ.QuorumQueues, notificationTransportURL, - keystoneInternalAuthURL, secret, + keystoneInternalAuthURL, region, secret, ) cells[cellName] = cell switch status { @@ -621,7 +621,7 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul result, err = r.ensureAPI( ctx, instance, cell0Template, cellDBs[novav1.Cell0Name].Database, apiDB, - keystoneInternalAuthURL, keystonePublicAuthURL, + keystoneInternalAuthURL, keystonePublicAuthURL, region, topLevelSecretName, ) if err != nil { @@ -630,7 +630,7 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul result, err = r.ensureScheduler( ctx, instance, cell0Template, - cellDBs[novav1.Cell0Name].Database, apiDB, keystoneInternalAuthURL, + cellDBs[novav1.Cell0Name].Database, apiDB, keystoneInternalAuthURL, region, topLevelSecretName, ) if err != nil { @@ -640,7 +640,7 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul if *instance.Spec.MetadataServiceTemplate.Enabled { result, err = r.ensureMetadata( ctx, instance, cell0Template, - cellDBs[novav1.Cell0Name].Database, apiDB, keystoneInternalAuthURL, + cellDBs[novav1.Cell0Name].Database, apiDB, keystoneInternalAuthURL, region, topLevelSecretName, ) if err != nil { @@ -1053,6 +1053,11 @@ func (r *NovaReconciler) ensureNovaManageJobSecret( return nil, "", "", err } + keystoneAPI, err := keystonev1.GetKeystoneAPI(ctx, h, instance.Namespace, map[string]string{}) + if err != nil { + return nil, "", "", err + } + // We configure the Job like it runs in the env of the conductor of the given cell // but we ensure that the config always has [api_database] section configure // even if the cell has no API access at all. @@ -1073,9 +1078,9 @@ func (r *NovaReconciler) ensureNovaManageJobSecret( "cell_db_password": string(cellDbSecret.Data[mariadbv1.DatabasePasswordSelector]), "cell_db_address": cell.Spec.CellDatabaseHostname, "cell_db_port": 3306, - "openstack_region_name": "regionOne", // fixme - "default_project_domain": "Default", // fixme - "default_user_domain": "Default", // fixme + "openstack_region_name": keystoneAPI.GetRegion(), + "default_project_domain": "Default", // fixme + "default_user_domain": "Default", // fixme } // NOTE(gibi): cell mapping for cell0 should not have transport_url @@ -1202,6 +1207,7 @@ func (r *NovaReconciler) ensureCell( cellQuorumQueues bool, notificationTransportURL string, keystoneAuthURL string, + region string, secret corev1.Secret, ) (*novav1.NovaCell, nova.CellDeploymentStatus, error) { Log := r.GetLogger(ctx) @@ -1228,6 +1234,7 @@ func (r *NovaReconciler) ensureCell( // TODO(gibi): this should be part of the secret ServiceUser: instance.Spec.ServiceUser, KeystoneAuthURL: keystoneAuthURL, + Region: region, ServiceAccount: instance.RbacResourceName(), APITimeout: instance.Spec.APITimeout, // The assumption is that the CA bundle for ironic compute in the cell @@ -1377,6 +1384,7 @@ func (r *NovaReconciler) ensureAPI( apiDB *mariadbv1.Database, keystoneInternalAuthURL string, keystonePublicAuthURL string, + region string, secretName string, ) (ctrl.Result, error) { Log := r.GetLogger(ctx) @@ -1401,6 +1409,7 @@ func (r *NovaReconciler) ensureAPI( Override: instance.Spec.APIServiceTemplate.Override, KeystoneAuthURL: keystoneInternalAuthURL, KeystonePublicAuthURL: keystonePublicAuthURL, + Region: region, ServiceUser: instance.Spec.ServiceUser, ServiceAccount: instance.RbacResourceName(), RegisteredCells: instance.Status.RegisteredCells, @@ -1467,6 +1476,7 @@ func (r *NovaReconciler) ensureScheduler( cell0DB *mariadbv1.Database, apiDB *mariadbv1.Database, keystoneAuthURL string, + region string, secretName string, ) (ctrl.Result, error) { Log := r.GetLogger(ctx) @@ -1492,6 +1502,7 @@ func (r *NovaReconciler) ensureScheduler( TopologyRef: instance.Spec.SchedulerServiceTemplate.TopologyRef, }, KeystoneAuthURL: keystoneAuthURL, + Region: region, ServiceUser: instance.Spec.ServiceUser, ServiceAccount: instance.RbacResourceName(), RegisteredCells: instance.Status.RegisteredCells, @@ -1652,29 +1663,31 @@ func (r *NovaReconciler) getKeystoneAuthURL( ctx context.Context, h *helper.Helper, instance *novav1.Nova, -) (string, string, error) { +) (string, string, string, error) { // TODO(gibi): change lib-common to take the name of the KeystoneAPI as // parameter instead of labels. Then use instance.Spec.KeystoneInstance as // the name. keystoneAPI, err := keystonev1.GetKeystoneAPI(ctx, h, instance.Namespace, map[string]string{}) if err != nil { - return "", "", err + return "", "", "", err } // NOTE(gibi): we use the internal endpoint as that is expected to be // available on the external compute nodes as well and we want to keep // thing consistent internalAuthURL, err := keystoneAPI.GetEndpoint(endpoint.EndpointInternal) if err != nil { - return "", "", err + return "", "", "", err } // NOTE(gibi): but there is one case the www_authenticate_uri of nova-api // the we need to configure the public keystone endpoint publicAuthURL, err := keystoneAPI.GetEndpoint(endpoint.EndpointInternal) if err != nil { - return "", "", err + return "", "", "", err } - return internalAuthURL, publicAuthURL, nil + region := keystoneAPI.GetRegion() + + return internalAuthURL, publicAuthURL, region, nil } func (r *NovaReconciler) reconcileDelete( @@ -1815,6 +1828,7 @@ func (r *NovaReconciler) ensureMetadata( cell0DB *mariadbv1.Database, apiDB *mariadbv1.Database, keystoneAuthURL string, + region string, secretName string, ) (ctrl.Result, error) { Log := r.GetLogger(ctx) @@ -1875,6 +1889,7 @@ func (r *NovaReconciler) ensureMetadata( Override: instance.Spec.MetadataServiceTemplate.Override, ServiceUser: instance.Spec.ServiceUser, KeystoneAuthURL: keystoneAuthURL, + Region: region, ServiceAccount: instance.RbacResourceName(), RegisteredCells: instance.Status.RegisteredCells, TLS: instance.Spec.MetadataServiceTemplate.TLS, diff --git a/internal/controller/novaapi_controller.go b/internal/controller/novaapi_controller.go index f49dea5f5..8536cb4ec 100644 --- a/internal/controller/novaapi_controller.go +++ b/internal/controller/novaapi_controller.go @@ -508,9 +508,9 @@ func (r *NovaAPIReconciler) generateConfigs( "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 + "openstack_region_name": instance.Spec.Region, + "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", diff --git a/internal/controller/novacell_controller.go b/internal/controller/novacell_controller.go index 1a2465fb2..4f0a63f6d 100644 --- a/internal/controller/novacell_controller.go +++ b/internal/controller/novacell_controller.go @@ -774,9 +774,9 @@ func (r *NovaCellReconciler) generateComputeConfigs( "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 + "openstack_region_name": instance.Spec.Region, + "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]), @@ -809,7 +809,7 @@ func (r *NovaCellReconciler) generateComputeConfigs( a := &corev1.EnvVar{} hashes[configName](a) instance.Status.Hash[configName] = a.Value - return err + return nil } func (r *NovaCellReconciler) getVNCProxyURL( diff --git a/internal/controller/novacompute_controller.go b/internal/controller/novacompute_controller.go index b25dde963..9548cfb7d 100644 --- a/internal/controller/novacompute_controller.go +++ b/internal/controller/novacompute_controller.go @@ -365,9 +365,9 @@ func (r *NovaComputeReconciler) generateConfigs( "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 + "openstack_region_name": instance.Spec.Region, + "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, diff --git a/internal/controller/novaconductor_controller.go b/internal/controller/novaconductor_controller.go index bf48fab2a..e42a8dc69 100644 --- a/internal/controller/novaconductor_controller.go +++ b/internal/controller/novaconductor_controller.go @@ -462,9 +462,9 @@ func (r *NovaConductorReconciler) generateConfigs( "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 + "openstack_region_name": instance.Spec.Region, + "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(), diff --git a/internal/controller/novametadata_controller.go b/internal/controller/novametadata_controller.go index 4aa1d4da5..fb98a03a5 100644 --- a/internal/controller/novametadata_controller.go +++ b/internal/controller/novametadata_controller.go @@ -499,9 +499,9 @@ func (r *NovaMetadataReconciler) generateConfigs( "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 + "openstack_region_name": instance.Spec.Region, + "default_project_domain": "Default", // fixme + "default_user_domain": "Default", // fixme "metadata_secret": string(secret.Data[MetadataSecretSelector]), "log_file": "/var/log/nova/nova-metadata.log", "transport_url": string(secret.Data[TransportURLSelector]), diff --git a/internal/controller/novanovncproxy_controller.go b/internal/controller/novanovncproxy_controller.go index 15ad06d44..86b6a78c7 100644 --- a/internal/controller/novanovncproxy_controller.go +++ b/internal/controller/novanovncproxy_controller.go @@ -483,9 +483,9 @@ func (r *NovaNoVNCProxyReconciler) generateConfigs( "cell_db_address": instance.Spec.CellDatabaseHostname, "cell_db_port": 3306, "transport_url": string(secret.Data[TransportURLSelector]), - "openstack_region_name": "regionOne", // fixme - "default_project_domain": "Default", // fixme - "default_user_domain": "Default", // fixme + "openstack_region_name": instance.Spec.Region, + "default_project_domain": "Default", // fixme + "default_user_domain": "Default", // fixme "MemcachedServers": memcachedInstance.GetMemcachedServerListString(), "MemcachedServersWithInet": memcachedInstance.GetMemcachedServerListWithInetString(), "MemcachedTLS": memcachedInstance.GetMemcachedTLSSupport(), diff --git a/internal/controller/novascheduler_controller.go b/internal/controller/novascheduler_controller.go index 3b8c7f5ba..d9b9cf83d 100644 --- a/internal/controller/novascheduler_controller.go +++ b/internal/controller/novascheduler_controller.go @@ -593,9 +593,9 @@ func (r *NovaSchedulerReconciler) generateConfigs( "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 + "openstack_region_name": instance.Spec.Region, + "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(), diff --git a/templates/nova.conf b/templates/nova.conf index 782f187b5..b9e634264 100644 --- a/templates/nova.conf +++ b/templates/nova.conf @@ -264,7 +264,9 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} +{{ if (index . "openstack_region_name") -}} region_name = {{ .openstack_region_name }} +{{ end -}} # This is part of hardening related to CVE-2023-2088 # https://docs.openstack.org/nova/latest/configuration/config.html#keystone_authtoken.service_token_roles_required # when enabled the service token user must have the service role to be considered valid. @@ -284,7 +286,9 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} +{{ if (index . "openstack_region_name") -}} region_name = {{ .openstack_region_name }} +{{ end -}} valid_interfaces = internal [glance] @@ -295,7 +299,9 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} +{{ if (index . "openstack_region_name") -}} region_name = {{ .openstack_region_name }} +{{ end -}} valid_interfaces = internal {{if (index . "debug") }}debug=true{{end}} @@ -307,7 +313,9 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} +{{ if (index . "openstack_region_name") -}} region_name = {{ .openstack_region_name }} +{{ end -}} valid_interfaces = internal {{if eq .service_name "nova-metadata"}} metadata_proxy_shared_secret = {{ .metadata_secret }} @@ -322,7 +330,9 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -region_name = {{ .openstack_region_name }} +{{ if (index . "openstack_region_name") -}} +os_region_name = {{ .openstack_region_name }} +{{ end -}} catalog_info = volumev3:cinderv3:internalURL [barbican] @@ -333,7 +343,9 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -region_name = {{ .openstack_region_name }} +{{ if (index . "openstack_region_name") -}} +barbican_region_name = {{ .openstack_region_name }} +{{ end -}} barbican_endpoint_type = internal [service_user] @@ -350,7 +362,9 @@ password = {{ .nova_keystone_password }} system_scope = all endpoint_interface = internal endpoint_service_type = compute +{{ if (index . "openstack_region_name") -}} endpoint_region_name = {{ .openstack_region_name }} +{{ end -}} auth_url = {{ .keystone_internal_url }} auth_type = password user_domain_name = {{ .default_user_domain}} @@ -367,6 +381,9 @@ username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} project_domain_name = {{ .default_project_domain }} user_domain_name = {{ .default_user_domain}} +{{ if (index . "openstack_region_name") -}} +region_name = {{ .openstack_region_name }} +{{ end -}} {{ end }} {{ end }} diff --git a/test/functional/nova_scheduler_test.go b/test/functional/nova_scheduler_test.go index 3d3ff1227..24f647792 100644 --- a/test/functional/nova_scheduler_test.go +++ b/test/functional/nova_scheduler_test.go @@ -20,6 +20,7 @@ import ( . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports . "github.com/onsi/gomega" //revive:disable:dot-imports "github.com/onsi/gomega/format" + "gopkg.in/ini.v1" //revive:disable-next-line:dot-imports . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" @@ -266,6 +267,71 @@ var _ = Describe("NovaScheduler controller", func() { }) + It("includes region_name in config when region is set in spec", func() { + const testRegion = "regionTwo" + // Update NovaScheduler spec to include region + Eventually(func(g Gomega) { + scheduler := GetNovaScheduler(novaNames.SchedulerName) + scheduler.Spec.Region = testRegion + g.Expect(k8sClient.Update(ctx, scheduler)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + // Trigger reconciliation + th.ExpectCondition( + novaNames.SchedulerName, + ConditionGetterFunc(NovaSchedulerConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + + // Wait for config to be regenerated with region + Eventually(func(g Gomega) { + configDataMap := th.GetSecret(novaNames.SchedulerConfigDataName) + g.Expect(configDataMap).ShouldNot(BeNil()) + g.Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData := string(configDataMap.Data["01-nova.conf"]) + + // Parse the INI file to properly access sections + cfg, err := ini.Load([]byte(configData)) + g.Expect(err).ShouldNot(HaveOccurred(), "Should be able to parse config as INI") + + // Verify region_name in [keystone_authtoken] + section := cfg.Section("keystone_authtoken") + g.Expect(section).ShouldNot(BeNil(), "Should find [keystone_authtoken] section") + g.Expect(section.Key("region_name").String()).Should(Equal(testRegion)) + + // Verify region_name in [placement] + section = cfg.Section("placement") + g.Expect(section).ShouldNot(BeNil(), "Should find [placement] section") + g.Expect(section.Key("region_name").String()).Should(Equal(testRegion)) + + // Verify region_name in [glance] + section = cfg.Section("glance") + g.Expect(section).ShouldNot(BeNil(), "Should find [glance] section") + g.Expect(section.Key("region_name").String()).Should(Equal(testRegion)) + + // Verify region_name in [neutron] + section = cfg.Section("neutron") + g.Expect(section).ShouldNot(BeNil(), "Should find [neutron] section") + g.Expect(section.Key("region_name").String()).Should(Equal(testRegion)) + + // Verify os_region_name in [cinder] + section = cfg.Section("cinder") + g.Expect(section).ShouldNot(BeNil(), "Should find [cinder] section") + g.Expect(section.Key("os_region_name").String()).Should(Equal(testRegion)) + + // Verify barbican_region_name in [barbican] + section = cfg.Section("barbican") + g.Expect(section).ShouldNot(BeNil(), "Should find [barbican] section") + g.Expect(section.Key("barbican_region_name").String()).Should(Equal(testRegion)) + + // Verify endpoint_region_name in [oslo_limit] + section = cfg.Section("oslo_limit") + g.Expect(section).ShouldNot(BeNil(), "Should find [oslo_limit] section") + g.Expect(section.Key("endpoint_region_name").String()).Should(Equal(testRegion)) + }, timeout, interval).Should(Succeed()) + }) + It("stored the input hash in the Status", func() { Eventually(func(g Gomega) { novaScheduler := GetNovaScheduler(novaNames.SchedulerName) diff --git a/test/functional/novaapi_controller_test.go b/test/functional/novaapi_controller_test.go index 36aa2a47e..648cccd75 100644 --- a/test/functional/novaapi_controller_test.go +++ b/test/functional/novaapi_controller_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports . "github.com/onsi/gomega" //revive:disable:dot-imports "github.com/onsi/gomega/format" + "gopkg.in/ini.v1" //revive:disable-next-line:dot-imports . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" @@ -288,6 +289,71 @@ endpoint_service_type = compute`)) ContainSubstring("[client]\nssl=0")) }) + It("includes region_name in config when region is set in spec", func() { + const testRegion = "regionTwo" + // Update NovaAPI spec to include region + Eventually(func(g Gomega) { + api := GetNovaAPI(novaNames.APIName) + api.Spec.Region = testRegion + g.Expect(k8sClient.Update(ctx, api)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + // Trigger reconciliation + th.ExpectCondition( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + + // Wait for config to be regenerated with region + Eventually(func(g Gomega) { + configDataMap := th.GetSecret(novaNames.APIConfigDataName) + g.Expect(configDataMap).ShouldNot(BeNil()) + g.Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData := string(configDataMap.Data["01-nova.conf"]) + + // Parse the INI file to properly access sections + cfg, err := ini.Load([]byte(configData)) + g.Expect(err).ShouldNot(HaveOccurred(), "Should be able to parse config as INI") + + // Verify region_name in [keystone_authtoken] + section := cfg.Section("keystone_authtoken") + g.Expect(section).ShouldNot(BeNil(), "Should find [keystone_authtoken] section") + g.Expect(section.Key("region_name").String()).Should(Equal(testRegion)) + + // Verify region_name in [placement] + section = cfg.Section("placement") + g.Expect(section).ShouldNot(BeNil(), "Should find [placement] section") + g.Expect(section.Key("region_name").String()).Should(Equal(testRegion)) + + // Verify region_name in [glance] + section = cfg.Section("glance") + g.Expect(section).ShouldNot(BeNil(), "Should find [glance] section") + g.Expect(section.Key("region_name").String()).Should(Equal(testRegion)) + + // Verify region_name in [neutron] + section = cfg.Section("neutron") + g.Expect(section).ShouldNot(BeNil(), "Should find [neutron] section") + g.Expect(section.Key("region_name").String()).Should(Equal(testRegion)) + + // Verify os_region_name in [cinder] + section = cfg.Section("cinder") + g.Expect(section).ShouldNot(BeNil(), "Should find [cinder] section") + g.Expect(section.Key("os_region_name").String()).Should(Equal(testRegion)) + + // Verify barbican_region_name in [barbican] + section = cfg.Section("barbican") + g.Expect(section).ShouldNot(BeNil(), "Should find [barbican] section") + g.Expect(section.Key("barbican_region_name").String()).Should(Equal(testRegion)) + + // Verify endpoint_region_name in [oslo_limit] + section = cfg.Section("oslo_limit") + g.Expect(section).ShouldNot(BeNil(), "Should find [oslo_limit] section") + g.Expect(section.Key("endpoint_region_name").String()).Should(Equal(testRegion)) + }, timeout, interval).Should(Succeed()) + }) + It("stored the input hash in the Status", func() { Eventually(func(g Gomega) { novaAPI := GetNovaAPI(novaNames.APIName)