Skip to content

Commit

Permalink
feat: Add ManagedClusterView controller and create or update ClientIn…
Browse files Browse the repository at this point in the history
…fo configmap

- Added initialization for ManagedClusterViewReconciler in manager.go to setup the ManagedClusterView controller.
- Creates or updates configMap odf-client-info which maps client to it provider cluster
- Created comprehensive unit tests to cover the creation and update scenarios of the ConfigMap.

Signed-off-by: vbadrina <[email protected]>
  • Loading branch information
vbnrh committed Jul 16, 2024
1 parent df4b7a4 commit 150f216
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ metadata:
]
capabilities: Basic Install
console.openshift.io/plugins: '["odf-multicluster-console"]'
createdAt: "2024-07-12T13:14:27Z"
createdAt: "2024-07-16T05:37:17Z"
olm.skipRange: ""
operators.openshift.io/infrastructure-features: '["disconnected"]'
operators.operatorframework.io/builder: operator-sdk-v1.34.1
Expand Down
8 changes: 0 additions & 8 deletions controllers/managedcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const (
OdfInfoClusterClaimNamespacedName = "odfinfo.odf.openshift.io"
)

// +kubebuilder:rbac:groups=view.open-cluster-management.io,resources=managedclusterviews,verbs=get;list;watch;create;update
func (r *ManagedClusterReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
logger := r.Logger.With("ManagedCluster", req.NamespacedName)
logger.Info("Reconciling ManagedCluster")
Expand Down Expand Up @@ -78,13 +77,6 @@ func (r *ManagedClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
}
return hasRequiredODFKey(obj)
},

DeleteFunc: func(e event.DeleteEvent) bool {
return false
},
GenericFunc: func(e event.GenericEvent) bool {
return false
},
}

return ctrl.NewControllerManagedBy(mgr).
Expand Down
209 changes: 209 additions & 0 deletions controllers/managedclusterview_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package controllers

import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"

viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/yaml"
)

type ManagedClusterViewReconciler struct {
Client client.Client
Logger *slog.Logger
}

const (
ODFInfoConfigMapName = "odf-info"
ConfigMapResourceType = "ConfigMap"
ClientInfoConfigMapName = "odf-client-info"
)

// The temporary ODFInfoData struct starts here, Once it is exported in OCS, then it will be removed
type connectedClient struct {
Name string `yaml:"name"`
ClusterID string `yaml:"clusterId"`
}

type infoStorageCluster struct {
NamespacedName types.NamespacedName `yaml:"namespacedName"`
StorageProviderEndpoint string `yaml:"storageProviderEndpoint"`
CephClusterFSID string `yaml:"cephClusterFSID"`
}

type OdfInfoData struct {
Version string `yaml:"version"`
DeploymentType string `yaml:"deploymentType"`
Clients []connectedClient `yaml:"clients"`
StorageCluster infoStorageCluster `yaml:"storageCluster"`
StorageSystemName string `yaml:"storageSystemName"`
}

// The temporary ODFInfoData struct ends here

type ProviderInfo struct {
Version string `json:"version"`
DeploymentType string `json:"deploymentType"`
StorageSystemName string `json:"storageSystemName"`
ProviderManagedClusterName string `json:"providerManagedClusterName"`
NamespacedName types.NamespacedName `json:"namespacedName"`
StorageProviderEndpoint string `json:"storageProviderEndpoint"`
CephClusterFSID string `json:"cephClusterFSID"`
}

type ClientInfo struct {
ClusterID string `json:"clusterId"`
Name string `json:"name"`
ProviderInfo ProviderInfo `json:"providerInfo,omitempty"`
}

func (r *ManagedClusterViewReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.Logger.Info("Setting up ManagedClusterViewReconciler with manager")
managedClusterViewPredicate := predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
obj, ok := e.ObjectNew.(*viewv1beta1.ManagedClusterView)
if !ok {
return false
}
return hasODFInfoInScope(obj)
},
CreateFunc: func(e event.CreateEvent) bool {
obj, ok := e.Object.(*viewv1beta1.ManagedClusterView)
if !ok {
return false
}
return hasODFInfoInScope(obj)
},
}

return ctrl.NewControllerManagedBy(mgr).
For(&viewv1beta1.ManagedClusterView{}, builder.WithPredicates(managedClusterViewPredicate, predicate.ResourceVersionChangedPredicate{}, predicate.GenerationChangedPredicate{})).
Owns(&corev1.ConfigMap{}).
Complete(r)
}

func hasODFInfoInScope(mc *viewv1beta1.ManagedClusterView) bool {
if mc.Spec.Scope.Name == ODFInfoConfigMapName && mc.Spec.Scope.Kind == ConfigMapResourceType {
return true
}
return false
}

