From 383abc432766894d73243140491fc3a7ce1ca4cc 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 | 207 ++++++++++++++++++ apis/core/v1beta1/conditions.go | 3 + .../v1beta1/openstackcontrolplane_types.go | 18 ++ apis/core/v1beta1/zz_generated.deepcopy.go | 24 ++ ....openstack.org_openstackcontrolplanes.yaml | 207 ++++++++++++++++++ ...controlplane_galera_network_isolation.yaml | 14 +- ...ne_galera_network_isolation_3replicas.yaml | 14 +- ...enstackcontrolplane_network_isolation.yaml | 14 +- ...ckcontrolplane_network_isolation_ceph.yaml | 14 +- pkg/openstack/nova.go | 128 +++++++++++ 10 files changed, 599 insertions(+), 44 deletions(-) diff --git a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml index 06f54a0e8..3c652089a 100644 --- a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -6780,6 +6780,213 @@ spec: type: object nova: properties: + apiOverride: + properties: + 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: + noVNCProxy: + properties: + 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/conditions.go b/apis/core/v1beta1/conditions.go index 85575211e..4c68e4d77 100644 --- a/apis/core/v1beta1/conditions.go +++ b/apis/core/v1beta1/conditions.go @@ -66,6 +66,9 @@ const ( // OpenStackControlPlaneNovaReadyCondition Status=True condition which indicates if Nova is configured and operational OpenStackControlPlaneNovaReadyCondition condition.Type = "OpenStackControlPlaneNovaReady" + // OpenStackControlPlaneExposeNovaReadyCondition Status=True condition which indicates if Nova is exposed via a route + OpenStackControlPlaneExposeNovaReadyCondition condition.Type = "OpenStackControlPlaneExposeNovaReady" + // OpenStackControlPlaneHeatReadyCondition Status=True condition which indicates if Heat is configured and operational OpenStackControlPlaneHeatReadyCondition condition.Type = "OpenStackControlPlaneHeatReady" diff --git a/apis/core/v1beta1/openstackcontrolplane_types.go b/apis/core/v1beta1/openstackcontrolplane_types.go index 37c6b25aa..c16bd5b73 100644 --- a/apis/core/v1beta1/openstackcontrolplane_types.go +++ b/apis/core/v1beta1/openstackcontrolplane_types.go @@ -432,6 +432,24 @@ 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 nova cell. cell0 never have compute nodes and therefore it won't have a noVNCProxy deployed. + // Providing an override for cell0 noVNCProxy does not have an effect. + CellOverride map[string]NovaCellOverrideSpec `json:"cellOverride,omitempty"` +} + +// NovaCellOverrideSpec to override the generated manifest of several child resources. +type NovaCellOverrideSpec struct { + // +kubebuilder:validation:Optional + NoVNCProxy Override `json:"noVNCProxy,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 5707465a9..4a5f1fcb4 100644 --- a/apis/core/v1beta1/zz_generated.deepcopy.go +++ b/apis/core/v1beta1/zz_generated.deepcopy.go @@ -282,10 +282,34 @@ 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.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 06f54a0e8..3c652089a 100644 --- a/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -6780,6 +6780,213 @@ spec: type: object nova: properties: + apiOverride: + properties: + 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: + noVNCProxy: + properties: + 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 4a23cd548..bf7e982b9 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml @@ -136,13 +136,10 @@ spec: replicas: 1 secret: osp-secret nova: + apiOverride: + route: {} template: apiServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 override: service: internal: @@ -153,13 +150,7 @@ spec: metallb.universe.tf/loadBalancerIPs: 172.17.0.80 spec: type: LoadBalancer - secret: osp-secret metadataServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 override: service: metadata: @@ -169,6 +160,7 @@ spec: metallb.universe.tf/loadBalancerIPs: 172.17.0.80 spec: type: LoadBalancer + secret: osp-secret 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 df881fbc7..1ff0a3a7b 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml @@ -136,13 +136,10 @@ spec: replicas: 1 secret: osp-secret nova: + apiOverride: + route: {} template: apiServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 override: service: internal: @@ -153,13 +150,7 @@ spec: metallb.universe.tf/loadBalancerIPs: 172.17.0.80 spec: type: LoadBalancer - secret: osp-secret metadataServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 override: service: metadata: @@ -169,6 +160,7 @@ spec: metallb.universe.tf/loadBalancerIPs: 172.17.0.80 spec: type: LoadBalancer + secret: osp-secret manila: template: manilaAPI: diff --git a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml index 5103d9b2d..f371493c6 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml @@ -124,13 +124,10 @@ spec: replicas: 1 secret: osp-secret nova: + apiOverride: + route: {} template: apiServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 override: service: internal: @@ -141,13 +138,7 @@ spec: metallb.universe.tf/loadBalancerIPs: 172.17.0.80 spec: type: LoadBalancer - secret: osp-secret metadataServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 override: service: metadata: @@ -157,6 +148,7 @@ spec: metallb.universe.tf/loadBalancerIPs: 172.17.0.80 spec: type: LoadBalancer + secret: osp-secret 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 93922d542..05b23d144 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation_ceph.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation_ceph.yaml @@ -169,13 +169,10 @@ spec: replicas: 1 secret: osp-secret nova: + apiOverride: + route: {} template: apiServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 override: service: internal: @@ -186,13 +183,7 @@ spec: metallb.universe.tf/loadBalancerIPs: 172.17.0.80 spec: type: LoadBalancer - secret: osp-secret metadataServiceTemplate: - externalEndpoints: - - endpoint: internal - ipAddressPool: internalapi - loadBalancerIPs: - - 172.17.0.80 override: service: metadata: @@ -202,6 +193,7 @@ spec: metallb.universe.tf/loadBalancerIPs: 172.17.0.80 spec: type: LoadBalancer + secret: osp-secret manila: template: manilaAPI: diff --git a/pkg/openstack/nova.go b/pkg/openstack/nova.go index 6ff791b9d..13e13b14e 100644 --- a/pkg/openstack/nova.go +++ b/pkg/openstack/nova.go @@ -20,14 +20,20 @@ import ( "context" "fmt" + "github.com/openstack-k8s-operators/lib-common/modules/common" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "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" + "sigs.k8s.io/controller-runtime/pkg/reconcile" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" corev1beta1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" ) @@ -45,9 +51,127 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl return res, err } instance.Status.Conditions.Remove(corev1beta1.OpenStackControlPlaneNovaReadyCondition) + instance.Status.Conditions.Remove(corev1beta1.OpenStackControlPlaneExposeNovaReadyCondition) return ctrl.Result{}, nil } + // add selector to service overrides + for _, endpointType := range []service.Endpoint{service.EndpointPublic, service.EndpointInternal} { + // NovaAPI + if instance.Spec.Nova.Template.APIServiceTemplate.Override.Service == nil { + instance.Spec.Nova.Template.APIServiceTemplate.Override.Service = map[service.Endpoint]service.RoutedOverrideSpec{} + } + instance.Spec.Nova.Template.APIServiceTemplate.Override.Service[endpointType] = + AddServiceComponentLabel( + instance.Spec.Nova.Template.APIServiceTemplate.Override.Service[endpointType], + nova.Name+"-api") + + // cell NoVNCProxy service override + for cellName, cellTemplate := range instance.Spec.Nova.Template.CellTemplates { + // skip adding override for all the cells where novncproxy is disabled + if cellTemplate.NoVNCProxyServiceTemplate.Enabled == ptr.To(false) { + continue + } + if cellTemplate.NoVNCProxyServiceTemplate.Override.Service == nil { + cellTemplate.NoVNCProxyServiceTemplate.Override.Service = &service.RoutedOverrideSpec{} + } + + *cellTemplate.NoVNCProxyServiceTemplate.Override.Service = + AddServiceComponentLabel( + *cellTemplate.NoVNCProxyServiceTemplate.Override.Service, + getNoVNCProxyServiceLabel(nova.Name, cellName)) + + instance.Spec.Nova.Template.CellTemplates[cellName] = cellTemplate + } + } + + // When component services got created check if there is the need to create a route + if err := helper.GetClient().Get(ctx, types.NamespacedName{Name: "nova", Namespace: instance.Namespace}, nova); err != nil { + if !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + } + + // Nova API + if nova.Status.Conditions.IsTrue(novav1.NovaAPIReadyCondition) { + svcs, err := service.GetServicesListWithLabel( + ctx, + helper, + instance.Namespace, + map[string]string{common.AppSelector: nova.Name + "-api"}, + ) + if err != nil { + return ctrl.Result{}, err + } + + var ctrlResult reconcile.Result + instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, ctrlResult, err = EnsureRoute( + ctx, + instance, + helper, + nova, + svcs, + instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, + instance.Spec.Nova.APIOverride.Route, + corev1beta1.OpenStackControlPlaneExposeNovaReadyCondition, + ) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + } + + if nova.Status.Conditions.IsTrue(novav1.NovaAllCellsReadyCondition) { + // cell NoVNCProxy + for cellName, cellTemplate := range instance.Spec.Nova.Template.CellTemplates { + // skip checking for/creating route if service is not enabled + if cellTemplate.NoVNCProxyServiceTemplate.Enabled == ptr.To(false) { + continue + } + + if cellTemplate.NoVNCProxyServiceTemplate.Override.Service == nil { + cellTemplate.NoVNCProxyServiceTemplate.Override.Service = &service.RoutedOverrideSpec{} + } + + svcs, err := service.GetServicesListWithLabel( + ctx, + helper, + instance.Namespace, + map[string]string{ + common.AppSelector: getNoVNCProxyServiceLabel(nova.Name, cellName), + }, + ) + if err != nil { + return ctrl.Result{}, err + } + + var ctrlResult reconcile.Result + routedOverrideSpec, ctrlResult, err := EnsureRoute( + ctx, + instance, + helper, + nova, + svcs, + map[service.Endpoint]service.RoutedOverrideSpec{ + service.EndpointInternal: *cellTemplate.NoVNCProxyServiceTemplate.Override.Service, + }, + instance.Spec.Nova.CellOverride[cellName].NoVNCProxy.Route, + corev1beta1.OpenStackControlPlaneExposeNovaReadyCondition, + ) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + cellTemplate.NoVNCProxyServiceTemplate.Override.Service = ptr.To(routedOverrideSpec[service.EndpointInternal]) + + instance.Spec.Nova.Template.CellTemplates[cellName] = cellTemplate + + } + } + helper.GetLogger().Info("Reconciling Nova", "Nova.Namespace", instance.Namespace, "Nova.Name", nova.Name) op, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), nova, func() error { // 1) @@ -100,3 +224,7 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl return ctrl.Result{}, nil } + +func getNoVNCProxyServiceLabel(name string, cellName string) string { + return name + "-novncproxy-" + cellName +}