Skip to content

Commit

Permalink
feat: Implement reconciliation for ManagedCluster and create ManagedC…
Browse files Browse the repository at this point in the history
…lusterView

- Added reconciliation logic for `ManagedCluster`
- Added tests for `ManagedClusterReconciler` reconciliation logic and ensure ManagedClusterViews function.
- Reconciliation for ManagedCluster creates ManagedClusterView which pulls the ‘odf-info’ configmap onto the hub.
- Updated RBAC rules in `config/rbac/role.yaml` to include permissions for ManagedClusterView resources. (ManagedCluster RBAC already there)
- Plumbing to make the controllers work like the items above
- Updated go.mod and go.sum to include `github.com/stolostron/multicloud-operators-foundation`.

Signed-off-by: vbadrina <[email protected]>
  • Loading branch information
vbnrh committed Jul 15, 2024
1 parent 0e61c75 commit 8cbd78d
Show file tree
Hide file tree
Showing 10 changed files with 414 additions and 1 deletion.
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-04-03T11:58:23Z"
createdAt: "2024-07-04T11:28:14Z"
olm.skipRange: ""
operators.openshift.io/infrastructure-features: '["disconnected"]'
operators.operatorframework.io/builder: operator-sdk-v1.34.1
Expand Down Expand Up @@ -224,6 +224,12 @@ spec:
- roles
verbs:
- '*'
- apiGroups:
- view.open-cluster-management.io
resources:
- managedclusterviews
verbs:
- '*'
- apiGroups:
- work.open-cluster-management.io
resources:
Expand Down
6 changes: 6 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ rules:
- roles
verbs:
- '*'
- apiGroups:
- view.open-cluster-management.io
resources:
- managedclusterviews
verbs:
- '*'
- apiGroups:
- work.open-cluster-management.io
resources:
Expand Down
123 changes: 123 additions & 0 deletions controllers/managedcluster_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package controllers

import (
"context"
"log/slog"

"github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils"
viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
clusterv1 "open-cluster-management.io/api/cluster/v1"
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/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type ManagedClusterReconciler struct {
Client client.Client
Scheme *runtime.Scheme
Logger *slog.Logger
}

const odfConfigMapName = "odf-info"

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

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

if err := r.ensureManagedClusterViews(ctx, managedCluster.Name); err != nil {
logger.Error("Failed to ensure ManagedClusterView", "error", err)
return ctrl.Result{}, err
}

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

return ctrl.Result{}, nil
}

func (r *ManagedClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.Logger.Info("Setting up ManagedClusterReconciler with manager")

return ctrl.NewControllerManagedBy(mgr).
For(&clusterv1.ManagedCluster{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Watches(
&viewv1beta1.ManagedClusterView{},
handler.EnqueueRequestsFromMapFunc(r.managedClusterViewRequestMapper),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, predicate.GenerationChangedPredicate{}),
).
Complete(r)
}

func (r *ManagedClusterReconciler) managedClusterViewRequestMapper(ctx context.Context, obj client.Object) []reconcile.Request {
mcv, ok := obj.(*viewv1beta1.ManagedClusterView)

if !ok {
r.Logger.Error("Something unexpected occurred when casting obj into ManagedClusterView")
return []reconcile.Request{}
}
logger := r.Logger.With("ManagedClusterView", mcv)
mcName, ok := mcv.Labels[utils.MCVLabelKey]

if !ok {
return []reconcile.Request{}
}

logger.Info("ManagedClusterView of interest just got updated. Raising a reconcile request for ManagedCluster", "ManagedCluster", mcName)

return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Name: mcName,
},
},
}

}
func (r *ManagedClusterReconciler) ensureManagedClusterViews(ctx context.Context, clusterName string) error {
namespace := "openshift-storage"
resourceType := "ConfigMap"
r.Logger.Info("Processing PeerRef", "ClusterName", clusterName)
mcv, err := utils.GetManagedClusterViewByLabel(r.Client, utils.MCVLabelKey, clusterName, clusterName)
if err != nil {
r.Logger.Info("ManagedClusterView does not exist, creating a new one")
mcv, err = utils.CreateOrUpdateManagedClusterView(ctx, r.Client, odfConfigMapName, namespace, resourceType, clusterName)
if err != nil {
r.Logger.Error("Failed to create or update ManagedClusterView", "error", err)
return err
}
r.Logger.Info("ManagedClusterView created or updated successfully", "ManagedClusterView", mcv.Name)
} else {
desiredSpec := viewv1beta1.ViewSpec{
Scope: viewv1beta1.ViewScope{
Name: odfConfigMapName,
Namespace: namespace,
Resource: resourceType,
},
}
if mcv.Spec != desiredSpec {
r.Logger.Info("ManagedClusterView spec does not match, updating", "ClusterName", clusterName)
mcv, err = utils.CreateOrUpdateManagedClusterView(ctx, r.Client, odfConfigMapName, namespace, resourceType, clusterName)
if err != nil {
r.Logger.Error("Failed to update ManagedClusterView", "error", err)
return err
}
r.Logger.Info("ManagedClusterView updated successfully", "ManagedClusterView", mcv.Name)
} else {
r.Logger.Info("ManagedClusterView spec matches, no update needed", "ManagedClusterView", mcv.Name)
}
}

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

package controllers

import (
"context"
"testing"

"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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
clusterv1 "open-cluster-management.io/api/cluster/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestManagedClusterReconcile(t *testing.T) {
scheme := runtime.NewScheme()
_ = clusterv1.AddToScheme(scheme)
_ = viewv1beta1.AddToScheme(scheme)

client := fake.NewClientBuilder().WithScheme(scheme).Build()
logger := utils.GetLogger(utils.GetZapLogger(true))

reconciler := &ManagedClusterReconciler{
Client: client,
Scheme: scheme,
Logger: logger,
}

req := ctrl.Request{
NamespacedName: types.NamespacedName{Name: "test-cluster"},
}

t.Run("ManagedCluster not found", func(t *testing.T) {
res, err := reconciler.Reconcile(context.TODO(), req)
assert.NoError(t, err)
assert.Equal(t, ctrl.Result{}, res)
})

t.Run("ManagedCluster found", func(t *testing.T) {
managedCluster := &clusterv1.ManagedCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
}
_ = client.Create(context.TODO(), managedCluster)

res, err := reconciler.Reconcile(context.TODO(), req)
assert.NoError(t, err)
assert.Equal(t, ctrl.Result{}, res)
})
}

