1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package statefulset
18
19import (
20	"sort"
21
22	apps "k8s.io/api/apps/v1"
23	v1 "k8s.io/api/core/v1"
24	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25	utilerrors "k8s.io/apimachinery/pkg/util/errors"
26	utilfeature "k8s.io/apiserver/pkg/util/feature"
27	"k8s.io/client-go/tools/record"
28	"k8s.io/klog/v2"
29	"k8s.io/kubernetes/pkg/controller/history"
30	"k8s.io/kubernetes/pkg/features"
31)
32
33// StatefulSetControl implements the control logic for updating StatefulSets and their children Pods. It is implemented
34// as an interface to allow for extensions that provide different semantics. Currently, there is only one implementation.
35type StatefulSetControlInterface interface {
36	// UpdateStatefulSet implements the control logic for Pod creation, update, and deletion, and
37	// persistent volume creation, update, and deletion.
38	// If an implementation returns a non-nil error, the invocation will be retried using a rate-limited strategy.
39	// Implementors should sink any errors that they do not wish to trigger a retry, and they may feel free to
40	// exit exceptionally at any point provided they wish the update to be re-run at a later point in time.
41	UpdateStatefulSet(set *apps.StatefulSet, pods []*v1.Pod) (*apps.StatefulSetStatus, error)
42	// ListRevisions returns a array of the ControllerRevisions that represent the revisions of set. If the returned
43	// error is nil, the returns slice of ControllerRevisions is valid.
44	ListRevisions(set *apps.StatefulSet) ([]*apps.ControllerRevision, error)
45	// AdoptOrphanRevisions adopts any orphaned ControllerRevisions that match set's Selector. If all adoptions are
46	// successful the returned error is nil.
47	AdoptOrphanRevisions(set *apps.StatefulSet, revisions []*apps.ControllerRevision) error
48}
49
50// NewDefaultStatefulSetControl returns a new instance of the default implementation StatefulSetControlInterface that
51// implements the documented semantics for StatefulSets. podControl is the PodControlInterface used to create, update,
52// and delete Pods and to create PersistentVolumeClaims. statusUpdater is the StatefulSetStatusUpdaterInterface used
53// to update the status of StatefulSets. You should use an instance returned from NewRealStatefulPodControl() for any
54// scenario other than testing.
55func NewDefaultStatefulSetControl(
56	podControl StatefulPodControlInterface,
57	statusUpdater StatefulSetStatusUpdaterInterface,
58	controllerHistory history.Interface,
59	recorder record.EventRecorder) StatefulSetControlInterface {
60	return &defaultStatefulSetControl{podControl, statusUpdater, controllerHistory, recorder}
61}
62
63type defaultStatefulSetControl struct {
64	podControl        StatefulPodControlInterface
65	statusUpdater     StatefulSetStatusUpdaterInterface
66	controllerHistory history.Interface
67	recorder          record.EventRecorder
68}
69
70// UpdateStatefulSet executes the core logic loop for a stateful set, applying the predictable and
71// consistent monotonic update strategy by default - scale up proceeds in ordinal order, no new pod
72// is created while any pod is unhealthy, and pods are terminated in descending order. The burst
73// strategy allows these constraints to be relaxed - pods will be created and deleted eagerly and
74// in no particular order. Clients using the burst strategy should be careful to ensure they
75// understand the consistency implications of having unpredictable numbers of pods available.
76func (ssc *defaultStatefulSetControl) UpdateStatefulSet(set *apps.StatefulSet, pods []*v1.Pod) (*apps.StatefulSetStatus, error) {
77	// list all revisions and sort them
78	revisions, err := ssc.ListRevisions(set)
79	if err != nil {
80		return nil, err
81	}
82	history.SortControllerRevisions(revisions)
83
84	currentRevision, updateRevision, status, err := ssc.performUpdate(set, pods, revisions)
85	if err != nil {
86		return nil, utilerrors.NewAggregate([]error{err, ssc.truncateHistory(set, pods, revisions, currentRevision, updateRevision)})
87	}
88
89	// maintain the set's revision history limit
90	return status, ssc.truncateHistory(set, pods, revisions, currentRevision, updateRevision)
91}
92
93func (ssc *defaultStatefulSetControl) performUpdate(
94	set *apps.StatefulSet, pods []*v1.Pod, revisions []*apps.ControllerRevision) (*apps.ControllerRevision, *apps.ControllerRevision, *apps.StatefulSetStatus, error) {
95	var currentStatus *apps.StatefulSetStatus
96	// get the current, and update revisions
97	currentRevision, updateRevision, collisionCount, err := ssc.getStatefulSetRevisions(set, revisions)
98	if err != nil {
99		return currentRevision, updateRevision, currentStatus, err
100	}
101
102	// perform the main update function and get the status
103	currentStatus, err = ssc.updateStatefulSet(set, currentRevision, updateRevision, collisionCount, pods)
104	if err != nil {
105		return currentRevision, updateRevision, currentStatus, err
106	}
107	// update the set's status
108	err = ssc.updateStatefulSetStatus(set, currentStatus)
109	if err != nil {
110		return currentRevision, updateRevision, currentStatus, err
111	}
112	klog.V(4).Infof("StatefulSet %s/%s pod status replicas=%d ready=%d current=%d updated=%d",
113		set.Namespace,
114		set.Name,
115		currentStatus.Replicas,
116		currentStatus.ReadyReplicas,
117		currentStatus.CurrentReplicas,
118		currentStatus.UpdatedReplicas)
119
120	klog.V(4).Infof("StatefulSet %s/%s revisions current=%s update=%s",
121		set.Namespace,
122		set.Name,
123		currentStatus.CurrentRevision,
124		currentStatus.UpdateRevision)
125
126	return currentRevision, updateRevision, currentStatus, nil
127}
128
129func (ssc *defaultStatefulSetControl) ListRevisions(set *apps.StatefulSet) ([]*apps.ControllerRevision, error) {
130	selector, err := metav1.LabelSelectorAsSelector(set.Spec.Selector)
131	if err != nil {
132		return nil, err
133	}
134	return ssc.controllerHistory.ListControllerRevisions(set, selector)
135}
136
137func (ssc *defaultStatefulSetControl) AdoptOrphanRevisions(
138	set *apps.StatefulSet,
139	revisions []*apps.ControllerRevision) error {
140	for i := range revisions {
141		adopted, err := ssc.controllerHistory.AdoptControllerRevision(set, controllerKind, revisions[i])
142		if err != nil {
143			return err
144		}
145		revisions[i] = adopted
146	}
147	return nil
148}
149
150// truncateHistory truncates any non-live ControllerRevisions in revisions from set's history. The UpdateRevision and
151// CurrentRevision in set's Status are considered to be live. Any revisions associated with the Pods in pods are also
152// considered to be live. Non-live revisions are deleted, starting with the revision with the lowest Revision, until
153// only RevisionHistoryLimit revisions remain. If the returned error is nil the operation was successful. This method
154// expects that revisions is sorted when supplied.
155func (ssc *defaultStatefulSetControl) truncateHistory(
156	set *apps.StatefulSet,
157	pods []*v1.Pod,
158	revisions []*apps.ControllerRevision,
159	current *apps.ControllerRevision,
160	update *apps.ControllerRevision) error {
161	history := make([]*apps.ControllerRevision, 0, len(revisions))
162	// mark all live revisions
163	live := map[string]bool{}
164	if current != nil {
165		live[current.Name] = true
166	}
167	if update != nil {
168		live[update.Name] = true
169	}
170	for i := range pods {
171		live[getPodRevision(pods[i])] = true
172	}
173	// collect live revisions and historic revisions
174	for i := range revisions {
175		if !live[revisions[i].Name] {
176			history = append(history, revisions[i])
177		}
178	}
179	historyLen := len(history)
180	historyLimit := int(*set.Spec.RevisionHistoryLimit)
181	if historyLen <= historyLimit {
182		return nil
183	}
184	// delete any non-live history to maintain the revision limit.
185	history = history[:(historyLen - historyLimit)]
186	for i := 0; i < len(history); i++ {
187		if err := ssc.controllerHistory.DeleteControllerRevision(history[i]); err != nil {
188			return err
189		}
190	}
191	return nil
192}
193
194// getStatefulSetRevisions returns the current and update ControllerRevisions for set. It also
195// returns a collision count that records the number of name collisions set saw when creating
196// new ControllerRevisions. This count is incremented on every name collision and is used in
197// building the ControllerRevision names for name collision avoidance. This method may create
198// a new revision, or modify the Revision of an existing revision if an update to set is detected.
199// This method expects that revisions is sorted when supplied.
200func (ssc *defaultStatefulSetControl) getStatefulSetRevisions(
201	set *apps.StatefulSet,
202	revisions []*apps.ControllerRevision) (*apps.ControllerRevision, *apps.ControllerRevision, int32, error) {
203	var currentRevision, updateRevision *apps.ControllerRevision
204
205	revisionCount := len(revisions)
206	history.SortControllerRevisions(revisions)
207
208	// Use a local copy of set.Status.CollisionCount to avoid modifying set.Status directly.
209	// This copy is returned so the value gets carried over to set.Status in updateStatefulSet.
210	var collisionCount int32
211	if set.Status.CollisionCount != nil {
212		collisionCount = *set.Status.CollisionCount
213	}
214
215	// create a new revision from the current set
216	updateRevision, err := newRevision(set, nextRevision(revisions), &collisionCount)
217	if err != nil {
218		return nil, nil, collisionCount, err
219	}
220
221	// find any equivalent revisions
222	equalRevisions := history.FindEqualRevisions(revisions, updateRevision)
223	equalCount := len(equalRevisions)
224
225	if equalCount > 0 && history.EqualRevision(revisions[revisionCount-1], equalRevisions[equalCount-1]) {
226		// if the equivalent revision is immediately prior the update revision has not changed
227		updateRevision = revisions[revisionCount-1]
228	} else if equalCount > 0 {
229		// if the equivalent revision is not immediately prior we will roll back by incrementing the
230		// Revision of the equivalent revision
231		updateRevision, err = ssc.controllerHistory.UpdateControllerRevision(
232			equalRevisions[equalCount-1],
233			updateRevision.Revision)
234		if err != nil {
235			return nil, nil, collisionCount, err
236		}
237	} else {
238		//if there is no equivalent revision we create a new one
239		updateRevision, err = ssc.controllerHistory.CreateControllerRevision(set, updateRevision, &collisionCount)
240		if err != nil {
241			return nil, nil, collisionCount, err
242		}
243	}
244
245	// attempt to find the revision that corresponds to the current revision
246	for i := range revisions {
247		if revisions[i].Name == set.Status.CurrentRevision {
248			currentRevision = revisions[i]
249			break
250		}
251	}
252
253	// if the current revision is nil we initialize the history by setting it to the update revision
254	if currentRevision == nil {
255		currentRevision = updateRevision
256	}
257
258	return currentRevision, updateRevision, collisionCount, nil
259}
260
261// updateStatefulSet performs the update function for a StatefulSet. This method creates, updates, and deletes Pods in
262// the set in order to conform the system to the target state for the set. The target state always contains
263// set.Spec.Replicas Pods with a Ready Condition. If the UpdateStrategy.Type for the set is
264// RollingUpdateStatefulSetStrategyType then all Pods in the set must be at set.Status.CurrentRevision.
265// If the UpdateStrategy.Type for the set is OnDeleteStatefulSetStrategyType, the target state implies nothing about
266// the revisions of Pods in the set. If the UpdateStrategy.Type for the set is PartitionStatefulSetStrategyType, then
267// all Pods with ordinal less than UpdateStrategy.Partition.Ordinal must be at Status.CurrentRevision and all other
268// Pods must be at Status.UpdateRevision. If the returned error is nil, the returned StatefulSetStatus is valid and the
269// update must be recorded. If the error is not nil, the method should be retried until successful.
270func (ssc *defaultStatefulSetControl) updateStatefulSet(
271	set *apps.StatefulSet,
272	currentRevision *apps.ControllerRevision,
273	updateRevision *apps.ControllerRevision,
274	collisionCount int32,
275	pods []*v1.Pod) (*apps.StatefulSetStatus, error) {
276	// get the current and update revisions of the set.
277	currentSet, err := ApplyRevision(set, currentRevision)
278	if err != nil {
279		return nil, err
280	}
281	updateSet, err := ApplyRevision(set, updateRevision)
282	if err != nil {
283		return nil, err
284	}
285
286	// set the generation, and revisions in the returned status
287	status := apps.StatefulSetStatus{}
288	status.ObservedGeneration = set.Generation
289	status.CurrentRevision = currentRevision.Name
290	status.UpdateRevision = updateRevision.Name
291	status.CollisionCount = new(int32)
292	*status.CollisionCount = collisionCount
293
294	replicaCount := int(*set.Spec.Replicas)
295	// slice that will contain all Pods such that 0 <= getOrdinal(pod) < set.Spec.Replicas
296	replicas := make([]*v1.Pod, replicaCount)
297	// slice that will contain all Pods such that set.Spec.Replicas <= getOrdinal(pod)
298	condemned := make([]*v1.Pod, 0, len(pods))
299	unhealthy := 0
300	var firstUnhealthyPod *v1.Pod
301
302	// First we partition pods into two lists valid replicas and condemned Pods
303	for i := range pods {
304		status.Replicas++
305
306		// count the number of running and ready replicas
307		if isRunningAndReady(pods[i]) {
308			status.ReadyReplicas++
309			// count the number of running and available replicas
310			if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) {
311				if isRunningAndAvailable(pods[i], set.Spec.MinReadySeconds) {
312					status.AvailableReplicas++
313				}
314			} else {
315				// If the featuregate is not enabled, all the ready replicas should be considered as available replicas
316				status.AvailableReplicas = status.ReadyReplicas
317			}
318		}
319
320		// count the number of current and update replicas
321		if isCreated(pods[i]) && !isTerminating(pods[i]) {
322			if getPodRevision(pods[i]) == currentRevision.Name {
323				status.CurrentReplicas++
324			}
325			if getPodRevision(pods[i]) == updateRevision.Name {
326				status.UpdatedReplicas++
327			}
328		}
329
330		if ord := getOrdinal(pods[i]); 0 <= ord && ord < replicaCount {
331			// if the ordinal of the pod is within the range of the current number of replicas,
332			// insert it at the indirection of its ordinal
333			replicas[ord] = pods[i]
334
335		} else if ord >= replicaCount {
336			// if the ordinal is greater than the number of replicas add it to the condemned list
337			condemned = append(condemned, pods[i])
338		}
339		// If the ordinal could not be parsed (ord < 0), ignore the Pod.
340	}
341
342	// for any empty indices in the sequence [0,set.Spec.Replicas) create a new Pod at the correct revision
343	for ord := 0; ord < replicaCount; ord++ {
344		if replicas[ord] == nil {
345			replicas[ord] = newVersionedStatefulSetPod(
346				currentSet,
347				updateSet,
348				currentRevision.Name,
349				updateRevision.Name, ord)
350		}
351	}
352
353	// sort the condemned Pods by their ordinals
354	sort.Sort(ascendingOrdinal(condemned))
355
356	// find the first unhealthy Pod
357	for i := range replicas {
358		if !isHealthy(replicas[i]) {
359			unhealthy++
360			if firstUnhealthyPod == nil {
361				firstUnhealthyPod = replicas[i]
362			}
363		}
364	}
365
366	for i := range condemned {
367		if !isHealthy(condemned[i]) {
368			unhealthy++
369			if firstUnhealthyPod == nil {
370				firstUnhealthyPod = condemned[i]
371			}
372		}
373	}
374
375	if unhealthy > 0 {
376		klog.V(4).Infof("StatefulSet %s/%s has %d unhealthy Pods starting with %s",
377			set.Namespace,
378			set.Name,
379			unhealthy,
380			firstUnhealthyPod.Name)
381	}
382
383	// If the StatefulSet is being deleted, don't do anything other than updating
384	// status.
385	if set.DeletionTimestamp != nil {
386		return &status, nil
387	}
388
389	monotonic := !allowsBurst(set)
390
391	// Examine each replica with respect to its ordinal
392	for i := range replicas {
393		// delete and recreate failed pods
394		if isFailed(replicas[i]) {
395			ssc.recorder.Eventf(set, v1.EventTypeWarning, "RecreatingFailedPod",
396				"StatefulSet %s/%s is recreating failed Pod %s",
397				set.Namespace,
398				set.Name,
399				replicas[i].Name)
400			if err := ssc.podControl.DeleteStatefulPod(set, replicas[i]); err != nil {
401				return &status, err
402			}
403			if getPodRevision(replicas[i]) == currentRevision.Name {
404				status.CurrentReplicas--
405			}
406			if getPodRevision(replicas[i]) == updateRevision.Name {
407				status.UpdatedReplicas--
408			}
409			status.Replicas--
410			replicas[i] = newVersionedStatefulSetPod(
411				currentSet,
412				updateSet,
413				currentRevision.Name,
414				updateRevision.Name,
415				i)
416		}
417		// If we find a Pod that has not been created we create the Pod
418		if !isCreated(replicas[i]) {
419			if err := ssc.podControl.CreateStatefulPod(set, replicas[i]); err != nil {
420				return &status, err
421			}
422			status.Replicas++
423			if getPodRevision(replicas[i]) == currentRevision.Name {
424				status.CurrentReplicas++
425			}
426			if getPodRevision(replicas[i]) == updateRevision.Name {
427				status.UpdatedReplicas++
428			}
429
430			// if the set does not allow bursting, return immediately
431			if monotonic {
432				return &status, nil
433			}
434			// pod created, no more work possible for this round
435			continue
436		}
437		// If we find a Pod that is currently terminating, we must wait until graceful deletion
438		// completes before we continue to make progress.
439		if isTerminating(replicas[i]) && monotonic {
440			klog.V(4).Infof(
441				"StatefulSet %s/%s is waiting for Pod %s to Terminate",
442				set.Namespace,
443				set.Name,
444				replicas[i].Name)
445			return &status, nil
446		}
447		// If we have a Pod that has been created but is not running and ready we can not make progress.
448		// We must ensure that all for each Pod, when we create it, all of its predecessors, with respect to its
449		// ordinal, are Running and Ready.
450		if !isRunningAndReady(replicas[i]) && monotonic {
451			klog.V(4).Infof(
452				"StatefulSet %s/%s is waiting for Pod %s to be Running and Ready",
453				set.Namespace,
454				set.Name,
455				replicas[i].Name)
456			return &status, nil
457		}
458		// If we have a Pod that has been created but is not available we can not make progress.
459		// We must ensure that all for each Pod, when we create it, all of its predecessors, with respect to its
460		// ordinal, are Available.
461		// TODO: Since available is superset of Ready, once we have this featuregate enabled by default, we can remove the
462		// isRunningAndReady block as only Available pods should be brought down.
463		if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) && !isRunningAndAvailable(replicas[i], set.Spec.MinReadySeconds) && monotonic {
464			klog.V(4).Infof(
465				"StatefulSet %s/%s is waiting for Pod %s to be Available",
466				set.Namespace,
467				set.Name,
468				replicas[i].Name)
469			return &status, nil
470		}
471		// Enforce the StatefulSet invariants
472		if identityMatches(set, replicas[i]) && storageMatches(set, replicas[i]) {
473			continue
474		}
475		// Make a deep copy so we don't mutate the shared cache
476		replica := replicas[i].DeepCopy()
477		if err := ssc.podControl.UpdateStatefulPod(updateSet, replica); err != nil {
478			return &status, err
479		}
480	}
481
482	// At this point, all of the current Replicas are Running, Ready and Available, we can consider termination.
483	// We will wait for all predecessors to be Running and Ready prior to attempting a deletion.
484	// We will terminate Pods in a monotonically decreasing order over [len(pods),set.Spec.Replicas).
485	// Note that we do not resurrect Pods in this interval. Also note that scaling will take precedence over
486	// updates.
487	for target := len(condemned) - 1; target >= 0; target-- {
488		// wait for terminating pods to expire
489		if isTerminating(condemned[target]) {
490			klog.V(4).Infof(
491				"StatefulSet %s/%s is waiting for Pod %s to Terminate prior to scale down",
492				set.Namespace,
493				set.Name,
494				condemned[target].Name)
495			// block if we are in monotonic mode
496			if monotonic {
497				return &status, nil
498			}
499			continue
500		}
501		// if we are in monotonic mode and the condemned target is not the first unhealthy Pod block
502		if !isRunningAndReady(condemned[target]) && monotonic && condemned[target] != firstUnhealthyPod {
503			klog.V(4).Infof(
504				"StatefulSet %s/%s is waiting for Pod %s to be Running and Ready prior to scale down",
505				set.Namespace,
506				set.Name,
507				firstUnhealthyPod.Name)
508			return &status, nil
509		}
510		// if we are in monotonic mode and the condemned target is not the first unhealthy Pod, block.
511		// TODO: Since available is superset of Ready, once we have this featuregate enabled by default, we can remove the
512		// isRunningAndReady block as only Available pods should be brought down.
513		if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) && !isRunningAndAvailable(condemned[target], set.Spec.MinReadySeconds) && monotonic && condemned[target] != firstUnhealthyPod {
514			klog.V(4).Infof(
515				"StatefulSet %s/%s is waiting for Pod %s to be Available prior to scale down",
516				set.Namespace,
517				set.Name,
518				firstUnhealthyPod.Name)
519			return &status, nil
520		}
521		klog.V(2).Infof("StatefulSet %s/%s terminating Pod %s for scale down",
522			set.Namespace,
523			set.Name,
524			condemned[target].Name)
525
526		if err := ssc.podControl.DeleteStatefulPod(set, condemned[target]); err != nil {
527			return &status, err
528		}
529		if getPodRevision(condemned[target]) == currentRevision.Name {
530			status.CurrentReplicas--
531		}
532		if getPodRevision(condemned[target]) == updateRevision.Name {
533			status.UpdatedReplicas--
534		}
535		if monotonic {
536			return &status, nil
537		}
538	}
539
540	// for the OnDelete strategy we short circuit. Pods will be updated when they are manually deleted.
541	if set.Spec.UpdateStrategy.Type == apps.OnDeleteStatefulSetStrategyType {
542		return &status, nil
543	}
544
545	// we compute the minimum ordinal of the target sequence for a destructive update based on the strategy.
546	updateMin := 0
547	if set.Spec.UpdateStrategy.RollingUpdate != nil {
548		updateMin = int(*set.Spec.UpdateStrategy.RollingUpdate.Partition)
549	}
550	// we terminate the Pod with the largest ordinal that does not match the update revision.
551	for target := len(replicas) - 1; target >= updateMin; target-- {
552
553		// delete the Pod if it is not already terminating and does not match the update revision.
554		if getPodRevision(replicas[target]) != updateRevision.Name && !isTerminating(replicas[target]) {
555			klog.V(2).Infof("StatefulSet %s/%s terminating Pod %s for update",
556				set.Namespace,
557				set.Name,
558				replicas[target].Name)
559			err := ssc.podControl.DeleteStatefulPod(set, replicas[target])
560			status.CurrentReplicas--
561			return &status, err
562		}
563
564		// wait for unhealthy Pods on update
565		if !isHealthy(replicas[target]) {
566			klog.V(4).Infof(
567				"StatefulSet %s/%s is waiting for Pod %s to update",
568				set.Namespace,
569				set.Name,
570				replicas[target].Name)
571			return &status, nil
572		}
573
574	}
575	return &status, nil
576}
577
578// updateStatefulSetStatus updates set's Status to be equal to status. If status indicates a complete update, it is
579// mutated to indicate completion. If status is semantically equivalent to set's Status no update is performed. If the
580// returned error is nil, the update is successful.
581func (ssc *defaultStatefulSetControl) updateStatefulSetStatus(
582	set *apps.StatefulSet,
583	status *apps.StatefulSetStatus) error {
584	// complete any in progress rolling update if necessary
585	completeRollingUpdate(set, status)
586
587	// if the status is not inconsistent do not perform an update
588	if !inconsistentStatus(set, status) {
589		return nil
590	}
591
592	// copy set and update its status
593	set = set.DeepCopy()
594	if err := ssc.statusUpdater.UpdateStatefulSetStatus(set, status); err != nil {
595		return err
596	}
597
598	return nil
599}
600
601var _ StatefulSetControlInterface = &defaultStatefulSetControl{}
602