diff --git a/Makefile b/Makefile index ee775785b..c7a1f807c 100644 --- a/Makefile +++ b/Makefile @@ -79,8 +79,7 @@ test: manifests gen-semver fmt vet envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out controller-unit-test: - go clean -cache && go test -v -coverpkg=github.com/dell/csm-operator/pkg/logger,github.com/dell/csm-operator/pkg/resources/daemonset,github.com/dell/csm-operator/pkg/resources/deployment,github.com/dell/csm-operator/pkg/resources/configmap,github.com/dell/csm-operator/pkg/resources/serviceaccount,github.com/dell/csm-operator/pkg/resources/rbac,github.com/dell/csm-operator/pkg/resources/csidriver,github.com/dell/csm-operator/pkg/constants,github.com/dell/csm-operator/controllers -coverprofile=c.out github.com/dell/csm-operator/controllers - + go clean -cache && go test -v -coverpkg=github.com/dell/csm-operator/pkg/logger,github.com/dell/csm-operator/pkg/resources/daemonset,github.com/dell/csm-operator/pkg/resources/deployment,github.com/dell/csm-operator/pkg/resources/statefulset,github.com/dell/csm-operator/pkg/resources/configmap,github.com/dell/csm-operator/pkg/resources/serviceaccount,github.com/dell/csm-operator/pkg/resources/rbac,github.com/dell/csm-operator/pkg/resources/csidriver,github.com/dell/csm-operator/pkg/constants,github.com/dell/csm-operator/controllers -coverprofile=c.out github.com/dell/csm-operator/controllers driver-unit-test: go clean -cache && go test -v -coverprofile=c.out github.com/dell/csm-operator/pkg/drivers diff --git a/api/v1/types.go b/api/v1/types.go index e32e921e7..679d0070e 100644 --- a/api/v1/types.go +++ b/api/v1/types.go @@ -42,6 +42,9 @@ type ObservabilityComponentType string // ClientType - the type of the client type ClientType string +// AccType - the type of the client +type AccType string + const ( // Replication - placeholder for replication constant Replication ModuleType = "replication" diff --git a/controllers/acc_controller.go b/controllers/acc_controller.go index a2fb3fca9..304cc55ef 100644 --- a/controllers/acc_controller.go +++ b/controllers/acc_controller.go @@ -14,15 +14,20 @@ package controllers import ( "context" + "errors" "fmt" "os" "path/filepath" "strconv" - "strings" "sync" "sync/atomic" "time" + "github.com/dell/csm-operator/pkg/drivers" + "github.com/dell/csm-operator/pkg/resources/rbac" + "github.com/dell/csm-operator/pkg/resources/serviceaccount" + "github.com/dell/csm-operator/pkg/resources/statefulset" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" crclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -54,51 +59,6 @@ const ( // AccManifest - deployment resources for Apex Connectivity Client AccManifest string = "statefulset.yaml" - // AccNamespace - deployment namespace - AccNamespace string = "" - - // AggregatorURLDefault - default aggregator location - AggregatorURLDefault string = "connect-into.dell.com" - - // AggregatorURL - tag for specifying aggregator endpoint - AggregatorURL string = "" - - // CaCertOption - tag for specifying if cacert option is used - CaCertOption string = "" - - // CaCertFlag - cacert option - CaCertFlag string = "--cacert" - - // CaCerts - tag for specifying --cacert value - CaCerts string = "" - - // CaCertsList - cert locations for aggregator and loadbalancer - CaCertsList string = "/opt/dellemc/certs/loadbalancer_root_ca_cert.crt,/opt/dellemc/certs/aggregator_internal_root_ca_cert.crt" - - // ConnectivityClientContainerName - name of the DCM client container - ConnectivityClientContainerName string = "connectivity-client-docker-k8s" - - // ConnectivityClientContainerImage - tag for DCM client image - ConnectivityClientContainerImage string = "" - - // KubernetesProxySidecarName - name of proxy sidecar container - KubernetesProxySidecarName string = "kubernetes-proxy" - - // KubernetesProxySidecarImage - tag for proxy image - KubernetesProxySidecarImage string = "" - - // CertPersisterSidecarName - name of cert persister image - CertPersisterSidecarName string = "cert-persister" - - // CertPersisterSidecarImage - name of cert persister image - CertPersisterSidecarImage string = "" - - // AccInitContainerName - name of init container image - AccInitContainerName string = "connectivity-client-init" - - // AccInitContainerImage - tag for init container image - AccInitContainerImage string = "" - // BrownfieldManifest - manifest for brownfield role/rolebinding creation BrownfieldManifest string = "brownfield-onboard.yaml" ) @@ -118,18 +78,32 @@ type ApexConnectivityClientReconciler struct { EventRecorder record.EventRecorder } +// AccConfig - Apex Connectivity Client Config +type AccConfig struct { + Controller *utils.StatefulControllerYAML +} + const ( // AccMetadataPrefix - prefix for all labels & annotations AccMetadataPrefix = "storage.dell.com" // AccFinalizerName - the name of the finalizer AccFinalizerName = "finalizer.dell.com" + + // ApexName AccClient name + ApexName = "ApexConnectivityClient" + + // AccVersion - the version of the connectivity client + AccVersion = "v1.0.0" ) var ( accdMutex sync.RWMutex accConfigVersionKey = fmt.Sprintf("%s/%s", AccMetadataPrefix, "ApexConnectivityClientConfigVersion") + // AccVersionKey - the version of the connectivity client + AccVersionKey = fmt.Sprintf("%s/%s", AccMetadataPrefix, "AccVersion") + // AccStopWatch - watcher stop handle AccStopWatch = make(chan struct{}) ) @@ -155,6 +129,8 @@ func (r *ApexConnectivityClientReconciler) Reconcile(ctx context.Context, req ct r.trcID = fmt.Sprintf("%d", r.GetUpdateCount()) name := req.Name + "-" + r.trcID ctx, log := logger.GetNewContextWithLogger(name) + unitTestRun := utils.DetermineUnitTestRun(ctx) + log.Info("################Starting Apex Connectivity Client Reconcile##############") acc := new(csmv1.ApexConnectivityClient) @@ -235,19 +211,31 @@ func (r *ApexConnectivityClientReconciler) Reconcile(ctx context.Context, req ct } newStatus := acc.GetApexConnectivityClientStatus() - _, err = utils.HandleAccSuccess(ctx, acc, r, newStatus, oldStatus) + requeue, err := utils.HandleAccSuccess(ctx, acc, r, newStatus, oldStatus) if err != nil { log.Error(err, "Failed to update CR status") } if err = DeployApexConnectivityClient(ctx, false, *op, *acc, crc); err == nil { - r.EventRecorder.Eventf(acc, corev1.EventTypeNormal, csmv1.EventCompleted, "install/update storage component: %s completed OK", acc.Name) - return utils.LogBannerAndReturn(reconcile.Result{}, nil) - } + syncErr := r.SyncACC(ctx, *acc, *op) + if syncErr == nil && !requeue.Requeue { + if err = utils.UpdateAccStatus(ctx, acc, r, newStatus); err != nil && !unitTestRun { + log.Error(err, "Failed to update CR status") + return utils.LogBannerAndReturn(reconcile.Result{Requeue: true}, err) + } + r.EventRecorder.Eventf(acc, corev1.EventTypeNormal, csmv1.EventCompleted, "install/update storage component: %s completed OK", acc.Name) + return utils.LogBannerAndReturn(reconcile.Result{}, nil) + } - // Failed deployment - r.EventRecorder.Eventf(acc, corev1.EventTypeWarning, csmv1.EventUpdated, "Failed install: %s", err.Error()) + // syncErr can be nil, even if ACC state = failed + if syncErr == nil { + syncErr = errors.New("ACC state is failed") + } + // Failed deployment + r.EventRecorder.Eventf(acc, corev1.EventTypeWarning, csmv1.EventUpdated, "Failed install: %s", syncErr.Error()) + return utils.LogBannerAndReturn(reconcile.Result{Requeue: true}, syncErr) + } return utils.LogBannerAndReturn(reconcile.Result{Requeue: true}, err) } @@ -275,9 +263,6 @@ func (r *ApexConnectivityClientReconciler) handleStatefulSetUpdate(oldObj interf name := d.Spec.Template.Labels[constants.AccLabel] key := name + "-" + fmt.Sprintf("%d", r.GetUpdateCount()) ctx, log := logger.GetNewContextWithLogger(key) - if name == "" { - return - } log.Debugw("statefulSet modified generation", d.Generation, old.Generation) @@ -321,9 +306,7 @@ func (r *ApexConnectivityClientReconciler) handlePodsUpdate(_ interface{}, obj i p, _ := obj.(*corev1.Pod) name := p.GetLabels()[constants.AccLabel] ns := p.Namespace - if name == "" { - return - } + key := name + "-" + fmt.Sprintf("%d", r.GetUpdateCount()) ctx, log := logger.GetNewContextWithLogger(key) @@ -423,12 +406,15 @@ func applyAccConfigVersionAnnotations(ctx context.Context, instance *csmv1.ApexC log := logger.GetLogger(ctx) // If client has not been initialized yet, we first annotate the client with the config version annotation - annotations := instance.GetAnnotations() isUpdated := false if annotations == nil { annotations = make(map[string]string) } + + annotations[AccVersionKey] = AccVersion + instance.SetAnnotations(annotations) + if _, ok := annotations[accConfigVersionKey]; !ok { annotations[accConfigVersionKey] = instance.Spec.Client.ConfigVersion isUpdated = true @@ -452,7 +438,7 @@ func DeployApexConnectivityClient(ctx context.Context, isDeleting bool, operator } YamlString = utils.ModifyCommonCRs(string(buf), cr) - ModifiedYamlString = ModifyApexConnectivityClientCR(YamlString, cr) + ModifiedYamlString = drivers.ModifyApexConnectivityClientCR(YamlString, cr) deployObjects, err := utils.GetModuleComponentObj([]byte(ModifiedYamlString)) if err != nil { return err @@ -477,64 +463,72 @@ func DeployApexConnectivityClient(ctx context.Context, isDeleting bool, operator return nil } -// ModifyApexConnectivityClientCR - update the custom resource -func ModifyApexConnectivityClientCR(yamlString string, cr csmv1.ApexConnectivityClient) string { - namespace := "" - aggregatorURL := AggregatorURLDefault - connectivityClientImage := "" - kubeProxyImage := "" - certPersisterImage := "" - accInitContainerImage := "" - caCertFlag := "" - caCertsList := "" +func getAccConfig(ctx context.Context, cr csmv1.ApexConnectivityClient, operatorConfig utils.OperatorConfig) (*AccConfig, error) { + var ( + err error + controller *utils.StatefulControllerYAML + log = logger.GetLogger(ctx) + ) - namespace = cr.Namespace + // if no ACC client is specified, return nil + if cr.Spec.Client.CSMClientType == "" { + log.Infof("No CSMClientType specified in manifest") + return nil, nil + } - if cr.Spec.Client.ConnectionTarget != "" { - aggregatorURL = string(cr.Spec.Client.ConnectionTarget) + controller, err = drivers.GetAccController(ctx, cr, operatorConfig, cr.Spec.Client.CSMClientType) + if err != nil { + return nil, fmt.Errorf("getting Apex connectivity client controller: %v", err) } - if cr.Spec.Client.UsePrivateCaCerts { - caCertFlag = CaCertFlag - caCertsList = CaCertsList + return &AccConfig{ + Controller: controller, + }, nil +} + +// SyncACC - sync apex connectivity client +func (r *ApexConnectivityClientReconciler) SyncACC(ctx context.Context, cr csmv1.ApexConnectivityClient, operatorConfig utils.OperatorConfig) error { + log := logger.GetLogger(ctx) + + // get acc resource + accconfig, err := getAccConfig(ctx, cr, operatorConfig) + if err != nil { + return err } - if cr.Spec.Client.Common.Name == ConnectivityClientContainerName { - if cr.Spec.Client.Common.Image != "" { - connectivityClientImage = string(cr.Spec.Client.Common.Image) - } + if accconfig == nil { + return nil } - for _, initContainer := range cr.Spec.Client.InitContainers { - if initContainer.Name == AccInitContainerName { - if initContainer.Image != "" { - accInitContainerImage = string(initContainer.Image) - } - } + controller := accconfig.Controller + + _, clusterClients, err := utils.GetAccDefaultClusters(ctx, cr, r) + if err != nil { + return err } - for _, sidecar := range cr.Spec.Client.SideCars { - if sidecar.Name == KubernetesProxySidecarName { - if sidecar.Image != "" { - kubeProxyImage = string(sidecar.Image) - } + for _, cluster := range clusterClients { + log.Infof("Starting SYNC for %s cluster", cluster.ClusterID) + + if err = serviceaccount.SyncServiceAccount(ctx, controller.Rbac.ServiceAccount, cluster.ClusterCTRLClient); err != nil { + return err } - if sidecar.Name == CertPersisterSidecarName { - if sidecar.Image != "" { - certPersisterImage = string(sidecar.Image) - } + + if err = rbac.SyncClusterRole(ctx, controller.Rbac.ClusterRole, cluster.ClusterCTRLClient); err != nil { + return err + } + + // create update ClusterRoleBinding + if err = rbac.SyncClusterRoleBindings(ctx, controller.Rbac.ClusterRoleBinding, cluster.ClusterCTRLClient); err != nil { + return err } - } - yamlString = strings.ReplaceAll(yamlString, AccNamespace, namespace) - yamlString = strings.ReplaceAll(yamlString, AggregatorURL, aggregatorURL) - yamlString = strings.ReplaceAll(yamlString, CaCertOption, caCertFlag) - yamlString = strings.ReplaceAll(yamlString, CaCerts, caCertsList) - yamlString = strings.ReplaceAll(yamlString, ConnectivityClientContainerImage, connectivityClientImage) - yamlString = strings.ReplaceAll(yamlString, AccInitContainerImage, accInitContainerImage) - yamlString = strings.ReplaceAll(yamlString, KubernetesProxySidecarImage, kubeProxyImage) - yamlString = strings.ReplaceAll(yamlString, CertPersisterSidecarImage, certPersisterImage) - return yamlString + // sync StatefulSet + if err = statefulset.SyncStatefulSet(ctx, controller.StatefulSet, cluster.ClusterK8sClient, ApexName); err != nil { + return err + } + } + return nil } // GetClient - returns the split client diff --git a/controllers/acc_controller_test.go b/controllers/acc_controller_test.go index 45c9d8462..1a7c6fbb2 100644 --- a/controllers/acc_controller_test.go +++ b/controllers/acc_controller_test.go @@ -16,6 +16,7 @@ import ( "context" "errors" "fmt" + "os" "strings" "testing" "time" @@ -25,7 +26,7 @@ import ( csmv1 "github.com/dell/csm-operator/api/v1" "github.com/dell/csm-operator/pkg/logger" - deploymentpkg "github.com/dell/csm-operator/pkg/resources/deployment" + statefulsetpkg "github.com/dell/csm-operator/pkg/resources/statefulset" "github.com/dell/csm-operator/pkg/utils" "github.com/dell/csm-operator/tests/shared" "github.com/dell/csm-operator/tests/shared/clientgoclient" @@ -71,7 +72,7 @@ var ( getAccCMErrorStr = "unable to get ConfigMap" updateAccCSMError bool - updateAccCSMErrorStr = "unable to get CSM" + updateAccCSMErrorStr = "unable to get ACC" updateAccCMError bool updateAccCMErrorStr = "unable to update ConfigMap" @@ -134,6 +135,9 @@ var ( accOperatorConfig = utils.OperatorConfig{ ConfigDirectory: "../operatorconfig", } + badAccOperatorConfig = utils.OperatorConfig{ + ConfigDirectory: "../in-valid-path", + } ) // AccContrllerTestSuite implements testify suite @@ -161,9 +165,11 @@ func (suite *AccControllerTestSuite) SetupTest() { suite.k8sClient = clientgoclient.NewFakeClient(suite.fakeClient) suite.namespace = "test" + + os.Setenv("UNIT_TEST", "true") } -// test a happy path scenerio with deletion +// test a happy path scenario with deletion func (suite *AccControllerTestSuite) TestReconcileAcc() { suite.makeFakeAcc(accName, suite.namespace, true) suite.runFakeAccManager("", false) @@ -171,6 +177,11 @@ func (suite *AccControllerTestSuite) TestReconcileAcc() { suite.runFakeAccManager("", true) } +// test error scenario +func (suite *AccControllerTestSuite) TestReconcileAccError() { + suite.runFakeAccManagerError("", false) +} + func (suite *AccControllerTestSuite) TestAccConnectivityClient() { csm := shared.MakeAcc(accName, suite.namespace, accConfigVersion) csm.Spec.Client.CSMClientType = csmv1.DreadnoughtClient @@ -431,6 +442,38 @@ func (suite *AccControllerTestSuite) runFakeAccManager(expectedErr string, recon } } +func (suite *AccControllerTestSuite) runFakeAccManagerError(expectedErr string, reconcileDelete bool) { + reconciler := suite.createAccReconciler() + + // invoke controller Reconcile to test. Typically k8s would call this when resource is changed + res, err := reconciler.Reconcile(accCtx, accReq) + + ctrl.Log.Info("reconcile response", "res is: ", res) + + if expectedErr == "" { + assert.NoError(suite.T(), err) + } else { + assert.NotNil(suite.T(), err) + } + + if err != nil { + ctrl.Log.Error(err, "Error returned") + assert.True(suite.T(), strings.Contains(err.Error(), expectedErr)) + } + + // after reconcile being run, we update deployment and daemonset + // then call handleDeployment/DaemonsetUpdate explicitly because + // in unit test listener does not get triggered + // If delete, we shouldn't call these methods since reconcile + // would return before this + if !reconcileDelete { + suite.handleStatefulSetUpdateTestFake(reconciler, "dell-connectivity-client") + suite.handleAccPodTest(reconciler, "") + _, err = reconciler.Reconcile(accCtx, accReq) + assert.Nil(suite.T(), err) + } +} + // call reconcile with different injection errors in k8s client func (suite *AccControllerTestSuite) reconcileAccWithErrorInjection(_, expectedErr string) { reconciler := suite.createAccReconciler() @@ -559,6 +602,33 @@ func (suite *AccControllerTestSuite) handleStatefulSetUpdateTest(r *ApexConnecti assert.Nil(suite.T(), err) } +func (suite *AccControllerTestSuite) handleStatefulSetUpdateTestFake(r *ApexConnectivityClientReconciler, name string) { + statefulSet := &appsv1.StatefulSet{} + err := suite.fakeClient.Get(accCtx, client.ObjectKey{Namespace: suite.namespace, Name: name}, statefulSet) + statefulSet.Spec.Template.Labels = map[string]string{"acc": "acc"} + + r.handleStatefulSetUpdate(statefulSet, statefulSet) + + // Make Pod and set pod status + pod := shared.MakePod(name, suite.namespace) + pod.Labels["acc"] = accName + pod.Status.Phase = corev1.PodPending + pod.Status.ContainerStatuses = []corev1.ContainerStatus{ + { + State: corev1.ContainerState{ + Waiting: &corev1.ContainerStateWaiting{ + Reason: "test", + }, + }, + }, + } + err = suite.fakeClient.Create(accCtx, &pod) + assert.Nil(suite.T(), err) + podList := &corev1.PodList{} + err = suite.fakeClient.List(accCtx, podList, nil) + assert.Nil(suite.T(), err) +} + func (suite *AccControllerTestSuite) handleAccPodTest(r *ApexConnectivityClientReconciler, name string) { suite.makeAccFakePod(name, suite.namespace) pod := &corev1.Pod{} @@ -650,7 +720,7 @@ func (suite *AccControllerTestSuite) ShouldFail(method string, obj runtime.Objec csm := obj.(*csmv1.ApexConnectivityClient) if method == "Update" && updateAccError { fmt.Printf("[ShouldFail] force Updatecsm error for obj of type %+v\n", csm) - return errors.New(updateAccErrorStr) + return errors.New(updateAccCSMErrorStr) } case *corev1.ConfigMap: @@ -728,25 +798,25 @@ func (suite *AccControllerTestSuite) debugAccFakeObjects() { } } -func TestSyncDeployment(t *testing.T) { +func TestSyncStatefulSet(t *testing.T) { labels := make(map[string]string, 1) - labels["*-8-csm"] = "/*-csm" - deployment := configv1.DeploymentApplyConfiguration{ - ObjectMetaApplyConfiguration: &v1.ObjectMetaApplyConfiguration{Name: &[]string{"csm"}[0], Namespace: &[]string{"default"}[0]}, - Spec: &configv1.DeploymentSpecApplyConfiguration{Template: &corev12.PodTemplateSpecApplyConfiguration{ + labels["*-8-acc"] = "/*-acc" + statefulset := configv1.StatefulSetApplyConfiguration{ + ObjectMetaApplyConfiguration: &v1.ObjectMetaApplyConfiguration{Name: &[]string{"acc"}[0], Namespace: &[]string{"default"}[0]}, + Spec: &configv1.StatefulSetSpecApplyConfiguration{Template: &corev12.PodTemplateSpecApplyConfiguration{ ObjectMetaApplyConfiguration: &v1.ObjectMetaApplyConfiguration{Labels: labels}, }}, } k8sClient := fake.NewSimpleClientset() - csmName = "csm" + accName = "acc" containers := make([]corev1.Container, 0) containers = append(containers, corev1.Container{Name: "fake-container", Image: "fake-image"}) - create, err := k8sClient.AppsV1().Deployments("default").Create(context.Background(), &appsv1.Deployment{ + create, err := k8sClient.AppsV1().StatefulSets("default").Create(context.Background(), &appsv1.StatefulSet{ ObjectMeta: apiv1.ObjectMeta{ - Name: csmName, + Name: accName, Namespace: "default", }, - Spec: appsv1.DeploymentSpec{ + Spec: appsv1.StatefulSetSpec{ Template: corev1.PodTemplateSpec{ ObjectMeta: apiv1.ObjectMeta{}, Spec: corev1.PodSpec{Containers: containers}, @@ -755,9 +825,39 @@ func TestSyncDeployment(t *testing.T) { }, apiv1.CreateOptions{}) assert.NoError(t, err) assert.NotNil(t, create) - k8sClient.PrependReactor("patch", "deployments", func(_ clienttesting.Action) (bool, runtime.Object, error) { + k8sClient.PrependReactor("patch", "statefulsets", func(_ clienttesting.Action) (bool, runtime.Object, error) { return true, nil, fmt.Errorf("fake error") }) - err = deploymentpkg.SyncDeployment(context.Background(), deployment, k8sClient, csmName) + err = statefulsetpkg.SyncStatefulSet(context.Background(), statefulset, k8sClient, csmName) assert.Error(t, err) } + +// Test all edge cases in SyncCSM +func (suite *AccControllerTestSuite) TestSyncACC() { + r := suite.createAccReconciler() + acc := shared.MakeAcc(accName, suite.namespace, accConfigVersion) + accBadType := shared.MakeAcc(accName, suite.namespace, accConfigVersion) + accBadType.Spec.Client.CSMClientType = "wrongclient" + + syncACCTests := []struct { + name string + acc csmv1.ApexConnectivityClient + op utils.OperatorConfig + expectedErr string + }{ + {"getClientConfig bad op config", acc, badAccOperatorConfig, ""}, + {"getClientConfig error", accBadType, badAccOperatorConfig, "no such file or directory"}, + } + + for _, tt := range syncACCTests { + suite.T().Run(tt.name, func(t *testing.T) { + err := r.SyncACC(ctx, tt.acc, tt.op) + if tt.expectedErr == "" { + assert.Nil(t, err) + } else { + assert.Error(t, err) + assert.Containsf(t, err.Error(), tt.expectedErr, "expected error containing %q, got %s", tt.expectedErr, err) + } + }) + } +} diff --git a/controllers/csm_controller.go b/controllers/csm_controller.go index d58b3270a..252d41415 100644 --- a/controllers/csm_controller.go +++ b/controllers/csm_controller.go @@ -381,9 +381,6 @@ func (r *ContainerStorageModuleReconciler) handleDeploymentUpdate(oldObj interfa name := d.Spec.Template.Labels[constants.CsmLabel] key := name + "-" + fmt.Sprintf("%d", r.GetUpdateCount()) ctx, log := logger.GetNewContextWithLogger(key) - if name == "" { - return - } log.Debugw("deployment modified generation", d.Name, d.Generation, old.Generation) @@ -442,9 +439,7 @@ func (r *ContainerStorageModuleReconciler) handlePodsUpdate(_ interface{}, obj i if ns == "" { ns = p.Namespace } - if name == "" { - return - } + key := name + "-" + fmt.Sprintf("%d", r.GetUpdateCount()) ctx, log := logger.GetNewContextWithLogger(key) @@ -484,9 +479,6 @@ func (r *ContainerStorageModuleReconciler) handleDaemonsetUpdate(oldObj interfac old, _ := oldObj.(*appsv1.DaemonSet) d, _ := obj.(*appsv1.DaemonSet) name := d.Spec.Template.Labels[constants.CsmLabel] - if name == "" { - return - } key := name + "-" + fmt.Sprintf("%d", r.GetUpdateCount()) ctx, log := logger.GetNewContextWithLogger(key) diff --git a/controllers/csm_controller_test.go b/controllers/csm_controller_test.go index 66910c240..5e988a431 100644 --- a/controllers/csm_controller_test.go +++ b/controllers/csm_controller_test.go @@ -180,7 +180,7 @@ func (suite *CSMControllerTestSuite) SetupTest() { os.Setenv("UNIT_TEST", "true") } -// test a happy path scenerio with deletion +// test a happy path scenario with deletion func (suite *CSMControllerTestSuite) TestReconcile() { suite.makeFakeCSM(csmName, suite.namespace, true, append(getReplicaModule(), getObservabilityModule()...)) suite.runFakeCSMManager("", false) @@ -188,6 +188,10 @@ func (suite *CSMControllerTestSuite) TestReconcile() { suite.runFakeCSMManager("", true) } +func (suite *CSMControllerTestSuite) TestReconcileError() { + suite.runFakeCSMManagerError("", false) +} + func (suite *CSMControllerTestSuite) TestAuthorizationServerReconcile() { suite.makeFakeAuthServerCSM(csmName, suite.namespace, getAuthProxyServer()) suite.runFakeAuthCSMManager("timed out waiting for the condition", false) @@ -1063,6 +1067,40 @@ func (suite *CSMControllerTestSuite) runFakeCSMManager(expectedErr string, recon } } +func (suite *CSMControllerTestSuite) runFakeCSMManagerError(expectedErr string, reconcileDelete bool) { + reconciler := suite.createReconciler() + + // invoke controller Reconcile to test. Typically, k8s would call this when resource is changed + res, err := reconciler.Reconcile(ctx, req) + + ctrl.Log.Info("reconcile response", "res is: ", res) + + if expectedErr == "" { + assert.NoError(suite.T(), err) + } else { + assert.NotNil(suite.T(), err) + } + + if err != nil { + ctrl.Log.Error(err, "Error returned") + assert.True(suite.T(), strings.Contains(err.Error(), expectedErr)) + } + + // after reconcile being run, we update deployment and daemonset + // then call handleDeployment/DaemonsetUpdate explicitly because + // in unit test listener does not get triggered + // If delete, we shouldn't call these methods since reconcile + // would return before this + if !reconcileDelete { + suite.handleDaemonsetTestFake(reconciler, "csm-node") + suite.handleDeploymentTestFake(reconciler, "csm-controller") + suite.handlePodTest(reconciler, "") + _, err = reconciler.Reconcile(ctx, req) + assert.Nil(suite.T(), err) + + } +} + func (suite *CSMControllerTestSuite) runFakeAuthCSMManager(expectedErr string, reconcileDelete bool) { reconciler := suite.createReconciler() @@ -1265,6 +1303,62 @@ func (suite *CSMControllerTestSuite) handleDeploymentTest(r *ContainerStorageMod assert.Nil(suite.T(), err) } +func (suite *CSMControllerTestSuite) handleDaemonsetTestFake(r *ContainerStorageModuleReconciler, name string) { + daemonset := &appsv1.DaemonSet{} + err := suite.fakeClient.Get(ctx, client.ObjectKey{Namespace: suite.namespace, Name: name}, daemonset) + assert.Error(suite.T(), err) + daemonset.Spec.Template.Labels = map[string]string{"csm": "csm"} + + r.handleDaemonsetUpdate(daemonset, daemonset) + + // Make Pod and set status + pod := shared.MakePod(name, suite.namespace) + pod.Labels["csm"] = csmName + pod.Status.Phase = corev1.PodPending + pod.Status.ContainerStatuses = []corev1.ContainerStatus{ + { + State: corev1.ContainerState{ + Waiting: &corev1.ContainerStateWaiting{ + Reason: "test", + }, + }, + }, + } + err = suite.fakeClient.Create(ctx, &pod) + assert.Nil(suite.T(), err) + podList := &corev1.PodList{} + err = suite.fakeClient.List(ctx, podList, nil) + assert.Nil(suite.T(), err) +} + +func (suite *CSMControllerTestSuite) handleDeploymentTestFake(r *ContainerStorageModuleReconciler, name string) { + deployment := &appsv1.Deployment{} + err := suite.fakeClient.Get(ctx, client.ObjectKey{Namespace: suite.namespace, Name: name}, deployment) + assert.Error(suite.T(), err) + deployment.Spec.Template.Labels = map[string]string{"csm": "csm"} + + r.handleDeploymentUpdate(deployment, deployment) + + // Make Pod and set pod status + pod := shared.MakePod(name, suite.namespace) + pod.Labels["csm"] = csmName + pod.Status.Phase = corev1.PodPending + pod.Status.ContainerStatuses = []corev1.ContainerStatus{ + { + State: corev1.ContainerState{ + Waiting: &corev1.ContainerStateWaiting{ + Reason: "test", + }, + }, + }, + } + err = suite.fakeClient.Create(ctx, &pod) + assert.Nil(suite.T(), err) + podList := &corev1.PodList{} + err = suite.fakeClient.List(ctx, podList, nil) + assert.Nil(suite.T(), err) +} + func (suite *CSMControllerTestSuite) handlePodTest(r *ContainerStorageModuleReconciler, name string) { suite.makeFakePod(name, suite.namespace) pod := &corev1.Pod{} diff --git a/operatorconfig/clientconfig/apexconnectivityclient/v1.0.0/statefulset.yaml b/operatorconfig/clientconfig/apexconnectivityclient/v1.0.0/statefulset.yaml index f62f5d36b..f50efd2ae 100644 --- a/operatorconfig/clientconfig/apexconnectivityclient/v1.0.0/statefulset.yaml +++ b/operatorconfig/clientconfig/apexconnectivityclient/v1.0.0/statefulset.yaml @@ -35,10 +35,10 @@ rules: verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["persistentvolumeclaims"] - verbs: ["list", "watch"] + verbs: ["list", "watch", "update"] - apiGroups: [""] resources: ["persistentvolumes"] - verbs: ["list", "watch"] + verbs: ["list", "watch", "create", "update", "delete"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["list", "watch", "create", "delete"] @@ -47,7 +47,7 @@ rules: verbs: ["list", "watch"] - apiGroups: ["apps"] resources: ["statefulsets"] - verbs: ["list", "watch"] + verbs: ["list", "watch", "update", "patch"] - apiGroups: ["apps"] resources: ["daemonsets"] verbs: ["list", "watch"] diff --git a/pkg/drivers/common_test.go b/pkg/drivers/common_test.go index a42329f0d..4c7a7ed69 100644 --- a/pkg/drivers/common_test.go +++ b/pkg/drivers/common_test.go @@ -204,10 +204,10 @@ func csmWithPowermax(driver csmv1.DriverType, version string) csmv1.ContainerSto // Add image name res.Spec.Driver.Common.Image = "thisIsAnImage" - // Add pstore driver version + // Add pmax driver version res.Spec.Driver.ConfigVersion = version - // Add pstore driver type + // Add pmax driver type res.Spec.Driver.CSIDriverType = driver // Add NodeSelector to node and controller @@ -290,6 +290,10 @@ func getPmaxCommonEnvs() []corev1.EnvVar { Name: "X_CSI_IG_NODENAME_TEMPLATE", Value: "", }, + { + Name: "CSI_LOG_FORMAT", + Value: "TEXT", + }, } } @@ -402,3 +406,54 @@ func csmWithUnity(driver csmv1.DriverType, version string, certProvided bool) cs return res } + +func accForApexConnecityClient(client csmv1.ClientType, version string) csmv1.ApexConnectivityClient { + res := shared.MakeAcc("acc", "client-test", shared.AccConfigVersion) + + // Add ForceRemoveClient + res.Spec.Client.ForceRemoveClient = true + + // Add image name + res.Spec.Client.Common.Image = "thisIsAnImage" + + // add connection target + res.Spec.Client.ConnectionTarget = "thisIsConnectionTarget" + + // add private cacert + res.Spec.Client.UsePrivateCaCerts = true + + // add common name + res.Spec.Client.Common.Name = "connectivity-client-docker-k8s" + + // Add client version + res.Spec.Client.ConfigVersion = version + + // Add client type + res.Spec.Client.CSMClientType = client + + res.Spec.Client.InitContainers = []csmv1.ContainerTemplate{{ + Name: "connectivity-client-init", + Image: "image", + ImagePullPolicy: "IfNotPresent", + Args: []string{}, + Envs: []corev1.EnvVar{{Name: "DCM_IDENTITY_LOCATION"}}, + Tolerations: []corev1.Toleration{}, + }} + + // Add Sidecar + res.Spec.Client.SideCars = []csmv1.ContainerTemplate{ + { + Name: "cert-persister", + Image: "image", + ImagePullPolicy: "Always", + Args: []string{}, + }, + { + Name: "kubernetes-proxy", + Image: "image", + ImagePullPolicy: "Always", + Args: []string{}, + }, + } + return res +} diff --git a/pkg/drivers/commonconfig.go b/pkg/drivers/commonconfig.go index e41da8c90..6024305c3 100644 --- a/pkg/drivers/commonconfig.go +++ b/pkg/drivers/commonconfig.go @@ -17,6 +17,7 @@ import ( "fmt" "os" "path/filepath" + "strings" csmv1 "github.com/dell/csm-operator/api/v1" "github.com/dell/csm-operator/pkg/logger" @@ -29,6 +30,53 @@ import ( "sigs.k8s.io/yaml" ) +const ( + // AccNamespace - deployment namespace + AccNamespace string = "" + + // AggregatorURLDefault - default aggregator location + AggregatorURLDefault string = "connect-into.dell.com" + + // AggregatorURL - tag for specifying aggregator endpoint + AggregatorURL string = "" + + // CaCertOption - tag for specifying if cacert option is used + CaCertOption string = "" + + // CaCertFlag - cacert option + CaCertFlag string = "--cacert" + + // CaCerts - tag for specifying --cacert value + CaCerts string = "" + + // CaCertsList - cert locations for aggregator and loadbalancer + CaCertsList string = "/opt/dellemc/certs/loadbalancer_root_ca_cert.crt,/opt/dellemc/certs/aggregator_internal_root_ca_cert.crt" + + // ConnectivityClientContainerName - name of the DCM client container + ConnectivityClientContainerName string = "connectivity-client-docker-k8s" + + // ConnectivityClientContainerImage - tag for DCM client image + ConnectivityClientContainerImage string = "" + + // KubernetesProxySidecarName - name of proxy sidecar container + KubernetesProxySidecarName string = "kubernetes-proxy" + + // KubernetesProxySidecarImage - tag for proxy image + KubernetesProxySidecarImage string = "" + + // CertPersisterSidecarName - name of cert persister image + CertPersisterSidecarName string = "cert-persister" + + // CertPersisterSidecarImage - name of cert persister image + CertPersisterSidecarImage string = "" + + // AccInitContainerName - name of init container image + AccInitContainerName string = "connectivity-client-init" + + // AccInitContainerImage - tag for init container image + AccInitContainerImage string = "" +) + var defaultVolumeConfigName = map[csmv1.DriverType]string{ csmv1.PowerScaleName: "isilon-configs", } @@ -170,6 +218,84 @@ func GetController(ctx context.Context, cr csmv1.ContainerStorageModule, operato return &controllerYAML, nil } +// GetAccController get acc StatefulSet yaml +func GetAccController(ctx context.Context, cr csmv1.ApexConnectivityClient, operatorConfig utils.OperatorConfig, clientName csmv1.ClientType) (*utils.StatefulControllerYAML, error) { + log := logger.GetLogger(ctx) + + clientNameLower := strings.ToLower(string(clientName)) + configMapPath := fmt.Sprintf("%s/clientconfig/%s/%s/statefulset.yaml", operatorConfig.ConfigDirectory, clientNameLower, cr.Spec.Client.ConfigVersion) + log.Debugw("GetAccController", "configMapPath", configMapPath) + buf, err := os.ReadFile(filepath.Clean(configMapPath)) + if err != nil { + return nil, err + } + + YamlString := utils.ModifyCommonCRs(string(buf), cr) + if cr.Spec.Client.CSMClientType == "apexConnectivityClient" { + YamlString = ModifyApexConnectivityClientCR(YamlString, cr) + } + + AccYAML, err := utils.GetDriverYaml(YamlString, "StatefulSet") + if err != nil { + log.Errorw("GetAccController", "Error getting driver yaml", "error", err) + return nil, err + } + + statefulsetYAML := AccYAML.(utils.StatefulControllerYAML) + + containers := statefulsetYAML.StatefulSet.Spec.Template.Spec.Containers + newcontainers := make([]acorev1.ContainerApplyConfiguration, 0) + for i, c := range containers { + if string(*c.Name) == "connectivity-client-docker-k8s" { + if string(cr.Spec.Client.Common.Image) != "" { + image := string(cr.Spec.Client.Common.Image) + c.Image = &image + } + } + + removeContainer := false + for _, s := range cr.Spec.Client.SideCars { + if s.Name == *c.Name { + if s.Enabled == nil { + log.Infow("Container to be enabled", "name", *c.Name) + break + } else if !*s.Enabled { + removeContainer = true + log.Infow("Container to be removed", "name", *c.Name) + } else { + log.Infow("Container to be enabled", "name", *c.Name) + } + break + } + } + if !removeContainer { + utils.ReplaceAllContainerImageApply(operatorConfig.K8sVersion, &containers[i]) + utils.UpdateSideCarApply(cr.Spec.Client.SideCars, &containers[i]) + newcontainers = append(newcontainers, c) + } + + } + + statefulsetYAML.StatefulSet.Spec.Template.Spec.Containers = newcontainers + + crUID := cr.GetUID() + bController := true + bOwnerDeletion := !cr.Spec.Client.ForceRemoveClient + kind := cr.Kind + v1 := "apps/v1" + statefulsetYAML.StatefulSet.OwnerReferences = []metacv1.OwnerReferenceApplyConfiguration{ + { + APIVersion: &v1, + Controller: &bController, + BlockOwnerDeletion: &bOwnerDeletion, + Kind: &kind, + Name: &cr.Name, + UID: &crUID, + }, + } + return &statefulsetYAML, nil +} + // GetNode get node yaml func GetNode(ctx context.Context, cr csmv1.ContainerStorageModule, operatorConfig utils.OperatorConfig, driverType csmv1.DriverType, filename string) (*utils.NodeYAML, error) { log := logger.GetLogger(ctx) @@ -427,3 +553,63 @@ func GetCSIDriver(ctx context.Context, cr csmv1.ContainerStorageModule, operator return &csidriver, nil } + +// ModifyApexConnectivityClientCR - update the custom resource +func ModifyApexConnectivityClientCR(yamlString string, cr csmv1.ApexConnectivityClient) string { + namespace := "" + aggregatorURL := AggregatorURLDefault + connectivityClientImage := "" + kubeProxyImage := "" + certPersisterImage := "" + accInitContainerImage := "" + caCertFlag := "" + caCertsList := "" + + namespace = cr.Namespace + + if cr.Spec.Client.ConnectionTarget != "" { + aggregatorURL = string(cr.Spec.Client.ConnectionTarget) + } + + if cr.Spec.Client.UsePrivateCaCerts { + caCertFlag = CaCertFlag + caCertsList = CaCertsList + } + + if cr.Spec.Client.Common.Name == ConnectivityClientContainerName { + if cr.Spec.Client.Common.Image != "" { + connectivityClientImage = string(cr.Spec.Client.Common.Image) + } + } + + for _, initContainer := range cr.Spec.Client.InitContainers { + if initContainer.Name == AccInitContainerName { + if initContainer.Image != "" { + accInitContainerImage = string(initContainer.Image) + } + } + } + + for _, sidecar := range cr.Spec.Client.SideCars { + if sidecar.Name == KubernetesProxySidecarName { + if sidecar.Image != "" { + kubeProxyImage = string(sidecar.Image) + } + } + if sidecar.Name == CertPersisterSidecarName { + if sidecar.Image != "" { + certPersisterImage = string(sidecar.Image) + } + } + } + + yamlString = strings.ReplaceAll(yamlString, AccNamespace, namespace) + yamlString = strings.ReplaceAll(yamlString, AggregatorURL, aggregatorURL) + yamlString = strings.ReplaceAll(yamlString, CaCertOption, caCertFlag) + yamlString = strings.ReplaceAll(yamlString, CaCerts, caCertsList) + yamlString = strings.ReplaceAll(yamlString, ConnectivityClientContainerImage, connectivityClientImage) + yamlString = strings.ReplaceAll(yamlString, AccInitContainerImage, accInitContainerImage) + yamlString = strings.ReplaceAll(yamlString, KubernetesProxySidecarImage, kubeProxyImage) + yamlString = strings.ReplaceAll(yamlString, CertPersisterSidecarImage, certPersisterImage) + return yamlString +} diff --git a/pkg/drivers/commonconfig_test.go b/pkg/drivers/commonconfig_test.go index 938980b6f..eb1de4517 100644 --- a/pkg/drivers/commonconfig_test.go +++ b/pkg/drivers/commonconfig_test.go @@ -58,6 +58,29 @@ var ( } ) +var ( + acc = accForApexConnecityClient("apexConnectivityClient", shared.AccConfigVersion) + fakeClient csmv1.ClientType = "fakeClient" + badClient csmv1.ClientType = "badClient" + + testacc = []struct { + // every single unit test name + name string + // acc object + acc csmv1.ApexConnectivityClient + // acc client + accClient csmv1.ClientType + // yaml file name to read + filename string + // expected error + expectedErr string + }{ + {"Acc happy path", acc, "apexConnectivityClient", "statefulset.yaml", ""}, + {"file does not exist", acc, fakeClient, "NonExist.yaml", "no such file or directory"}, + {"config file is invalid", acc, badClient, "statefulset.yaml", "unmarshal"}, + } +) + func TestGetCsiDriver(t *testing.T) { ctx := context.Background() for _, tt := range tests { @@ -137,3 +160,17 @@ func TestGetNode(t *testing.T) { }) } } + +func TestGetAccController(t *testing.T) { + ctx := context.Background() + for _, tt := range testacc { + t.Run(tt.name, func(t *testing.T) { + _, err := GetAccController(ctx, tt.acc, config, tt.accClient) + if tt.expectedErr == "" { + assert.Nil(t, err) + } else { + assert.Containsf(t, err.Error(), tt.expectedErr, "expected error containing %q, got %s", tt.expectedErr, err) + } + }) + } +} diff --git a/pkg/resources/statefulset/statefulset.go b/pkg/resources/statefulset/statefulset.go new file mode 100644 index 000000000..c62bd029b --- /dev/null +++ b/pkg/resources/statefulset/statefulset.go @@ -0,0 +1,59 @@ +// Copyright © 2021 - 2022 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// 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 statefulset + +import ( + "context" + "time" + + //"fmt" + + "github.com/dell/csm-operator/pkg/logger" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + appsv1 "k8s.io/client-go/applyconfigurations/apps/v1" + "k8s.io/client-go/kubernetes" + //"reflect" +) + +// SleepTime - minimum time to sleep before checking the state of controller pod +var SleepTime = 10 * time.Second + +// SyncStatefulSet - Syncs a StatefulSet for controller +func SyncStatefulSet(ctx context.Context, StatefulSet appsv1.StatefulSetApplyConfiguration, k8sClient kubernetes.Interface, accName string) error { + log := logger.GetLogger(ctx) + + log.Infow("Sync StatefulSet:", "name", *StatefulSet.ObjectMetaApplyConfiguration.Name) + + StatefulSets := k8sClient.AppsV1().StatefulSets(*StatefulSet.ObjectMetaApplyConfiguration.Namespace) + + found, err := StatefulSets.Get(ctx, *StatefulSet.ObjectMetaApplyConfiguration.Name, metav1.GetOptions{}) + if err != nil { + log.Errorw("get SyncStatefulSet error", "Error", err.Error()) + } + opts := metav1.ApplyOptions{Force: true, FieldManager: "application/apply-patch"} + if found == nil || found.Name == "" { + log.Infow("No existing StatefulSet", "Name:", StatefulSet.Name) + } else { + log.Infow("found StatefulSet", "image", found.Spec.Template.Spec.Containers[0].Image) + } + + StatefulSet.Spec.Template.Labels["app.kubernetes.io/instance"] = accName + + set, err := StatefulSets.Apply(ctx, &StatefulSet, opts) + if err != nil { + log.Errorw("Apply StatefulSet error", "set", err.Error()) + return err + } + log.Infow("StatefulSet apply done", "name", set.Name) + return nil +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index add695955..25b5610a7 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -93,6 +93,12 @@ type ControllerYAML struct { Rbac RbacYAML } +// StatefulControllerYAML - +type StatefulControllerYAML struct { + StatefulSet confv1.StatefulSetApplyConfiguration + Rbac RbacYAML +} + // NodeYAML - type NodeYAML struct { DaemonSetApplyConfig confv1.DaemonSetApplyConfiguration @@ -700,6 +706,18 @@ func GetDriverYaml(YamlString, kind string) (interface{}, error) { } } + if kind == "StatefulSet" { + var ss confv1.StatefulSetApplyConfiguration + err := yaml.Unmarshal(podBuf, &ss) + if err != nil { + return nil, err + } + return StatefulControllerYAML{ + StatefulSet: ss, + Rbac: rbac, + }, nil + } + if kind == "Deployment" { var dp confv1.DeploymentApplyConfiguration err := yaml.Unmarshal(podBuf, &dp) diff --git a/tests/config/clientconfig/apex/v1.0.0/bad.yaml b/tests/config/clientconfig/apexconnectivityclient/v1.0.0/bad.yaml similarity index 100% rename from tests/config/clientconfig/apex/v1.0.0/bad.yaml rename to tests/config/clientconfig/apexconnectivityclient/v1.0.0/bad.yaml diff --git a/tests/config/clientconfig/apex/v1.0.0/statefulset.yaml b/tests/config/clientconfig/apexconnectivityclient/v1.0.0/statefulset.yaml similarity index 100% rename from tests/config/clientconfig/apex/v1.0.0/statefulset.yaml rename to tests/config/clientconfig/apexconnectivityclient/v1.0.0/statefulset.yaml diff --git a/tests/config/clientconfig/badClient/bad.yaml b/tests/config/clientconfig/badclient/badClient/bad.yaml similarity index 100% rename from tests/config/clientconfig/badClient/bad.yaml rename to tests/config/clientconfig/badclient/badClient/bad.yaml diff --git a/tests/config/clientconfig/badclient/statefulset.yaml b/tests/config/clientconfig/badclient/statefulset.yaml new file mode 100644 index 000000000..f90b8b7a7 --- /dev/null +++ b/tests/config/clientconfig/badclient/statefulset.yaml @@ -0,0 +1,4 @@ +this snfoiasga + is + + 843*&(*(% invalid YAml diff --git a/tests/config/clientconfig/badclient/v1.0.0/statefulset.yaml b/tests/config/clientconfig/badclient/v1.0.0/statefulset.yaml new file mode 100644 index 000000000..f90b8b7a7 --- /dev/null +++ b/tests/config/clientconfig/badclient/v1.0.0/statefulset.yaml @@ -0,0 +1,4 @@ +this snfoiasga + is + + 843*&(*(% invalid YAml diff --git a/tests/shared/clientgoclient/fakeStatefulset.go b/tests/shared/clientgoclient/fakeStatefulset.go new file mode 100644 index 000000000..3fe93c49f --- /dev/null +++ b/tests/shared/clientgoclient/fakeStatefulset.go @@ -0,0 +1,143 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// 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 clientgoclient + +import ( + "context" + "encoding/json" + + appsv1 "k8s.io/api/apps/v1" + autoscalingv1 "k8s.io/api/autoscaling/v1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + applyconfigurationsappsv1 "k8s.io/client-go/applyconfigurations/apps/v1" + applyconfigurationsautoscalingv1 "k8s.io/client-go/applyconfigurations/autoscaling/v1" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// FakeStatefulsets implements StatefulSetInterface +type FakeStatefulsets struct { + FakeClient client.Client + Namespace string +} + +// Apply takes the given apply declarative configuration, applies it and returns the applied statefulset. +func (c *FakeStatefulsets) Apply(ctx context.Context, statefulset *applyconfigurationsappsv1.StatefulSetApplyConfiguration, _ v1.ApplyOptions) (result *appsv1.StatefulSet, err error) { + result = new(appsv1.StatefulSet) + + data, err := json.Marshal(statefulset) + if err != nil { + return result, err + } + + err = json.Unmarshal(data, result) + + _, err = c.Get(ctx, *statefulset.Name, v1.GetOptions{}) + if errors.IsNotFound(err) { + // if not found, we create it + return c.Create(ctx, result, v1.CreateOptions{}) + } else if err != nil { + return + } + + // otherwise we update it + err = c.FakeClient.Update(ctx, result) + + return result, err +} + +// Get takes name of the statefulset, and returns the corresponding statefulset object, and an error if there is any. +func (c *FakeStatefulsets) Get(ctx context.Context, name string, _ v1.GetOptions) (result *appsv1.StatefulSet, err error) { + result = new(appsv1.StatefulSet) + + k := types.NamespacedName{ + Name: name, + Namespace: c.Namespace, + } + + err = c.FakeClient.Get(ctx, k, result) + return +} + +// Create takes the representation of a statefulset and creates it. Returns the server's representation of the statefulset, and an error, if there is any. +func (c *FakeStatefulsets) Create(ctx context.Context, statefulset *appsv1.StatefulSet, _ v1.CreateOptions) (result *appsv1.StatefulSet, err error) { + err = c.FakeClient.Create(ctx, statefulset) + return statefulset, err +} + +// List takes label and field selectors, and returns the list of StatefulSets that match those selectors. +func (c *FakeStatefulsets) List(_ context.Context, _ v1.ListOptions) (result *appsv1.StatefulSetList, err error) { + panic("implement me") +} + +// Watch returns a watch.Interface that watches the requested statefulsets. +func (c *FakeStatefulsets) Watch(_ context.Context, _ v1.ListOptions) (watch.Interface, error) { + panic("implement me") +} + +// Update takes the representation of a statefulset and updates it. Returns the server's representation of the statefulset, and an error, if there is any. +func (c *FakeStatefulsets) Update(_ context.Context, _ *appsv1.StatefulSet, _ v1.UpdateOptions) (result *appsv1.StatefulSet, err error) { + panic("implement me") +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeStatefulsets) UpdateStatus(_ context.Context, _ *appsv1.StatefulSet, _ v1.UpdateOptions) (*appsv1.StatefulSet, error) { + panic("implement me") +} + +// Delete takes name of the statefulset and deletes it. Returns an error if one occurs. +func (c *FakeStatefulsets) Delete(_ context.Context, _ string, _ v1.DeleteOptions) error { + panic("implement me") +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeStatefulsets) DeleteCollection(_ context.Context, _ v1.DeleteOptions, _ v1.ListOptions) error { + panic("implement me") +} + +// Patch applies the patch and returns the patched statefulset. +func (c *FakeStatefulsets) Patch(_ context.Context, _ string, _ types.PatchType, _ []byte, _ v1.PatchOptions, _ ...string) (result *appsv1.StatefulSet, err error) { + panic("implement me") +} + +// ApplyStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). +func (c *FakeStatefulsets) ApplyStatus(_ context.Context, _ *applyconfigurationsappsv1.StatefulSetApplyConfiguration, _ v1.ApplyOptions) (result *appsv1.StatefulSet, err error) { + panic("implement me") +} + +// GetScale takes name of the statefulset, and returns the corresponding scale object, and an error if there is any. +func (c *FakeStatefulsets) GetScale(_ context.Context, _ string, _ v1.GetOptions) (result *autoscalingv1.Scale, err error) { + panic("implement me") +} + +// UpdateScale takes the representation of a scale and updates it. Returns the server's representation of the scale, and an error, if there is any. +func (c *FakeStatefulsets) UpdateScale(_ context.Context, _ string, _ *autoscalingv1.Scale, _ v1.UpdateOptions) (result *autoscalingv1.Scale, err error) { + panic("implement me") +} + +// ApplyScale takes top resource name and the apply declarative configuration for scale, +// applies it and returns the applied scale, and an error, if there is any. +func (c *FakeStatefulsets) ApplyScale(_ context.Context, _ string, _ *applyconfigurationsautoscalingv1.ScaleApplyConfiguration, _ v1.ApplyOptions) (result *autoscalingv1.Scale, err error) { + panic("implement me") +} + +// AutoscalingV2 takes top resource name and the apply declarative configuration for scale, +// applies it and returns the applied scale, and an error, if there is any. +func (c *FakeStatefulsets) AutoscalingV2(_ context.Context, _ string, _ *applyconfigurationsautoscalingv1.ScaleApplyConfiguration, _ v1.ApplyOptions) (result *autoscalingv1.Scale, err error) { + panic("implement me") +} diff --git a/tests/shared/clientgoclient/fakeappv1.go b/tests/shared/clientgoclient/fakeappv1.go index db7d05696..0c43561bd 100644 --- a/tests/shared/clientgoclient/fakeappv1.go +++ b/tests/shared/clientgoclient/fakeappv1.go @@ -23,7 +23,7 @@ type FakeAppsV1 struct { FakeClient client.Client } -// DaemonSets takea a namespace and returns an DaemonSetInterface +// DaemonSets takes a namespace and returns a DaemonSetInterface func (c *FakeAppsV1) DaemonSets(namespace string) v1.DaemonSetInterface { return &FakeDaemonSets{ FakeClient: c.FakeClient, @@ -31,7 +31,7 @@ func (c *FakeAppsV1) DaemonSets(namespace string) v1.DaemonSetInterface { } } -// Deployments takea a namespace and returns an DeploymentInterface +// Deployments takes a namespace and returns a DeploymentInterface func (c *FakeAppsV1) Deployments(namespace string) v1.DeploymentInterface { return &FakeDeployments{ FakeClient: c.FakeClient, @@ -39,6 +39,14 @@ func (c *FakeAppsV1) Deployments(namespace string) v1.DeploymentInterface { } } +// StatefulSets takes a namespace and returns a StatefulSetInterface +func (c *FakeAppsV1) StatefulSets(namespace string) v1.StatefulSetInterface { + return &FakeStatefulsets{ + FakeClient: c.FakeClient, + Namespace: namespace, + } +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeAppsV1) RESTClient() rest.Interface { @@ -55,8 +63,3 @@ func (c *FakeAppsV1) ControllerRevisions(_ string) v1.ControllerRevisionInterfac func (c *FakeAppsV1) ReplicaSets(_ string) v1.ReplicaSetInterface { panic("implement me") } - -// StatefulSets takes a namespace and returns an StatefulSetInterface -func (c *FakeAppsV1) StatefulSets(_ string) v1.StatefulSetInterface { - panic("implement me") -}