Skip to content

Commit 1ef05f9

Browse files
Unify placement controller with internal/common helpers
Use shared EnsureSecret, EnsureNetworkAttachments, and EnsureTopology from internal/common in the placement API controller. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent b14304e commit 1ef05f9

2 files changed

Lines changed: 38 additions & 181 deletions

File tree

api/placement/v1beta1/api_types.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,26 @@ func SetupDefaults() {
247247
SetupPlacementAPIDefaults(placementDefaults)
248248
}
249249

250-
// GetSecret returns the value of the Nova.Spec.Secret
250+
// GetSecret returns the value of the PlacementAPI.Spec.Secret
251251
func (instance PlacementAPI) GetSecret() string {
252252
return instance.Spec.Secret
253253
}
254254

255+
// GetSpecTopologyRef returns the TopologyRef from the Spec
256+
func (instance *PlacementAPI) GetSpecTopologyRef() *topologyv1.TopoRef {
257+
return instance.Spec.TopologyRef
258+
}
259+
260+
// GetLastAppliedTopology returns the LastAppliedTopology from the Status
261+
func (instance *PlacementAPI) GetLastAppliedTopology() *topologyv1.TopoRef {
262+
return instance.Status.LastAppliedTopology
263+
}
264+
265+
// SetLastAppliedTopology sets the LastAppliedTopology value in the Status
266+
func (instance *PlacementAPI) SetLastAppliedTopology(topologyRef *topologyv1.TopoRef) {
267+
instance.Status.LastAppliedTopology = topologyRef
268+
}
269+
255270
// ValidateTopology -
256271
func (instance *PlacementAPISpecCore) ValidateTopology(
257272
basePath *field.Path,

internal/controller/placement/api_controller.go

Lines changed: 22 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package controller
1919

2020
import (
2121
"context"
22-
"errors"
2322
"fmt"
2423
"maps"
2524
"slices"
@@ -40,7 +39,6 @@ import (
4039
"sigs.k8s.io/controller-runtime/pkg/reconcile"
4140

4241
"github.com/go-logr/logr"
43-
networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
4442
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
4543
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
4644
common "github.com/openstack-k8s-operators/lib-common/modules/common"
@@ -61,6 +59,7 @@ import (
6159
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
6260

6361
placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1"
62+
internalcommon "github.com/openstack-k8s-operators/nova-operator/internal/common"
6463
placement "github.com/openstack-k8s-operators/nova-operator/internal/placement"
6564

6665
appsv1 "k8s.io/api/apps/v1"
@@ -70,91 +69,6 @@ import (
7069
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
7170
)
7271

73-
// Static errors for Application Credential handling
74-
var (
75-
ErrACSecretNotFound = errors.New("ApplicationCredential secret not found")
76-
ErrACSecretMissingKeys = errors.New("ApplicationCredential secret missing required keys")
77-
)
78-
79-
type conditionUpdater interface {
80-
Set(c *condition.Condition)
81-
MarkTrue(t condition.Type, messageFormat string, messageArgs ...any)
82-
}
83-
84-
// GetSecret interface defines methods for objects that can provide secret names
85-
type GetSecret interface {
86-
GetSecret() string
87-
client.Object
88-
}
89-
90-
// ensureSecret - ensures that the Secret object exists and the expected fields
91-
// are in the Secret. It returns a hash of the values of the expected fields.
92-
func ensureSecret(
93-
ctx context.Context,
94-
secretName types.NamespacedName,
95-
expectedFields []string,
96-
reader client.Reader,
97-
conditionUpdater conditionUpdater,
98-
) (string, ctrl.Result, corev1.Secret, error) {
99-
secret := &corev1.Secret{}
100-
err := reader.Get(ctx, secretName, secret)
101-
if err != nil {
102-
if k8s_errors.IsNotFound(err) {
103-
// This is currently only used to acquire the password secret, which should have been
104-
// manually created by the user and referenced in the spec, so we treat this as a
105-
// warning because it means that the service will not be able to start.
106-
conditionUpdater.Set(condition.FalseCondition(
107-
condition.InputReadyCondition,
108-
condition.ErrorReason,
109-
condition.SeverityWarning,
110-
"%s", fmt.Sprintf("Input data resources missing: %s", "secret/"+secretName.Name)))
111-
return "",
112-
ctrl.Result{},
113-
*secret,
114-
fmt.Errorf("%w: Secret %s not found", err, secretName)
115-
}
116-
conditionUpdater.Set(condition.FalseCondition(
117-
condition.InputReadyCondition,
118-
condition.ErrorReason,
119-
condition.SeverityWarning,
120-
condition.InputReadyErrorMessage,
121-
err.Error()))
122-
return "", ctrl.Result{}, *secret, err
123-
}
124-
125-
// collect the secret values the caller expects to exist
126-
values := [][]byte{}
127-
for _, field := range expectedFields {
128-
val, ok := secret.Data[field]
129-
if !ok {
130-
err := fmt.Errorf("%w: field '%s' not found in secret/%s", util.ErrFieldNotFound, field, secretName.Name)
131-
conditionUpdater.Set(condition.FalseCondition(
132-
condition.InputReadyCondition,
133-
condition.ErrorReason,
134-
condition.SeverityWarning,
135-
condition.InputReadyErrorMessage,
136-
err.Error()))
137-
return "", ctrl.Result{}, *secret, err
138-
}
139-
values = append(values, val)
140-
}
141-
142-
// TODO(gibi): Do we need to watch the Secret for changes?
143-
144-
hash, err := util.ObjectHash(values)
145-
if err != nil {
146-
conditionUpdater.Set(condition.FalseCondition(
147-
condition.InputReadyCondition,
148-
condition.ErrorReason,
149-
condition.SeverityWarning,
150-
condition.InputReadyErrorMessage,
151-
err.Error()))
152-
return "", ctrl.Result{}, *secret, err
153-
}
154-
155-
return hash, ctrl.Result{}, *secret, nil
156-
}
157-
15872
// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields
15973
func (r *PlacementAPIReconciler) GetLogger(ctx context.Context) logr.Logger {
16074
return log.FromContext(ctx).WithName("Controllers").WithName("PlacementAPI")
@@ -307,34 +221,26 @@ func (r *PlacementAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request
307221
//
308222
// check for required OpenStack secret holding passwords for service/admin user and add hash to the vars map
309223
//
310-
hash, result, secret, err := ensureSecret(
224+
hash, result, secret, err := internalcommon.EnsureSecret(
311225
ctx,
312226
types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret},
313227
[]string{
314228
instance.Spec.PasswordSelectors.Service,
315229
},
316230
h.GetClient(),
317-
&instance.Status.Conditions)
231+
&instance.Status.Conditions,
232+
time.Second*10,
233+
)
318234
if err != nil {
319-
if k8s_errors.IsNotFound(err) {
320-
// Since the service secret should have been manually created by the user and referenced in the spec,
321-
// we treat this as a warning because it means that the service will not be able to start.
322-
Log.Info(fmt.Sprintf("OpenStack secret %s not found", instance.Spec.Secret))
323-
instance.Status.Conditions.Set(condition.FalseCondition(
324-
condition.InputReadyCondition,
325-
condition.ErrorReason,
326-
condition.SeverityWarning,
327-
condition.InputReadyWaitingMessage))
328-
return ctrl.Result{RequeueAfter: time.Second * 10}, nil
329-
}
330-
instance.Status.Conditions.Set(condition.FalseCondition(
331-
condition.InputReadyCondition,
332-
condition.ErrorReason,
333-
condition.SeverityWarning,
334-
condition.InputReadyErrorMessage,
335-
err.Error()))
336235
return result, err
337236
}
237+
if (result != ctrl.Result{}) {
238+
// Since the service secret should have been manually created by the user and referenced in the spec,
239+
// we treat this as a warning because it means that the service will not be able to start.
240+
// InputReady condition and requeue are already set by EnsureSecret above.
241+
Log.Info(fmt.Sprintf("OpenStack secret %s not found", instance.Spec.Secret))
242+
return result, nil
243+
}
338244
configMapVars[instance.Spec.Secret] = env.SetValue(hash)
339245

340246
// all our input checks out so report InputReady
@@ -472,7 +378,7 @@ func (r *PlacementAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request
472378

473379
instance.Status.Conditions.MarkTrue(condition.ServiceConfigReadyCondition, condition.ServiceConfigReadyMessage)
474380

475-
serviceAnnotations, result, err := r.ensureNetworkAttachments(ctx, h, instance)
381+
serviceAnnotations, result, err := internalcommon.EnsureNetworkAttachments(ctx, h, instance.Spec.NetworkAttachments, &instance.Status.Conditions, time.Second*10)
476382
if (err != nil || result != ctrl.Result{}) {
477383
return result, err
478384
}
@@ -660,54 +566,6 @@ func (r *PlacementAPIReconciler) ensureServiceExposed(
660566
return apiEndpoints, ctrl.Result{}, nil
661567
}
662568

663-
func (r *PlacementAPIReconciler) ensureNetworkAttachments(
664-
ctx context.Context,
665-
h *helper.Helper,
666-
instance *placementv1.PlacementAPI,
667-
) (map[string]string, ctrl.Result, error) {
668-
var nadAnnotations map[string]string
669-
var err error
670-
671-
Log := r.GetLogger(ctx)
672-
// networks to attach to
673-
nadList := []networkv1.NetworkAttachmentDefinition{}
674-
for _, netAtt := range instance.Spec.NetworkAttachments {
675-
nad, err := nad.GetNADWithName(ctx, h, netAtt, instance.Namespace)
676-
if err != nil {
677-
if k8s_errors.IsNotFound(err) {
678-
// Since the net-attach-def CR should have been manually created by the user and referenced in the spec,
679-
// we treat this as a warning because it means that the service will not be able to start.
680-
Log.Info(fmt.Sprintf("network-attachment-definition %s not found", netAtt))
681-
instance.Status.Conditions.Set(condition.FalseCondition(
682-
condition.NetworkAttachmentsReadyCondition,
683-
condition.ErrorReason,
684-
condition.SeverityWarning,
685-
condition.NetworkAttachmentsReadyWaitingMessage,
686-
netAtt))
687-
return nadAnnotations, ctrl.Result{RequeueAfter: time.Second * 10}, nil
688-
}
689-
instance.Status.Conditions.Set(condition.FalseCondition(
690-
condition.NetworkAttachmentsReadyCondition,
691-
condition.ErrorReason,
692-
condition.SeverityWarning,
693-
condition.NetworkAttachmentsReadyErrorMessage,
694-
err.Error()))
695-
return nadAnnotations, ctrl.Result{}, err
696-
}
697-
698-
if nad != nil {
699-
nadList = append(nadList, *nad)
700-
}
701-
}
702-
703-
nadAnnotations, err = nad.EnsureNetworksAnnotation(nadList)
704-
if err != nil {
705-
return nadAnnotations, ctrl.Result{}, fmt.Errorf("failed create network annotation from %s: %w",
706-
instance.Spec.NetworkAttachments, err)
707-
}
708-
return nadAnnotations, ctrl.Result{}, nil
709-
}
710-
711569
func (r *PlacementAPIReconciler) ensureKeystoneServiceUser(
712570
ctx context.Context,
713571
h *helper.Helper,
@@ -1268,35 +1126,19 @@ func (r *PlacementAPIReconciler) ensureDeployment(
12681126
//
12691127
// Handle Topology
12701128
//
1271-
topology, err := topologyv1.EnsureServiceTopology(
1129+
// If TopologyRef is present and ensureServiceTopology returned a valid
1130+
// topology object, set .Status.LastAppliedTopology to the referenced one
1131+
// and mark the condition as true
1132+
topology, err := internalcommon.EnsureTopology(
12721133
ctx,
12731134
h,
1274-
instance.Spec.TopologyRef,
1275-
instance.Status.LastAppliedTopology,
1135+
instance,
12761136
instance.Name,
1137+
&instance.Status.Conditions,
12771138
labels.GetLabelSelector(serviceLabels),
12781139
)
12791140
if err != nil {
1280-
instance.Status.Conditions.Set(condition.FalseCondition(
1281-
condition.TopologyReadyCondition,
1282-
condition.ErrorReason,
1283-
condition.SeverityWarning,
1284-
condition.TopologyReadyErrorMessage,
1285-
err.Error()))
1286-
return ctrl.Result{}, fmt.Errorf("waiting for Topology requirements: %w", err)
1287-
}
1288-
1289-
// If TopologyRef is present and ensureServiceTopology returned a valid
1290-
// topology object, set .Status.LastAppliedTopology to the referenced one
1291-
// and mark the condition as true
1292-
if instance.Spec.TopologyRef != nil {
1293-
// update the Status with the last retrieved Topology name
1294-
instance.Status.LastAppliedTopology = instance.Spec.TopologyRef
1295-
// update the TopologyRef associated condition
1296-
instance.Status.Conditions.MarkTrue(condition.TopologyReadyCondition, condition.TopologyReadyMessage)
1297-
} else {
1298-
// remove LastAppliedTopology from the .Status
1299-
instance.Status.LastAppliedTopology = nil
1141+
return ctrl.Result{}, err
13001142
}
13011143

13021144
// Define a new Deployment object
@@ -1472,7 +1314,7 @@ func (r *PlacementAPIReconciler) generateServiceConfigMaps(
14721314
if err != nil {
14731315
if k8s_errors.IsNotFound(err) {
14741316
h.GetLogger().Info("ApplicationCredential secret not found, waiting", "secret", instance.Spec.Auth.ApplicationCredentialSecret)
1475-
return fmt.Errorf("%w: %s", ErrACSecretNotFound, instance.Spec.Auth.ApplicationCredentialSecret)
1317+
return fmt.Errorf("%w: %s", internalcommon.ErrACSecretNotFound, instance.Spec.Auth.ApplicationCredentialSecret)
14761318
}
14771319
h.GetLogger().Error(err, "Failed to get ApplicationCredential secret", "secret", instance.Spec.Auth.ApplicationCredentialSecret)
14781320
return err
@@ -1481,7 +1323,7 @@ func (r *PlacementAPIReconciler) generateServiceConfigMaps(
14811323
acSecretData, okSecret := acSecretObj.Data[keystonev1.ACSecretSecretKey]
14821324
if !okID || len(acID) == 0 || !okSecret || len(acSecretData) == 0 {
14831325
h.GetLogger().Info("ApplicationCredential secret missing required keys", "secret", instance.Spec.Auth.ApplicationCredentialSecret)
1484-
return fmt.Errorf("%w: %s", ErrACSecretMissingKeys, instance.Spec.Auth.ApplicationCredentialSecret)
1326+
return fmt.Errorf("%w: %s", internalcommon.ErrACSecretMissingKeys, instance.Spec.Auth.ApplicationCredentialSecret)
14851327
}
14861328
templateParameters["UseApplicationCredentials"] = true
14871329
templateParameters["ACID"] = string(acID)

0 commit comments

Comments
 (0)