From 72152af2e54eed336f2638fb092f6aadd383b2ad Mon Sep 17 00:00:00 2001 From: Mikalai Radchuk <509198+m1kola@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:00:11 +0200 Subject: [PATCH] Add annotation to disable reconciliation The new `mongodb.com/v1.disable-reconciliation` annotation allows disabling reconciliation for `MongoDBMultiCluster` objects. This will be used later when migrating multi-cluster replica sets from `MongoDBMultiCluster` to `MongoDB`. --- .../mongodbmultireplicaset_controller.go | 5 ++ .../mongodbmultireplicaset_controller_test.go | 70 +++++++++++++++++++ pkg/util/constants.go | 3 + 3 files changed, 78 insertions(+) diff --git a/controllers/operator/mongodbmultireplicaset_controller.go b/controllers/operator/mongodbmultireplicaset_controller.go index 5d963c00e..25bfaa9d8 100644 --- a/controllers/operator/mongodbmultireplicaset_controller.go +++ b/controllers/operator/mongodbmultireplicaset_controller.go @@ -135,6 +135,11 @@ func (r *ReconcileMongoDbMultiReplicaSet) Reconcile(ctx context.Context, request return reconcileResult, err } + if val, ok := mrs.Annotations[util.DisableReconciliation]; ok && val == util.DisableReconciliationValue { + log.Info("Reconciliation disabled") + return r.updateStatus(ctx, &mrs, workflow.Disabled(), log) + } + if !architectures.IsRunningStaticArchitecture(mrs.Annotations) { agents.UpgradeAllIfNeeded(ctx, agents.ClientSecret{Client: r.client, SecretClient: r.SecretClient}, r.omConnectionFactory, GetWatchedNamespace(), true) } diff --git a/controllers/operator/mongodbmultireplicaset_controller_test.go b/controllers/operator/mongodbmultireplicaset_controller_test.go index 9766d7c7a..269b585be 100644 --- a/controllers/operator/mongodbmultireplicaset_controller_test.go +++ b/controllers/operator/mongodbmultireplicaset_controller_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "maps" "sort" "testing" @@ -1425,6 +1426,75 @@ func TestValidationsRunOnReconcile(t *testing.T) { assert.Equal(t, fmt.Sprintf("Multiple clusters with the same name (%s) are not allowed", duplicateName), mrs.Status.Message) } +func TestReconcileDisableReconciliationAnnotation(t *testing.T) { + tests := []struct { + name string + annotations map[string]string + expectedPhase status.Phase + expectedReconcileResult reconcile.Result + }{ + { + name: "reconciliation disabled when annotation is true", + annotations: map[string]string{util.DisableReconciliation: util.DisableReconciliationValue}, + expectedPhase: status.PhaseDisabled, + expectedReconcileResult: reconcile.Result{Requeue: false, RequeueAfter: 0}, + }, + { + name: "reconciliation proceeds when annotation is false", + annotations: map[string]string{util.DisableReconciliation: "false"}, + expectedPhase: status.PhaseRunning, + expectedReconcileResult: reconcile.Result{RequeueAfter: util.TWENTY_FOUR_HOURS}, + }, + { + name: "reconciliation proceeds when annotation is empty string", + annotations: map[string]string{util.DisableReconciliation: ""}, + expectedPhase: status.PhaseRunning, + expectedReconcileResult: reconcile.Result{RequeueAfter: util.TWENTY_FOUR_HOURS}, + }, + { + name: "reconciliation proceeds when annotation is arbitrary value", + annotations: map[string]string{util.DisableReconciliation: "some-other-value"}, + expectedPhase: status.PhaseRunning, + expectedReconcileResult: reconcile.Result{RequeueAfter: util.TWENTY_FOUR_HOURS}, + }, + { + name: "reconciliation proceeds when annotation is absent", + expectedPhase: status.PhaseRunning, + expectedReconcileResult: reconcile.Result{RequeueAfter: util.TWENTY_FOUR_HOURS}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + mrs := mdbmulti.DefaultMultiReplicaSetBuilder().SetClusterSpecList(clusters).Build() + + // Set up test annotations + if mrs.Annotations == nil { + mrs.Annotations = map[string]string{} + } + maps.Copy(mrs.Annotations, tt.annotations) + + reconciler, client, _, _ := defaultMultiReplicaSetReconciler(ctx, nil, "", "", mrs) + + // Update the resource with the annotation + err := client.Update(ctx, mrs) + assert.NoError(t, err) + + // Reconcile should return without error and with expected result + result, err := reconciler.Reconcile(ctx, requestFromObject(mrs)) + assert.NoError(t, err) + assert.Equal(t, tt.expectedReconcileResult, result) + + // Fetch the updated resource to check the status + err = client.Get(ctx, kube.ObjectKey(mrs.Namespace, mrs.Name), mrs) + assert.NoError(t, err) + + assert.Equal(t, tt.expectedPhase, mrs.Status.Phase) + }) + } +} + func assertClusterpresent(t *testing.T, m map[string]int, specs mdb.ClusterSpecList, arr []int) { tmp := make([]int, 0) for _, s := range specs { diff --git a/pkg/util/constants.go b/pkg/util/constants.go index ee51115ce..e50c37b4f 100644 --- a/pkg/util/constants.go +++ b/pkg/util/constants.go @@ -285,6 +285,9 @@ const ( LastAchievedSpec = "mongodb.com/v1.lastSuccessfulConfiguration" LastAchievedRsMemberIds = "mongodb.com/v1.lastAchievedRsMemberIds" + DisableReconciliation = "mongodb.com/v1.disableReconciliation" + DisableReconciliationValue = "true" + // SecretVolumeName is the name of the volume resource. SecretVolumeName = "secret-certs"