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