func TestEnsureManagedClusterViews(t *testing.T) {
scheme := runtime.NewScheme()
_ = clusterv1.AddToScheme(scheme)
_ = viewv1beta1.AddToScheme(scheme)

client := fake.NewClientBuilder().WithScheme(scheme).Build()
logger := utils.GetLogger(utils.GetZapLogger(true))

reconciler := &ManagedClusterReconciler{
Client: client,
Scheme: scheme,
Logger: logger,
}

t.Run("ManagedClusterView does not exist. It should create it", func(t *testing.T) {
err := reconciler.ensureManagedClusterViews(context.TODO(), "test-cluster")
assert.NoError(t, err)

createdMCV := &viewv1beta1.ManagedClusterView{}
err = client.Get(context.TODO(), types.NamespacedName{Name: "test-cluster-mcv", Namespace: "test-cluster"}, createdMCV)
assert.NoError(t, err)
assert.Equal(t, "test-cluster-mcv", createdMCV.Name)
assert.Equal(t, "test-cluster", createdMCV.Namespace)
assert.Equal(t, "odf-info", createdMCV.Spec.Scope.Name)
assert.Equal(t, "openshift-storage", createdMCV.Spec.Scope.Namespace)
})

t.Run("ManagedClusterView exists but spec does not match. Desired spec should be reached", func(t *testing.T) {
existingMCV := &viewv1beta1.ManagedClusterView{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster-mcv",
Namespace: "test-cluster",
},
Spec: viewv1beta1.ViewSpec{
Scope: viewv1beta1.ViewScope{
Name: "old-configmap",
Namespace: "old-namespace",
Resource: "ConfigMap",
},
},
}
_ = client.Create(context.TODO(), existingMCV)

err := reconciler.ensureManagedClusterViews(context.TODO(), "test-cluster")
assert.NoError(t, err)

updatedMCV := &viewv1beta1.ManagedClusterView{}
_ = client.Get(context.TODO(), types.NamespacedName{Name: "test-cluster-mcv", Namespace: "test-cluster"}, updatedMCV)
assert.Equal(t, "odf-info", updatedMCV.Spec.Scope.Name)
assert.Equal(t, "openshift-storage", updatedMCV.Spec.Scope.Namespace)
})

t.Run("ManagedClusterView exists and spec matches. Nothing should change. It should be error free", func(t *testing.T) {
existingMCV := &viewv1beta1.ManagedClusterView{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster-mcv",
Namespace: "test-cluster",
},
Spec: viewv1beta1.ViewSpec{
Scope: viewv1beta1.ViewScope{
Name: "odf-info",
Namespace: "openshift-storage",
Resource: "ConfigMap",
},
},
}
_ = client.Create(context.TODO(), existingMCV)

err := reconciler.ensureManagedClusterViews(context.TODO(), "test-cluster")
assert.NoError(t, err)
})
}
11 changes: 11 additions & 0 deletions controllers/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/red-hat-storage/odf-multicluster-orchestrator/console"
"github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils"
"github.com/spf13/cobra"
viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1"
"golang.org/x/sync/errgroup"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
Expand Down Expand Up @@ -43,6 +44,7 @@ func init() {

utilruntime.Must(ramenv1alpha1.AddToScheme(mgrScheme))
utilruntime.Must(workv1.AddToScheme(mgrScheme))
utilruntime.Must(viewv1beta1.AddToScheme(mgrScheme))
//+kubebuilder:scaffold:scheme
}

Expand Down Expand Up @@ -140,6 +142,15 @@ func (o *ManagerOptions) runManager() {
os.Exit(1)
}

if err = (&ManagedClusterReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Logger: logger.With("controller", "ManagedClusterReconciler"),
}).SetupWithManager(mgr); err != nil {
logger.Error("Failed to create ManagedCluster 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
1 change: 1 addition & 0 deletions controllers/mirrorpeer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const spokeClusterRoleBindingName = "spoke-clusterrole-bindings"
//+kubebuilder:rbac:groups=addon.open-cluster-management.io,resources=managedclusteraddons;clustermanagementaddons,verbs=*
//+kubebuilder:rbac:groups=addon.open-cluster-management.io,resources=managedclusteraddons/status,verbs=*
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;create;update;watch
//+kubebuilder:rbac:groups=view.open-cluster-management.io,resources=managedclusterviews,verbs=*
//+kubebuilder:rbac:groups=console.openshift.io,resources=consoleplugins,verbs=get;list;create;update;watch
//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;create;update;watch
//+kubebuilder:rbac:groups=ramendr.openshift.io,resources=drclusters;drpolicies,verbs=get;list;create;update;watch
Expand Down
Loading

0 comments on commit 8cbd78d

Please sign in to comment.