-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement reconciliation for ManagedCluster and create ManagedC…
…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
Showing
10 changed files
with
414 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.