Skip to content

Commit 46ab5df

Browse files
authored
Fixed OnDelete strategy (#83)
1 parent 327005f commit 46ab5df

File tree

9 files changed

+97
-65
lines changed

9 files changed

+97
-65
lines changed

pkg/apis/mongodb/v1/mongodb_types.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ const (
2121
Running Phase = "Running"
2222
)
2323

24-
const (
25-
// LastVersionAnnotationKey should indicate which version of MongoDB was last
26-
// configured
27-
LastVersionAnnotationKey = "lastVersion"
28-
)
29-
3024
// MongoDBSpec defines the desired state of MongoDB
3125
type MongoDBSpec struct {
3226
// Members is the number of members in the replica set
@@ -70,13 +64,6 @@ func (m *MongoDB) UpdateSuccess() {
7064
m.Status.Phase = Running
7165
}
7266

73-
func (m MongoDB) IsChangingVersion() bool {
74-
if lastVersion, ok := m.Annotations[LastVersionAnnotationKey]; ok {
75-
return (m.Spec.Version != lastVersion) && lastVersion != ""
76-
}
77-
return false
78-
}
79-
8067
// MongoURI returns a mongo uri which can be used to connect to this deployment
8168
func (m MongoDB) MongoURI() string {
8269
members := make([]string, m.Spec.Members)

pkg/controller/mongodb/mongodb_controller.go

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ const (
5757
clusterFilePath = "/var/lib/automation/config/automation-config"
5858
operatorServiceAccountName = "mongodb-kubernetes-operator"
5959
agentHealthStatusFilePathValue = "/var/log/mongodb-mms-automation/healthstatus/agent-health-status.json"
60+
61+
// lastVersionAnnotationKey should indicate which version of MongoDB was last
62+
// configured
63+
lastVersionAnnotationKey = "mongodb.com/v1.lastVersion"
64+
hasLeftReadyStateAnnotationKey = "mongodb.com/v1.hasLeftReadyStateAnnotationKey"
6065
)
6166

6267
// Add creates a new MongoDB Controller and adds it to the Manager. The Manager will set fields on the Controller
@@ -176,8 +181,13 @@ func (r *ReplicaSetReconciler) Reconcile(request reconcile.Request) (reconcile.R
176181
}
177182

178183
r.log.Debug("Setting MongoDB Annotations")
179-
if err := r.setAnnotation(types.NamespacedName{Name: mdb.Name, Namespace: mdb.Namespace}, mdbv1.LastVersionAnnotationKey, mdb.Spec.Version); err != nil {
180-
r.log.Warnf("Error setting annotation: %+v", err)
184+
185+
annotations := map[string]string{
186+
lastVersionAnnotationKey: mdb.Spec.Version,
187+
hasLeftReadyStateAnnotationKey: "false",
188+
}
189+
if err := r.setAnnotations(mdb.NamespacedName(), annotations); err != nil {
190+
r.log.Warnf("Error setting annotations: %+v", err)
181191
return reconcile.Result{}, err
182192
}
183193

@@ -195,7 +205,7 @@ func (r *ReplicaSetReconciler) Reconcile(request reconcile.Request) (reconcile.R
195205
// resetStatefulSetUpdateStrategy ensures the stateful set is configured back to using RollingUpdateStatefulSetStrategyType
196206
// and does not keep using OnDelete
197207
func (r *ReplicaSetReconciler) resetStatefulSetUpdateStrategy(mdb mdbv1.MongoDB) error {
198-
if !mdb.IsChangingVersion() {
208+
if !isChangingVersion(mdb) {
199209
return nil
200210
}
201211
// if we changed the version, we need to reset the UpdatePolicy back to OnUpdate
@@ -220,10 +230,28 @@ func (r *ReplicaSetReconciler) isStatefulSetReady(mdb mdbv1.MongoDB, existingSta
220230
return false, err
221231
}
222232

223-
// comparison is done with bytes instead of reflect.DeepEqual as there are
224-
// some issues with nil/empty maps not being compared correctly otherwise
225-
areEqual := bytes.Compare(stsCopyBytes, stsBytes) == 0
233+
//comparison is done with bytes instead of reflect.DeepEqual as there are
234+
//some issues with nil/empty maps not being compared correctly otherwise
235+
areEqual := bytes.Equal(stsCopyBytes, stsBytes)
236+
226237
isReady := statefulset.IsReady(*existingStatefulSet, mdb.Spec.Members)
238+
if existingStatefulSet.Spec.UpdateStrategy.Type == appsv1.OnDeleteStatefulSetStrategyType && !isReady {
239+
r.log.Info("StatefulSet has left ready state, version upgrade in progress")
240+
annotations := map[string]string{
241+
hasLeftReadyStateAnnotationKey: "true",
242+
}
243+
if err := r.setAnnotations(mdb.NamespacedName(), annotations); err != nil {
244+
return false, fmt.Errorf("failed setting %s annotation to true: %s", hasLeftReadyStateAnnotationKey, err)
245+
}
246+
}
247+
248+
hasPerformedUpgrade := mdb.Annotations[hasLeftReadyStateAnnotationKey] == "true"
249+
r.log.Infow("StatefulSet Readiness", "isReady", isReady, "hasPerformedUpgrade", hasPerformedUpgrade, "areEqual", areEqual)
250+
251+
if existingStatefulSet.Spec.UpdateStrategy.Type == appsv1.OnDeleteStatefulSetStrategyType {
252+
return areEqual && isReady && hasPerformedUpgrade, nil
253+
}
254+
227255
return areEqual && isReady, nil
228256
}
229257

@@ -251,15 +279,17 @@ func (r *ReplicaSetReconciler) createOrUpdateStatefulSet(mdb mdbv1.MongoDB) erro
251279
return nil
252280
}
253281

254-
// setAnnotation updates the monogdb resource with the given namespaced name and sets the annotation
255-
// "key" with the provided value "val"
256-
func (r ReplicaSetReconciler) setAnnotation(nsName types.NamespacedName, key, val string) error {
282+
// setAnnotations updates the monogdb resource annotations by applying the provided annotations
283+
// on top of the existing ones
284+
func (r ReplicaSetReconciler) setAnnotations(nsName types.NamespacedName, annotations map[string]string) error {
257285
mdb := mdbv1.MongoDB{}
258286
return r.client.GetAndUpdate(nsName, &mdb, func() {
259287
if mdb.Annotations == nil {
260288
mdb.Annotations = map[string]string{}
261289
}
262-
mdb.Annotations[key] = val
290+
for key, val := range annotations {
291+
mdb.Annotations[key] = val
292+
}
263293
})
264294
}
265295

@@ -383,7 +413,7 @@ func (r ReplicaSetReconciler) buildAutomationConfigConfigMap(mdb mdbv1.MongoDB)
383413
// getUpdateStrategyType returns the type of RollingUpgradeStrategy that the StatefulSet
384414
// should be configured with
385415
func getUpdateStrategyType(mdb mdbv1.MongoDB) appsv1.StatefulSetUpdateStrategyType {
386-
if !mdb.IsChangingVersion() {
416+
if !isChangingVersion(mdb) {
387417
return appsv1.RollingUpdateStatefulSetStrategyType
388418
}
389419
return appsv1.OnDeleteStatefulSetStrategyType
@@ -397,6 +427,13 @@ func buildStatefulSet(mdb mdbv1.MongoDB) (appsv1.StatefulSet, error) {
397427
return sts, nil
398428
}
399429

430+
func isChangingVersion(mdb mdbv1.MongoDB) bool {
431+
if lastVersion, ok := mdb.Annotations[lastVersionAnnotationKey]; ok {
432+
return (mdb.Spec.Version != lastVersion) && lastVersion != ""
433+
}
434+
return false
435+
}
436+
400437
func mongodbAgentContainer(volumeMounts []corev1.VolumeMount) container.Modification {
401438
return container.Apply(
402439
container.WithName(agentName),

pkg/controller/mongodb/replicaset_controller_test.go

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"os"
66
"reflect"
77
"testing"
8+
"time"
9+
10+
k8sClient "sigs.k8s.io/controller-runtime/pkg/client"
811

912
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/probes"
1013

@@ -111,11 +114,11 @@ func TestChangingVersion_ResultsInRollingUpdateStrategyType(t *testing.T) {
111114
mgr := client.NewManager(&mdb)
112115
mgrClient := mgr.GetClient()
113116
r := newReconciler(mgr, mockManifestProvider(mdb.Spec.Version))
114-
res, err := r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
117+
res, err := r.Reconcile(reconcile.Request{NamespacedName: mdb.NamespacedName()})
115118
assertReconciliationSuccessful(t, res, err)
116119

117120
// fetch updated resource after first reconciliation
118-
_ = mgrClient.Get(context.TODO(), types.NamespacedName{Name: mdb.Name, Namespace: mdb.Namespace}, &mdb)
121+
_ = mgrClient.Get(context.TODO(), mdb.NamespacedName(), &mdb)
119122

120123
sts := appsv1.StatefulSet{}
121124
err = mgrClient.Get(context.TODO(), types.NamespacedName{Name: mdb.Name, Namespace: mdb.Namespace}, &sts)
@@ -127,6 +130,26 @@ func TestChangingVersion_ResultsInRollingUpdateStrategyType(t *testing.T) {
127130

128131
_ = mgrClient.Update(context.TODO(), &mdb)
129132

133+
// agents start the upgrade, they are not all ready
134+
sts.Status.UpdatedReplicas = 1
135+
sts.Status.ReadyReplicas = 2
136+
err = mgrClient.Update(context.TODO(), &sts)
137+
138+
_ = mgrClient.Get(context.TODO(), mdb.NamespacedName(), &sts)
139+
140+
// the request is requeued as the agents are still doing the upgrade
141+
res, err = r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
142+
assert.NoError(t, err)
143+
assert.Equal(t, res.RequeueAfter, time.Second*10)
144+
145+
_ = mgrClient.Get(context.TODO(), mdb.NamespacedName(), &sts)
146+
assert.Equal(t, appsv1.OnDeleteStatefulSetStrategyType, sts.Spec.UpdateStrategy.Type)
147+
// upgrade is now complete
148+
sts.Status.UpdatedReplicas = 3
149+
sts.Status.ReadyReplicas = 3
150+
err = mgrClient.Update(context.TODO(), &sts)
151+
152+
// reconcilliation is successful
130153
res, err = r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
131154
assertReconciliationSuccessful(t, res, err)
132155

@@ -142,23 +165,23 @@ func TestBuildStatefulSet_ConfiguresUpdateStrategyCorrectly(t *testing.T) {
142165
t.Run("On No Version Change, Same Version", func(t *testing.T) {
143166
mdb := newTestReplicaSet()
144167
mdb.Spec.Version = "4.0.0"
145-
mdb.Annotations[mdbv1.LastVersionAnnotationKey] = "4.0.0"
168+
mdb.Annotations[lastVersionAnnotationKey] = "4.0.0"
146169
sts, err := buildStatefulSet(mdb)
147170
assert.NoError(t, err)
148171
assert.Equal(t, appsv1.RollingUpdateStatefulSetStrategyType, sts.Spec.UpdateStrategy.Type)
149172
})
150173
t.Run("On No Version Change, First Version", func(t *testing.T) {
151174
mdb := newTestReplicaSet()
152175
mdb.Spec.Version = "4.0.0"
153-
delete(mdb.Annotations, mdbv1.LastVersionAnnotationKey)
176+
delete(mdb.Annotations, lastVersionAnnotationKey)
154177
sts, err := buildStatefulSet(mdb)
155178
assert.NoError(t, err)
156179
assert.Equal(t, appsv1.RollingUpdateStatefulSetStrategyType, sts.Spec.UpdateStrategy.Type)
157180
})
158181
t.Run("On Version Change", func(t *testing.T) {
159182
mdb := newTestReplicaSet()
160183
mdb.Spec.Version = "4.0.0"
161-
mdb.Annotations[mdbv1.LastVersionAnnotationKey] = "4.2.0"
184+
mdb.Annotations[lastVersionAnnotationKey] = "4.2.0"
162185
sts, err := buildStatefulSet(mdb)
163186
assert.NoError(t, err)
164187
assert.Equal(t, appsv1.OnDeleteStatefulSetStrategyType, sts.Spec.UpdateStrategy.Type)
@@ -198,6 +221,7 @@ func TestAutomationConfig_versionIsBumpedOnChange(t *testing.T) {
198221
assert.Equal(t, 1, currentAc.Version)
199222

200223
mdb.Spec.Members += 1
224+
makeStatefulSetReady(mgr.GetClient(), mdb)
201225

202226
_ = mgr.GetClient().Update(context.TODO(), &mdb)
203227
res, err = r.Reconcile(reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}})
@@ -228,3 +252,13 @@ func TestAutomationConfig_versionIsNotBumpedWithNoChanges(t *testing.T) {
228252
assert.NoError(t, err)
229253
assert.Equal(t, currentAc.Version, 1)
230254
}
255+
256+
// makeStatefulSetReady updates the StatefulSet corresponding to the
257+
// provided MongoDB resource to mark it as ready for the case of `statefulset.IsReady`
258+
func makeStatefulSetReady(c k8sClient.Client, mdb mdbv1.MongoDB) {
259+
sts := appsv1.StatefulSet{}
260+
_ = c.Get(context.TODO(), mdb.NamespacedName(), &sts)
261+
sts.Status.ReadyReplicas = int32(mdb.Spec.Members)
262+
sts.Status.UpdatedReplicas = int32(mdb.Spec.Members)
263+
_ = c.Update(context.TODO(), &sts)
264+
}

pkg/kube/client/mocked_client.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,6 @@ func (m *mockedClient) Update(_ context.Context, obj runtime.Object, _ ...k8sCli
8686
if err != nil {
8787
return err
8888
}
89-
switch v := obj.(type) {
90-
case *appsv1.StatefulSet:
91-
makeStatefulSetReady(v)
92-
}
9389
relevantMap[objKey] = obj
9490
return nil
9591
}

test/e2e/e2eutil.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/statefulset"
10+
911
mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/pkg/apis/mongodb/v1"
1012
f "github.com/operator-framework/operator-sdk/pkg/test"
1113
appsv1 "k8s.io/api/apps/v1"
@@ -77,15 +79,7 @@ func WaitForStatefulSetToHaveUpdateStrategy(t *testing.T, mdb *mdbv1.MongoDB, st
7779
// have reached the ready status
7880
func WaitForStatefulSetToBeReady(t *testing.T, mdb *mdbv1.MongoDB, retryInterval, timeout time.Duration) error {
7981
return waitForStatefulSetCondition(t, mdb, retryInterval, timeout, func(sts appsv1.StatefulSet) bool {
80-
return sts.Status.ReadyReplicas == int32(mdb.Spec.Members)
81-
})
82-
}
83-
84-
// waitForStatefulSetToBeUpdated waits until all replicas of the StatefulSet with the given name
85-
// are updated.
86-
func WaitForStatefulSetToBeUpdated(t *testing.T, mdb *mdbv1.MongoDB, retryInterval, timeout time.Duration) error {
87-
return waitForStatefulSetCondition(t, mdb, retryInterval, timeout, func(sts appsv1.StatefulSet) bool {
88-
return sts.Status.UpdatedReplicas == int32(mdb.Spec.Members)
82+
return statefulset.IsReady(sts, mdb.Spec.Members)
8983
})
9084
}
9185

test/e2e/feature_compatibility_version/feature_compatibility_version_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestFeatureCompatibilityVersion(t *testing.T) {
3131
t.Run("MongoDB is reachable while version is upgraded", mongodbtests.IsReachableDuring(&mdb, time.Second*10,
3232
func() {
3333
t.Run("Test Version can be upgraded", mongodbtests.ChangeVersion(&mdb, "4.2.6"))
34-
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsUpdated(&mdb))
34+
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsReady(&mdb))
3535
},
3636
))
3737
t.Run("Test Basic Connectivity after upgrade has completed", mongodbtests.BasicConnectivity(&mdb))
@@ -41,7 +41,7 @@ func TestFeatureCompatibilityVersion(t *testing.T) {
4141
t.Run("MongoDB is reachable while version is downgraded", mongodbtests.IsReachableDuring(&mdb, time.Second*10,
4242
func() {
4343
t.Run("Test Version can be downgraded", mongodbtests.ChangeVersion(&mdb, "4.0.6"))
44-
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsUpdated(&mdb))
44+
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsReady(&mdb))
4545
},
4646
))
4747
t.Run("Test FeatureCompatibilityVersion, after downgrade, is 4.0", mongodbtests.HasFeatureCompatibilityVersion(&mdb, "4.0", 3))

test/e2e/feature_compatibility_version_upgrade/feature_compatibility_version_upgrade_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func TestFeatureCompatibilityVersionUpgrade(t *testing.T) {
3333
t.Run("MongoDB is reachable", mongodbtests.IsReachableDuring(&mdb, time.Second*10,
3434
func() {
3535
t.Run("Test Version can be upgraded", mongodbtests.ChangeVersion(&mdb, "4.2.6"))
36-
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsUpdated(&mdb))
36+
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsReady(&mdb))
3737
t.Run("Test Basic Connectivity after upgrade has completed", mongodbtests.BasicConnectivity(&mdb))
3838
},
3939
))
@@ -47,7 +47,7 @@ func TestFeatureCompatibilityVersionUpgrade(t *testing.T) {
4747
})
4848
assert.NoError(t, err)
4949
})
50-
t.Run("Stateful Set Reaches Ready State", mongodbtests.StatefulSetIsUpdated(&mdb))
50+
t.Run("Stateful Set Reaches Ready State", mongodbtests.StatefulSetIsReady(&mdb))
5151
t.Run("MongoDB Reaches Running Phase", mongodbtests.MongoDBReachesRunningPhase(&mdb))
5252
},
5353
))

test/e2e/mongodbtests/mongodbtests.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,11 @@ func StatefulSetHasOwnerReference(mdb *mdbv1.MongoDB, expectedOwnerReference met
6060
}
6161
}
6262

63-
// StatefulSetIsUpdated ensures that all the Pods of the StatefulSet are
64-
// in "Updated" condition.
65-
func StatefulSetIsUpdated(mdb *mdbv1.MongoDB) func(t *testing.T) {
66-
return func(t *testing.T) {
67-
err := e2eutil.WaitForStatefulSetToBeUpdated(t, mdb, time.Second*15, time.Minute*5)
68-
if err != nil {
69-
t.Fatal(err)
70-
}
71-
t.Logf("StatefulSet %s/%s is updated!", mdb.Namespace, mdb.Name)
72-
}
73-
}
74-
7563
// StatefulSetHasUpdateStrategy verifies that the StatefulSet holding this MongoDB
7664
// resource has the correct Update Strategy
7765
func StatefulSetHasUpdateStrategy(mdb *mdbv1.MongoDB, strategy appsv1.StatefulSetUpdateStrategyType) func(t *testing.T) {
7866
return func(t *testing.T) {
79-
err := e2eutil.WaitForStatefulSetToHaveUpdateStrategy(t, mdb, appsv1.RollingUpdateStatefulSetStrategyType, time.Second*15, time.Minute*5)
67+
err := e2eutil.WaitForStatefulSetToHaveUpdateStrategy(t, mdb, strategy, time.Second*15, time.Minute*5)
8068
if err != nil {
8169
t.Fatal(err)
8270
}
@@ -142,8 +130,6 @@ func HasFeatureCompatibilityVersion(mdb *mdbv1.MongoDB, fcv string, tries int) f
142130
case <-time.After(10 * time.Second):
143131
var result bson.M
144132
err = database.RunCommand(ctx, runCommand).Decode(&result)
145-
assert.NoError(t, err)
146-
147133
expected := primitive.M{"version": fcv}
148134
if reflect.DeepEqual(expected, result["featureCompatibilityVersion"]) {
149135
found = true

test/e2e/replica_set_change_version/replica_set_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@ func TestReplicaSetUpgradeVersion(t *testing.T) {
4343
func() {
4444
t.Run("Test Version can be upgraded", mongodbtests.ChangeVersion(&mdb, "4.0.8"))
4545
t.Run("StatefulSet has OnDelete update strategy", mongodbtests.StatefulSetHasUpdateStrategy(&mdb, appsv1.OnDeleteStatefulSetStrategyType))
46-
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsUpdated(&mdb))
46+
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsReady(&mdb))
4747
t.Run("AutomationConfig's version has been increased", mongodbtests.AutomationConfigVersionHasTheExpectedVersion(&mdb, 2))
48-
4948
},
5049
))
5150
t.Run("StatefulSet has RollingUpgrade restart strategy", mongodbtests.StatefulSetHasUpdateStrategy(&mdb, appsv1.RollingUpdateStatefulSetStrategyType))
@@ -55,9 +54,8 @@ func TestReplicaSetUpgradeVersion(t *testing.T) {
5554
func() {
5655
t.Run("Test Version can be downgraded", mongodbtests.ChangeVersion(&mdb, "4.0.6"))
5756
t.Run("StatefulSet has OnDelete restart strategy", mongodbtests.StatefulSetHasUpdateStrategy(&mdb, appsv1.OnDeleteStatefulSetStrategyType))
58-
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsUpdated(&mdb))
57+
t.Run("Stateful Set Reaches Ready State, after Upgrading", mongodbtests.StatefulSetIsReady(&mdb))
5958
t.Run("AutomationConfig's version has been increased", mongodbtests.AutomationConfigVersionHasTheExpectedVersion(&mdb, 3))
60-
6159
},
6260
))
6361
t.Run("StatefulSet has RollingUpgrade restart strategy", mongodbtests.StatefulSetHasUpdateStrategy(&mdb, appsv1.RollingUpdateStatefulSetStrategyType))

0 commit comments

Comments
 (0)