Skip to content

Commit e4cbde8

Browse files
Merge pull request #351 from kstrenkova/refactor-reconciler-functions
Refactor common controller functions
2 parents ae53577 + 5ad106f commit e4cbde8

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)