From 5899bfa27128c0120f5c083a97977d60c0288404 Mon Sep 17 00:00:00 2001 From: vbadrina Date: Tue, 12 Nov 2024 16:54:26 +0530 Subject: [PATCH] Manage client pairings via createManifestWorkForClusterPairingConfigMap - Added `ClientID` to `ClientInfo` struct for unique client identification. - Updated `Reconcile` to invoke `createManifestWorkForClusterPairingConfigMap`. - Implemented `createManifestWorkForClusterPairingConfigMap` to manage client pairings via ConfigMap in ManifestWork. - Ensured each client is paired with exactly one other client in the ConfigMap. - Added helper functions `DecodeConfigMap` and `GetManifestWork` in utils. - Updated dependencies in `go.mod` and `go.sum`. Signed-off-by: vbadrina --- controllers/managedclusterview_controller.go | 2 + .../managedclusterview_controller_test.go | 61 ++++++------ controllers/mirrorpeer_controller.go | 94 +++++++++++++++++++ controllers/utils/configmap.go | 23 +++++ controllers/utils/manifestwork.go | 14 +++ go.mod | 2 +- go.sum | 4 +- 7 files changed, 167 insertions(+), 33 deletions(-) diff --git a/controllers/managedclusterview_controller.go b/controllers/managedclusterview_controller.go index 689a6a7d..95961c67 100644 --- a/controllers/managedclusterview_controller.go +++ b/controllers/managedclusterview_controller.go @@ -51,6 +51,7 @@ type ClientInfo struct { Name string `json:"name"` ProviderInfo ProviderInfo `json:"providerInfo,omitempty"` ClientManagedClusterName string `json:"clientManagedClusterName,omitempty"` + ClientID string `json:"clientId"` } func (r *ManagedClusterViewReconciler) SetupWithManager(mgr ctrl.Manager) error { @@ -158,6 +159,7 @@ func createOrUpdateConfigMap(ctx context.Context, c client.Client, managedCluste Name: client.Name, ProviderInfo: providerInfo, ClientManagedClusterName: managedCluster.Name, + ClientID: client.ClientID, } clientInfoJSON, err := json.Marshal(clientInfo) if err != nil { diff --git a/controllers/managedclusterview_controller_test.go b/controllers/managedclusterview_controller_test.go index a8e46bd3..271b64cb 100644 --- a/controllers/managedclusterview_controller_test.go +++ b/controllers/managedclusterview_controller_test.go @@ -72,19 +72,20 @@ func TestCreateOrUpdateConfigMap(t *testing.T) { data := map[string]string{ "openshift-storage_ocs-storagecluster.config.yaml": ` - version: "4.Y.Z" - deploymentType: "internal" - clients: - - name: "client1" - clusterId: "cluster1" - storageCluster: - namespacedName: - name: "ocs-storagecluster" - namespace: "openshift-storage" - storageProviderEndpoint: "" - cephClusterFSID: "7a3d6b81-a55d-44fe-84d0-46c67cd395ca" - storageSystemName: "ocs-storagecluster-storagesystem" - `, +version: "4.Y.Z" +deploymentType: "internal" +clients: + - name: "client1" + clusterId: "cluster1" + clientId: "client1" +storageCluster: + namespacedName: + name: "ocs-storagecluster" + namespace: "openshift-storage" + storageProviderEndpoint: "" + cephClusterFSID: "7a3d6b81-a55d-44fe-84d0-46c67cd395ca" +storageSystemName: "ocs-storagecluster-storagesystem" +`, } ownerRefs := []metav1.OwnerReference{ *metav1.NewControllerRef(mc1, clusterv1.SchemeGroupVersion.WithKind("ManagedCluster")), @@ -104,7 +105,7 @@ func TestCreateOrUpdateConfigMap(t *testing.T) { assert.NotNil(t, cm) expectedData := map[string]string{ - "cluster1-name/client1": `{"clusterId":"cluster1","name":"client1","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"cluster1","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"7a3d6b81-a55d-44fe-84d0-46c67cd395ca"},"clientManagedClusterName":"cluster1-name"}`, + "cluster1-name/client1": `{"clusterId":"cluster1","name":"client1","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"cluster1","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"7a3d6b81-a55d-44fe-84d0-46c67cd395ca"},"clientManagedClusterName":"cluster1-name","clientId":"client1"}`, } assert.Equal(t, expectedData, cm.Data) @@ -112,7 +113,6 @@ func TestCreateOrUpdateConfigMap(t *testing.T) { assert.Equal(t, mc1.Name, cm.OwnerReferences[0].Name) assert.Equal(t, "ManagedCluster", cm.OwnerReferences[0].Kind) assert.Equal(t, clusterv1.GroupVersion.String(), cm.OwnerReferences[0].APIVersion) - }) t.Run("Update ConfigMap with MCV in cluster2", func(t *testing.T) { @@ -120,19 +120,20 @@ func TestCreateOrUpdateConfigMap(t *testing.T) { ctx := context.TODO() data := map[string]string{ "openshift-storage_ocs-storagecluster.config.yaml": ` - version: "4.Y.Z" - deploymentType: "internal" - clients: - - name: "client2" - clusterId: "cluster2" - storageCluster: - namespacedName: - name: "ocs-storagecluster" - namespace: "openshift-storage" - storageProviderEndpoint: "" - cephClusterFSID: "8b3d6b81-b55d-55fe-94d0-56c67cd495ca" - storageSystemName: "ocs-storagecluster-storagesystem" - `, +version: "4.Y.Z" +deploymentType: "internal" +clients: + - name: "client2" + clusterId: "cluster2" + clientId: "client2" +storageCluster: + namespacedName: + name: "ocs-storagecluster" + namespace: "openshift-storage" + storageProviderEndpoint: "" + cephClusterFSID: "8b3d6b81-b55d-55fe-94d0-56c67cd495ca" +storageSystemName: "ocs-storagecluster-storagesystem" +`, } ownerRefs := []metav1.OwnerReference{ *metav1.NewControllerRef(mc2, clusterv1.SchemeGroupVersion.WithKind("ManagedCluster")), @@ -151,8 +152,8 @@ func TestCreateOrUpdateConfigMap(t *testing.T) { assert.NotNil(t, cm) expectedData := map[string]string{ - "cluster1-name/client1": `{"clusterId":"cluster1","name":"client1","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"cluster1","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"7a3d6b81-a55d-44fe-84d0-46c67cd395ca"},"clientManagedClusterName":"cluster1-name"}`, - "cluster2-name/client2": `{"clusterId":"cluster2","name":"client2","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"cluster2","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"8b3d6b81-b55d-55fe-94d0-56c67cd495ca"},"clientManagedClusterName":"cluster2-name"}`, + "cluster1-name/client1": `{"clusterId":"cluster1","name":"client1","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"cluster1","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"7a3d6b81-a55d-44fe-84d0-46c67cd395ca"},"clientManagedClusterName":"cluster1-name","clientId":"client1"}`, + "cluster2-name/client2": `{"clusterId":"cluster2","name":"client2","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"cluster2","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"8b3d6b81-b55d-55fe-94d0-56c67cd495ca"},"clientManagedClusterName":"cluster2-name","clientId":"client2"}`, } assert.Equal(t, expectedData, cm.Data) diff --git a/controllers/mirrorpeer_controller.go b/controllers/mirrorpeer_controller.go index 93b02d5d..5fc0eb8e 100644 --- a/controllers/mirrorpeer_controller.go +++ b/controllers/mirrorpeer_controller.go @@ -259,10 +259,104 @@ func (r *MirrorPeerReconciler) Reconcile(ctx context.Context, req ctrl.Request) logger.Error("Failed to create StorageClusterPeer", "error", err) return result, err } + + result, err = createManifestWorkForClusterPairingConfigMap(ctx, r.Client, logger, mirrorPeer) + if err != nil { + logger.Error("Failed to create ManifestWork for ClusterPairingConfigMap", "error", err) + return result, err + } } return r.updateMirrorPeerStatus(ctx, mirrorPeer) } +func createManifestWorkForClusterPairingConfigMap(ctx context.Context, client client.Client, logger *slog.Logger, mirrorPeer multiclusterv1alpha1.MirrorPeer) (ctrl.Result, error) { + clientInfoMap, err := fetchClientInfoConfigMap(ctx, client) + if err != nil { + if k8serrors.IsNotFound(err) { + logger.Info("Client info config map not found. Retrying request another time...") + return ctrl.Result{Requeue: true}, nil + } + return ctrl.Result{}, err + } + + items := mirrorPeer.Spec.Items + + ci1, err := getClientInfoFromConfigMap(clientInfoMap.Data, getKey(items[0].ClusterName, items[0].StorageClusterRef.Name)) + if err != nil { + return ctrl.Result{}, err + } + ci2, err := getClientInfoFromConfigMap(clientInfoMap.Data, getKey(items[1].ClusterName, items[1].StorageClusterRef.Name)) + if err != nil { + return ctrl.Result{}, err + } + + if err := updateProviderConfigMap(ctx, client, mirrorPeer, ci1, ci2); err != nil { + return ctrl.Result{}, err + } + + if err := updateProviderConfigMap(ctx, client, mirrorPeer, ci2, ci1); err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// updateProviderConfigMap updates the ConfigMap on the provider with the new client pairing +func updateProviderConfigMap(ctx context.Context, client client.Client, mirrorPeer multiclusterv1alpha1.MirrorPeer, providerClientInfo ClientInfo, pairedClientInfo ClientInfo) error { + providerName := providerClientInfo.ProviderInfo.ProviderManagedClusterName + manifestWorkName := "dr-client-pair" + manifestWorkNamespace := providerName + + // Attempt to get the existing ManifestWork + manifestWork, err := utils.GetManifestWork(ctx, client, manifestWorkName, manifestWorkNamespace) + var configMap *corev1.ConfigMap + + if err != nil { + if k8serrors.IsNotFound(err) { + // ManifestWork does not exist; create a new ConfigMap + configMap = &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dr-client-pair-config", + }, + Data: make(map[string]string), + } + } else { + return fmt.Errorf("failed to get ManifestWork: %w", err) + } + } else { + // Decode existing ConfigMap + if len(manifestWork.Spec.Workload.Manifests) == 0 { + return fmt.Errorf("ManifestWork %s has no manifests", manifestWorkName) + } + objJson := manifestWork.Spec.Workload.Manifests[0].RawExtension.Raw + configMap, err = utils.DecodeConfigMap(objJson) + if err != nil { + return fmt.Errorf("failed to decode ConfigMap: %w", err) + } + } + + configMap.Data[providerClientInfo.ClientID] = pairedClientInfo.ClientID + + updatedObjJson, err := json.Marshal(configMap) + if err != nil { + return fmt.Errorf("failed to marshal updated ConfigMap: %w", err) + } + + ownerRef := metav1.OwnerReference{ + APIVersion: mirrorPeer.APIVersion, + Kind: mirrorPeer.Kind, + Name: mirrorPeer.Name, + UID: mirrorPeer.UID, + } + + _, err = utils.CreateOrUpdateManifestWork(ctx, client, manifestWorkName, manifestWorkNamespace, updatedObjJson, ownerRef) + if err != nil { + return fmt.Errorf("failed to update ManifestWork for provider %s: %w", providerName, err) + } + + return nil +} + func getKey(clusterName, clientName string) string { return fmt.Sprintf("%s/%s", clusterName, clientName) } diff --git a/controllers/utils/configmap.go b/controllers/utils/configmap.go index 2f6fc357..b03b5c5a 100644 --- a/controllers/utils/configmap.go +++ b/controllers/utils/configmap.go @@ -7,6 +7,8 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -44,3 +46,24 @@ func SplitKeyForNamespacedName(key string) types.NamespacedName { namespacedName := strings.Split(splitKey[0], "_") // [openshift-storage,ocs-storagecluster] return types.NamespacedName{Namespace: namespacedName[0], Name: namespacedName[1]} } + +func DecodeConfigMap(objJson []byte) (*corev1.ConfigMap, error) { + scheme := runtime.NewScheme() + err := corev1.AddToScheme(scheme) + if err != nil { + return nil, fmt.Errorf("failed to add to scheme in decode config map: %w", err) + } + decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer() + + obj, _, err := decoder.Decode(objJson, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to decode objJson: %w", err) + } + + configMap, ok := obj.(*corev1.ConfigMap) + if !ok { + return nil, fmt.Errorf("decoded object is not a ConfigMap") + } + + return configMap, nil +} diff --git a/controllers/utils/manifestwork.go b/controllers/utils/manifestwork.go index ad640bab..0a8baa1c 100644 --- a/controllers/utils/manifestwork.go +++ b/controllers/utils/manifestwork.go @@ -6,6 +6,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" workv1 "open-cluster-management.io/api/work/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -43,3 +44,16 @@ func CreateOrUpdateManifestWork(ctx context.Context, c client.Client, name strin return operationResult, nil } + +func GetManifestWork(ctx context.Context, c client.Client, manifestWorkName string, namespace string) (*workv1.ManifestWork, error) { + var manifestWork workv1.ManifestWork + + if err := c.Get(ctx, types.NamespacedName{ + Name: manifestWorkName, + Namespace: namespace, + }, &manifestWork); err != nil { + return nil, fmt.Errorf("failed to get ManifestWork %s in namespace %s: %w", manifestWorkName, namespace, err) + } + + return &manifestWork, nil +} diff --git a/go.mod b/go.mod index 4cd5ba89..191cc3f9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/openshift/api v0.0.0-20240828125535-01b3675ba7b3 github.com/openshift/library-go v0.0.0-20240124134907-4dfbf6bc7b11 github.com/ramendr/ramen/api v0.0.0-20241001141243-29d6f22ad237 - github.com/red-hat-storage/ocs-operator/api/v4 v4.0.0-20241111204837-199e115469a1 + github.com/red-hat-storage/ocs-operator/api/v4 v4.0.0-20241112092644-99fdb662050d github.com/rook/rook/pkg/apis v0.0.0-20240828225153-88eab510dd2b github.com/spf13/cobra v1.8.1 github.com/stolostron/multicloud-operators-foundation v0.0.0-20220824091202-e9cd9710d009 diff --git a/go.sum b/go.sum index c672a5e8..df6b93c7 100644 --- a/go.sum +++ b/go.sum @@ -731,8 +731,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/ramendr/ramen/api v0.0.0-20241001141243-29d6f22ad237 h1:ig6ePD0yopC5Qi5BRmhsIsKaOkdsGXTSmG3HTYIpquo= github.com/ramendr/ramen/api v0.0.0-20241001141243-29d6f22ad237/go.mod h1:nO6VM/+PEhcPGyFIQJdhY6ip822cA61PAy/s6IjenAA= -github.com/red-hat-storage/ocs-operator/api/v4 v4.0.0-20241111204837-199e115469a1 h1:3fZJ6LVBgriKYuhBCSz0X5wYuyivcxhrbG9XaMhUt3M= -github.com/red-hat-storage/ocs-operator/api/v4 v4.0.0-20241111204837-199e115469a1/go.mod h1:fcFoM7FdQba/2m0CecUGNu38JaTt8quRKXhCqt8C3Jg= +github.com/red-hat-storage/ocs-operator/api/v4 v4.0.0-20241112092644-99fdb662050d h1:+5S4F665PL+8TK9OGHX/2A+N9jPoT2PRHO69g7z7ZLk= +github.com/red-hat-storage/ocs-operator/api/v4 v4.0.0-20241112092644-99fdb662050d/go.mod h1:fcFoM7FdQba/2m0CecUGNu38JaTt8quRKXhCqt8C3Jg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=