Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ test: manifests generate fmt vet envtest ginkgo ## Run tests.
OPERATOR_TEMPLATES="$(PWD)/templates" \
$(GINKGO) --trace --cover --coverpkg=../../internal/...,../../api/nova/v1beta1,../../api/placement/v1beta1 --coverprofile cover.out --covermode=atomic --randomize-all ${PROC_CMD} $(GINKGO_ARGS) ./test/...

.PHONY: gotest-unit
gotest-unit: ## Run unit tests under test/unit/ (also run via ginkgo in the test target).
go test -v ./test/unit/...

##@ Build

.PHONY: build
Expand Down
17 changes: 16 additions & 1 deletion api/placement/v1beta1/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,26 @@ func SetupDefaults() {
SetupPlacementAPIDefaults(placementDefaults)
}

// GetSecret returns the value of the Nova.Spec.Secret
// GetSecret returns the value of the PlacementAPI.Spec.Secret
func (instance PlacementAPI) GetSecret() string {
return instance.Spec.Secret
}

// GetSpecTopologyRef returns the TopologyRef from the Spec
func (instance *PlacementAPI) GetSpecTopologyRef() *topologyv1.TopoRef {
return instance.Spec.TopologyRef
}

// GetLastAppliedTopology returns the LastAppliedTopology from the Status
func (instance *PlacementAPI) GetLastAppliedTopology() *topologyv1.TopoRef {
return instance.Status.LastAppliedTopology
}

// SetLastAppliedTopology sets the LastAppliedTopology value in the Status
func (instance *PlacementAPI) SetLastAppliedTopology(topologyRef *topologyv1.TopoRef) {
instance.Status.LastAppliedTopology = topologyRef
}

// ValidateTopology -
func (instance *PlacementAPISpecCore) ValidateTopology(
basePath *field.Path,
Expand Down
7 changes: 3 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import (
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"

placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1"
internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common"
novacontroller "github.com/openstack-k8s-operators/nova-operator/internal/controller/nova"
placementcontroller "github.com/openstack-k8s-operators/nova-operator/internal/controller/placement"
webhookv1beta1 "github.com/openstack-k8s-operators/nova-operator/internal/webhook/nova/v1beta1"
Expand All @@ -51,7 +53,6 @@ import (
"github.com/openstack-k8s-operators/lib-common/modules/common/operator"
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1"
placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -265,9 +266,7 @@ func main() {

// Setup PlacementAPI controller
if err = (&placementcontroller.PlacementAPIReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Kclient: kclient,
ReconcilerBase: internalcommon.NewReconcilerBase(mgr, kclient),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "PlacementAPI")
os.Exit(1)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/grpc v1.71.1 // indirect
google.golang.org/protobuf v1.36.7 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apiextensions-apiserver v0.33.2 // indirect
Expand Down
102 changes: 102 additions & 0 deletions internal/common/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
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"

"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"
)

// GenerateConfigs helper function to generate config maps
func 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 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 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 generateConfigsGeneric(ctx, h, instance, GetServiceConfigSecretName(instance.GetName()),
envVars, templateParameters, extraData,
cmLabels, additionalTemplates, commonTemplates, templateDir, true)
}

// generateConfigsGeneric helper function to generate config maps
func 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 {
if templateDir == "" {
return ErrTemplateDirUnset
}

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: additionalTemplates,
CommonTemplates: commonTemplates,
},
}
if withScripts {
cms = append(cms, util.Template{
Name: 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)
}
30 changes: 30 additions & 0 deletions internal/common/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
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"

// Static errors shared across operator services.
var (
// ErrACSecretMissingKeys indicates that the ApplicationCredential secret is missing required keys.
Comment thread
mrkisaolamb marked this conversation as resolved.
ErrACSecretMissingKeys = errors.New("ApplicationCredential secret missing required keys")
// ErrTemplateDirUnset indicates that no template directory was provided.
ErrTemplateDirUnset = errors.New("templateDir must be set")

// ErrUnexpectedObjectType is returned when a webhook receives an unexpected object type.
ErrUnexpectedObjectType = errors.New("unexpected object type")
)
37 changes: 37 additions & 0 deletions internal/common/kolla.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
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 provides shared helpers for nova, placement, and cyborg controllers.
package common //nolint:revive // common is the established package name for multi-group shared code

import "fmt"

const (
// ServiceCommand - the command to start the service binary in the kolla container
ServiceCommand = "/usr/local/bin/kolla_start"
)

// 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)
}
82 changes: 82 additions & 0 deletions internal/common/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
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 the requested network attachments exists and
// returns the annotation to be set on the deployment objects.
func EnsureNetworkAttachments(
Comment thread
mrkisaolamb marked this conversation as resolved.
ctx context.Context,
h *helper.Helper,
networkAttachments []string,
conditionUpdater ConditionUpdater,
requeueTimeout time.Duration,
) (map[string]string, ctrl.Result, error) {
var nadAnnotations map[string]string

// networks to attach to
nadList := []networkv1.NetworkAttachmentDefinition{}
for _, netAtt := range networkAttachments {
nadObject, 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 nadObject != nil {
nadList = append(nadList, *nadObject)
}
}

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
}
Loading
Loading