From bbe20bea8dd9c984ab043810059606a62147ae04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kara=C5=9B?= Date: Wed, 1 Oct 2025 12:23:36 +0200 Subject: [PATCH 1/3] CLOUDP-324655 - fix MongoDBMultiCluster not ready if clusterSpec contains 0 members --- ...godbmulticluster_not_ready_if_0_members.md | 6 ++++++ .../mongodbmultireplicaset_controller.go | 21 ++----------------- .../multi_cluster_replica_set_scale_down.py | 14 ++++++++----- 3 files changed, 17 insertions(+), 24 deletions(-) create mode 100644 changelog/20251001_fix_mongodbmulticluster_not_ready_if_0_members.md diff --git a/changelog/20251001_fix_mongodbmulticluster_not_ready_if_0_members.md b/changelog/20251001_fix_mongodbmulticluster_not_ready_if_0_members.md new file mode 100644 index 000000000..7c568e53e --- /dev/null +++ b/changelog/20251001_fix_mongodbmulticluster_not_ready_if_0_members.md @@ -0,0 +1,6 @@ +--- +kind: fix +date: 2025-10-01 +--- + +* **MongoDBMultiCluster**: fix resource stuck in Pending state if any `clusterSpecList` item has 0 members. After the fix 0 members value is handled correctly similarly how it's done in **MongoDB** resource diff --git a/controllers/operator/mongodbmultireplicaset_controller.go b/controllers/operator/mongodbmultireplicaset_controller.go index aad481b32..6e1511a7f 100644 --- a/controllers/operator/mongodbmultireplicaset_controller.go +++ b/controllers/operator/mongodbmultireplicaset_controller.go @@ -235,23 +235,17 @@ func (r *ReconcileMongoDbMultiReplicaSet) Reconcile(ctx context.Context, request return r.updateStatus(ctx, &mrs, workflow.Failed(xerrors.Errorf("Failed to set annotation: %w", err)), log) } - // for purposes of comparison, we don't want to compare entries with 0 members since they will not be present - // as a desired entry. desiredSpecList := mrs.GetDesiredSpecList() actualSpecList, err := mrs.GetClusterSpecItems() if err != nil { return r.updateStatus(ctx, &mrs, workflow.Failed(err), log) } - effectiveSpecList := filterClusterSpecItem(actualSpecList, func(item mdb.ClusterSpecItem) bool { - return item.Members > 0 - }) - // sort both actual and desired to match the effective and desired list version before comparing sortClusterSpecList(desiredSpecList) - sortClusterSpecList(effectiveSpecList) + sortClusterSpecList(actualSpecList) - needToRequeue := !clusterSpecListsEqual(effectiveSpecList, desiredSpecList) + needToRequeue := !clusterSpecListsEqual(actualSpecList, desiredSpecList) if needToRequeue { return r.updateStatus(ctx, &mrs, workflow.Pending("MongoDBMultiCluster deployment is not yet ready, requeuing reconciliation."), log) } @@ -1276,17 +1270,6 @@ func (r *ReconcileMongoDbMultiReplicaSet) deleteManagedResources(ctx context.Con return errs } -// filterClusterSpecItem filters items out of a list based on provided predicate. -func filterClusterSpecItem(items mdb.ClusterSpecList, fn func(item mdb.ClusterSpecItem) bool) mdb.ClusterSpecList { - var result mdb.ClusterSpecList - for _, item := range items { - if fn(item) { - result = append(result, item) - } - } - return result -} - func sortClusterSpecList(clusterSpecList mdb.ClusterSpecList) { sort.SliceStable(clusterSpecList, func(i, j int) bool { return clusterSpecList[i].ClusterName < clusterSpecList[j].ClusterName diff --git a/docker/mongodb-kubernetes-tests/tests/multicluster/multi_cluster_replica_set_scale_down.py b/docker/mongodb-kubernetes-tests/tests/multicluster/multi_cluster_replica_set_scale_down.py index 398b43ec1..5c99e3db3 100644 --- a/docker/mongodb-kubernetes-tests/tests/multicluster/multi_cluster_replica_set_scale_down.py +++ b/docker/mongodb-kubernetes-tests/tests/multicluster/multi_cluster_replica_set_scale_down.py @@ -2,6 +2,7 @@ import kubernetes import pytest +from kubetester import try_load from kubetester.automation_config_tester import AutomationConfigTester from kubetester.certs_mongodb_multi import create_multi_cluster_mongodb_tls_certs from kubetester.kubetester import fixture as yaml_fixture @@ -59,7 +60,10 @@ def server_certs( @pytest.fixture(scope="module") def mongodb_multi(mongodb_multi_unmarshalled: MongoDBMulti, server_certs: str) -> MongoDBMulti: - return mongodb_multi_unmarshalled.create() + if try_load(mongodb_multi_unmarshalled): + return mongodb_multi_unmarshalled + + return mongodb_multi_unmarshalled.update() @pytest.mark.e2e_multi_cluster_replica_set_scale_down @@ -101,8 +105,8 @@ def test_ops_manager_has_been_updated_correctly_before_scaling(): def test_scale_mongodb_multi(mongodb_multi: MongoDBMulti): mongodb_multi.load() mongodb_multi["spec"]["clusterSpecList"][0]["members"] = 1 - mongodb_multi["spec"]["clusterSpecList"][1]["members"] = 1 - mongodb_multi["spec"]["clusterSpecList"][2]["members"] = 1 + mongodb_multi["spec"]["clusterSpecList"][1]["members"] = 0 + mongodb_multi["spec"]["clusterSpecList"][2]["members"] = 2 mongodb_multi.update() mongodb_multi.assert_reaches_phase(Phase.Running, timeout=1800) @@ -120,11 +124,11 @@ def test_statefulsets_have_been_scaled_down_correctly( cluster_two_client = member_cluster_clients[1] cluster_two_sts = statefulsets[cluster_two_client.cluster_name] - assert cluster_two_sts.status.ready_replicas == 1 + assert cluster_two_sts.status.ready_replicas is None cluster_three_client = member_cluster_clients[2] cluster_three_sts = statefulsets[cluster_three_client.cluster_name] - assert cluster_three_sts.status.ready_replicas == 1 + assert cluster_three_sts.status.ready_replicas == 2 @pytest.mark.e2e_multi_cluster_replica_set_scale_down From 57c9d677d80f5ea184830f26b86273f5420c667a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kara=C5=9B?= Date: Wed, 1 Oct 2025 14:09:31 +0200 Subject: [PATCH 2/3] Unit test fixes --- .../mongodbmultireplicaset_controller.go | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/controllers/operator/mongodbmultireplicaset_controller.go b/controllers/operator/mongodbmultireplicaset_controller.go index 6e1511a7f..5d963c00e 100644 --- a/controllers/operator/mongodbmultireplicaset_controller.go +++ b/controllers/operator/mongodbmultireplicaset_controller.go @@ -241,10 +241,6 @@ func (r *ReconcileMongoDbMultiReplicaSet) Reconcile(ctx context.Context, request return r.updateStatus(ctx, &mrs, workflow.Failed(err), log) } - // sort both actual and desired to match the effective and desired list version before comparing - sortClusterSpecList(desiredSpecList) - sortClusterSpecList(actualSpecList) - needToRequeue := !clusterSpecListsEqual(actualSpecList, desiredSpecList) if needToRequeue { return r.updateStatus(ctx, &mrs, workflow.Pending("MongoDBMultiCluster deployment is not yet ready, requeuing reconciliation."), log) @@ -1277,8 +1273,32 @@ func sortClusterSpecList(clusterSpecList mdb.ClusterSpecList) { } func clusterSpecListsEqual(effective, desired mdb.ClusterSpecList) bool { + // for purposes of comparison, we don't want to compare entries with 0 members + effective = filterClusterSpecItem(effective, func(item mdb.ClusterSpecItem) bool { + return item.Members > 0 + }) + desired = filterClusterSpecItem(desired, func(item mdb.ClusterSpecItem) bool { + return item.Members > 0 + }) + + // sort both actual and desired to match the effective and desired list version before comparing + sortClusterSpecList(effective) + sortClusterSpecList(desired) + comparer := cmp.Comparer(func(x, y automationconfig.MemberOptions) bool { return true }) + return cmp.Equal(effective, desired, comparer) } + +// filterClusterSpecItem filters items out of a list based on provided predicate. +func filterClusterSpecItem(items mdb.ClusterSpecList, fn func(item mdb.ClusterSpecItem) bool) mdb.ClusterSpecList { + var result mdb.ClusterSpecList + for _, item := range items { + if fn(item) { + result = append(result, item) + } + } + return result +} From cad135c3a6ffe3e4618ee09b08d1eb2adbb9fd30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kara=C5=9B?= Date: Wed, 1 Oct 2025 15:31:54 +0200 Subject: [PATCH 3/3] Review fixes --- .../20251001_fix_mongodbmulticluster_not_ready_if_0_members.md | 2 +- .../tests/multicluster/multi_cluster_replica_set_scale_down.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/20251001_fix_mongodbmulticluster_not_ready_if_0_members.md b/changelog/20251001_fix_mongodbmulticluster_not_ready_if_0_members.md index 7c568e53e..d76e5a092 100644 --- a/changelog/20251001_fix_mongodbmulticluster_not_ready_if_0_members.md +++ b/changelog/20251001_fix_mongodbmulticluster_not_ready_if_0_members.md @@ -3,4 +3,4 @@ kind: fix date: 2025-10-01 --- -* **MongoDBMultiCluster**: fix resource stuck in Pending state if any `clusterSpecList` item has 0 members. After the fix 0 members value is handled correctly similarly how it's done in **MongoDB** resource +* **MongoDBMultiCluster**: fix resource stuck in Pending state if any `clusterSpecList` item has 0 members. After the fix, a value of 0 members is handled correctly, similarly to how it's done in the **MongoDB** resource. diff --git a/docker/mongodb-kubernetes-tests/tests/multicluster/multi_cluster_replica_set_scale_down.py b/docker/mongodb-kubernetes-tests/tests/multicluster/multi_cluster_replica_set_scale_down.py index 5c99e3db3..7ba868397 100644 --- a/docker/mongodb-kubernetes-tests/tests/multicluster/multi_cluster_replica_set_scale_down.py +++ b/docker/mongodb-kubernetes-tests/tests/multicluster/multi_cluster_replica_set_scale_down.py @@ -105,6 +105,7 @@ def test_ops_manager_has_been_updated_correctly_before_scaling(): def test_scale_mongodb_multi(mongodb_multi: MongoDBMulti): mongodb_multi.load() mongodb_multi["spec"]["clusterSpecList"][0]["members"] = 1 + # Testing scaling down to zero is required to test fix for https://jira.mongodb.org/browse/CLOUDP-324655 mongodb_multi["spec"]["clusterSpecList"][1]["members"] = 0 mongodb_multi["spec"]["clusterSpecList"][2]["members"] = 2 mongodb_multi.update()