88 "os"
99 "time"
1010
11+ "github.com/mongodb/mongodb-kubernetes-operator/pkg/controller/predicates"
12+
1113 mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/pkg/apis/mongodb/v1"
1214 "github.com/mongodb/mongodb-kubernetes-operator/pkg/automationconfig"
1315 mdbClient "github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/client"
@@ -57,6 +59,7 @@ func newReconciler(mgr manager.Manager, manifestProvider ManifestProvider) recon
5759 client : mdbClient .NewClient (mgrClient ),
5860 scheme : mgr .GetScheme (),
5961 manifestProvider : manifestProvider ,
62+ log : zap .S (),
6063 }
6164}
6265
@@ -69,7 +72,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
6972 }
7073
7174 // Watch for changes to primary resource MongoDB
72- err = c .Watch (& source.Kind {Type : & mdbv1.MongoDB {}}, & handler.EnqueueRequestForObject {})
75+ err = c .Watch (& source.Kind {Type : & mdbv1.MongoDB {}}, & handler.EnqueueRequestForObject {}, predicates . OnlyOnSpecChange () )
7376 if err != nil {
7477 return err
7578 }
@@ -86,6 +89,7 @@ type ReplicaSetReconciler struct {
8689 client mdbClient.Client
8790 scheme * runtime.Scheme
8891 manifestProvider func () (automationconfig.VersionManifest , error )
92+ log * zap.SugaredLogger
8993}
9094
9195// Reconcile reads that state of the cluster for a MongoDB object and makes changes based on the state read
@@ -94,8 +98,8 @@ type ReplicaSetReconciler struct {
9498// The Controller will requeue the Request to be processed again if the returned error is non-nil or
9599// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
96100func (r * ReplicaSetReconciler ) Reconcile (request reconcile.Request ) (reconcile.Result , error ) {
97- log : = zap .S ().With ("ReplicaSet" , request .NamespacedName )
98- log .Info ("Reconciling MongoDB" )
101+ r . log = zap .S ().With ("ReplicaSet" , request .NamespacedName )
102+ r . log .Info ("Reconciling MongoDB" )
99103
100104 // TODO: generalize preparation for resource
101105 // Fetch the MongoDB instance
@@ -108,61 +112,116 @@ func (r *ReplicaSetReconciler) Reconcile(request reconcile.Request) (reconcile.R
108112 // Return and don't requeue
109113 return reconcile.Result {}, nil
110114 }
111- log .Errorf ("error reconciling MongoDB resource: %s" , err )
115+ r . log .Errorf ("error reconciling MongoDB resource: %s" , err )
112116 // Error reading the object - requeue the request.
113117 return reconcile.Result {}, err
114118 }
115119
116120 // TODO: Read current automation config version from config map
117121 if err := r .ensureAutomationConfig (mdb ); err != nil {
118- log .Warnf ("error creating automation config config map: %s" , err )
122+ r . log .Infof ("error creating automation config config map: %s" , err )
119123 return reconcile.Result {}, err
120124 }
121125
122126 svc := buildService (mdb )
123127 if err = r .client .CreateOrUpdate (& svc ); err != nil {
124- log .Warnf ("The service already exists... moving forward: %s" , err )
128+ r . log .Infof ("The service already exists... moving forward: %s" , err )
125129 }
126130
127- sts , err := buildStatefulSet (mdb )
128- if err != nil {
129- log .Infof ("Error building StatefulSet: %s" , err )
130- return reconcile.Result {}, nil
131+ if err := r .createOrUpdateStatefulSet (mdb ); err != nil {
132+ r .log .Infof ("Error creating/updating StatefulSet: %+v" , err )
133+ return reconcile.Result {}, err
131134 }
132135
133- if err = r .client . CreateOrUpdate ( & sts ); err != nil {
134- log .Infof ("Error creating/updating StatefulSet: %s " , err )
136+ if ready , err : = r .isStatefulSetReady ( mdb ); err != nil {
137+ r . log .Infof ("error checking StatefulSet status : %+v " , err )
135138 return reconcile.Result {}, err
136- } else {
137- log .Infof ("StatefulSet successfully Created/Updated" )
139+ } else if ! ready {
140+ r .log .Infof ("StatefulSet %s/%s is not yet ready, retrying in 10 seconds" , mdb .Namespace , mdb .Name )
141+ return reconcile.Result {RequeueAfter : time .Second * 10 }, nil
138142 }
139143
140- log .Debugf ("waiting for StatefulSet %s/%s to reach ready state" , mdb .Namespace , mdb .Name )
141- set := appsv1.StatefulSet {}
142- if err := r .client .Get (context .TODO (), types.NamespacedName {Name : mdb .Name , Namespace : mdb .Namespace }, & set ); err != nil {
143- log .Infof ("Error getting StatefulSet: %s" , err )
144+ if err := r .resetStatefulSetUpdateStrategy (mdb ); err != nil {
145+ r .log .Infof ("error resetting StatefulSet UpdateStrategyType: %+v" , err )
144146 return reconcile.Result {}, err
145147 }
146148
147- if ! statefulset . IsReady ( set ) {
148- log .Infof ("Stateful Set has not yet reached the ready state, requeuing reconciliation" )
149- return reconcile.Result {RequeueAfter : time . Second * 10 }, nil
149+ if err := r . setAnnotation (types. NamespacedName { Name : mdb . Name , Namespace : mdb . Namespace }, mdbv1 . LastVersionAnnotationKey , mdb . Spec . Version ); err != nil {
150+ r . log .Infof ("Error setting annotation: %+v" , err )
151+ return reconcile.Result {}, err
150152 }
151153
152- log .Infof ("Stateful Set reached ready state!" )
153-
154154 if err := r .updateStatusSuccess (& mdb ); err != nil {
155- log .Infof ("Error updating the status of the MongoDB resource: %+v" , err )
156- return reconcile.Result {}, nil
155+ r . log .Infof ("Error updating the status of the MongoDB resource: %+v" , err )
156+ return reconcile.Result {}, err
157157 }
158158
159- log .Info ("Successfully finished reconciliation" , "MongoDB.Spec:" , mdb .Spec , "MongoDB.Status" , mdb .Status )
159+ r . log .Info ("Successfully finished reconciliation" , "MongoDB.Spec:" , mdb .Spec , "MongoDB.Status" , mdb .Status )
160160 return reconcile.Result {}, nil
161161}
162162
163+ // resetStatefulSetUpdateStrategy ensures the stateful set is configured back to using RollingUpdateStatefulSetStrategyType
164+ // and does not keep using OnDelete
165+ func (r * ReplicaSetReconciler ) resetStatefulSetUpdateStrategy (mdb mdbv1.MongoDB ) error {
166+ if ! mdb .ChangingVersion () {
167+ return nil
168+ }
169+ // if we changed the version, we need to reset the UpdatePolicy back to OnUpdate
170+ sts := & appsv1.StatefulSet {}
171+ return r .client .GetAndUpdate (types.NamespacedName {Name : mdb .Name , Namespace : mdb .Namespace }, sts , func () {
172+ sts .Spec .UpdateStrategy .Type = appsv1 .RollingUpdateStatefulSetStrategyType
173+ })
174+ }
175+
176+ // isStatefulSetReady checks to see if the stateful set corresponding to the given MongoDB resource
177+ // is currently in the ready state
178+ func (r * ReplicaSetReconciler ) isStatefulSetReady (mdb mdbv1.MongoDB ) (bool , error ) {
179+ set := appsv1.StatefulSet {}
180+ if err := r .client .Get (context .TODO (), types.NamespacedName {Name : mdb .Name , Namespace : mdb .Namespace }, & set ); err != nil {
181+ return false , fmt .Errorf ("error getting StatefulSet: %s" , err )
182+ }
183+ return statefulset .IsReady (set ), nil
184+ }
185+
186+ func (r * ReplicaSetReconciler ) createOrUpdateStatefulSet (mdb mdbv1.MongoDB ) error {
187+ sts , err := buildStatefulSet (mdb )
188+ if err != nil {
189+ return fmt .Errorf ("error building StatefulSet: %s" , err )
190+ }
191+ if err = r .client .CreateOrUpdate (& sts ); err != nil {
192+ return fmt .Errorf ("error creating/updating StatefulSet: %s" , err )
193+ }
194+
195+ r .log .Debugf ("Waiting for StatefulSet %s/%s to reach ready state" , mdb .Namespace , mdb .Name )
196+ set := appsv1.StatefulSet {}
197+ if err := r .client .Get (context .TODO (), types.NamespacedName {Name : mdb .Name , Namespace : mdb .Namespace }, & set ); err != nil {
198+ return fmt .Errorf ("error getting StatefulSet: %s" , err )
199+ }
200+ return nil
201+ }
202+
203+ // setAnnotation updates the monogdb resource with the given namespaced name and sets the annotation
204+ // "key" with the provided value "val"
205+ func (r ReplicaSetReconciler ) setAnnotation (nsName types.NamespacedName , key , val string ) error {
206+ mdb := mdbv1.MongoDB {}
207+ return r .client .GetAndUpdate (nsName , & mdb , func () {
208+ if mdb .Annotations == nil {
209+ mdb .Annotations = map [string ]string {}
210+ }
211+ mdb .Annotations [key ] = val
212+ })
213+ }
214+
215+ // updateStatusSuccess should be called after a successful reconciliation
216+ // the resource's status is updated to reflect to the state, and any other cleanup
217+ // operators should be performed here
163218func (r ReplicaSetReconciler ) updateStatusSuccess (mdb * mdbv1.MongoDB ) error {
164- mdb .UpdateSuccess ()
165- if err := r .client .Status ().Update (context .TODO (), mdb ); err != nil {
219+ newMdb := & mdbv1.MongoDB {}
220+ if err := r .client .Get (context .TODO (), types.NamespacedName {Name : mdb .Name , Namespace : mdb .Namespace }, newMdb ); err != nil {
221+ return fmt .Errorf ("error getting resource: %+v" , err )
222+ }
223+ newMdb .UpdateSuccess ()
224+ if err := r .client .Status ().Update (context .TODO (), newMdb ); err != nil {
166225 return fmt .Errorf ("error updating status: %+v" , err )
167226 }
168227 return nil
@@ -293,6 +352,15 @@ func defaultReadinessProbe() corev1.Probe {
293352 }
294353}
295354
355+ // getUpdateStrategyType returns the type of RollingUpgradeStrategy that the StatefulSet
356+ // should be configured with
357+ func getUpdateStrategyType (mdb mdbv1.MongoDB ) appsv1.StatefulSetUpdateStrategyType {
358+ if ! mdb .ChangingVersion () {
359+ return appsv1 .RollingUpdateStatefulSetStrategyType
360+ }
361+ return appsv1 .OnDeleteStatefulSetStrategyType
362+ }
363+
296364// buildStatefulSet takes a MongoDB resource and converts it into
297365// the corresponding stateful set
298366func buildStatefulSet (mdb mdbv1.MongoDB ) (appsv1.StatefulSet , error ) {
@@ -316,7 +384,8 @@ func buildStatefulSet(mdb mdbv1.MongoDB) (appsv1.StatefulSet, error) {
316384 SetReplicas (mdb .Spec .Members ).
317385 SetLabels (labels ).
318386 SetMatchLabels (labels ).
319- SetServiceName (mdb .ServiceName ())
387+ SetServiceName (mdb .ServiceName ()).
388+ SetUpdateStrategy (getUpdateStrategyType (mdb ))
320389
321390 // TODO: Add this section to architecture document.
322391 // The design of the multi-container and the different volumes mounted to them is as follows:
0 commit comments