From 77d02657248079dd8d0701836f2211afa9f613a7 Mon Sep 17 00:00:00 2001 From: Gianluca Mardente Date: Wed, 16 Sep 2026 16:26:37 +0200 Subject: [PATCH] fix: make ManagementClusterClassifierReconciler shard-aware `ManagementClusterClassifierReconciler` never checked `ShardKey`, unlike `ClassifierReconciler`. In `fv-sharding` (two classifier-manager deployments, default + shard1), both instances reconciled the same `ManagementClusterClassifier` and raced to update labels on the same Cluster object. That caused the "multi-GVK requirement" FV test to time out waiting for labels to be removed after CR deletion. This PR fixes that. When `ShardKey` is set, no-op immediately, same as `ClassifierReconciler` already does. This resource is evaluated entirely against the management cluster itself, so sharded deployments have no role to play for it. --- .../mgmtcluster_classifier_controller.go | 10 +++++- controllers/mgmtcluster_classifier_test.go | 33 +++++++++++++++++++ main.go | 1 + 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/controllers/mgmtcluster_classifier_controller.go b/controllers/mgmtcluster_classifier_controller.go index ff5aaa9..e3b9618 100644 --- a/controllers/mgmtcluster_classifier_controller.go +++ b/controllers/mgmtcluster_classifier_controller.go @@ -48,7 +48,8 @@ import ( // no deployment to managed clusters is needed. type ManagementClusterClassifierReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + ShardKey string // when set, only the default (unsharded) deployment reconciles // Mux guards GVKToClassifiers and other in-memory state. Mux sync.Mutex @@ -75,6 +76,13 @@ type ManagementClusterClassifierReconciler struct { //+kubebuilder:rbac:groups=lib.projectsveltos.io,resources=managementclusterclassifierreports/status,verbs=get;update;patch func (r *ManagementClusterClassifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { + if r.ShardKey != "" { + // Only the default classifier deployment reconciles ManagementClusterClassifiers. + // Unlike Classifier, this resource is evaluated entirely against the management + // cluster itself, so sharded deployments have no role to play for it. + return reconcile.Result{}, nil + } + logger := ctrl.LoggerFrom(ctx) logger.V(logs.LogDebug).Info("Reconciling") diff --git a/controllers/mgmtcluster_classifier_test.go b/controllers/mgmtcluster_classifier_test.go index 946faf0..d7a146a 100644 --- a/controllers/mgmtcluster_classifier_test.go +++ b/controllers/mgmtcluster_classifier_test.go @@ -28,7 +28,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2/textlogger" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" @@ -209,6 +211,37 @@ end }) }) + Context("Reconcile", func() { + It("no-ops when ShardKey is set, leaving the ManagementClusterClassifier untouched", func() { + mcc := &libsveltosv1beta1.ManagementClusterClassifier{ + ObjectMeta: metav1.ObjectMeta{Name: randomString()}, + Spec: libsveltosv1beta1.ManagementClusterClassifierSpec{ + ClassifierLabels: []libsveltosv1beta1.ClassifierLabel{ + {Key: testLabelEnv, Value: testValueProd}, + }, + }, + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(mgmtTestScheme()). + WithObjects(mcc). + Build() + + reconciler := &controllers.ManagementClusterClassifierReconciler{ + Client: fakeClient, + ShardKey: "shard1", + } + + _, err := reconciler.Reconcile(context.TODO(), + ctrl.Request{NamespacedName: types.NamespacedName{Name: mcc.Name}}) + Expect(err).ToNot(HaveOccurred()) + + current := &libsveltosv1beta1.ManagementClusterClassifier{} + Expect(fakeClient.Get(context.TODO(), types.NamespacedName{Name: mcc.Name}, current)).To(Succeed()) + Expect(controllerutil.ContainsFinalizer(current, libsveltosv1beta1.ManagementClusterClassifierFinalizer)).To(BeFalse()) + }) + }) + Context("classifierLabelKeys", func() { It("returns all keys from ClassifierLabel slice", func() { labels := []libsveltosv1beta1.ClassifierLabel{ diff --git a/main.go b/main.go index 5156fe9..5ee3e41 100644 --- a/main.go +++ b/main.go @@ -379,6 +379,7 @@ func getMgmtClassifierReconciler(mgr manager.Manager) *controllers.ManagementClu return &controllers.ManagementClusterClassifierReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), + ShardKey: shardKey, GVKToClassifiers: make(map[schema.GroupVersionKind]*libsveltosset.Set), Logger: ctrl.Log.WithName("managementclusterclassifierreconciler"), }