func (r *ManagedClusterViewReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
logger := r.Logger.With("ManagedClusterView", req.NamespacedName)
logger.Info("Reconciling ManagedClusterView")

var managedClusterView viewv1beta1.ManagedClusterView
if err := r.Client.Get(ctx, req.NamespacedName, &managedClusterView); err != nil {
if client.IgnoreNotFound(err) != nil {
logger.Error("Failed to get ManagedClusterView", "error", err)
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}

if err := createOrUpdateConfigMap(ctx, r.Client, managedClusterView, r.Logger); err != nil {
logger.Error("Failed to create or update ConfigMap for ManagedClusterView", "error", err)
return ctrl.Result{}, err
}

logger.Info("Successfully reconciled ManagedClusterView", "name", managedClusterView.Name)

return ctrl.Result{}, nil
}

func createOrUpdateConfigMap(ctx context.Context, c client.Client, managedClusterView viewv1beta1.ManagedClusterView, logger *slog.Logger) error {
logger = logger.With("ManagedClusterView", managedClusterView.Name, "Namespace", managedClusterView.Namespace)

var resultData map[string]string
err := json.Unmarshal(managedClusterView.Status.Result.Raw, &resultData)
if err != nil {
return fmt.Errorf("failed to unmarshal result data: %v", err)
}

clientInfoMap := make(map[string]ClientInfo)

for _, value := range resultData {
var odfInfo OdfInfoData
err := yaml.Unmarshal([]byte(value), &odfInfo)
if err != nil {
return fmt.Errorf("failed to unmarshal ODF info data: %v", err)
}

providerInfo := ProviderInfo{
Version: odfInfo.Version,
DeploymentType: odfInfo.DeploymentType,
CephClusterFSID: odfInfo.StorageCluster.CephClusterFSID,
StorageProviderEndpoint: odfInfo.StorageCluster.StorageProviderEndpoint,
NamespacedName: odfInfo.StorageCluster.NamespacedName,
StorageSystemName: odfInfo.StorageSystemName,
ProviderManagedClusterName: managedClusterView.Namespace,
}

for _, client := range odfInfo.Clients {
clientInfo := ClientInfo{
ClusterID: client.ClusterID,
Name: client.Name,
ProviderInfo: providerInfo,
}
clientInfoMap[client.Name] = clientInfo
}
}

configMapData := make(map[string]string)
for clientName, clientInfo := range clientInfoMap {
clientInfoJSON, err := json.Marshal(clientInfo)
if err != nil {
return fmt.Errorf("failed to marshal client info: %v", err)
}
configMapData[clientName] = string(clientInfoJSON)
}

operatorNamespace := os.Getenv("POD_NAMESPACE")
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: ClientInfoConfigMapName,
Namespace: operatorNamespace,
},
}

op, err := controllerutil.CreateOrUpdate(ctx, c, configMap, func() error {
configMap.Data = configMapData

ownerExists := false
for _, ownerRef := range configMap.OwnerReferences {
if ownerRef.UID == managedClusterView.UID {
ownerExists = true
break
}
}

if !ownerExists {
ownerRef := *metav1.NewControllerRef(&managedClusterView, viewv1beta1.GroupVersion.WithKind("ManagedClusterView"))
logger.Info("OwnerRef added", "UID", string(ownerRef.UID))
configMap.OwnerReferences = append(configMap.OwnerReferences, ownerRef)
}

return nil
})

if err != nil {
return fmt.Errorf("failed to create or update ConfigMap: %v", err)
}

logger.Info(fmt.Sprintf("ConfigMap %s in namespace %s has been %s", ClientInfoConfigMapName, operatorNamespace, op))

return nil
}
135 changes: 135 additions & 0 deletions controllers/managedclusterview_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//go:build unit
// +build unit

package controllers

