diff --git a/internal/common/config.go b/internal/common/config.go new file mode 100644 index 000000000..1acf27ba4 --- /dev/null +++ b/internal/common/config.go @@ -0,0 +1,87 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common //nolint:revive // common is the established package name for multi-group shared code + +import ( + "context" + "maps" + + env "github.com/openstack-k8s-operators/lib-common/modules/common/env" + helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + util "github.com/openstack-k8s-operators/lib-common/modules/common/util" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ConfigGeneratorOptions configures template-based service config generation. +type ConfigGeneratorOptions struct { + ConfigName string + TemplateDir string + BaseExtraTemplates map[string]string + AdditionalTemplates map[string]string + CommonTemplates []string + TemplateParameters map[string]any + ExtraData map[string]string + Labels map[string]string + WithScripts bool +} + +// GenerateConfigs renders service configuration secrets from templates. +func (r *ReconcilerBase) GenerateConfigs( + ctx context.Context, + h *helper.Helper, + instance client.Object, + envVars *map[string]env.Setter, + opts ConfigGeneratorOptions, +) error { + if opts.TemplateDir == "" { + return ErrTemplateDirUnset + } + + extraTemplates := map[string]string{} + maps.Copy(extraTemplates, opts.BaseExtraTemplates) + maps.Copy(extraTemplates, opts.AdditionalTemplates) + + cms := []util.Template{ + { + Name: opts.ConfigName, + Namespace: instance.GetNamespace(), + Type: util.TemplateTypeConfig, + InstanceType: instance.GetObjectKind().GroupVersionKind().Kind, + MultiTemplateDir: opts.TemplateDir, + ConfigOptions: opts.TemplateParameters, + Labels: opts.Labels, + CustomData: opts.ExtraData, + Annotations: map[string]string{}, + AdditionalTemplate: extraTemplates, + CommonTemplates: opts.CommonTemplates, + }, + } + if opts.WithScripts { + cms = append(cms, util.Template{ + Name: GetScriptSecretName(instance.GetName()), + Namespace: instance.GetNamespace(), + Type: util.TemplateTypeScripts, + InstanceType: instance.GetObjectKind().GroupVersionKind().Kind, + MultiTemplateDir: opts.TemplateDir, + AdditionalTemplate: map[string]string{}, + Annotations: map[string]string{}, + Labels: opts.Labels, + }) + } + return secret.EnsureSecrets(ctx, h, instance, cms, envVars) +} diff --git a/internal/common/errors.go b/internal/common/errors.go new file mode 100644 index 000000000..2c89aaadc --- /dev/null +++ b/internal/common/errors.go @@ -0,0 +1,28 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common //nolint:revive // common is the established package name for multi-group shared code + +import "errors" + +var ( + // ErrACSecretNotFound indicates that the ApplicationCredential secret was not found. + ErrACSecretNotFound = errors.New("ApplicationCredential secret not found") + // ErrACSecretMissingKeys indicates that the ApplicationCredential secret is missing required keys. + ErrACSecretMissingKeys = errors.New("ApplicationCredential secret missing required keys") + // ErrTemplateDirUnset indicates that no template directory was provided. + ErrTemplateDirUnset = errors.New("templateDir must be set") +) diff --git a/internal/common/kolla.go b/internal/common/kolla.go new file mode 100644 index 000000000..7a1ef25a3 --- /dev/null +++ b/internal/common/kolla.go @@ -0,0 +1,34 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common //nolint:revive // common is the established package name for multi-group shared code + +import "fmt" + +const ( + // ServiceCommand is the command used to start a service binary in Kolla containers. + ServiceCommand = "/usr/local/bin/kolla_start" +) + +// GetScriptSecretName returns the name of the Secret used for db sync scripts. +func GetScriptSecretName(crName string) string { + return fmt.Sprintf("%s-scripts", crName) +} + +// GetServiceConfigSecretName returns the name of the Secret used to store service configuration. +func GetServiceConfigSecretName(crName string) string { + return fmt.Sprintf("%s-config-data", crName) +} diff --git a/internal/common/network.go b/internal/common/network.go new file mode 100644 index 000000000..89dc4a638 --- /dev/null +++ b/internal/common/network.go @@ -0,0 +1,80 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common //nolint:revive // common is the established package name for multi-group shared code + +import ( + "context" + "fmt" + "time" + + networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// EnsureNetworkAttachments checks requested network attachments exist and returns pod annotations. +func EnsureNetworkAttachments( + ctx context.Context, + h *helper.Helper, + networkAttachments []string, + namespace string, + conditionUpdater ConditionUpdater, + requeueTimeout time.Duration, +) (map[string]string, ctrl.Result, error) { + var nadAnnotations map[string]string + + nadList := []networkv1.NetworkAttachmentDefinition{} + for _, netAtt := range networkAttachments { + netDef, err := nad.GetNADWithName(ctx, h, netAtt, namespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + log.FromContext(ctx).Info(fmt.Sprintf("network-attachment-definition %s not found", netAtt)) + conditionUpdater.Set(condition.FalseCondition( + condition.NetworkAttachmentsReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.NetworkAttachmentsReadyWaitingMessage, + netAtt)) + return nadAnnotations, ctrl.Result{RequeueAfter: requeueTimeout}, nil + } + conditionUpdater.Set(condition.FalseCondition( + condition.NetworkAttachmentsReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.NetworkAttachmentsErrorMessage, + err.Error())) + return nadAnnotations, ctrl.Result{}, err + } + + if netDef != nil { + nadList = append(nadList, *netDef) + } + } + + var err error + nadAnnotations, err = nad.EnsureNetworksAnnotation(nadList) + if err != nil { + return nadAnnotations, ctrl.Result{}, fmt.Errorf("failed create network annotation from %s: %w", + networkAttachments, err) + } + + return nadAnnotations, ctrl.Result{}, nil +} diff --git a/internal/common/reconciler.go b/internal/common/reconciler.go new file mode 100644 index 000000000..f87d063da --- /dev/null +++ b/internal/common/reconciler.go @@ -0,0 +1,98 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common //nolint:revive // common is the established package name for multi-group shared code + +import ( + "context" + "time" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// ReconcilerBase provides a common set of clients, scheme, and settings for reconcilers. +type ReconcilerBase struct { + Client client.Client + Kclient kubernetes.Interface + Scheme *runtime.Scheme + RequeueTimeout time.Duration +} + +// Manageable can be registered with a controller-runtime manager. +type Manageable interface { + SetupWithManager(mgr ctrl.Manager) error +} + +// Reconciler represents a generic reconciler managed by the operator. +type Reconciler interface { + Manageable + SetRequeueTimeout(timeout time.Duration) +} + +// NewReconcilerBase constructs a ReconcilerBase given a manager and Kubernetes client. +func NewReconcilerBase( + mgr ctrl.Manager, kclient kubernetes.Interface, +) ReconcilerBase { + return ReconcilerBase{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Kclient: kclient, + RequeueTimeout: time.Duration(5) * time.Second, + } +} + +// SetRequeueTimeout overrides the default RequeueTimeout of the Reconciler. +func (r *ReconcilerBase) SetRequeueTimeout(timeout time.Duration) { + r.RequeueTimeout = timeout +} + +// Reconcilers holds reconciler objects to allow generic management. +type Reconcilers struct { + reconcilers map[string]Reconciler +} + +// NewReconcilersRegistry constructs a Reconcilers registry from the provided map. +func NewReconcilersRegistry(reconcilers map[string]Reconciler) *Reconcilers { + return &Reconcilers{reconcilers: reconcilers} +} + +// Setup starts the reconcilers by connecting them to the Manager. +func (r *Reconcilers) Setup(mgr ctrl.Manager, setupLog logr.Logger) error { + for name, controller := range r.reconcilers { + if err := controller.SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", name) + return err + } + } + return nil +} + +// OverrideRequeueTimeout overrides the default RequeueTimeout of all reconcilers. +func (r *Reconcilers) OverrideRequeueTimeout(timeout time.Duration) { + for _, reconciler := range r.reconcilers { + reconciler.SetRequeueTimeout(timeout) + } +} + +// GetLogger returns a logger with controller context fields. +func (r *ReconcilerBase) GetLogger(ctx context.Context) logr.Logger { + return log.FromContext(ctx).WithName("Controllers").WithName("ReconcilerBase") +} diff --git a/internal/common/secret.go b/internal/common/secret.go new file mode 100644 index 000000000..466b6eae7 --- /dev/null +++ b/internal/common/secret.go @@ -0,0 +1,104 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common //nolint:revive // common is the established package name for multi-group shared code + +import ( + "context" + "fmt" + "time" + + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + util "github.com/openstack-k8s-operators/lib-common/modules/common/util" + corev1 "k8s.io/api/core/v1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// EnsureSecret verifies that the Secret exists and contains the expected fields. +// It returns a hash of the field values. When notFoundMessage is empty, +// condition.InputReadyWaitingMessage is used for the waiting condition. +func EnsureSecret( + ctx context.Context, + secretName types.NamespacedName, + expectedFields []string, + reader client.Reader, + conditionUpdater ConditionUpdater, + requeueTimeout time.Duration, + notFoundMessage string, + notFoundMessageArgs ...any, +) (string, ctrl.Result, corev1.Secret, error) { + secret := &corev1.Secret{} + err := reader.Get(ctx, secretName, secret) + if err != nil { + if k8s_errors.IsNotFound(err) { + log.FromContext(ctx).Info(fmt.Sprintf("secret %s not found", secretName)) + waitMessage := notFoundMessage + if waitMessage == "" { + waitMessage = condition.InputReadyWaitingMessage + } + conditionUpdater.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + waitMessage, + notFoundMessageArgs...)) + return "", + ctrl.Result{RequeueAfter: requeueTimeout}, + *secret, + nil + } + conditionUpdater.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.InputReadyErrorMessage, + err.Error())) + return "", ctrl.Result{}, *secret, err + } + + values := [][]byte{} + for _, field := range expectedFields { + val, ok := secret.Data[field] + if !ok { + err := fmt.Errorf("%w: '%s' not found in secret/%s", util.ErrFieldNotFound, field, secretName.Name) + conditionUpdater.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.InputReadyErrorMessage, + err.Error())) + return "", ctrl.Result{}, *secret, err + } + values = append(values, val) + } + + hash, err := util.ObjectHash(values) + if err != nil { + conditionUpdater.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.InputReadyErrorMessage, + err.Error())) + return "", ctrl.Result{}, *secret, err + } + + return hash, ctrl.Result{}, *secret, nil +} diff --git a/internal/common/topology.go b/internal/common/topology.go new file mode 100644 index 000000000..1ba943e70 --- /dev/null +++ b/internal/common/topology.go @@ -0,0 +1,72 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common //nolint:revive // common is the established package name for multi-group shared code + +import ( + "context" + "fmt" + + topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TopologyHandler provides topology reference state for a reconciled object. +type TopologyHandler interface { + GetSpecTopologyRef() *topologyv1.TopoRef + GetLastAppliedTopology() *topologyv1.TopoRef + SetLastAppliedTopology(t *topologyv1.TopoRef) +} + +// EnsureTopology retrieves the referenced Topology and updates status accordingly. +func EnsureTopology( + ctx context.Context, + h *helper.Helper, + instance TopologyHandler, + finalizer string, + conditionUpdater ConditionUpdater, + defaultLabelSelector metav1.LabelSelector, +) (*topologyv1.Topology, error) { + topology, err := topologyv1.EnsureServiceTopology( + ctx, + h, + instance.GetSpecTopologyRef(), + instance.GetLastAppliedTopology(), + finalizer, + defaultLabelSelector, + ) + if err != nil { + conditionUpdater.Set(condition.FalseCondition( + condition.TopologyReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TopologyReadyErrorMessage, + err.Error())) + return nil, fmt.Errorf("waiting for Topology requirements: %w", err) + } + + tr := instance.GetSpecTopologyRef() + instance.SetLastAppliedTopology(tr) + if tr != nil { + conditionUpdater.MarkTrue( + condition.TopologyReadyCondition, + condition.TopologyReadyMessage, + ) + } + return topology, nil +} diff --git a/internal/common/util.go b/internal/common/util.go new file mode 100644 index 000000000..17595e9ae --- /dev/null +++ b/internal/common/util.go @@ -0,0 +1,81 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common //nolint:revive // common is the established package name for multi-group shared code + +import ( + "sort" + + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + util "github.com/openstack-k8s-operators/lib-common/modules/common/util" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ConditionUpdater updates conditions on a reconciled object. +type ConditionUpdater interface { + Set(c *condition.Condition) + MarkTrue(t condition.Type, messageFormat string, messageArgs ...any) +} + +// ConditionsGetter provides access to an object's conditions. +type ConditionsGetter interface { + GetConditions() condition.Conditions +} + +// GetSecret defines an interface for objects that can provide a secret name. +type GetSecret interface { + GetSecret() string + client.Object +} + +// AllSubConditionIsTrue reports whether all non-Ready conditions are True. +func AllSubConditionIsTrue(conditionsGetter ConditionsGetter) bool { + for _, c := range conditionsGetter.GetConditions() { + if c.Type == condition.ReadyCondition { + continue + } + if c.Status != corev1.ConditionTrue { + return false + } + } + return true +} + +// OwnedBy returns true if the owner has an OwnerReference on the owned object. +func OwnedBy(owned client.Object, owner client.Object) bool { + for _, ref := range owned.GetOwnerReferences() { + if owner.GetUID() == ref.UID { + return true + } + } + return false +} + +// HashOfStringMap returns a stable hash of a string map. +func HashOfStringMap(input map[string]string) (string, error) { + keys := make([]string, 0, len(input)) + for k := range input { + keys = append(keys, k) + } + sort.Strings(keys) + keyValues := []string{} + for _, key := range keys { + value := input[key] + keyValues = append(keyValues, key+value) + } + return util.ObjectHash(keyValues) +} diff --git a/internal/controller/nova/common.go b/internal/controller/nova/common.go index a533d165c..21b0959ac 100644 --- a/internal/controller/nova/common.go +++ b/internal/controller/nova/common.go @@ -19,9 +19,7 @@ package controller import ( "context" - "errors" "fmt" - "maps" "net/url" "sort" "strconv" @@ -32,25 +30,21 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" - "github.com/openstack-k8s-operators/nova-operator/internal/nova" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" gophercloud "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/services" - networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/env" helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" - nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/tls" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" @@ -155,63 +149,177 @@ var ( endpointPlacement, } + novaDefaultExtraTemplates = map[string]string{ + "01-nova.conf": "/nova/nova.conf", + "nova-blank.conf": "/nova/nova-blank.conf", + } + // ErrACSecretMissingKeys indicates that the ApplicationCredential secret is missing required keys - ErrACSecretMissingKeys = errors.New("ApplicationCredential secret missing required keys") - // ErrTemplateDirUnset indicates that no template directory was provided. - ErrTemplateDirUnset = errors.New("templateDir must be set") + ErrACSecretMissingKeys = internalcommon.ErrACSecretMissingKeys ) -type conditionsGetter interface { - GetConditions() condition.Conditions +type conditionsGetter = internalcommon.ConditionsGetter + +type topologyHandler = internalcommon.TopologyHandler + +type conditionUpdater = internalcommon.ConditionUpdater + +// ReconcilerBase provides a common set of clients scheme and loggers for all reconcilers. +type ReconcilerBase struct { + internalcommon.ReconcilerBase } -type topologyHandler interface { - GetSpecTopologyRef() *topologyv1.TopoRef - GetLastAppliedTopology() *topologyv1.TopoRef - SetLastAppliedTopology(t *topologyv1.TopoRef) +// Reconcilers holds all the Reconciler objects of the nova-operator. +type Reconcilers = internalcommon.Reconcilers + +// Reconciler represents a generic interface for all Reconciler objects in nova. +type Reconciler = internalcommon.Reconciler + +// NewReconcilerBase constructs a ReconcilerBase given a manager and Kclient. +func NewReconcilerBase( + mgr ctrl.Manager, kclient kubernetes.Interface, +) ReconcilerBase { + return ReconcilerBase{ReconcilerBase: internalcommon.NewReconcilerBase(mgr, kclient)} } -// ensureTopology - when a Topology CR is referenced, remove the -// finalizer from a previous referenced Topology (if any), and retrieve the -// newly referenced topology object +// NewReconcilers constructs all nova Reconciler objects +func NewReconcilers(mgr ctrl.Manager, kclient *kubernetes.Clientset) *Reconcilers { + return internalcommon.NewReconcilersRegistry(map[string]Reconciler{ + "Nova": &NovaReconciler{ + ReconcilerBase: NewReconcilerBase(mgr, kclient), + }, + "NovaCell": &NovaCellReconciler{ + ReconcilerBase: NewReconcilerBase(mgr, kclient), + }, + "NovaAPI": &NovaAPIReconciler{ + ReconcilerBase: NewReconcilerBase(mgr, kclient), + }, + "NovaScheduler": &NovaSchedulerReconciler{ + ReconcilerBase: NewReconcilerBase(mgr, kclient), + }, + "NovaConductor": &NovaConductorReconciler{ + ReconcilerBase: NewReconcilerBase(mgr, kclient), + }, + "NovaMetadata": &NovaMetadataReconciler{ + ReconcilerBase: NewReconcilerBase(mgr, kclient), + }, + "NovaNoVNCProxy": &NovaNoVNCProxyReconciler{ + ReconcilerBase: NewReconcilerBase(mgr, kclient), + }, + "NovaCompute": &NovaComputeReconciler{ + ReconcilerBase: NewReconcilerBase(mgr, kclient), + }, + }) +} + +// GenerateConfigs helper function to generate config maps +func (r *ReconcilerBase) GenerateConfigs( + ctx context.Context, h *helper.Helper, + instance client.Object, configName string, envVars *map[string]env.Setter, + templateParameters map[string]any, + extraData map[string]string, cmLabels map[string]string, + additionalTemplates map[string]string, + commonTemplates []string, + templateDir string, +) error { + return r.ReconcilerBase.GenerateConfigs(ctx, h, instance, envVars, internalcommon.ConfigGeneratorOptions{ + ConfigName: configName, + TemplateDir: templateDir, + BaseExtraTemplates: novaDefaultExtraTemplates, + AdditionalTemplates: additionalTemplates, + CommonTemplates: commonTemplates, + TemplateParameters: templateParameters, + ExtraData: extraData, + Labels: cmLabels, + }) +} + +// GenerateConfigsWithScripts helper function to generate config maps +// for service configs and scripts +func (r *ReconcilerBase) GenerateConfigsWithScripts( + ctx context.Context, h *helper.Helper, + instance client.Object, envVars *map[string]env.Setter, + templateParameters map[string]any, + extraData map[string]string, cmLabels map[string]string, + additionalTemplates map[string]string, + commonTemplates []string, + templateDir string, +) error { + return r.ReconcilerBase.GenerateConfigs(ctx, h, instance, envVars, internalcommon.ConfigGeneratorOptions{ + ConfigName: internalcommon.GetServiceConfigSecretName(instance.GetName()), + TemplateDir: templateDir, + BaseExtraTemplates: novaDefaultExtraTemplates, + AdditionalTemplates: additionalTemplates, + CommonTemplates: commonTemplates, + TemplateParameters: templateParameters, + ExtraData: extraData, + Labels: cmLabels, + WithScripts: true, + }) +} + +// GetSecret defines an interface for objects that can provide a secret name +type GetSecret = internalcommon.GetSecret + func ensureTopology( ctx context.Context, - helper *helper.Helper, + h *helper.Helper, instance topologyHandler, finalizer string, conditionUpdater conditionUpdater, defaultLabelSelector metav1.LabelSelector, ) (*topologyv1.Topology, error) { - topology, err := topologyv1.EnsureServiceTopology( + return internalcommon.EnsureTopology(ctx, h, instance, finalizer, conditionUpdater, defaultLabelSelector) +} + +func ensureSecret( + ctx context.Context, + secretName types.NamespacedName, + expectedFields []string, + reader client.Reader, + conditionUpdater conditionUpdater, + requeueTimeout time.Duration, +) (string, ctrl.Result, corev1.Secret, error) { + return internalcommon.EnsureSecret( ctx, - helper, - instance.GetSpecTopologyRef(), - instance.GetLastAppliedTopology(), - finalizer, - defaultLabelSelector, + secretName, + expectedFields, + reader, + conditionUpdater, + requeueTimeout, + novav1.InputReadyWaitingMessage, + "secret/"+secretName.Name, + ) +} + +func ensureNetworkAttachments( + ctx context.Context, + h *helper.Helper, + networkAttachments []string, + conditionUpdater conditionUpdater, + requeueTimeout time.Duration, +) (map[string]string, ctrl.Result, error) { + return internalcommon.EnsureNetworkAttachments( + ctx, + h, + networkAttachments, + h.GetBeforeObject().GetNamespace(), + conditionUpdater, + requeueTimeout, ) - if err != nil { - conditionUpdater.Set(condition.FalseCondition( - condition.TopologyReadyCondition, - condition.ErrorReason, - condition.SeverityWarning, - condition.TopologyReadyErrorMessage, - err.Error())) - return nil, fmt.Errorf("waiting for Topology requirements: %w", err) - } - // update the Status with the last retrieved Topology (or set it to nil) - tr := instance.GetSpecTopologyRef() - instance.SetLastAppliedTopology(tr) - // update the Topology condition only when a Topology is referenced and has - // been retrieved (err == nil) - if tr != nil { - // update the TopologyRef associated condition - conditionUpdater.MarkTrue( - condition.TopologyReadyCondition, - condition.TopologyReadyMessage, - ) - } - return topology, nil +} + +func allSubConditionIsTrue(conditionsGetter conditionsGetter) bool { + return internalcommon.AllSubConditionIsTrue(conditionsGetter) +} + +func hashOfStringMap(input map[string]string) (string, error) { + return internalcommon.HashOfStringMap(input) +} + +// OwnedBy returns true if the owner has an OwnerReference on the owned object. +func OwnedBy(owned client.Object, owner client.Object) bool { + return internalcommon.OwnedBy(owned, owner) } func cleanNovaServiceFromNovaDb( @@ -237,14 +345,10 @@ func cleanNovaServiceFromNovaDb( } for _, service := range allServices { - // delete only if serviceHost is for our cell. If no cell is - // provided (e.g. scheduler cleanup case) then this check is - // non-operational (noop) if !strings.Contains(service.Host, cellName) { continue } - // extract 0 (suffix) from hostname nova-scheduler-0 hostSplits := strings.Split(service.Host, "-") hostIndexStr := hostSplits[len(hostSplits)-1] @@ -253,9 +357,6 @@ func cleanNovaServiceFromNovaDb( return err } - // name index start from 0 - // which means if replicaCount is 1, only nova-scheduler-0 is valid case - // so delete >= 1 if hostIndex >= int(replicaCount) { rsp := services.Delete(ctx, computeClient, service.ID) if rsp.Err != nil { @@ -269,363 +370,6 @@ func cleanNovaServiceFromNovaDb( return err } -func allSubConditionIsTrue(conditionsGetter conditionsGetter) bool { - // It assumes that all of our conditions report success via the True status - for _, c := range conditionsGetter.GetConditions() { - if c.Type == condition.ReadyCondition { - continue - } - if c.Status != corev1.ConditionTrue { - return false - } - } - return true -} - -type conditionUpdater interface { - Set(c *condition.Condition) - MarkTrue(t condition.Type, messageFormat string, messageArgs ...any) -} - -func ensureSecret( - ctx context.Context, - secretName types.NamespacedName, - expectedFields []string, - reader client.Reader, - conditionUpdater conditionUpdater, - requeueTimeout time.Duration, -) (string, ctrl.Result, corev1.Secret, error) { - secret := &corev1.Secret{} - err := reader.Get(ctx, secretName, secret) - if err != nil { - if k8s_errors.IsNotFound(err) { - // This is only currently used to get the password secret provided by the user in the spec. - // Therefore, since the secret should have been manually created by the user and referenced - // in the spec, we treat this as a warning because it means that the service will not be - // able to start. - log.FromContext(ctx).Info(fmt.Sprintf("secret %s not found", secretName)) - conditionUpdater.Set(condition.FalseCondition( - condition.InputReadyCondition, - condition.ErrorReason, - condition.SeverityWarning, - novav1.InputReadyWaitingMessage, "secret/"+secretName.Name)) - return "", - ctrl.Result{RequeueAfter: requeueTimeout}, - *secret, - nil - } - conditionUpdater.Set(condition.FalseCondition( - condition.InputReadyCondition, - condition.ErrorReason, - condition.SeverityWarning, - condition.InputReadyErrorMessage, - err.Error())) - return "", ctrl.Result{}, *secret, err - } - - // collect the secret values the caller expects to exist - values := [][]byte{} - for _, field := range expectedFields { - val, ok := secret.Data[field] - if !ok { - err := fmt.Errorf("%w: '%s' not found in secret/%s", util.ErrFieldNotFound, field, secretName.Name) - conditionUpdater.Set(condition.FalseCondition( - condition.InputReadyCondition, - condition.ErrorReason, - condition.SeverityWarning, - condition.InputReadyErrorMessage, - err.Error())) - return "", ctrl.Result{}, *secret, err - } - values = append(values, val) - } - - // TODO(gibi): Do we need to watch the Secret for changes? - - hash, err := util.ObjectHash(values) - if err != nil { - conditionUpdater.Set(condition.FalseCondition( - condition.InputReadyCondition, - condition.ErrorReason, - condition.SeverityWarning, - condition.InputReadyErrorMessage, - err.Error())) - return "", ctrl.Result{}, *secret, err - } - - return hash, ctrl.Result{}, *secret, nil -} - -// ensureNetworkAttachments - checks the requested network attachments exists and -// returns the annotation to be set on the deployment objects. -func ensureNetworkAttachments( - ctx context.Context, - h *helper.Helper, - networkAttachments []string, - conditionUpdater conditionUpdater, - requeueTimeout time.Duration, -) (map[string]string, ctrl.Result, error) { - var nadAnnotations map[string]string - var err error - - // networks to attach to - nadList := []networkv1.NetworkAttachmentDefinition{} - for _, netAtt := range networkAttachments { - nad, err := nad.GetNADWithName(ctx, h, netAtt, h.GetBeforeObject().GetNamespace()) - if err != nil { - if k8s_errors.IsNotFound(err) { - // Since the net-attach-def CR should have been manually created by the user and referenced in the spec, - // we treat this as a warning because it means that the service will not be able to start. - log.FromContext(ctx).Info(fmt.Sprintf("network-attachment-definition %s not found", netAtt)) - conditionUpdater.Set(condition.FalseCondition( - condition.NetworkAttachmentsReadyCondition, - condition.ErrorReason, - condition.SeverityWarning, - condition.NetworkAttachmentsReadyWaitingMessage, - netAtt)) - return nadAnnotations, ctrl.Result{RequeueAfter: requeueTimeout}, nil - } - conditionUpdater.Set(condition.FalseCondition( - condition.NetworkAttachmentsReadyCondition, - condition.ErrorReason, - condition.SeverityWarning, - condition.NetworkAttachmentsErrorMessage, - err.Error())) - return nadAnnotations, ctrl.Result{}, err - } - - if nad != nil { - nadList = append(nadList, *nad) - } - } - - nadAnnotations, err = nad.EnsureNetworksAnnotation(nadList) - if err != nil { - return nadAnnotations, ctrl.Result{}, fmt.Errorf("failed create network annotation from %s: %w", - networkAttachments, err) - } - - return nadAnnotations, ctrl.Result{}, nil -} - -// ReconcilerBase provides a common set of clients scheme and loggers for all reconcilers. -type ReconcilerBase struct { - Client client.Client - Kclient kubernetes.Interface - Scheme *runtime.Scheme - RequeueTimeout time.Duration -} - -// Manageable all types that conform to this interface can be setup with a controller-runtime manager. -type Manageable interface { - SetupWithManager(mgr ctrl.Manager) error -} - -// Reconciler represents a generic interface for all Reconciler objects in nova -type Reconciler interface { - Manageable - SetRequeueTimeout(timeout time.Duration) -} - -// NewReconcilerBase constructs a ReconcilerBase given a manager and Kclient. -func NewReconcilerBase( - mgr ctrl.Manager, kclient kubernetes.Interface, -) ReconcilerBase { - return ReconcilerBase{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Kclient: kclient, - RequeueTimeout: time.Duration(5) * time.Second, - } -} - -// SetRequeueTimeout overrides the default RequeueTimeout of the Reconciler -func (r *ReconcilerBase) SetRequeueTimeout(timeout time.Duration) { - r.RequeueTimeout = timeout -} - -// Reconcilers holds all the Reconciler objects of the nova-operator to -// allow generic management of them. -type Reconcilers struct { - reconcilers map[string]Reconciler -} - -// NewReconcilers constructs all nova Reconciler objects -func NewReconcilers(mgr ctrl.Manager, kclient *kubernetes.Clientset) *Reconcilers { - return &Reconcilers{ - reconcilers: map[string]Reconciler{ - "Nova": &NovaReconciler{ - ReconcilerBase: NewReconcilerBase(mgr, kclient), - }, - "NovaCell": &NovaCellReconciler{ - ReconcilerBase: NewReconcilerBase(mgr, kclient), - }, - "NovaAPI": &NovaAPIReconciler{ - ReconcilerBase: NewReconcilerBase(mgr, kclient), - }, - "NovaScheduler": &NovaSchedulerReconciler{ - ReconcilerBase: NewReconcilerBase(mgr, kclient), - }, - "NovaConductor": &NovaConductorReconciler{ - ReconcilerBase: NewReconcilerBase(mgr, kclient), - }, - "NovaMetadata": &NovaMetadataReconciler{ - ReconcilerBase: NewReconcilerBase(mgr, kclient), - }, - "NovaNoVNCProxy": &NovaNoVNCProxyReconciler{ - ReconcilerBase: NewReconcilerBase(mgr, kclient), - }, - "NovaCompute": &NovaComputeReconciler{ - ReconcilerBase: NewReconcilerBase(mgr, kclient), - }, - }, - } -} - -// Setup starts the reconcilers by connecting them to the Manager -func (r *Reconcilers) Setup(mgr ctrl.Manager, setupLog logr.Logger) error { - var err error - for name, controller := range r.reconcilers { - if err = controller.SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", name) - return err - } - } - return nil -} - -// OverrideRequeueTimeout overrides the default RequeueTimeout of our reconcilers -func (r *Reconcilers) OverrideRequeueTimeout(timeout time.Duration) { - for _, reconciler := range r.reconcilers { - reconciler.SetRequeueTimeout(timeout) - } -} - -// generateConfigsGeneric helper function to generate config maps -func (r *ReconcilerBase) generateConfigsGeneric( - ctx context.Context, h *helper.Helper, - instance client.Object, configName string, envVars *map[string]env.Setter, - templateParameters map[string]any, - extraData map[string]string, cmLabels map[string]string, - additionalTemplates map[string]string, - commonTemplates []string, - templateDir string, - withScripts bool, -) error { - extraTemplates := map[string]string{ - "01-nova.conf": "/nova/nova.conf", - "nova-blank.conf": "/nova/nova-blank.conf", - } - if templateDir == "" { - return ErrTemplateDirUnset - } - - maps.Copy(extraTemplates, additionalTemplates) - cms := []util.Template{ - { - Name: configName, - Namespace: instance.GetNamespace(), - Type: util.TemplateTypeConfig, - InstanceType: instance.GetObjectKind().GroupVersionKind().Kind, - MultiTemplateDir: templateDir, - ConfigOptions: templateParameters, - Labels: cmLabels, - CustomData: extraData, - Annotations: map[string]string{}, - AdditionalTemplate: extraTemplates, - CommonTemplates: commonTemplates, - }, - } - if withScripts { - cms = append(cms, util.Template{ - Name: nova.GetScriptSecretName(instance.GetName()), - Namespace: instance.GetNamespace(), - Type: util.TemplateTypeScripts, - InstanceType: instance.GetObjectKind().GroupVersionKind().Kind, - MultiTemplateDir: templateDir, - AdditionalTemplate: map[string]string{}, - Annotations: map[string]string{}, - Labels: cmLabels, - }) - } - return secret.EnsureSecrets(ctx, h, instance, cms, envVars) -} - -// GenerateConfigs helper function to generate config maps -func (r *ReconcilerBase) GenerateConfigs( - ctx context.Context, h *helper.Helper, - instance client.Object, configName string, envVars *map[string]env.Setter, - templateParameters map[string]any, - extraData map[string]string, cmLabels map[string]string, - additionalTemplates map[string]string, - commonTemplates []string, - templateDir string, -) error { - return r.generateConfigsGeneric( - ctx, h, instance, configName, envVars, templateParameters, extraData, - cmLabels, additionalTemplates, commonTemplates, templateDir, false, - ) -} - -// GenerateConfigsWithScripts helper function to generate config maps -// for service configs and scripts -func (r *ReconcilerBase) GenerateConfigsWithScripts( - ctx context.Context, h *helper.Helper, - instance client.Object, envVars *map[string]env.Setter, - templateParameters map[string]any, - extraData map[string]string, cmLabels map[string]string, - additionalTemplates map[string]string, - commonTemplates []string, - templateDir string, -) error { - return r.generateConfigsGeneric( - ctx, h, instance, nova.GetServiceConfigSecretName(instance.GetName()), - envVars, templateParameters, extraData, - cmLabels, additionalTemplates, commonTemplates, templateDir, true, - ) -} - -func getNovaCellCRName(novaCRName string, cellName string) string { - return novaCRName + "-" + cellName -} - -func hashOfStringMap(input map[string]string) (string, error) { - // we need to make sure that we have a stable map iteration order and - // hash both the keys and the values - keys := make([]string, 0, len(input)) - for k := range input { - keys = append(keys, k) - } - sort.Strings(keys) - keyValues := []string{} - for _, key := range keys { - value := input[key] - keyValues = append(keyValues, key+value) - } - return util.ObjectHash(keyValues) -} - -// GetSecret defines an interface for objects that can provide a secret name -type GetSecret interface { - GetSecret() string - client.Object -} - -// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields -func (r *ReconcilerBase) GetLogger(ctx context.Context) logr.Logger { - return log.FromContext(ctx).WithName("Controllers").WithName("ReconcilerBase") -} - -// OwnedBy returns true if the owner has an OwnerReference on the owned object -func OwnedBy(owned client.Object, owner client.Object) bool { - for _, ref := range owned.GetOwnerReferences() { - if owner.GetUID() == ref.UID { - return true - } - } - return false -} - func (r *ReconcilerBase) ensureMetadataDeleted( ctx context.Context, instance client.Object, @@ -635,13 +379,11 @@ func (r *ReconcilerBase) ensureMetadataDeleted( metadata := &novav1.NovaMetadata{} err := r.Client.Get(ctx, metadataName, metadata) if k8s_errors.IsNotFound(err) { - // Nothing to do as it does not exists return nil } if err != nil { return err } - // If it is not created by us, we don't touch it if !OwnedBy(metadata, instance) { Log.Info("NovaMetadata is disabled, but there is a "+ "NovaMetadata CR not owned by us. Not deleting it.", @@ -649,7 +391,6 @@ func (r *ReconcilerBase) ensureMetadataDeleted( return nil } - // OK this was created by us so we go and delete it err = r.Client.Delete(ctx, metadata) if err != nil && k8s_errors.IsNotFound(err) { return nil @@ -689,8 +430,6 @@ func getNovaClient( ctx, h, auth.GetCABundleSecretName(), - // requeue is translated to error below as the secret already - // verified to exists and has the expected fields. time.Second, tls.CABundleKey) if err != nil { @@ -731,12 +470,14 @@ func getNovaClient( } client := computeClient.GetOSClient() - // NOTE(gibi): We use Antelope maximum because we can. In reality we only - // need 2.53 to be able to delete services based on UUID. client.Microversion = "2.95" return client, nil } +func getNovaCellCRName(novaCRName string, cellName string) string { + return novaCRName + "-" + cellName +} + func getCellDatabaseName(cellName string) string { return "nova_" + cellName } @@ -748,7 +489,6 @@ func getMemcachedInstance(instance *novav1.Nova, cellTemplate novav1.NovaCellTem return instance.Spec.MemcachedInstance } -// ensureMemcached - gets the Memcached instance cell specific used for nova services cache backend func ensureMemcached( ctx context.Context, h *helper.Helper, @@ -759,11 +499,6 @@ func ensureMemcached( memcached, err := memcachedv1.GetMemcachedByName(ctx, h, memcachedName, namespaceName) if err != nil { if k8s_errors.IsNotFound(err) { - // Memcached should be automatically created by the encompassing OpenStackControlPlane, - // but we don't propagate its name into the "memcachedInstance" field of other sub-resources, - // so if it is missing at this point, it *could* be because there's a mismatch between the - // name of the Memcached CR and the name of the Memcached instance referenced by this CR. - // Since that situation would block further reconciliation, we treat it as a warning. conditionUpdater.Set(condition.FalseCondition( condition.MemcachedReadyCondition, condition.ErrorReason, @@ -793,14 +528,13 @@ func ensureMemcached( return memcached, err } -// SortNovaCellListByName sorts a NovaCellList by name in ascending order +// SortNovaCellListByName sorts a NovaCellList by name in ascending order. func SortNovaCellListByName(cellList *novav1.NovaCellList) { sort.SliceStable(cellList.Items, func(i, j int) bool { return cellList.Items[i].Name < cellList.Items[j].Name }) } -// parseQuorumQueues parses the quorum queues value from secret data func parseQuorumQueues(data []byte) bool { if data == nil { return false diff --git a/internal/controller/nova/novaapi_controller.go b/internal/controller/nova/novaapi_controller.go index 8d967f8c4..c41b38149 100644 --- a/internal/controller/nova/novaapi_controller.go +++ b/internal/controller/nova/novaapi_controller.go @@ -54,7 +54,7 @@ import ( topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" - "github.com/openstack-k8s-operators/nova-operator/internal/nova" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" novaapi "github.com/openstack-k8s-operators/nova-operator/internal/nova/api" k8s_errors "k8s.io/apimachinery/pkg/api/errors" @@ -572,7 +572,7 @@ func (r *NovaAPIReconciler) generateConfigs( ) err = r.GenerateConfigs( - ctx, h, instance, nova.GetServiceConfigSecretName(instance.GetName()), + ctx, h, instance, internalcommon.GetServiceConfigSecretName(instance.GetName()), hashes, templateParameters, extraData, cmLabels, map[string]string{}, []string{"ssl.conf"}, "nova/api", ) diff --git a/internal/controller/nova/novacompute_controller.go b/internal/controller/nova/novacompute_controller.go index 02163a10e..e5dcfc427 100644 --- a/internal/controller/nova/novacompute_controller.go +++ b/internal/controller/nova/novacompute_controller.go @@ -48,7 +48,7 @@ import ( util "github.com/openstack-k8s-operators/lib-common/modules/common/util" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" - "github.com/openstack-k8s-operators/nova-operator/internal/nova" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" novacompute "github.com/openstack-k8s-operators/nova-operator/internal/nova/compute" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" @@ -396,7 +396,7 @@ func (r *NovaComputeReconciler) generateConfigs( ) err := r.GenerateConfigs( - ctx, h, instance, nova.GetServiceConfigSecretName(instance.GetName()), hashes, templateParameters, extraData, cmLabels, map[string]string{}, + ctx, h, instance, internalcommon.GetServiceConfigSecretName(instance.GetName()), hashes, templateParameters, extraData, cmLabels, map[string]string{}, []string{}, "nova/compute", ) return err diff --git a/internal/controller/nova/novametadata_controller.go b/internal/controller/nova/novametadata_controller.go index ebcb87d11..9fb9934b3 100644 --- a/internal/controller/nova/novametadata_controller.go +++ b/internal/controller/nova/novametadata_controller.go @@ -54,7 +54,7 @@ import ( util "github.com/openstack-k8s-operators/lib-common/modules/common/util" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" - "github.com/openstack-k8s-operators/nova-operator/internal/nova" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" novametadata "github.com/openstack-k8s-operators/nova-operator/internal/nova/metadata" k8s_errors "k8s.io/apimachinery/pkg/api/errors" ) @@ -577,7 +577,7 @@ func (r *NovaMetadataReconciler) generateConfigs( ) err = r.GenerateConfigs( - ctx, h, instance, nova.GetServiceConfigSecretName(instance.GetName()), + ctx, h, instance, internalcommon.GetServiceConfigSecretName(instance.GetName()), hashes, templateParameters, extraData, cmLabels, map[string]string{}, []string{"ssl.conf"}, "nova/metadata", ) diff --git a/internal/controller/nova/novanovncproxy_controller.go b/internal/controller/nova/novanovncproxy_controller.go index 0f35d7e50..3c77d9dfb 100644 --- a/internal/controller/nova/novanovncproxy_controller.go +++ b/internal/controller/nova/novanovncproxy_controller.go @@ -51,7 +51,7 @@ import ( util "github.com/openstack-k8s-operators/lib-common/modules/common/util" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" - "github.com/openstack-k8s-operators/nova-operator/internal/nova" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova/novncproxy" k8s_errors "k8s.io/apimachinery/pkg/api/errors" ) @@ -534,7 +534,7 @@ func (r *NovaNoVNCProxyReconciler) generateConfigs( ) err = r.GenerateConfigs( - ctx, h, instance, nova.GetServiceConfigSecretName(instance.GetName()), + ctx, h, instance, internalcommon.GetServiceConfigSecretName(instance.GetName()), hashes, templateParameters, extraData, cmLabels, map[string]string{}, []string{}, "nova/novncproxy", ) diff --git a/internal/controller/nova/novascheduler_controller.go b/internal/controller/nova/novascheduler_controller.go index 1c987e777..48b2f5b65 100644 --- a/internal/controller/nova/novascheduler_controller.go +++ b/internal/controller/nova/novascheduler_controller.go @@ -52,7 +52,7 @@ import ( topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" - "github.com/openstack-k8s-operators/nova-operator/internal/nova" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" novascheduler "github.com/openstack-k8s-operators/nova-operator/internal/nova/scheduler" ) @@ -636,7 +636,7 @@ func (r *NovaSchedulerReconciler) generateConfigs( ) return r.GenerateConfigs( - ctx, h, instance, nova.GetServiceConfigSecretName(instance.GetName()), + ctx, h, instance, internalcommon.GetServiceConfigSecretName(instance.GetName()), hashes, templateParameters, extraData, cmLabels, map[string]string{}, []string{}, "nova/scheduler", ) diff --git a/internal/nova/api/deployment.go b/internal/nova/api/deployment.go index 0bd3bd033..d7822374d 100644 --- a/internal/nova/api/deployment.go +++ b/internal/nova/api/deployment.go @@ -27,6 +27,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/tls" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova" appsv1 "k8s.io/api/apps/v1" @@ -106,7 +107,7 @@ func StatefulSet( // create Volume and VolumeMounts volumes := []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetConfigVolume(internalcommon.GetServiceConfigSecretName(instance.Name)), nova.GetLogVolume(), } volumeMounts := []corev1.VolumeMount{ diff --git a/internal/nova/common.go b/internal/nova/common.go index 900ef31a7..6751be98b 100644 --- a/internal/nova/common.go +++ b/internal/nova/common.go @@ -17,14 +17,13 @@ limitations under the License. package nova import ( - "fmt" - mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" ) const ( // KollaServiceCommand - the command to start the service binary in the kolla container - KollaServiceCommand = "/usr/local/bin/kolla_start" + KollaServiceCommand = internalcommon.ServiceCommand // NovaUserID is the linux user ID used by Kolla for the nova user // in the service containers NovaUserID int64 = 42436 @@ -35,18 +34,6 @@ const ( ACConsumerFinalizer = "openstack.org/nova-ac-consumer" ) -// GetScriptSecretName returns the name of the Secret used for the -// db sync scripts -func GetScriptSecretName(crName string) string { - return fmt.Sprintf("%s-scripts", crName) -} - -// GetServiceConfigSecretName returns the name of the Secret used to -// store the service configuration files -func GetServiceConfigSecretName(crName string) string { - return fmt.Sprintf("%s-config-data", crName) -} - // DatabaseStatus - type DatabaseStatus int diff --git a/internal/nova/compute/deployment.go b/internal/nova/compute/deployment.go index d90b6a7c0..718da3eeb 100644 --- a/internal/nova/compute/deployment.go +++ b/internal/nova/compute/deployment.go @@ -22,6 +22,7 @@ import ( affinity "github.com/openstack-k8s-operators/lib-common/modules/common/affinity" env "github.com/openstack-k8s-operators/lib-common/modules/common/env" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" @@ -75,7 +76,7 @@ func StatefulSet( // create Volume and VolumeMounts volumes := []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetConfigVolume(internalcommon.GetServiceConfigSecretName(instance.Name)), } volumeMounts := []corev1.VolumeMount{ nova.GetConfigVolumeMount(), diff --git a/internal/nova/conductor/dbpurge.go b/internal/nova/conductor/dbpurge.go index ddd38b3ef..660e690a1 100644 --- a/internal/nova/conductor/dbpurge.go +++ b/internal/nova/conductor/dbpurge.go @@ -13,6 +13,7 @@ import ( memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/env" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova" ) @@ -35,8 +36,8 @@ func DBPurgeCronJob( env := env.MergeEnvs([]corev1.EnvVar{}, envVars) volumes := []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), - nova.GetScriptVolume(nova.GetScriptSecretName(instance.Name)), + nova.GetConfigVolume(internalcommon.GetServiceConfigSecretName(instance.Name)), + nova.GetScriptVolume(internalcommon.GetScriptSecretName(instance.Name)), } volumeMounts := []corev1.VolumeMount{ nova.GetConfigVolumeMount(), diff --git a/internal/nova/conductor/dbsync.go b/internal/nova/conductor/dbsync.go index 856bc8f0f..e682c1d31 100644 --- a/internal/nova/conductor/dbsync.go +++ b/internal/nova/conductor/dbsync.go @@ -18,6 +18,7 @@ package novaconductor import ( novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova" env "github.com/openstack-k8s-operators/lib-common/modules/common/env" @@ -48,8 +49,8 @@ func CellDBSyncJob( // create Volume and VolumeMounts volumes := []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), - nova.GetScriptVolume(nova.GetScriptSecretName(instance.Name)), + nova.GetConfigVolume(internalcommon.GetServiceConfigSecretName(instance.Name)), + nova.GetScriptVolume(internalcommon.GetScriptSecretName(instance.Name)), } volumeMounts := []corev1.VolumeMount{ nova.GetConfigVolumeMount(), diff --git a/internal/nova/conductor/deployment.go b/internal/nova/conductor/deployment.go index aeedcad59..32a2e758c 100644 --- a/internal/nova/conductor/deployment.go +++ b/internal/nova/conductor/deployment.go @@ -23,6 +23,7 @@ import ( affinity "github.com/openstack-k8s-operators/lib-common/modules/common/affinity" env "github.com/openstack-k8s-operators/lib-common/modules/common/env" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova" appsv1 "k8s.io/api/apps/v1" @@ -82,7 +83,7 @@ func StatefulSet( // create Volume and VolumeMounts volumes := []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetConfigVolume(internalcommon.GetServiceConfigSecretName(instance.Name)), } volumeMounts := []corev1.VolumeMount{ nova.GetConfigVolumeMount(), diff --git a/internal/nova/metadata/deployment.go b/internal/nova/metadata/deployment.go index 3c914fac2..a60366a0a 100644 --- a/internal/nova/metadata/deployment.go +++ b/internal/nova/metadata/deployment.go @@ -25,6 +25,7 @@ import ( affinity "github.com/openstack-k8s-operators/lib-common/modules/common/affinity" env "github.com/openstack-k8s-operators/lib-common/modules/common/env" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova" appsv1 "k8s.io/api/apps/v1" @@ -104,7 +105,7 @@ func StatefulSet( // create Volume and VolumeMounts volumes := []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetConfigVolume(internalcommon.GetServiceConfigSecretName(instance.Name)), nova.GetLogVolume(), } volumeMounts := []corev1.VolumeMount{ diff --git a/internal/nova/novncproxy/deployment.go b/internal/nova/novncproxy/deployment.go index 9066de09a..fd8fe9d4e 100644 --- a/internal/nova/novncproxy/deployment.go +++ b/internal/nova/novncproxy/deployment.go @@ -23,6 +23,7 @@ import ( affinity "github.com/openstack-k8s-operators/lib-common/modules/common/affinity" env "github.com/openstack-k8s-operators/lib-common/modules/common/env" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova" appsv1 "k8s.io/api/apps/v1" @@ -90,7 +91,7 @@ func StatefulSet( // create Volume and VolumeMounts volumes := []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetConfigVolume(internalcommon.GetServiceConfigSecretName(instance.Name)), } volumeMounts := []corev1.VolumeMount{ nova.GetConfigVolumeMount(), diff --git a/internal/nova/scheduler/deployment.go b/internal/nova/scheduler/deployment.go index aa3bfc5d1..47f1271ec 100644 --- a/internal/nova/scheduler/deployment.go +++ b/internal/nova/scheduler/deployment.go @@ -22,6 +22,7 @@ import ( affinity "github.com/openstack-k8s-operators/lib-common/modules/common/affinity" env "github.com/openstack-k8s-operators/lib-common/modules/common/env" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" "github.com/openstack-k8s-operators/nova-operator/internal/nova" memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" @@ -87,7 +88,7 @@ func StatefulSet( // create Volume and VolumeMounts volumes := []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetConfigVolume(internalcommon.GetServiceConfigSecretName(instance.Name)), } volumeMounts := []corev1.VolumeMount{ nova.GetConfigVolumeMount(), diff --git a/internal/placement/const.go b/internal/placement/const.go index b678734d1..ec0f5a7f0 100644 --- a/internal/placement/const.go +++ b/internal/placement/const.go @@ -16,6 +16,10 @@ limitations under the License. // Package placement provides constants and utilities for managing OpenStack Placement service package placement +import ( + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" +) + const ( // ServiceName - ServiceName = "placement" @@ -31,7 +35,7 @@ const ( PlacementInternalPort int32 = 8778 // KollaServiceCommand is the command used to start the placement service in Kolla containers - KollaServiceCommand = "/usr/local/bin/kolla_start" + KollaServiceCommand = internalcommon.ServiceCommand // PlacementUserID is the linux user ID used by Kolla for the placement // user in the service containers diff --git a/internal/placement/volumes.go b/internal/placement/volumes.go index fa60ef15e..84b9a37ff 100644 --- a/internal/placement/volumes.go +++ b/internal/placement/volumes.go @@ -16,6 +16,8 @@ limitations under the License. package placement import ( + internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common" + corev1 "k8s.io/api/core/v1" ) @@ -30,7 +32,7 @@ func getVolumes(name string) []corev1.Volume { VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ DefaultMode: &scriptsVolumeDefaultMode, - SecretName: name + "-scripts", + SecretName: internalcommon.GetScriptSecretName(name), }, }, }, @@ -39,7 +41,7 @@ func getVolumes(name string) []corev1.Volume { VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ DefaultMode: &configMode, - SecretName: name + "-config-data", + SecretName: internalcommon.GetServiceConfigSecretName(name), }, }, },