Skip to content

Commit 5ad106f

Browse files
committed
Refactor common controller functions
This patch removes duplicate code from common controller functions. In the future the code will be easier to maintain thanks to these small changes. This patch includes: - GetContainerImage function refactor - GetPodName function refactor - Functions moved thanks to being Tempest specific
1 parent ae53577 commit 5ad106f

File tree

5 files changed

+106
-162
lines changed

5 files changed

+106
-162
lines changed

controllers/ansibletest_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ func (r *AnsibleTestReconciler) Reconcile(ctx context.Context, req ctrl.Request)
209209
podName := r.GetPodName(instance, nextWorkflowStep)
210210
envVars := r.PrepareAnsibleEnv(instance)
211211
logsPVCName := r.GetPVCLogsName(instance, 0)
212-
containerImage, err := r.GetContainerImage(ctx, instance.Spec.ContainerImage, instance)
212+
containerImage, err := r.GetContainerImage(ctx, instance)
213213
if err != nil {
214214
return ctrl.Result{}, err
215215
}

controllers/common.go

Lines changed: 88 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"reflect"
88
"strconv"
9+
"strings"
910
"time"
1011

1112
"crypto/sha256"
@@ -15,7 +16,6 @@ import (
1516
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
1617
"github.com/openstack-k8s-operators/lib-common/modules/common/pvc"
1718
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
18-
v1beta1 "github.com/openstack-k8s-operators/test-operator/api/v1beta1"
1919
"gopkg.in/yaml.v3"
2020
corev1 "k8s.io/api/core/v1"
2121
rbacv1 "k8s.io/api/rbac/v1"
@@ -30,15 +30,13 @@ import (
3030
)
3131

3232
const (
33-
podNameStepInfix = "-s"
34-
envVarsConfigMapInfix = "-env-vars-s"
35-
customDataConfigMapInfix = "-custom-data-s"
36-
workflowStepNumInvalid = -1
37-
workflowStepNameInvalid = "no-name"
38-
workflowStepLabel = "workflowStep"
39-
instanceNameLabel = "instanceName"
40-
operatorNameLabel = "operator"
41-
33+
podNameStepInfix = "-s"
34+
envVarsConfigMapInfix = "-env-vars-s"
35+
customDataConfigMapInfix = "-custom-data-s"
36+
workflowStepNameInvalid = "no-name"
37+
workflowStepLabel = "workflowStep"
38+
instanceNameLabel = "instanceName"
39+
operatorNameLabel = "operator"
4240
testOperatorLockName = "test-operator-lock"
4341
testOperatorLockOwnerField = "owner"
4442
)
@@ -82,6 +80,15 @@ var (
8280

8381
// ErrLockFieldMissing indicates that a required field is missing in the lock config map.
8482
ErrLockFieldMissing = errors.New("field is missing in the config map")
83+
84+
// ErrFieldExpectedStruct indicates attempting to access a field on a non-struct type.
85+
ErrFieldExpectedStruct = errors.New("field cannot be accessed: expected struct")
86+
87+
// ErrFieldNilPointer indicates attempting to dereference a nil pointer.
88+
ErrFieldNilPointer = errors.New("field cannot be accessed: nil pointer")
89+
90+
// ErrFieldNotFound indicates a field name does not exist on the struct.
91+
ErrFieldNotFound = errors.New("field not found")
8592
)
8693

8794
// Reconciler provides common functionality for all test framework reconcilers
@@ -253,164 +260,68 @@ func (r *Reconciler) GetLastPod(
253260
return maxPod, nil
254261
}
255262

256-
// GetEnvVarsConfigMapName returns the name of the environment variables ConfigMap for the given instance and workflow step
257-
func GetEnvVarsConfigMapName(instance interface{}, workflowStepNum int) string {
258-
if _, ok := instance.(*v1beta1.Tobiko); ok {
259-
return "not-implemented"
260-
} else if typedInstance, ok := instance.(*v1beta1.Tempest); ok {
261-
return typedInstance.Name + envVarsConfigMapInfix + strconv.Itoa(workflowStepNum)
262-
}
263-
264-
return "not-implemented"
265-
}
266-
267-
// GetCustomDataConfigMapName returns the name of the custom data ConfigMap for the given instance and workflow step
268-
func GetCustomDataConfigMapName(instance interface{}, workflowStepNum int) string {
269-
if _, ok := instance.(*v1beta1.Tobiko); ok {
270-
return "not-implemented"
271-
} else if typedInstance, ok := instance.(*v1beta1.Tempest); ok {
272-
return typedInstance.Name + customDataConfigMapInfix + strconv.Itoa(workflowStepNum)
273-
}
274-
275-
return "not-implemented"
276-
}
277-
278263
// GetContainerImage returns the container image to use for the given instance, either from the provided parameter or from configuration
279264
func (r *Reconciler) GetContainerImage(
280265
ctx context.Context,
281-
containerImage string,
282266
instance interface{},
283267
) (string, error) {
284-
cm := &corev1.ConfigMap{}
285-
testOperatorConfigMapName := "test-operator-config"
286-
if typedInstance, ok := instance.(*v1beta1.Tempest); ok {
287-
if len(containerImage) > 0 {
288-
return containerImage, nil
289-
}
290-
291-
objectKey := client.ObjectKey{Namespace: typedInstance.Namespace, Name: testOperatorConfigMapName}
292-
err := r.Client.Get(ctx, objectKey, cm)
293-
if err != nil {
294-
return "", err
295-
}
296-
297-
if cm.Data == nil {
298-
return util.GetEnvVar("RELATED_IMAGE_TEST_TEMPEST_IMAGE_URL_DEFAULT", ""), nil
299-
300-
}
301-
302-
if cmImage, exists := cm.Data["tempest-image"]; exists {
303-
return cmImage, nil
304-
}
305-
306-
return util.GetEnvVar("RELATED_IMAGE_TEST_TEMPEST_IMAGE_URL_DEFAULT", ""), nil
307-
} else if typedInstance, ok := instance.(*v1beta1.Tobiko); ok {
308-
if len(containerImage) > 0 {
309-
return containerImage, nil
310-
}
311-
312-
objectKey := client.ObjectKey{Namespace: typedInstance.Namespace, Name: testOperatorConfigMapName}
313-
err := r.Client.Get(ctx, objectKey, cm)
314-
if err != nil {
315-
return "", err
316-
}
317-
318-
if cm.Data == nil {
319-
return util.GetEnvVar("RELATED_IMAGE_TEST_TOBIKO_IMAGE_URL_DEFAULT", ""), nil
320-
321-
}
322-
323-
if cmImage, exists := cm.Data["tobiko-image"]; exists {
324-
return cmImage, nil
325-
}
326-
327-
return util.GetEnvVar("RELATED_IMAGE_TEST_TOBIKO_IMAGE_URL_DEFAULT", ""), nil
328-
} else if typedInstance, ok := instance.(*v1beta1.HorizonTest); ok {
329-
if len(containerImage) > 0 {
330-
return containerImage, nil
331-
}
332-
333-
objectKey := client.ObjectKey{Namespace: typedInstance.Namespace, Name: testOperatorConfigMapName}
334-
err := r.Client.Get(ctx, objectKey, cm)
335-
if err != nil {
336-
return "", err
337-
}
338-
339-
if cm.Data == nil {
340-
return util.GetEnvVar("RELATED_IMAGE_TEST_HORIZONTEST_IMAGE_URL_DEFAULT", ""), nil
341-
342-
}
268+
v := reflect.ValueOf(instance)
343269

344-
if cmImage, exists := cm.Data["horizontest-image"]; exists {
345-
return cmImage, nil
346-
}
270+
spec, err := SafetyCheck(v, "Spec")
271+
if err != nil {
272+
return "", err
273+
}
347274

348-
return util.GetEnvVar("RELATED_IMAGE_TEST_HORIZONTEST_IMAGE_URL_DEFAULT", ""), nil
349-
} else if typedInstance, ok := instance.(*v1beta1.AnsibleTest); ok {
350-
if len(containerImage) > 0 {
351-
return containerImage, nil
352-
}
275+
containerImage := GetStringField(spec, "ContainerImage")
276+
if containerImage != "" {
277+
return containerImage, nil
278+
}
353279

354-
objectKey := client.ObjectKey{Namespace: typedInstance.Namespace, Name: testOperatorConfigMapName}
355-
err := r.Client.Get(ctx, objectKey, cm)
356-
if err != nil {
357-
return "", err
358-
}
280+
namespace := GetStringField(v, "Namespace")
281+
kind := GetStringField(v, "Kind")
359282

360-
if cm.Data == nil {
361-
return util.GetEnvVar("RELATED_IMAGE_TEST_ANSIBLETEST_IMAGE_URL_DEFAULT", ""), nil
362-
}
283+
cm := &corev1.ConfigMap{}
284+
objectKey := client.ObjectKey{Namespace: namespace, Name: "test-operator-config"}
285+
if err := r.Client.Get(ctx, objectKey, cm); err != nil {
286+
return "", err
287+
}
363288

364-
if cmImage, exists := cm.Data["ansibletest-image"]; exists {
365-
return cmImage, nil
289+
imageKey := strings.ToLower(kind) + "-image"
290+
if cm.Data != nil {
291+
if image, exists := cm.Data[imageKey]; exists && image != "" {
292+
return image, nil
366293
}
367-
368-
return util.GetEnvVar("RELATED_IMAGE_TEST_ANSIBLETEST_IMAGE_URL_DEFAULT", ""), nil
369294
}
370295

371-
return "", nil
296+
relatedImage := "RELATED_IMAGE_TEST_" + strings.ToUpper(kind) + "_IMAGE_URL_DEFAULT"
297+
return util.GetEnvVar(relatedImage, ""), nil
372298
}
373299

374300
// GetPodName returns the name of the pod for the given instance and workflow step
375-
func (r *Reconciler) GetPodName(instance interface{}, workflowStepNum int) string {
376-
if typedInstance, ok := instance.(*v1beta1.Tobiko); ok {
377-
if len(typedInstance.Spec.Workflow) == 0 || workflowStepNum == workflowStepNumInvalid {
378-
return typedInstance.Name
379-
}
380-
381-
workflowStepName := workflowStepNameInvalid
382-
if workflowStepNum < len(typedInstance.Spec.Workflow) {
383-
workflowStepName = typedInstance.Spec.Workflow[workflowStepNum].StepName
384-
}
385-
386-
return typedInstance.Name + podNameStepInfix + fmt.Sprintf("%02d", workflowStepNum) + "-" + workflowStepName
387-
} else if typedInstance, ok := instance.(*v1beta1.Tempest); ok {
388-
if len(typedInstance.Spec.Workflow) == 0 || workflowStepNum == workflowStepNumInvalid {
389-
return typedInstance.Name
390-
}
301+
func (r *Reconciler) GetPodName(instance interface{}, stepNum int) string {
302+
v := reflect.ValueOf(instance)
391303

392-
workflowStepName := workflowStepNameInvalid
393-
if workflowStepNum < len(typedInstance.Spec.Workflow) {
394-
workflowStepName = typedInstance.Spec.Workflow[workflowStepNum].StepName
395-
}
304+
name := GetStringField(v, "Name")
305+
spec, err := SafetyCheck(v, "Spec")
306+
if err != nil {
307+
return name
308+
}
396309

397-
return typedInstance.Name + podNameStepInfix + fmt.Sprintf("%02d", workflowStepNum) + "-" + workflowStepName
398-
} else if typedInstance, ok := instance.(*v1beta1.HorizonTest); ok {
399-
return typedInstance.Name
400-
} else if typedInstance, ok := instance.(*v1beta1.AnsibleTest); ok {
401-
if len(typedInstance.Spec.Workflow) == 0 || workflowStepNum == workflowStepNumInvalid {
402-
return typedInstance.Name
403-
}
310+
workflow, err := SafetyCheck(spec, "Workflow")
311+
if err != nil || workflow.Len() == 0 {
312+
return name
313+
}
404314

405-
workflowStepName := workflowStepNameInvalid
406-
if workflowStepNum < len(typedInstance.Spec.Workflow) {
407-
workflowStepName = typedInstance.Spec.Workflow[workflowStepNum].StepName
315+
// Get workflow step name
316+
stepName := workflowStepNameInvalid
317+
if stepNum >= 0 && stepNum < workflow.Len() {
318+
stepName = GetStringField(workflow.Index(stepNum), "StepName")
319+
if stepName == "" {
320+
stepName = workflowStepNameInvalid
408321
}
409-
410-
return typedInstance.Name + podNameStepInfix + fmt.Sprintf("%02d", workflowStepNum) + "-" + workflowStepName
411322
}
412323

413-
return workflowStepNameInvalid
324+
return name + podNameStepInfix + fmt.Sprintf("%02d", stepNum) + "-" + stepName
414325
}
415326

416327
// GetPVCLogsName returns the name of the PVC for logs for the given instance and workflow step
@@ -493,11 +404,6 @@ func (r *Reconciler) EnsureLogsPVCExists(
493404
return ctrlResult, nil
494405
}
495406

496-
// GetClient returns the Kubernetes client
497-
func (r *Reconciler) GetClient() client.Client {
498-
return r.Client
499-
}
500-
501407
// GetLogger returns the logger instance
502408
func (r *Reconciler) GetLogger() logr.Logger {
503409
return r.Log
@@ -624,12 +530,6 @@ func (r *Reconciler) PodExists(ctx context.Context, instance client.Object, work
624530
return true
625531
}
626532

627-
func (r *Reconciler) setConfigOverwrite(customData map[string]string, configOverwrite map[string]string) {
628-
for key, data := range configOverwrite {
629-
customData[key] = data
630-
}
631-
}
632-
633533
// GetCommonRbacRules returns the common RBAC rules for test operations, with optional privileged permissions
634534
func GetCommonRbacRules(privileged bool) []rbacv1.PolicyRule {
635535
rbacPolicyRule := rbacv1.PolicyRule{
@@ -717,6 +617,37 @@ func EnsureCloudsConfigMapExists(
717617
return ctrl.Result{}, nil
718618
}
719619

620+
// GetStringField returns reflect string field safely
621+
func GetStringField(v reflect.Value, fieldName string) string {
622+
field, err := SafetyCheck(v, fieldName)
623+
if err != nil || field.Kind() != reflect.String {
624+
return ""
625+
}
626+
627+
return field.String()
628+
}
629+
630+
// SafetyCheck returns reflect value after checking its validity
631+
func SafetyCheck(v reflect.Value, fieldName string) (reflect.Value, error) {
632+
if v.Kind() == reflect.Ptr {
633+
if v.IsNil() {
634+
return reflect.Value{}, fmt.Errorf("%s: %w", fieldName, ErrFieldNilPointer)
635+
}
636+
v = v.Elem()
637+
}
638+
639+
if v.Kind() != reflect.Struct {
640+
return reflect.Value{}, fmt.Errorf("%s: %w, got %s", fieldName, ErrFieldExpectedStruct, v.Kind())
641+
}
642+
643+
field := v.FieldByName(fieldName)
644+
if !field.IsValid() {
645+
return reflect.Value{}, fmt.Errorf("%s: %w", fieldName, ErrFieldNotFound)
646+
}
647+
648+
return field, nil
649+
}
650+
720651
// IsEmpty checks if the provided value is empty based on its type
721652
func IsEmpty(value interface{}) bool {
722653
if v, ok := value.(reflect.Value); ok {

controllers/horizontest_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ func (r *HorizonTestReconciler) Reconcile(ctx context.Context, req ctrl.Request)
215215
envVars := r.PrepareHorizonTestEnvVars(instance)
216216
podName := r.GetPodName(instance, 0)
217217
logsPVCName := r.GetPVCLogsName(instance, 0)
218-
containerImage, err := r.GetContainerImage(ctx, instance.Spec.ContainerImage, instance)
218+
containerImage, err := r.GetContainerImage(ctx, instance)
219219
if err != nil {
220220
return ctrl.Result{}, err
221221
}

controllers/tempest_controller.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ func (r *TempestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re
290290
EnvVarsConfigMapName := GetEnvVarsConfigMapName(instance, nextWorkflowStep)
291291
podName := r.GetPodName(instance, nextWorkflowStep)
292292
logsPVCName := r.GetPVCLogsName(instance, workflowStepNum)
293-
containerImage, err := r.GetContainerImage(ctx, instance.Spec.ContainerImage, instance)
293+
containerImage, err := r.GetContainerImage(ctx, instance)
294294
if err != nil {
295295
return ctrl.Result{}, err
296296
}
@@ -589,7 +589,10 @@ func (r *TempestReconciler) generateServiceConfigMaps(
589589

590590
r.setTempestConfigVars(envVars, customData, instance, workflowStepNum)
591591
r.setTempestconfConfigVars(envVars, customData, instance)
592-
r.setConfigOverwrite(customData, instance.Spec.ConfigOverwrite)
592+
593+
for key, data := range instance.Spec.ConfigOverwrite {
594+
customData[key] = data
595+
}
593596

594597
envVars["TEMPEST_DEBUG_MODE"] = strconv.FormatBool(instance.Spec.Debug)
595598
envVars["TEMPEST_CLEANUP"] = strconv.FormatBool(instance.Spec.Cleanup)
@@ -620,3 +623,13 @@ func (r *TempestReconciler) generateServiceConfigMaps(
620623

621624
return configmap.EnsureConfigMaps(ctx, h, instance, cms, nil)
622625
}
626+
627+
// GetEnvVarsConfigMapName returns the name of the environment variables ConfigMap for the given workflow step
628+
func GetEnvVarsConfigMapName(instance *testv1beta1.Tempest, workflowStepNum int) string {
629+
return instance.Name + envVarsConfigMapInfix + strconv.Itoa(workflowStepNum)
630+
}
631+
632+
// GetCustomDataConfigMapName returns the name of the custom data ConfigMap for the given workflow step
633+
func GetCustomDataConfigMapName(instance *testv1beta1.Tempest, workflowStepNum int) string {
634+
return instance.Name + customDataConfigMapInfix + strconv.Itoa(workflowStepNum)
635+
}

controllers/tobiko_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ func (r *TobikoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
306306
envVars := r.PrepareTobikoEnvVars(ctx, serviceLabels, instance, helper, nextWorkflowStep)
307307
podName := r.GetPodName(instance, nextWorkflowStep)
308308
logsPVCName := r.GetPVCLogsName(instance, workflowStepNum)
309-
containerImage, err := r.GetContainerImage(ctx, instance.Spec.ContainerImage, instance)
309+
containerImage, err := r.GetContainerImage(ctx, instance)
310310
if err != nil {
311311
return ctrl.Result{}, err
312312
}

0 commit comments

Comments
 (0)