import (
"context"
"encoding/json"
"os"
"testing"

"github.com/google/uuid"
"github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils"
viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestCreateOrUpdateConfigMap(t *testing.T) {

s := scheme.Scheme
_ = viewv1beta1.AddToScheme(s)
_ = corev1.AddToScheme(s)

c := fake.NewClientBuilder().WithScheme(s).Build()
os.Setenv("POD_NAMESPACE", "openshift-operators")
logger := utils.GetLogger(utils.GetZapLogger(true))

createManagedClusterView := func(name, namespace string, data map[string]string) *viewv1beta1.ManagedClusterView {
raw, _ := json.Marshal(data)
return &viewv1beta1.ManagedClusterView{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
UID: types.UID(uuid.New().String()),
},
Status: viewv1beta1.ViewStatus{
Result: runtime.RawExtension{Raw: raw},
},
}
}

t.Run("Create ConfigMap", func(t *testing.T) {
data := map[string]string{
"openshift-storage_ocs-storagecluster.config.yaml": `
version: "4.Y.Z"
deploymentType: "internal"
clients:
- name: "client1"
clusterId: "cluster1"
- name: "client2"
clusterId: "cluster2"
storageCluster:
namespacedName:
name: "ocs-storagecluster"
namespace: "openshift-storage"
storageProviderEndpoint: ""
cephClusterFSID: "7a3d6b81-a55d-44fe-84d0-46c67cd395ca"
storageSystemName: "ocs-storagecluster-storagesystem"
`,
}
mcv := createManagedClusterView("test-view", "local-cluster", data)

ctx := context.TODO()
err := c.Create(ctx, mcv)
assert.NoError(t, err)

err = createOrUpdateConfigMap(ctx, c, *mcv, logger)
assert.NoError(t, err)

cm := &corev1.ConfigMap{}
err = c.Get(ctx, types.NamespacedName{Name: ClientInfoConfigMapName, Namespace: os.Getenv("POD_NAMESPACE")}, cm)
assert.NoError(t, err)
assert.NotNil(t, cm)

expectedData := map[string]string{
"client1": `{"clusterId":"cluster1","name":"client1","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"local-cluster","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"7a3d6b81-a55d-44fe-84d0-46c67cd395ca"}}`,
"client2": `{"clusterId":"cluster2","name":"client2","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"local-cluster","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"7a3d6b81-a55d-44fe-84d0-46c67cd395ca"}}`,
}

assert.Equal(t, expectedData, cm.Data)
assert.Equal(t, 1, len(cm.OwnerReferences))
assert.Equal(t, mcv.Name, cm.OwnerReferences[0].Name)
assert.Equal(t, "ManagedClusterView", cm.OwnerReferences[0].Kind)
assert.Equal(t, viewv1beta1.GroupVersion.String(), cm.OwnerReferences[0].APIVersion)

})

t.Run("Update ConfigMap with additional owner reference", func(t *testing.T) {
ctx := context.TODO()
data := map[string]string{
"openshift-storage_ocs-storagecluster.config.yaml": `
version: "4.Y.Z"
deploymentType: "internal"
clients:
- name: "client1"
clusterId: "cluster1"
- name: "client2"
clusterId: "cluster2"
storageCluster:
namespacedName:
name: "ocs-storagecluster"
namespace: "openshift-storage"
storageProviderEndpoint: ""
cephClusterFSID: "7a3d6b81-a55d-44fe-84d0-46c67cd395ca"
storageSystemName: "ocs-storagecluster-storagesystem"
`,
}
mcv := createManagedClusterView("new-view", "local-cluster", data)

err := c.Create(ctx, mcv)
assert.NoError(t, err)

err = createOrUpdateConfigMap(ctx, c, *mcv, logger)
assert.NoError(t, err)

cm := &corev1.ConfigMap{}
err = c.Get(ctx, types.NamespacedName{Name: ClientInfoConfigMapName, Namespace: os.Getenv("POD_NAMESPACE")}, cm)
assert.NoError(t, err)
assert.NotNil(t, cm)

expectedData := map[string]string{
"client1": `{"clusterId":"cluster1","name":"client1","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"local-cluster","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"7a3d6b81-a55d-44fe-84d0-46c67cd395ca"}}`,
"client2": `{"clusterId":"cluster2","name":"client2","providerInfo":{"version":"4.Y.Z","deploymentType":"internal","storageSystemName":"ocs-storagecluster-storagesystem","providerManagedClusterName":"local-cluster","namespacedName":{"Namespace":"openshift-storage","Name":"ocs-storagecluster"},"storageProviderEndpoint":"","cephClusterFSID":"7a3d6b81-a55d-44fe-84d0-46c67cd395ca"}}`,
}

assert.Equal(t, expectedData, cm.Data)
assert.Equal(t, 2, len(cm.OwnerReferences))
})
}
8 changes: 8 additions & 0 deletions controllers/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ func (o *ManagerOptions) runManager() {
os.Exit(1)
}

if err = (&ManagedClusterViewReconciler{
Client: mgr.GetClient(),
Logger: logger.With("controller", "ManagedClusterViewReconciler"),
}).SetupWithManager(mgr); err != nil {
logger.Error("Failed to create ManagedClusterView controller", "error", err)
os.Exit(1)
}

if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
err = console.InitConsole(ctx, mgr.GetClient(), o.MulticlusterConsolePort, namespace)
if err != nil {
Expand Down
Loading

0 comments on commit 150f216

Please sign in to comment.