From 35b9b823449a0e07742901a8e1b003b4eb61394d Mon Sep 17 00:00:00 2001 From: ashleyvjoy <110008193+ashleyvjoy@users.noreply.github.com> Date: Tue, 11 Jun 2024 19:22:26 +0530 Subject: [PATCH] changes to support brownfield onboarding to ANK8s (#588) * changes to support brownfield onboarding to ANK8s * code refactoring * remove commented code * remove commented code * remove commented code * remove commented code * remove commented code * Fixed lint issues * Fixed lint issues * Added UT tescases * Removing the failed testcases * add new unit test case * add new unit test case * refactor the code * refactor the code * refactor the code * refactor the code * refactor the code * lint-fix * lint-fix * Fixed lint issues Signed-off-by: Harish P * Fixed lint issues Signed-off-by: Harish P --------- Signed-off-by: Harish P Co-authored-by: Harish P Co-authored-by: meggm --- controllers/acc_controller.go | 9 +++ controllers/acc_controller_test.go | 48 +++++++++++++ controllers/csm_controller.go | 5 ++ pkg/utils/utils.go | 110 ++++++++++++++++++++++++++++- 4 files changed, 171 insertions(+), 1 deletion(-) diff --git a/controllers/acc_controller.go b/controllers/acc_controller.go index fd0e64f08..d6b5b9b89 100644 --- a/controllers/acc_controller.go +++ b/controllers/acc_controller.go @@ -98,6 +98,9 @@ const ( // AccInitContainerImage - tag for init container image AccInitContainerImage string = "" + + // BrownfieldManifest - manifest for brownfield role/rolebinding creation + BrownfieldManifest string = "brownfield-onboard.yaml" ) // ApexConnectivityClientReconciler reconciles a ApexConnectivityClient object @@ -438,6 +441,8 @@ func applyAccConfigVersionAnnotations(ctx context.Context, instance *csmv1.ApexC // DeployApexConnectivityClient - perform deployment func DeployApexConnectivityClient(ctx context.Context, isDeleting bool, operatorConfig utils.OperatorConfig, cr csmv1.ApexConnectivityClient, ctrlClient crclient.Client) error { + log := logger.GetLogger(ctx) + YamlString := "" ModifiedYamlString := "" deploymentPath := fmt.Sprintf("%s/clientconfig/%s/%s/%s", operatorConfig.ConfigDirectory, csmv1.DreadnoughtClient, cr.Spec.Client.ConfigVersion, AccManifest) @@ -465,6 +470,10 @@ func DeployApexConnectivityClient(ctx context.Context, isDeleting bool, operator } } + if err = utils.CreateBrownfieldRbac(ctx, operatorConfig, cr, ctrlClient); err != nil { + log.Error(err, "error creating role/rolebindings") + } + return nil } diff --git a/controllers/acc_controller_test.go b/controllers/acc_controller_test.go index b19dd2174..45c9d8462 100644 --- a/controllers/acc_controller_test.go +++ b/controllers/acc_controller_test.go @@ -20,10 +20,12 @@ import ( "testing" "time" + clienttesting "k8s.io/client-go/testing" "k8s.io/client-go/util/workqueue" csmv1 "github.com/dell/csm-operator/api/v1" "github.com/dell/csm-operator/pkg/logger" + deploymentpkg "github.com/dell/csm-operator/pkg/resources/deployment" "github.com/dell/csm-operator/pkg/utils" "github.com/dell/csm-operator/tests/shared" "github.com/dell/csm-operator/tests/shared/clientgoclient" @@ -37,6 +39,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/record" @@ -45,6 +48,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + apiv1 "k8s.io/apimachinery/pkg/apis/meta/v1" + configv1 "k8s.io/client-go/applyconfigurations/apps/v1" + corev12 "k8s.io/client-go/applyconfigurations/core/v1" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) var ( @@ -163,6 +171,12 @@ func (suite *AccControllerTestSuite) TestReconcileAcc() { suite.runFakeAccManager("", true) } +func (suite *AccControllerTestSuite) TestAccConnectivityClient() { + csm := shared.MakeAcc(accName, suite.namespace, accConfigVersion) + csm.Spec.Client.CSMClientType = csmv1.DreadnoughtClient + csm.Spec.Client.Common.Image = "image" +} + func (suite *AccControllerTestSuite) TestAccConnectivityClientConnectionTarget() { csm := shared.MakeAcc(accName, suite.namespace, accConfigVersion) csm.Spec.Client.CSMClientType = csmv1.DreadnoughtClient @@ -713,3 +727,37 @@ func (suite *AccControllerTestSuite) debugAccFakeObjects() { accUnittestLogger.Info("found fake object ", "object", fmt.Sprintf("%#v", o)) } } + +func TestSyncDeployment(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{ + ObjectMetaApplyConfiguration: &v1.ObjectMetaApplyConfiguration{Labels: labels}, + }}, + } + k8sClient := fake.NewSimpleClientset() + csmName = "csm" + 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{ + ObjectMeta: apiv1.ObjectMeta{ + Name: csmName, + Namespace: "default", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: apiv1.ObjectMeta{}, + Spec: corev1.PodSpec{Containers: containers}, + }, + }, + }, apiv1.CreateOptions{}) + assert.NoError(t, err) + assert.NotNil(t, create) + k8sClient.PrependReactor("patch", "deployments", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf("fake error") + }) + err = deploymentpkg.SyncDeployment(context.Background(), deployment, k8sClient, csmName) + assert.Error(t, err) +} diff --git a/controllers/csm_controller.go b/controllers/csm_controller.go index dd2f76010..70d0b937c 100644 --- a/controllers/csm_controller.go +++ b/controllers/csm_controller.go @@ -879,6 +879,11 @@ func (r *ContainerStorageModuleReconciler) SyncCSM(ctx context.Context, cr csmv1 } } + // If dell connectivity client is deployed, create role/rolebindings in the csm namespaces + if err = utils.CheckAccAndCreateRbac(ctx, operatorConfig, ctrlClient); err != nil { + return err + } + return nil } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 1cfbeb70e..4c0563b8b 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -42,6 +42,7 @@ import ( t1 "k8s.io/apimachinery/pkg/types" confv1 "k8s.io/client-go/applyconfigurations/apps/v1" acorev1 "k8s.io/client-go/applyconfigurations/core/v1" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/yaml" @@ -139,6 +140,12 @@ const ( PodmonNodeComponent = "podmon-node" // ApplicationMobilityNamespace - application-mobility ApplicationMobilityNamespace = "application-mobility" + // ExistingNamespace - existing namespace + ExistingNamespace = "" + // ClientNamespace - client namespace + ClientNamespace = "" + // BrownfieldManifest - brownfield-onboard.yaml + BrownfieldManifest = "brownfield-onboard.yaml" ) // SplitYaml divides a big bytes of yaml files in individual yaml files. @@ -1202,7 +1209,39 @@ func getUpgradeInfo[T csmv1.CSMComponentType](ctx context.Context, operatorConfi return upgradePath.MinUpgradePath, nil } -func getNamespaces(ctx context.Context, ctrlClient crclient.Client) ([]string, error) { +// BrownfieldOnboard will onboard the brownfield cluster +func BrownfieldOnboard(ctx context.Context, path string, cr csmv1.ApexConnectivityClient, ctrlClient crclient.Client) error { + logInstance := logger.GetLogger(ctx) + + namespaces, err := GetNamespaces(ctx, ctrlClient) + if err != nil { + logInstance.Error(err, "Failed to get namespaces") + return err + } + + manifestFile, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + logInstance.Error(err, "Failed to read manifest file") + return err + } + + yamlFile := string(manifestFile) + + for _, ns := range namespaces { + + yamlFile := strings.ReplaceAll(yamlFile, ExistingNamespace, ns) + yamlFile = strings.ReplaceAll(yamlFile, ClientNamespace, cr.Namespace) + + err := CreateObjects(ctx, yamlFile, ctrlClient) + if err != nil { + return err + } + } + return nil +} + +// GetNamespaces returns the list of namespaces in the cluster +func GetNamespaces(ctx context.Context, ctrlClient crclient.Client) ([]string, error) { // Set to store unique namespaces namespaceMap := make(map[string]struct{}) @@ -1223,3 +1262,72 @@ func getNamespaces(ctx context.Context, ctrlClient crclient.Client) ([]string, e return namespaces, nil } + +// CreateObjects creates the objects in the cluster +func CreateObjects(ctx context.Context, yamlFile string, ctrlClient crclient.Client) error { + deployObjects, err := GetModuleComponentObj([]byte(yamlFile)) + if err != nil { + return err + } + for _, obj := range deployObjects { + log.FromContext(ctx).Info("namespace of parsed object is", "object", obj.GetNamespace()) + + found := obj.DeepCopyObject().(crclient.Object) + err := ctrlClient.Get(ctx, crclient.ObjectKey{Namespace: obj.GetNamespace(), Name: obj.GetName()}, found) + if err != nil && k8serror.IsNotFound(err) { + log.FromContext(ctx).Info("Creating a new object", "object", obj.GetObjectKind().GroupVersionKind().String()) + err := ctrlClient.Create(ctx, obj) + if err != nil { + return err + } + } else if err != nil { + log.FromContext(ctx).Info("Unknown error trying to retrieve the object.", "Error", err.Error()) + return err + } else { + log.FromContext(ctx).Info("Updating Object", "object", obj.GetObjectKind().GroupVersionKind().String()) + err = ctrlClient.Update(ctx, obj) + if err != nil { + return err + } + } + } + return nil +} + +// CheckAccAndCreateRbac checks if the dell connectivity client exists and creates the role and rolebindings +func CheckAccAndCreateRbac(ctx context.Context, operatorConfig OperatorConfig, ctrlClient crclient.Client) error { + logInstance := logger.GetLogger(ctx) + accList := &csmv1.ApexConnectivityClientList{} + if err := ctrlClient.List(ctx, accList); err != nil { + logInstance.Info("dell connectivity client not found") + } else if len(accList.Items) <= 0 { + logInstance.Info("dell connectivity client not found") + } else { + logInstance.Info("dell connectivity client found") + cr := new(csmv1.ApexConnectivityClient) + accConfigVersion := accList.Items[0].Spec.Client.ConfigVersion + brownfieldManifestFilePath := fmt.Sprintf("%s/clientconfig/%s/%s/%s", operatorConfig.ConfigDirectory, + csmv1.DreadnoughtClient, accConfigVersion, BrownfieldManifest) + if err = BrownfieldOnboard(ctx, brownfieldManifestFilePath, *cr, ctrlClient); err != nil { + logInstance.Error(err, "error creating role/rolebindings") + return err + } + } + return nil +} + +// CreateBrownfieldRbac creates the role and rolebindings +func CreateBrownfieldRbac(ctx context.Context, operatorConfig OperatorConfig, cr csmv1.ApexConnectivityClient, ctrlClient crclient.Client) error { + logInstance := logger.GetLogger(ctx) + csmList := &csmv1.ContainerStorageModuleList{} + err := ctrlClient.List(ctx, csmList) + if err == nil && len(csmList.Items) > 0 { + logInstance.Info("Found existing csm installations. Proceeding to create role/rolebindings") + brownfieldManifestFilePath := fmt.Sprintf("%s/clientconfig/%s/%s/%s", operatorConfig.ConfigDirectory, csmv1.DreadnoughtClient, cr.Spec.Client.ConfigVersion, BrownfieldManifest) + if err = BrownfieldOnboard(ctx, brownfieldManifestFilePath, cr, ctrlClient); err != nil { + logInstance.Error(err, "error creating role/rolebindings") + return err + } + } + return nil +}