Skip to content
This repository was archived by the owner on Dec 12, 2025. It is now read-only.

Commit 244f35b

Browse files
authored
CLOUDP-71334: Fix MongoDB 4.4 Scaling Up (#186)
1 parent f2bbf87 commit 244f35b

File tree

5 files changed

+174
-7
lines changed

5 files changed

+174
-7
lines changed

pkg/apis/mongodb/v1/mongodb_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"strings"
77

8+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/scale"
9+
810
appsv1 "k8s.io/api/apps/v1"
911

1012
"k8s.io/apimachinery/pkg/runtime"
@@ -218,6 +220,18 @@ type MongoDB struct {
218220
Status MongoDBStatus `json:"status,omitempty"`
219221
}
220222

223+
func (m MongoDB) DesiredReplicas() int {
224+
return m.Spec.Members
225+
}
226+
227+
func (m MongoDB) CurrentReplicas() int {
228+
return m.Status.Members
229+
}
230+
231+
func (m *MongoDB) ReplicasThisReconciliation() int {
232+
return scale.ReplicasThisReconciliation(m)
233+
}
234+
221235
// MongoURI returns a mongo uri which can be used to connect to this deployment
222236
func (m MongoDB) MongoURI() string {
223237
members := make([]string, m.Spec.Members)

pkg/controller/mongodb/replica_set_controller.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"os"
1010
"strings"
1111

12+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/scale"
13+
1214
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/envvar"
1315
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/status"
1416

@@ -174,7 +176,10 @@ func (r *ReplicaSetReconciler) Reconcile(request reconcile.Request) (reconcile.R
174176
}
175177

176178
r.log = zap.S().With("ReplicaSet", request.NamespacedName)
177-
r.log.Infow("Reconciling MongoDB", "MongoDB.Spec", mdb.Spec, "MongoDB.Status", mdb.Status)
179+
r.log.Infow("Reconciling MongoDB", "MongoDB.Spec", mdb.Spec, "MongoDB.Status", mdb.Status,
180+
"desiredMembers", mdb.DesiredReplicas(),
181+
"currentMembers", mdb.CurrentReplicas(),
182+
)
178183

179184
if err := r.ensureAutomationConfig(mdb); err != nil {
180185
return status.Update(r.client.Status(), &mdb,
@@ -287,9 +292,18 @@ func (r *ReplicaSetReconciler) Reconcile(request reconcile.Request) (reconcile.R
287292
)
288293
}
289294

295+
if scale.IsStillScaling(mdb) {
296+
return status.Update(r.client.Status(), &mdb, statusOptions().
297+
withMessage(Info, fmt.Sprintf("Performing scaling operation, currentMembers=%d, desiredMembers=%d",
298+
mdb.CurrentReplicas(), mdb.DesiredReplicas())).
299+
withMembers(mdb.ReplicasThisReconciliation()).
300+
withPendingPhase(0),
301+
)
302+
}
303+
290304
res, err := status.Update(r.client.Status(), &mdb, statusOptions().
291305
withMongoURI(mdb.MongoURI()).
292-
withMembers(mdb.Spec.Members).
306+
withMembers(mdb.ReplicasThisReconciliation()).
293307
withMessage(None, "").
294308
withRunningPhase(),
295309
)

pkg/controller/mongodb/replicaset_controller_test.go

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -393,23 +393,106 @@ func TestExistingPasswordAndKeyfile_AreUsedWhenTheSecretExists(t *testing.T) {
393393
}
394394

395395
func TestScramIsConfigured(t *testing.T) {
396-
AssertReplicaSetIsConfiguredWithScram(t, newScramReplicaSet())
396+
assertReplicaSetIsConfiguredWithScram(t, newScramReplicaSet())
397397
}
398398

399399
func TestScramIsConfiguredWhenNotSpecified(t *testing.T) {
400-
AssertReplicaSetIsConfiguredWithScram(t, newTestReplicaSet())
400+
assertReplicaSetIsConfiguredWithScram(t, newTestReplicaSet())
401401
}
402402

403-
func AssertReplicaSetIsConfiguredWithScram(t *testing.T, mdb mdbv1.MongoDB) {
403+
func TestReplicaSet_IsScaledDown_OneMember_AtATime_WhenItAlreadyExists(t *testing.T) {
404+
mdb := newTestReplicaSet()
405+
mdb.Spec.Members = 5
406+
407+
mgr := client.NewManager(&mdb)
408+
r := newReconciler(mgr, mockManifestProvider(mdb.Spec.Version))
409+
res, err := r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
410+
assertReconciliationSuccessful(t, res, err)
411+
412+
err = mgr.GetClient().Get(context.TODO(), mdb.NamespacedName(), &mdb)
413+
414+
assert.NoError(t, err)
415+
assert.Equal(t, 5, mdb.Status.Members)
416+
417+
// scale members from five to three
418+
mdb.Spec.Members = 3
419+
420+
err = mgr.GetClient().Update(context.TODO(), &mdb)
421+
assert.NoError(t, err)
422+
423+
makeStatefulSetReady(t, mgr.GetClient(), mdb)
424+
425+
res, err = r.Reconcile(reconcile.Request{NamespacedName: mdb.NamespacedName()})
426+
427+
assert.NoError(t, err)
428+
429+
err = mgr.GetClient().Get(context.TODO(), mdb.NamespacedName(), &mdb)
430+
assert.NoError(t, err)
431+
432+
assert.Equal(t, true, res.Requeue)
433+
assert.Equal(t, 4, mdb.Status.Members)
434+
435+
res, err = r.Reconcile(reconcile.Request{NamespacedName: mdb.NamespacedName()})
436+
437+
assert.NoError(t, err)
438+
439+
err = mgr.GetClient().Get(context.TODO(), mdb.NamespacedName(), &mdb)
440+
assert.NoError(t, err)
441+
assert.Equal(t, false, res.Requeue)
442+
assert.Equal(t, 3, mdb.Status.Members)
443+
}
444+
445+
func TestReplicaSet_IsScaledUp_OneMember_AtATime_WhenItAlreadyExists(t *testing.T) {
446+
mdb := newTestReplicaSet()
447+
448+
mgr := client.NewManager(&mdb)
449+
r := newReconciler(mgr, mockManifestProvider(mdb.Spec.Version))
450+
res, err := r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
451+
assertReconciliationSuccessful(t, res, err)
452+
453+
err = mgr.GetClient().Get(context.TODO(), mdb.NamespacedName(), &mdb)
454+
assert.NoError(t, err)
455+
assert.Equal(t, 3, mdb.Status.Members)
456+
457+
// scale members from three to five
458+
mdb.Spec.Members = 5
459+
460+
err = mgr.GetClient().Update(context.TODO(), &mdb)
461+
assert.NoError(t, err)
462+
463+
makeStatefulSetReady(t, mgr.GetClient(), mdb)
464+
465+
res, err = r.Reconcile(reconcile.Request{NamespacedName: mdb.NamespacedName()})
466+
467+
assert.NoError(t, err)
468+
469+
err = mgr.GetClient().Get(context.TODO(), mdb.NamespacedName(), &mdb)
470+
471+
assert.NoError(t, err)
472+
assert.Equal(t, true, res.Requeue)
473+
assert.Equal(t, 4, mdb.Status.Members)
474+
475+
res, err = r.Reconcile(reconcile.Request{NamespacedName: mdb.NamespacedName()})
476+
477+
assert.NoError(t, err)
478+
479+
err = mgr.GetClient().Get(context.TODO(), mdb.NamespacedName(), &mdb)
480+
assert.NoError(t, err)
481+
482+
assert.Equal(t, false, res.Requeue)
483+
assert.Equal(t, 5, mdb.Status.Members)
484+
}
485+
486+
func assertReplicaSetIsConfiguredWithScram(t *testing.T, mdb mdbv1.MongoDB) {
404487
mgr := client.NewManager(&mdb)
405488
r := newReconciler(mgr, mockManifestProvider(mdb.Spec.Version))
406489
res, err := r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
407490
assertReconciliationSuccessful(t, res, err)
408491

409492
currentAc, err := getCurrentAutomationConfig(client.NewClient(mgr.GetClient()), mdb)
410493
t.Run("Automation Config is configured with SCRAM", func(t *testing.T) {
411-
assert.NoError(t, err)
412494
assert.NotEmpty(t, currentAc.Auth.Key)
495+
assert.NoError(t, err)
413496
assert.NotEmpty(t, currentAc.Auth.KeyFileWindows)
414497
assert.NotEmpty(t, currentAc.Auth.AutoPwd)
415498
assert.False(t, currentAc.Auth.Disabled)
@@ -423,6 +506,20 @@ func AssertReplicaSetIsConfiguredWithScram(t *testing.T, mdb mdbv1.MongoDB) {
423506
})
424507
}
425508

509+
func TestReplicaSet_IsScaledUpToDesiredMembers_WhenFirstCreated(t *testing.T) {
510+
mdb := newTestReplicaSet()
511+
512+
mgr := client.NewManager(&mdb)
513+
r := newReconciler(mgr, mockManifestProvider(mdb.Spec.Version))
514+
res, err := r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
515+
assertReconciliationSuccessful(t, res, err)
516+
517+
err = mgr.GetClient().Get(context.TODO(), mdb.NamespacedName(), &mdb)
518+
assert.NoError(t, err)
519+
520+
assert.Equal(t, 3, mdb.Status.Members)
521+
}
522+
426523
func TestOpenshift_Configuration(t *testing.T) {
427524
sts := performReconciliationAndGetStatefulSet(t, "openshift_mdb.yaml")
428525
assert.Equal(t, "MANAGED_SECURITY_CONTEXT", sts.Spec.Template.Spec.Containers[0].Env[1].Name)

pkg/util/scale/scale.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package scale
2+
3+
// ReplicaSetScaler is an interface which is able to scale up and down a replicaset
4+
// a single member at a time
5+
type ReplicaSetScaler interface {
6+
DesiredReplicas() int
7+
CurrentReplicas() int
8+
}
9+
10+
// ReplicasThisReconciliation returns the number of replicas that should be configured
11+
// for that reconciliation. As of MongoDB 4.4 we can only scale members up / down 1 at a time.
12+
func ReplicasThisReconciliation(replicaSetScaler ReplicaSetScaler) int {
13+
// the current replica set members will be 0 when we are creating a new deployment
14+
// if this is the case, we want to jump straight to the desired members and not make changes incrementally
15+
if replicaSetScaler.CurrentReplicas() == 0 || replicaSetScaler.CurrentReplicas() == replicaSetScaler.DesiredReplicas() {
16+
return replicaSetScaler.DesiredReplicas()
17+
}
18+
19+
if isScalingDown(replicaSetScaler) {
20+
return replicaSetScaler.CurrentReplicas() - 1
21+
}
22+
23+
return replicaSetScaler.CurrentReplicas() + 1
24+
25+
}
26+
27+
func IsStillScaling(replicaSetScaler ReplicaSetScaler) bool {
28+
return ReplicasThisReconciliation(replicaSetScaler) != replicaSetScaler.DesiredReplicas()
29+
}
30+
31+
func isScalingDown(replicaSetScaler ReplicaSetScaler) bool {
32+
return replicaSetScaler.DesiredReplicas() < replicaSetScaler.CurrentReplicas()
33+
}
34+
35+
// AnyAreStillScaling reports true if any of one the provided members is still scaling
36+
func AnyAreStillScaling(scalers ...ReplicaSetScaler) bool {
37+
for _, s := range scalers {
38+
if IsStillScaling(s) {
39+
return true
40+
}
41+
}
42+
return false
43+
}

test/e2e/replica_set_scale/replica_set_scaling_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ func TestReplicaSetScale(t *testing.T) {
2525
}
2626

2727
mdb, user := e2eutil.NewTestMongoDB("mdb0")
28-
mdb.Spec.Version = "4.0.6" // TOOD: Scaling will currently not work with MongoDB 4.4
2928

3029
_, err := setup.GeneratePasswordForUser(user, ctx)
3130
if err != nil {

0 commit comments

Comments
 (0)