Skip to content

Commit

Permalink
Add ManagedClusterView controller and create or update ClientInfo
Browse files Browse the repository at this point in the history
- 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 19, 2024
1 parent d743b7b commit ef0af41
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 11 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
12 changes: 3 additions & 9 deletions controllers/managedcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils"
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"
clusterv1 "open-cluster-management.io/api/cluster/v1"
Expand All @@ -28,7 +29,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 All @@ -46,7 +46,7 @@ func (r *ManagedClusterReconciler) Reconcile(ctx context.Context, req reconcile.
return ctrl.Result{}, err
}

logger.Info("Successfully reconciled ManagedCluster", "name", managedCluster.Name)
logger.Info("Successfully reconciled ManagedCluster")

return ctrl.Result{}, nil
}
Expand Down Expand Up @@ -78,18 +78,12 @@ 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).
For(&clusterv1.ManagedCluster{}, builder.WithPredicates(managedClusterPredicate, predicate.ResourceVersionChangedPredicate{})).
Owns(&viewv1beta1.ManagedClusterView{}).
Owns(&corev1.ConfigMap{}).
Complete(r)
}

Expand Down
199 changes: 199 additions & 0 deletions controllers/managedclusterview_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package controllers

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

ocsv1alpha1 "github.com/red-hat-storage/ocs-operator/api/v4/v1alpha1"
"github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils"
viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
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"
)

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"`
ClientManagedClusterName string `json:"clientManagedClusterName,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{})).
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")

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. %w", err)
}

clientInfoMap := make(map[string]ClientInfo)

for _, value := range resultData {
var odfInfo ocsv1alpha1.OdfInfoData
err := yaml.Unmarshal([]byte(value), &odfInfo)
if err != nil {
return fmt.Errorf("failed to unmarshal ODF info data. %w", 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 {
managedCluster, err := utils.GetManagedClusterById(ctx, c, client.ClusterID)
if err != nil {
return err
}
clientInfo := ClientInfo{
ClusterID: client.ClusterID,
Name: client.Name,
ProviderInfo: providerInfo,
ClientManagedClusterName: managedCluster.Name,
}
clientInfoMap[fmt.Sprintf("%s/%s", managedCluster.Name, client.Name)] = clientInfo
}
}

operatorNamespace := os.Getenv("POD_NAMESPACE")
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: ClientInfoConfigMapName,
Namespace: operatorNamespace,
},
}
err = c.Get(ctx, types.NamespacedName{Name: ClientInfoConfigMapName, Namespace: operatorNamespace}, configMap)
if err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("failed to get ConfigMap. %w", err)
}

if configMap.Data == nil {
configMap.Data = make(map[string]string)
}

op, err := controllerutil.CreateOrUpdate(ctx, c, configMap, func() error {
for clientKey, clientInfo := range clientInfoMap {
clientInfoJSON, err := json.Marshal(clientInfo)
if err != nil {
return fmt.Errorf("failed to marshal client info. %w", err)
}
configMap.Data[clientKey] = string(clientInfoJSON)
}

mcvOwnerRefs := managedClusterView.GetOwnerReferences()
for _, mcvOwnerRef := range mcvOwnerRefs {
exists := false
for _, existingOwnerRef := range configMap.OwnerReferences {
if existingOwnerRef.UID == mcvOwnerRef.UID {
exists = true
break
}
}
if !exists {
configMap.OwnerReferences = append(configMap.OwnerReferences, mcvOwnerRef)
}
}
return nil
})

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

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

return nil
}
Loading

0 comments on commit ef0af41

Please sign in to comment.