Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
47 changes: 25 additions & 22 deletions controllers/operator/mongodbmultireplicaset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -1276,26 +1266,39 @@ 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
})
}

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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down