From 21679f06f72ac30c114ad1c02787fe2cf44dfab0 Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Mon, 7 Aug 2023 18:16:57 +0200 Subject: [PATCH] Create nova route and svc endpoint overrides Creates the route for the nova components, also allows to customize the route via override. Generats the service override for the env with what is configured in the externalEndpoints, or specified in the service template override. Depends-On: https://github.com/openstack-k8s-operators/lib-common/pull/313 Depends-On: https://github.com/openstack-k8s-operators/keystone-operator/pull/289 Depends-On: https://github.com/openstack-k8s-operators/nova-operator/pull/489 Jira: OSP-26690 --- ....openstack.org_openstackcontrolplanes.yaml | 288 ++++++++++++++++++ .../v1beta1/openstackcontrolplane_types.go | 26 ++ apis/core/v1beta1/zz_generated.deepcopy.go | 47 +++ ....openstack.org_openstackcontrolplanes.yaml | 288 ++++++++++++++++++ ...controlplane_galera_network_isolation.yaml | 26 +- ...ne_galera_network_isolation_3replicas.yaml | 26 +- ...enstackcontrolplane_network_isolation.yaml | 26 +- ...ckcontrolplane_network_isolation_ceph.yaml | 26 +- pkg/openstack/nova.go | 162 ++++++++++ 9 files changed, 867 insertions(+), 48 deletions(-) diff --git a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml index b82e0e7a1..b04e2d812 100644 --- a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -6730,6 +6730,294 @@ spec: type: object nova: properties: + apiOverride: + properties: + externalEndpoints: + items: + properties: + endpoint: + default: internal + enum: + - internal + - public + type: string + ipAddressPool: + minLength: 1 + type: string + loadBalancerIPs: + items: + type: string + type: array + sharedIP: + default: true + type: boolean + sharedIPKey: + default: "" + type: string + required: + - ipAddressPool + type: object + type: array + route: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + spec: + properties: + alternateBackends: + items: + properties: + kind: + enum: + - Service + - "" + type: string + name: + type: string + weight: + format: int32 + maximum: 256 + minimum: 0 + type: integer + type: object + maxItems: 3 + type: array + host: + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + path: + pattern: ^/ + type: string + port: + properties: + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - targetPort + type: object + subdomain: + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + properties: + caCertificate: + type: string + certificate: + type: string + destinationCACertificate: + type: string + insecureEdgeTerminationPolicy: + type: string + key: + type: string + termination: + enum: + - edge + - reencrypt + - passthrough + type: string + required: + - termination + type: object + to: + properties: + kind: + enum: + - Service + - "" + type: string + name: + type: string + weight: + format: int32 + maximum: 256 + minimum: 0 + type: integer + type: object + wildcardPolicy: + enum: + - None + - Subdomain + - "" + type: string + type: object + type: object + type: object + cellOverride: + additionalProperties: + properties: + metadata: + properties: + externalEndpoints: + items: + properties: + endpoint: + default: internal + enum: + - internal + - public + type: string + ipAddressPool: + minLength: 1 + type: string + loadBalancerIPs: + items: + type: string + type: array + sharedIP: + default: true + type: boolean + sharedIPKey: + default: "" + type: string + required: + - ipAddressPool + type: object + type: array + type: object + noVNCProxy: + properties: + externalEndpoints: + items: + properties: + endpoint: + default: internal + enum: + - internal + - public + type: string + ipAddressPool: + minLength: 1 + type: string + loadBalancerIPs: + items: + type: string + type: array + sharedIP: + default: true + type: boolean + sharedIPKey: + default: "" + type: string + required: + - ipAddressPool + type: object + type: array + route: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + spec: + properties: + alternateBackends: + items: + properties: + kind: + enum: + - Service + - "" + type: string + name: + type: string + weight: + format: int32 + maximum: 256 + minimum: 0 + type: integer + type: object + maxItems: 3 + type: array + host: + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + path: + pattern: ^/ + type: string + port: + properties: + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - targetPort + type: object + subdomain: + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + properties: + caCertificate: + type: string + certificate: + type: string + destinationCACertificate: + type: string + insecureEdgeTerminationPolicy: + type: string + key: + type: string + termination: + enum: + - edge + - reencrypt + - passthrough + type: string + required: + - termination + type: object + to: + properties: + kind: + enum: + - Service + - "" + type: string + name: + type: string + weight: + format: int32 + maximum: 256 + minimum: 0 + type: integer + type: object + wildcardPolicy: + enum: + - None + - Subdomain + - "" + type: string + type: object + type: object + type: object + type: object + type: object enabled: default: true type: boolean diff --git a/apis/core/v1beta1/openstackcontrolplane_types.go b/apis/core/v1beta1/openstackcontrolplane_types.go index 452d4aa74..a52d6d608 100644 --- a/apis/core/v1beta1/openstackcontrolplane_types.go +++ b/apis/core/v1beta1/openstackcontrolplane_types.go @@ -433,6 +433,32 @@ type NovaSection struct { //+operator-sdk:csv:customresourcedefinitions:type=spec // Template - Overrides to use when creating the Nova services Template novav1.NovaSpec `json:"template,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // APIOverride, provides the ability to override the generated manifest of several child resources. + APIOverride Override `json:"apiOverride,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // CellOverride, provides the ability to override the generated manifest of several child resources + // for a nove cell. + CellOverride map[string]NovaCellOverrideSpec `json:"cellOverride,omitempty"` +} + +// NovaCellOverrideSpec to override the generated manifest of several child resources. +type NovaCellOverrideSpec struct { + // +kubebuilder:validation:Optional + Metadata MetadataOverride `json:"metadata,omitempty"` + + // +kubebuilder:validation:Optional + NoVNCProxy Override `json:"noVNCProxy,omitempty"` +} + +// MetadataOverride to override the generated manifest of several child resources. +type MetadataOverride struct { + // ExternalEndpoints, expose a VIP using a pre-created IPAddressPool + ExternalEndpoints []MetalLBConfig `json:"externalEndpoints,omitempty"` } // HeatSection defines the desired state of Heat services diff --git a/apis/core/v1beta1/zz_generated.deepcopy.go b/apis/core/v1beta1/zz_generated.deepcopy.go index e28acee35..e1461c5fa 100644 --- a/apis/core/v1beta1/zz_generated.deepcopy.go +++ b/apis/core/v1beta1/zz_generated.deepcopy.go @@ -244,6 +244,28 @@ func (in *MemcachedSection) DeepCopy() *MemcachedSection { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MetadataOverride) DeepCopyInto(out *MetadataOverride) { + *out = *in + if in.ExternalEndpoints != nil { + in, out := &in.ExternalEndpoints, &out.ExternalEndpoints + *out = make([]MetalLBConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataOverride. +func (in *MetadataOverride) DeepCopy() *MetadataOverride { + if in == nil { + return nil + } + out := new(MetadataOverride) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MetalLBConfig) DeepCopyInto(out *MetalLBConfig) { *out = *in @@ -281,10 +303,35 @@ func (in *NeutronSection) DeepCopy() *NeutronSection { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NovaCellOverrideSpec) DeepCopyInto(out *NovaCellOverrideSpec) { + *out = *in + in.Metadata.DeepCopyInto(&out.Metadata) + in.NoVNCProxy.DeepCopyInto(&out.NoVNCProxy) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaCellOverrideSpec. +func (in *NovaCellOverrideSpec) DeepCopy() *NovaCellOverrideSpec { + if in == nil { + return nil + } + out := new(NovaCellOverrideSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NovaSection) DeepCopyInto(out *NovaSection) { *out = *in in.Template.DeepCopyInto(&out.Template) + in.APIOverride.DeepCopyInto(&out.APIOverride) + if in.CellOverride != nil { + in, out := &in.CellOverride, &out.CellOverride + *out = make(map[string]NovaCellOverrideSpec, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaSection. diff --git a/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml b/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml index b82e0e7a1..b04e2d812 100644 --- a/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -6730,6 +6730,294 @@ spec: type: object nova: properties: + apiOverride: + properties: + externalEndpoints: + items: + properties: + endpoint: + default: internal + enum: + - internal + - public + type: string + ipAddressPool: + minLength: 1 + type: string + loadBalancerIPs: + items: + type: string + type: array + sharedIP: + default: true + type: boolean + sharedIPKey: + default: "" + type: string + required: + - ipAddressPool + type: object + type: array + route: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + spec: + properties: + alternateBackends: + items: + properties: + kind: + enum: + - Service + - "" + type: string + name: + type: string + weight: + format: int32 + maximum: 256 + minimum: 0 + type: integer + type: object + maxItems: 3 + type: array + host: + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + path: + pattern: ^/ + type: string + port: + properties: + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - targetPort + type: object + subdomain: + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + properties: + caCertificate: + type: string + certificate: + type: string + destinationCACertificate: + type: string + insecureEdgeTerminationPolicy: + type: string + key: + type: string + termination: + enum: + - edge + - reencrypt + - passthrough + type: string + required: + - termination + type: object + to: + properties: + kind: + enum: + - Service + - "" + type: string + name: + type: string + weight: + format: int32 + maximum: 256 + minimum: 0 + type: integer + type: object + wildcardPolicy: + enum: + - None + - Subdomain + - "" + type: string + type: object + type: object + type: object + cellOverride: + additionalProperties: + properties: + metadata: + properties: + externalEndpoints: + items: + properties: + endpoint: + default: internal + enum: + - internal + - public + type: string + ipAddressPool: + minLength: 1 + type: string + loadBalancerIPs: + items: + type: string + type: array + sharedIP: + default: true + type: boolean + sharedIPKey: + default: "" + type: string + required: + - ipAddressPool + type: object + type: array + type: object + noVNCProxy: + properties: + externalEndpoints: + items: + properties: + endpoint: + default: internal + enum: + - internal + - public + type: string + ipAddressPool: + minLength: 1 + type: string + loadBalancerIPs: + items: + type: string + type: array + sharedIP: + default: true + type: boolean + sharedIPKey: + default: "" + type: string + required: + - ipAddressPool + type: object + type: array + route: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + spec: + properties: + alternateBackends: + items: + properties: + kind: + enum: + - Service + - "" + type: string + name: + type: string + weight: + format: int32 + maximum: 256 + minimum: 0 + type: integer + type: object + maxItems: 3 + type: array + host: + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + path: + pattern: ^/ + type: string + port: + properties: + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - targetPort + type: object + subdomain: + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + properties: + caCertificate: + type: string + certificate: + type: string + destinationCACertificate: + type: string + insecureEdgeTerminationPolicy: + type: string + key: + type: string + termination: + enum: + - edge + - reencrypt + - passthrough + type: string + required: + - termination + type: object + to: + properties: + kind: + enum: + - Service + - "" + type: string + name: + type: string + weight: + format: int32 + maximum: 256 + minimum: 0 + type: integer + type: object + wildcardPolicy: + enum: + - None + - Subdomain + - "" + type: string + type: object + type: object + type: object + type: object + type: object enabled: default: true type: boolean diff --git a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml index 3f5752735..df7b83c9b 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml @@ -104,20 +104,22 @@ spec: replicas: 1 secret: osp-secret nova: + apiOverride: + externalEndpoints: + - endpoint: internal + ipAddressPool: internalapi + loadBalancerIPs: + - 172.17.0.80 + cellOverride: + cell0: + metadata: + externalEndpoints: + - endpoint: internal + ipAddressPool: internalapi + loadBalancerIPs: + - 172.17.0.80 template: - apiServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 secret: osp-secret - metadataServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 manila: template: manilaAPI: diff --git a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml index 908a58b2e..6649163b1 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml @@ -104,20 +104,22 @@ spec: replicas: 1 secret: osp-secret nova: + apiOverride: + externalEndpoints: + - endpoint: internal + ipAddressPool: internalapi + loadBalancerIPs: + - 172.17.0.80 + cellOverride: + cell0: + metadata: + externalEndpoints: + - endpoint: internal + ipAddressPool: internalapi + loadBalancerIPs: + - 172.17.0.80 template: - apiServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 secret: osp-secret - metadataServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 manila: template: manilaAPI: diff --git a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml index 910aa74f5..e48a5b0b6 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml @@ -92,20 +92,22 @@ spec: replicas: 1 secret: osp-secret nova: + apiOverride: + externalEndpoints: + - endpoint: internal + ipAddressPool: internalapi + loadBalancerIPs: + - 172.17.0.80 + cellOverride: + cell0: + metadata: + externalEndpoints: + - endpoint: internal + ipAddressPool: internalapi + loadBalancerIPs: + - 172.17.0.80 template: - apiServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 secret: osp-secret - metadataServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 manila: template: manilaAPI: diff --git a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation_ceph.yaml b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation_ceph.yaml index 143953694..aaece3488 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation_ceph.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation_ceph.yaml @@ -136,20 +136,22 @@ spec: replicas: 1 secret: osp-secret nova: + apiOverride: + externalEndpoints: + - endpoint: internal + ipAddressPool: internalapi + loadBalancerIPs: + - 172.17.0.80 + cellOverride: + cell0: + metadata: + externalEndpoints: + - endpoint: internal + ipAddressPool: internalapi + loadBalancerIPs: + - 172.17.0.80 template: - apiServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 secret: osp-secret - metadataServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 manila: template: manilaAPI: diff --git a/pkg/openstack/nova.go b/pkg/openstack/nova.go index 6ff791b9d..acbb48be1 100644 --- a/pkg/openstack/nova.go +++ b/pkg/openstack/nova.go @@ -21,13 +21,17 @@ import ( "fmt" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/endpoint" "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" corev1beta1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" ) @@ -48,6 +52,131 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl return ctrl.Result{}, nil } + // Create service overrides to pass into the service CR + // and expose the public endpoint using a route per default. + // Any trailing path will be added on the service-operator level. + var endpoints = map[service.Endpoint]endpoint.Data{ + service.EndpointPublic: {}, + service.EndpointInternal: {}, + } + + apiServiceOverrides := []service.OverrideSpec{} + serviceDetails := []ServiceDetails{} + + for endpointType := range endpoints { + sd := ServiceDetails{ + ServiceName: nova.Name, + Namespace: instance.Namespace, + Endpoint: endpointType, + ExternalEndpoints: instance.Spec.Nova.APIOverride.ExternalEndpoints, + ServiceOverrideSpec: instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, + RouteOverrideSpec: instance.Spec.Nova.APIOverride.Route, + } + + svcOverride, ctrlResult, err := sd.CreateRouteAndServiceOverride(ctx, instance, helper) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + serviceDetails = append( + serviceDetails, + sd, + ) + if svcOverride != nil { + apiServiceOverrides = append(apiServiceOverrides, *svcOverride) + } + + instance.Status.Conditions.MarkTrue(corev1beta1.OpenStackControlPlaneServiceOverrideReadyCondition, corev1beta1.OpenStackControlPlaneServiceOverrideReadyMessage) + } + + // Create service overrides to pass into the service CR + // and expose the public endpoint using a route per default. + // Any trailing path will be added on the service-operator level. + var metadataEndpoints = map[service.Endpoint]endpoint.Data{ + service.EndpointInternal: {}, + } + metadataServiceOverrides := map[string][]service.OverrideSpec{} + novncproxyServiceOverrides := map[string][]service.OverrideSpec{} + + // cell service override + for cellName, template := range instance.Spec.Nova.Template.CellTemplates { + cellOverride := instance.Spec.Nova.CellOverride[cellName] + + for endpointType := range metadataEndpoints { + metadataServiceName := nova.Name + "-metadata" + if cellName != novav1.Cell0Name { + metadataServiceName = metadataServiceName + "-" + cellName + } + + metadataSD := ServiceDetails{ + ServiceName: metadataServiceName, + Namespace: instance.Namespace, + Endpoint: endpointType, + ExternalEndpoints: cellOverride.Metadata.ExternalEndpoints, + ServiceOverrideSpec: template.MetadataServiceTemplate.Override.Service, + RouteOverrideSpec: nil, + } + + metadataSVCOverride, ctrlResult, err := metadataSD.CreateRouteAndServiceOverride(ctx, instance, helper) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + serviceDetails = append( + serviceDetails, + metadataSD, + ) + + if metadataServiceOverrides[cellName] == nil { + metadataServiceOverrides[cellName] = []service.OverrideSpec{} + } + + if metadataSVCOverride != nil { + metadataServiceOverrides[cellName] = append(metadataServiceOverrides[cellName], *metadataSVCOverride) + } + } + + for endpointType := range endpoints { + // no novncproxy for cell0 + if cellName == novav1.Cell0Name { + break + } + novncProxySD := ServiceDetails{ + ServiceName: nova.Name + "-novncproxy" + "-" + cellName, + Namespace: instance.Namespace, + Endpoint: endpointType, + ExternalEndpoints: cellOverride.NoVNCProxy.ExternalEndpoints, + ServiceOverrideSpec: template.NoVNCProxyServiceTemplate.Override.Service, + RouteOverrideSpec: cellOverride.NoVNCProxy.Route, + } + + novncSVCOverride, ctrlResult, err := novncProxySD.CreateRouteAndServiceOverride(ctx, instance, helper) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + serviceDetails = append( + serviceDetails, + novncProxySD, + ) + + if novncproxyServiceOverrides[cellName] == nil { + novncproxyServiceOverrides[cellName] = []service.OverrideSpec{} + } + + if novncSVCOverride != nil { + novncproxyServiceOverrides[cellName] = append(novncproxyServiceOverrides[cellName], *novncSVCOverride) + } + } + } + instance.Status.Conditions.MarkTrue(corev1beta1.OpenStackControlPlaneServiceOverrideReadyCondition, corev1beta1.OpenStackControlPlaneServiceOverrideReadyMessage) + helper.GetLogger().Info("Reconciling Nova", "Nova.Namespace", instance.Namespace, "Nova.Name", nova.Name) op, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), nova, func() error { // 1) @@ -67,6 +196,17 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl // we need to support either rabbitmq vhosts or deploy a separate // RabbitMQCluster per nova cell. instance.Spec.Nova.Template.DeepCopyInto(&nova.Spec) + nova.Spec.APIServiceTemplate.Override.Service = apiServiceOverrides + + for cellName, cellTemplate := range nova.Spec.CellTemplates { + if override, exist := metadataServiceOverrides[cellName]; exist { + cellTemplate.MetadataServiceTemplate.Override.Service = override + } + if override, exist := novncproxyServiceOverrides[cellName]; exist { + cellTemplate.NoVNCProxyServiceTemplate.Override.Service = override + } + nova.Spec.CellTemplates[cellName] = cellTemplate + } err := controllerutil.SetControllerReference(helper.GetBeforeObject(), nova, helper.GetScheme()) if err != nil { @@ -98,5 +238,27 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl corev1beta1.OpenStackControlPlaneNovaReadyRunningMessage)) } + for _, sd := range serviceDetails { + // Add the service CR to the ownerRef list of the route to prevent the route being deleted + // before the service is deleted. Otherwise this can result cleanup issues which require + // the endpoint to be reachable. + // If ALL objects in the list have been deleted, this object will be garbage collected. + // https://github.com/kubernetes/apimachinery/blob/15d95c0b2af3f4fcf46dce24105e5fbb9379af5a/pkg/apis/meta/v1/types.go#L240-L247 + scheme := runtime.NewScheme() + gvk := schema.GroupVersionKind{ + Group: novav1.GroupVersion.Group, + Version: novav1.GroupVersion.Version, + Kind: nova.Kind, + } + + // Add the GVK to the scheme + scheme.AddKnownTypeWithName(gvk, &novav1.Nova{}) + + err = sd.AddOwnerRef(ctx, helper, nova, scheme) + if err != nil { + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil }