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..d76e5a092 --- /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, a value of 0 members is handled correctly, similarly to how it's done in the **MongoDB** resource. diff --git a/controllers/operator/mongodbmultireplicaset_controller.go b/controllers/operator/mongodbmultireplicaset_controller.go index aad481b32..5d963c00e 100644 --- a/controllers/operator/mongodbmultireplicaset_controller.go +++ b/controllers/operator/mongodbmultireplicaset_controller.go @@ -235,23 +235,13 @@ 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) - - 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 +1266,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 @@ -1294,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 +} 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..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 @@ -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,9 @@ 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 + # 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() mongodb_multi.assert_reaches_phase(Phase.Running, timeout=1800) @@ -120,11 +125,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