From 42c7be05a8dee6d99a79124f3da6ce8eb13edea3 Mon Sep 17 00:00:00 2001 From: Kirsten Laskoski Date: Thu, 2 May 2024 09:45:14 -0400 Subject: [PATCH] cgu: added PreCachingConfig resource and unit tests --- pkg/cgu/precachingconfig.go | 245 ++++++++++++++++++++++++ pkg/cgu/precachingconfig_test.go | 312 +++++++++++++++++++++++++++++++ pkg/clients/clients.go | 2 + 3 files changed, 559 insertions(+) create mode 100644 pkg/cgu/precachingconfig.go create mode 100644 pkg/cgu/precachingconfig_test.go diff --git a/pkg/cgu/precachingconfig.go b/pkg/cgu/precachingconfig.go new file mode 100644 index 000000000..1b31bb9cc --- /dev/null +++ b/pkg/cgu/precachingconfig.go @@ -0,0 +1,245 @@ +package cgu + +import ( + "context" + "fmt" + + "github.com/golang/glog" + "github.com/openshift-kni/cluster-group-upgrades-operator/pkg/api/clustergroupupgrades/v1alpha1" + "github.com/openshift-kni/eco-goinfra/pkg/clients" + "github.com/openshift-kni/eco-goinfra/pkg/msg" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +// PreCachingConfigBuilder provides a struct for the PreCachingConfig object containing a connection to the cluster and +// the PreCachingConfig definition. +type PreCachingConfigBuilder struct { + // Definition of the PreCachingConfig used to create the object. + Definition *v1alpha1.PreCachingConfig + // Object of the PreCachingConfig as it is on the cluster. + Object *v1alpha1.PreCachingConfig + // api client to interact with the cluster. + apiClient *clients.Settings + // used to store latest error message upon defining or mutating application definition. + errorMsg string +} + +// NewPreCachingConfigBuilder creates a new instance of PreCachingConfig. +func NewPreCachingConfigBuilder(apiClient *clients.Settings, name, nsname string) *PreCachingConfigBuilder { + glog.V(100).Infof( + "Initializing new PreCachingConfig structure with the following params: name: %s, nsname: %s", name, nsname) + + builder := PreCachingConfigBuilder{ + apiClient: apiClient, + Definition: &v1alpha1.PreCachingConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: nsname, + }, + }} + + if name == "" { + glog.V(100).Infof("The name of the PreCachingConfig is empty") + + builder.errorMsg = "preCachingConfig 'name' cannot be empty" + } + + if nsname == "" { + glog.V(100).Infof("The namespace of the PreCachingConfig is empty") + + builder.errorMsg = "preCachingConfig 'nsname' cannot be empty" + } + + return &builder +} + +// PullPreCachingConfig pulls an existing PreCachingConfig into a PreCachingConfigBuilder struct. +func PullPreCachingConfig(apiClient *clients.Settings, name, nsname string) (*PreCachingConfigBuilder, error) { + glog.V(100).Infof("Pulling existing PreCachingConfig %s under namespace %s from cluster", name, nsname) + + if apiClient == nil { + glog.V(100).Info("The apiClient is empty") + + return nil, fmt.Errorf("preCachingConfig 'apiClient' cannot be empty") + } + + builder := PreCachingConfigBuilder{ + apiClient: apiClient, + Definition: &v1alpha1.PreCachingConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: nsname, + }, + }, + } + + if name == "" { + glog.V(100).Infof("The name of the PreCachingConfig is empty") + + return nil, fmt.Errorf("preCachingConfig 'name' cannot be empty") + } + + if nsname == "" { + glog.V(100).Infof("The namespace of the PreCachingConfig is empty") + + return nil, fmt.Errorf("preCachingConfig 'nsname' cannot be empty") + } + + if !builder.Exists() { + return nil, fmt.Errorf("preCachingConfig object %s doesn't exist in namespace %s", name, nsname) + } + + builder.Definition = builder.Object + + return &builder, nil +} + +// Exists checks whether the given PreCachingConfig exists on the apiClient. +func (builder *PreCachingConfigBuilder) Exists() bool { + if valid, _ := builder.validate(); !valid { + return false + } + + glog.V(100).Infof( + "Checking if preCachingConfig %s exists in namespace %s", builder.Definition.Name, builder.Definition.Namespace) + + var err error + builder.Object, err = builder.Get() + + return err == nil || !k8serrors.IsNotFound(err) +} + +// Get pulls the PreCachingConfig from the apiClient into the PreCachingConfigBuilder. +func (builder *PreCachingConfigBuilder) Get() (*v1alpha1.PreCachingConfig, error) { + if valid, err := builder.validate(); !valid { + return nil, err + } + + glog.V(100).Infof("Getting PreCachingConfig %s in namespace %s", builder.Definition.Name, builder.Definition.Namespace) + + preCachingConfig := &v1alpha1.PreCachingConfig{} + + err := builder.apiClient.Get(context.TODO(), runtimeclient.ObjectKey{ + Name: builder.Definition.Name, + Namespace: builder.Definition.Namespace, + }, preCachingConfig) + if err != nil { + return nil, err + } + + return preCachingConfig, nil +} + +// Create makes a PreCachingConfig on the apiClient if it doesn't already exist. +func (builder *PreCachingConfigBuilder) Create() (*PreCachingConfigBuilder, error) { + if valid, err := builder.validate(); !valid { + return nil, err + } + + glog.V(100).Infof( + "Creating the PreCachingConfig %s in namespace %s", builder.Definition.Name, builder.Definition.Namespace) + + if builder.Exists() { + return builder, nil + } + + err := builder.apiClient.Create(context.TODO(), builder.Definition) + if err != nil { + return nil, err + } + + builder.Object = builder.Definition + + return builder, nil +} + +// Delete removes a PreCachingConfig from the apiClient if it exists. +func (builder *PreCachingConfigBuilder) Delete() error { + if valid, err := builder.validate(); !valid { + return err + } + + glog.V(100).Infof( + "Deleting the PreCachingConfig %s in namespace %s", builder.Definition.Name, builder.Definition.Namespace) + + if !builder.Exists() { + builder.Object = nil + + return nil + } + + err := builder.apiClient.Delete(context.TODO(), builder.Definition) + if err != nil { + return err + } + + builder.Object = nil + + return nil +} + +// Update changes the existing PreCachingConfig object on the apiClient, falling back to deleting and recreating it if +// force is set. +func (builder *PreCachingConfigBuilder) Update(force bool) (*PreCachingConfigBuilder, error) { + if valid, err := builder.validate(); !valid { + return nil, err + } + + glog.V(100).Infof( + "Updating the PreCachingConfig %s in namespace %s", builder.Definition.Name, builder.Definition.Namespace) + + err := builder.apiClient.Update(context.TODO(), builder.Definition) + if err != nil { + if force { + glog.V(100).Infof(msg.FailToUpdateNotification("preCachingConfig", builder.Definition.Name)) + + err := builder.Delete() + if err != nil { + glog.V(100).Infof(msg.FailToUpdateError("preCachingConfig", builder.Definition.Name)) + + return nil, err + } + + return builder.Create() + } + + return nil, err + } + + builder.Object = builder.Definition + + return builder, nil +} + +// validate checks that the builder, definition, and apiClient are properly initialized and there is no errorMsg. +func (builder *PreCachingConfigBuilder) validate() (bool, error) { + resourceCRD := "preCachingConfig" + + if builder == nil { + glog.V(100).Infof("The %s builder is uninitialized", resourceCRD) + + return false, fmt.Errorf("error received nil %s builder", resourceCRD) + } + + if builder.Definition == nil { + glog.V(100).Infof("The %s is uninitialized", resourceCRD) + + return false, fmt.Errorf(msg.UndefinedCrdObjectErrString(resourceCRD)) + } + + if builder.apiClient == nil { + glog.V(100).Infof("The %s builder apiClient is nil", resourceCRD) + + return false, fmt.Errorf("%s builder cannot have nil apiClient", resourceCRD) + } + + if builder.errorMsg != "" { + glog.V(100).Infof("The %s builder has error message %s", resourceCRD, builder.errorMsg) + + return false, fmt.Errorf(builder.errorMsg) + } + + return true, nil +} diff --git a/pkg/cgu/precachingconfig_test.go b/pkg/cgu/precachingconfig_test.go new file mode 100644 index 000000000..2c3041ff1 --- /dev/null +++ b/pkg/cgu/precachingconfig_test.go @@ -0,0 +1,312 @@ +package cgu + +import ( + "fmt" + "testing" + + "github.com/openshift-kni/cluster-group-upgrades-operator/pkg/api/clustergroupupgrades/v1alpha1" + "github.com/openshift-kni/eco-goinfra/pkg/clients" + "github.com/stretchr/testify/assert" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + defaultPreCachingConfigName = "precachingconfig-test" + defaultPreCachingConfigNsName = "test-ns" +) + +func TestNewPreCachingConfigBuilder(t *testing.T) { + testCases := []struct { + preCachingConfigName string + preCachingConfigNamespace string + expectedErrorText string + }{ + { + preCachingConfigName: defaultPreCachingConfigName, + preCachingConfigNamespace: defaultPreCachingConfigNsName, + expectedErrorText: "", + }, + { + preCachingConfigName: "", + preCachingConfigNamespace: defaultPreCachingConfigNsName, + expectedErrorText: "preCachingConfig 'name' cannot be empty", + }, + { + preCachingConfigName: defaultPreCachingConfigName, + preCachingConfigNamespace: "", + expectedErrorText: "preCachingConfig 'nsname' cannot be empty", + }, + } + + for _, testCase := range testCases { + testSettings := clients.GetTestClients(clients.TestClientParams{}) + preCachingConfigBuilder := NewPreCachingConfigBuilder( + testSettings, testCase.preCachingConfigName, testCase.preCachingConfigNamespace) + assert.NotNil(t, preCachingConfigBuilder) + assert.Equal(t, testCase.expectedErrorText, preCachingConfigBuilder.errorMsg) + } +} + +func TestPullPreCachingConfig(t *testing.T) { + testCases := []struct { + preCachingConfigName string + preCachingConfigNamespace string + addToRuntimeObjects bool + client bool + expectedErrorText string + }{ + { + preCachingConfigName: defaultPreCachingConfigName, + preCachingConfigNamespace: defaultPreCachingConfigNsName, + addToRuntimeObjects: true, + client: true, + expectedErrorText: "", + }, + { + preCachingConfigName: defaultPreCachingConfigName, + preCachingConfigNamespace: defaultPreCachingConfigNsName, + addToRuntimeObjects: false, + client: true, + expectedErrorText: fmt.Sprintf( + "preCachingConfig object %s doesn't exist in namespace %s", + defaultPreCachingConfigName, defaultPreCachingConfigNsName), + }, + { + preCachingConfigName: "", + preCachingConfigNamespace: defaultPreCachingConfigNsName, + addToRuntimeObjects: false, + client: true, + expectedErrorText: "preCachingConfig 'name' cannot be empty", + }, + { + preCachingConfigName: defaultPreCachingConfigName, + preCachingConfigNamespace: "", + addToRuntimeObjects: false, + client: true, + expectedErrorText: "preCachingConfig 'nsname' cannot be empty", + }, + { + preCachingConfigName: defaultPreCachingConfigName, + preCachingConfigNamespace: defaultPreCachingConfigNsName, + addToRuntimeObjects: false, + client: false, + expectedErrorText: "preCachingConfig 'apiClient' cannot be empty", + }, + } + + for _, testCase := range testCases { + var ( + runtimeObjects []runtime.Object + testSettings *clients.Settings + ) + + testPreCachingConfig := buildDummyPreCachingConfig(testCase.preCachingConfigName, testCase.preCachingConfigNamespace) + + if testCase.addToRuntimeObjects { + runtimeObjects = append(runtimeObjects, testPreCachingConfig) + } + + if testCase.client { + testSettings = clients.GetTestClients(clients.TestClientParams{ + K8sMockObjects: runtimeObjects, + }) + } + + preCachingConfigBuilder, err := PullPreCachingConfig( + testSettings, testPreCachingConfig.Name, testPreCachingConfig.Namespace) + + if testCase.expectedErrorText != "" { + assert.NotNil(t, err) + assert.Equal(t, testCase.expectedErrorText, err.Error()) + } else { + assert.Nil(t, err) + assert.Equal(t, testPreCachingConfig.Name, preCachingConfigBuilder.Object.Name) + assert.Equal(t, testPreCachingConfig.Namespace, preCachingConfigBuilder.Object.Namespace) + } + } +} + +func TestPreCachingConfigExists(t *testing.T) { + testCases := []struct { + testBuilder *PreCachingConfigBuilder + exists bool + }{ + { + testBuilder: buildValidPreCachingConfigTestBuilder(buildTestClientWithDummyPreCachingConfig()), + exists: true, + }, + { + testBuilder: buildInvalidPreCachingConfigTestBuilder(buildTestClientWithDummyPreCachingConfig()), + exists: false, + }, + } + + for _, testCase := range testCases { + exists := testCase.testBuilder.Exists() + assert.Equal(t, testCase.exists, exists) + } +} + +func TestPreCachingConfigGet(t *testing.T) { + testCases := []struct { + testBuilder *PreCachingConfigBuilder + expectedPreCachingConfig *v1alpha1.PreCachingConfig + }{ + { + testBuilder: buildValidPreCachingConfigTestBuilder(buildTestClientWithDummyPreCachingConfig()), + expectedPreCachingConfig: buildDummyPreCachingConfig(defaultPreCachingConfigName, defaultPreCachingConfigNsName), + }, + { + testBuilder: buildValidPreCachingConfigTestBuilder(clients.GetTestClients(clients.TestClientParams{})), + expectedPreCachingConfig: nil, + }, + } + + for _, testCase := range testCases { + preCachingConfig, err := testCase.testBuilder.Get() + + if testCase.expectedPreCachingConfig == nil { + assert.Nil(t, preCachingConfig) + assert.True(t, k8serrors.IsNotFound(err)) + } else { + assert.Nil(t, err) + assert.Equal(t, testCase.expectedPreCachingConfig.Name, preCachingConfig.Name) + assert.Equal(t, testCase.expectedPreCachingConfig.Namespace, preCachingConfig.Namespace) + } + } +} + +func TestPreCachingConfigCreate(t *testing.T) { + testCases := []struct { + testBuilder *PreCachingConfigBuilder + expectedError error + }{ + { + testBuilder: buildValidPreCachingConfigTestBuilder(clients.GetTestClients(clients.TestClientParams{})), + expectedError: nil, + }, + { + testBuilder: buildInvalidPreCachingConfigTestBuilder(clients.GetTestClients(clients.TestClientParams{})), + expectedError: fmt.Errorf("preCachingConfig 'nsname' cannot be empty"), + }, + } + + for _, testCase := range testCases { + preCachingConfigBuilder, err := testCase.testBuilder.Create() + assert.Equal(t, testCase.expectedError, err) + + if testCase.expectedError == nil { + assert.Equal(t, preCachingConfigBuilder.Definition, preCachingConfigBuilder.Object) + } + } +} + +func TestPreCachingConfigDelete(t *testing.T) { + testCases := []struct { + testBuilder *PreCachingConfigBuilder + expectedError error + }{ + { + testBuilder: buildValidPreCachingConfigTestBuilder(buildTestClientWithDummyPreCachingConfig()), + expectedError: nil, + }, + { + testBuilder: buildInvalidPreCachingConfigTestBuilder(buildTestClientWithDummyPreCachingConfig()), + expectedError: fmt.Errorf("preCachingConfig 'nsname' cannot be empty"), + }, + } + + for _, testCase := range testCases { + err := testCase.testBuilder.Delete() + assert.Equal(t, testCase.expectedError, err) + + if testCase.expectedError == nil { + assert.Nil(t, testCase.testBuilder.Object) + } + } +} + +func TestPreCachingConfigUpdate(t *testing.T) { + testCases := []struct { + alreadyExists bool + force bool + }{ + { + alreadyExists: false, + force: false, + }, + { + alreadyExists: true, + force: false, + }, + { + alreadyExists: false, + force: true, + }, + { + alreadyExists: true, + force: true, + }, + } + + for _, testCase := range testCases { + testBuilder := buildValidPreCachingConfigTestBuilder(clients.GetTestClients(clients.TestClientParams{})) + + // Create the builder rather than just adding it to the client so that the proper metadata is added and + // the update won't fail. + if testCase.alreadyExists { + var err error + + testBuilder = buildValidPreCachingConfigTestBuilder(clients.GetTestClients(clients.TestClientParams{})) + testBuilder, err = testBuilder.Create() + assert.Nil(t, err) + } + + assert.NotNil(t, testBuilder.Definition) + assert.Empty(t, testBuilder.Definition.Spec.SpaceRequired) + + testBuilder.Definition.Spec.SpaceRequired = "10 GiB" + + preCachingConfigBuilder, err := testBuilder.Update(testCase.force) + assert.NotNil(t, testBuilder.Definition) + + if testCase.alreadyExists || testCase.force { + assert.Nil(t, err) + assert.Equal(t, testBuilder.Definition.Name, preCachingConfigBuilder.Definition.Name) + assert.Equal(t, testBuilder.Definition.Spec.SpaceRequired, preCachingConfigBuilder.Definition.Spec.SpaceRequired) + } else { + assert.NotNil(t, err) + } + } +} + +// buildDummyPreCachingConfig returns a PreCachingConfig with the provided name and namespace. +func buildDummyPreCachingConfig(name, nsname string) *v1alpha1.PreCachingConfig { + return &v1alpha1.PreCachingConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: nsname, + }, + } +} + +// buildTestClientWithDummyPreCachingConfig returns a client with a mock dummy PreCachingConfig. +func buildTestClientWithDummyPreCachingConfig() *clients.Settings { + return clients.GetTestClients(clients.TestClientParams{ + K8sMockObjects: []runtime.Object{ + buildDummyPreCachingConfig(defaultPreCachingConfigName, defaultPreCachingConfigNsName), + }, + }) +} + +// buildValidPreCachingConfigTestBuilder returns a valid PreCachingConfigBuilder for testing. +func buildValidPreCachingConfigTestBuilder(apiClient *clients.Settings) *PreCachingConfigBuilder { + return NewPreCachingConfigBuilder(apiClient, defaultPreCachingConfigName, defaultPreCachingConfigNsName) +} + +// buildInvalidPreCachingConfigTestBuilder returns an invalid PreCachingConfigBuilder for testing. +func buildInvalidPreCachingConfigTestBuilder(apiClient *clients.Settings) *PreCachingConfigBuilder { + return NewPreCachingConfigBuilder(apiClient, defaultPreCachingConfigName, "") +} diff --git a/pkg/clients/clients.go b/pkg/clients/clients.go index 04736c04d..af01d8cc0 100644 --- a/pkg/clients/clients.go +++ b/pkg/clients/clients.go @@ -453,6 +453,8 @@ func GetTestClients(tcp TestClientParams) *Settings { genericClientObjects = append(genericClientObjects, v) case *configV1.ClusterOperator: genericClientObjects = append(genericClientObjects, v) + case *cguapiv1alpha1.PreCachingConfig: + genericClientObjects = append(genericClientObjects, v) // ArgoCD Client Objects case *argocdOperatorv1alpha1.ArgoCD: genericClientObjects = append(genericClientObjects, v)