2015-09-18 20:35:56 +00:00
/ *
Copyright 2015 The Kubernetes Authors All rights reserved .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package deployment
import (
"fmt"
2016-01-20 23:48:52 +00:00
"strconv"
2015-11-11 23:22:57 +00:00
"time"
2015-09-18 20:35:56 +00:00
2016-02-25 00:41:26 +00:00
"github.com/golang/glog"
2015-09-18 20:35:56 +00:00
"k8s.io/kubernetes/pkg/api"
2016-02-06 02:43:02 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2015-10-09 22:04:41 +00:00
"k8s.io/kubernetes/pkg/apis/extensions"
2016-02-05 21:58:03 +00:00
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
2016-03-05 00:32:32 +00:00
"k8s.io/kubernetes/pkg/controller"
2015-09-18 20:35:56 +00:00
"k8s.io/kubernetes/pkg/labels"
2016-03-03 23:48:56 +00:00
"k8s.io/kubernetes/pkg/util/errors"
2016-02-05 02:05:38 +00:00
"k8s.io/kubernetes/pkg/util/integer"
intstrutil "k8s.io/kubernetes/pkg/util/intstr"
2016-01-12 23:37:51 +00:00
labelsutil "k8s.io/kubernetes/pkg/util/labels"
podutil "k8s.io/kubernetes/pkg/util/pod"
2016-02-11 01:49:11 +00:00
"k8s.io/kubernetes/pkg/util/wait"
2015-09-18 20:35:56 +00:00
)
2016-01-13 01:52:18 +00:00
const (
2016-01-20 00:40:18 +00:00
// The revision annotation of a deployment's replica sets which records its rollout sequence
2016-01-13 01:52:18 +00:00
RevisionAnnotation = "deployment.kubernetes.io/revision"
2016-01-19 22:50:03 +00:00
// Here are the possible rollback event reasons
RollbackRevisionNotFound = "DeploymentRollbackRevisionNotFound"
RollbackTemplateUnchanged = "DeploymentRollbackTemplateUnchanged"
RollbackDone = "DeploymentRollback"
2016-01-13 01:52:18 +00:00
)
2016-01-20 00:40:18 +00:00
// GetOldReplicaSets returns the old replica sets targeted by the given Deployment; get PodList and ReplicaSetList from client interface.
// Note that the first set of old replica sets doesn't include the ones with no pods, and the second set of old replica sets include all old replica sets.
2016-02-28 02:13:32 +00:00
func GetOldReplicaSets ( deployment * extensions . Deployment , c clientset . Interface ) ( [ ] * extensions . ReplicaSet , [ ] * extensions . ReplicaSet , error ) {
2016-03-14 19:07:56 +00:00
rsList , err := ListReplicaSets ( deployment ,
func ( namespace string , options api . ListOptions ) ( [ ] extensions . ReplicaSet , error ) {
rsList , err := c . Extensions ( ) . ReplicaSets ( namespace ) . List ( options )
return rsList . Items , err
} )
if err != nil {
return nil , nil , fmt . Errorf ( "error listing ReplicaSets: %v" , err )
}
podList , err := ListPods ( deployment ,
2015-12-04 00:00:13 +00:00
func ( namespace string , options api . ListOptions ) ( * api . PodList , error ) {
2016-02-03 21:21:05 +00:00
return c . Core ( ) . Pods ( namespace ) . List ( options )
2016-03-14 19:07:56 +00:00
} )
if err != nil {
return nil , nil , fmt . Errorf ( "error listing Pods: %v" , err )
}
return FindOldReplicaSets ( deployment , rsList , podList )
}
// GetNewReplicaSet returns a replica set that matches the intent of the given deployment; get ReplicaSetList from client interface.
// Returns nil if the new replica set doesn't exist yet.
func GetNewReplicaSet ( deployment * extensions . Deployment , c clientset . Interface ) ( * extensions . ReplicaSet , error ) {
rsList , err := ListReplicaSets ( deployment ,
2016-01-20 00:40:18 +00:00
func ( namespace string , options api . ListOptions ) ( [ ] extensions . ReplicaSet , error ) {
rsList , err := c . Extensions ( ) . ReplicaSets ( namespace ) . List ( options )
return rsList . Items , err
2015-11-18 23:12:11 +00:00
} )
2016-03-14 19:07:56 +00:00
if err != nil {
return nil , fmt . Errorf ( "error listing ReplicaSets: %v" , err )
}
return FindNewReplicaSet ( deployment , rsList )
2015-11-18 23:12:11 +00:00
}
2016-02-19 18:25:34 +00:00
// TODO: switch this to full namespacers
2016-02-11 01:49:11 +00:00
type rsListFunc func ( string , api . ListOptions ) ( [ ] extensions . ReplicaSet , error )
type podListFunc func ( string , api . ListOptions ) ( * api . PodList , error )
2016-03-14 19:07:56 +00:00
// ListReplicaSets returns a slice of RSes the given deployment targets.
func ListReplicaSets ( deployment * extensions . Deployment , getRSList rsListFunc ) ( [ ] extensions . ReplicaSet , error ) {
// TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector
// should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830;
// or use controllerRef, see https://github.com/kubernetes/kubernetes/issues/2210
namespace := deployment . Namespace
selector , err := unversioned . LabelSelectorAsSelector ( deployment . Spec . Selector )
if err != nil {
return nil , err
}
options := api . ListOptions { LabelSelector : selector }
return getRSList ( namespace , options )
}
// ListPods returns a list of pods the given deployment targets.
func ListPods ( deployment * extensions . Deployment , getPodList podListFunc ) ( * api . PodList , error ) {
namespace := deployment . Namespace
selector , err := unversioned . LabelSelectorAsSelector ( deployment . Spec . Selector )
if err != nil {
return nil , err
}
options := api . ListOptions { LabelSelector : selector }
return getPodList ( namespace , options )
}
// FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template).
func FindNewReplicaSet ( deployment * extensions . Deployment , rsList [ ] extensions . ReplicaSet ) ( * extensions . ReplicaSet , error ) {
newRSTemplate := GetNewReplicaSetTemplate ( deployment )
for i := range rsList {
if api . Semantic . DeepEqual ( rsList [ i ] . Spec . Template , newRSTemplate ) {
// This is the new ReplicaSet.
return & rsList [ i ] , nil
}
}
// new ReplicaSet does not exist.
return nil , nil
}
// FindOldReplicaSets returns the old replica sets targeted by the given Deployment, with the given PodList and slice of RSes.
2016-01-20 00:40:18 +00:00
// Note that the first set of old replica sets doesn't include the ones with no pods, and the second set of old replica sets include all old replica sets.
2016-03-14 19:07:56 +00:00
func FindOldReplicaSets ( deployment * extensions . Deployment , rsList [ ] extensions . ReplicaSet , podList * api . PodList ) ( [ ] * extensions . ReplicaSet , [ ] * extensions . ReplicaSet , error ) {
2016-02-11 18:57:42 +00:00
// Find all pods whose labels match deployment.Spec.Selector, and corresponding replica sets for pods in podList.
// All pods and replica sets are labeled with pod-template-hash to prevent overlapping
2016-01-20 00:40:18 +00:00
oldRSs := map [ string ] extensions . ReplicaSet { }
allOldRSs := map [ string ] extensions . ReplicaSet { }
newRSTemplate := GetNewReplicaSetTemplate ( deployment )
2015-09-18 20:35:56 +00:00
for _ , pod := range podList . Items {
podLabelsSelector := labels . Set ( pod . ObjectMeta . Labels )
2016-01-20 00:40:18 +00:00
for _ , rs := range rsList {
2016-02-06 02:43:02 +00:00
rsLabelsSelector , err := unversioned . LabelSelectorAsSelector ( rs . Spec . Selector )
if err != nil {
2016-02-10 17:43:30 +00:00
return nil , nil , fmt . Errorf ( "invalid label selector: %v" , err )
2016-02-06 02:43:02 +00:00
}
2016-01-20 00:40:18 +00:00
// Filter out replica set that has the same pod template spec as the deployment - that is the new replica set.
2016-03-09 21:11:13 +00:00
if api . Semantic . DeepEqual ( rs . Spec . Template , newRSTemplate ) {
2016-01-13 01:52:18 +00:00
continue
}
2016-01-20 00:40:18 +00:00
allOldRSs [ rs . ObjectMeta . Name ] = rs
if rsLabelsSelector . Matches ( podLabelsSelector ) {
oldRSs [ rs . ObjectMeta . Name ] = rs
2015-09-18 20:35:56 +00:00
}
}
}
2016-01-20 00:40:18 +00:00
requiredRSs := [ ] * extensions . ReplicaSet { }
for key := range oldRSs {
value := oldRSs [ key ]
requiredRSs = append ( requiredRSs , & value )
2015-09-18 20:35:56 +00:00
}
2016-01-20 00:40:18 +00:00
allRSs := [ ] * extensions . ReplicaSet { }
for key := range allOldRSs {
value := allOldRSs [ key ]
allRSs = append ( allRSs , & value )
2016-01-13 01:52:18 +00:00
}
2016-01-20 00:40:18 +00:00
return requiredRSs , allRSs , nil
2015-09-18 20:35:56 +00:00
}
2016-03-14 19:07:56 +00:00
func WaitForReplicaSetUpdated ( c clientset . Interface , desiredGeneration int64 , namespace , name string ) error {
2016-02-19 18:25:34 +00:00
return wait . Poll ( 10 * time . Millisecond , 1 * time . Minute , func ( ) ( bool , error ) {
rs , err := c . Extensions ( ) . ReplicaSets ( namespace ) . Get ( name )
if err != nil {
return false , err
}
return rs . Status . ObservedGeneration >= desiredGeneration , nil
} )
}
2016-03-14 19:07:56 +00:00
func WaitForPodsHashPopulated ( c clientset . Interface , desiredGeneration int64 , namespace , name string ) error {
2016-03-11 18:34:13 +00:00
return wait . Poll ( 1 * time . Second , 1 * time . Minute , func ( ) ( bool , error ) {
rs , err := c . Extensions ( ) . ReplicaSets ( namespace ) . Get ( name )
if err != nil {
return false , err
}
return rs . Status . ObservedGeneration >= desiredGeneration &&
rs . Status . FullyLabeledReplicas == rs . Spec . Replicas , nil
} )
}
2016-03-14 19:07:56 +00:00
// LabelPodsWithHash labels all pods in the given podList with the new hash label.
2016-03-02 20:52:12 +00:00
// The returned bool value can be used to tell if all pods are actually labeled.
2016-03-14 19:07:56 +00:00
func LabelPodsWithHash ( podList * api . PodList , rs * extensions . ReplicaSet , c clientset . Interface , namespace , hash string ) ( bool , error ) {
2016-03-02 20:52:12 +00:00
allPodsLabeled := true
2016-02-11 01:49:11 +00:00
for _ , pod := range podList . Items {
2016-02-18 19:45:24 +00:00
// Only label the pod that doesn't already have the new hash
if pod . Labels [ extensions . DefaultDeploymentUniqueLabelKey ] != hash {
2016-03-02 23:06:00 +00:00
if _ , podUpdated , err := podutil . UpdatePodWithRetries ( c . Core ( ) . Pods ( namespace ) , & pod ,
2016-03-03 23:48:56 +00:00
func ( podToUpdate * api . Pod ) error {
// Precondition: the pod doesn't contain the new hash in its label.
if podToUpdate . Labels [ extensions . DefaultDeploymentUniqueLabelKey ] == hash {
return errors . ErrPreconditionViolated
}
2016-03-02 23:06:00 +00:00
podToUpdate . Labels = labelsutil . AddLabel ( podToUpdate . Labels , extensions . DefaultDeploymentUniqueLabelKey , hash )
2016-03-03 23:48:56 +00:00
return nil
2016-03-02 23:06:00 +00:00
} ) ; err != nil {
2016-03-02 20:52:12 +00:00
return false , fmt . Errorf ( "error in adding template hash label %s to pod %+v: %s" , hash , pod , err )
} else if podUpdated {
glog . V ( 4 ) . Infof ( "Labeled %s %s/%s of %s %s/%s with hash %s." , pod . Kind , pod . Namespace , pod . Name , rs . Kind , rs . Namespace , rs . Name , hash )
} else {
2016-03-03 23:48:56 +00:00
// If the pod wasn't updated but didn't return error when we try to update it, we've hit "pod not found" or "precondition violated" error.
2016-03-02 20:52:12 +00:00
// Then we can't say all pods are labeled
allPodsLabeled = false
2016-02-11 01:49:11 +00:00
}
}
2016-03-01 02:28:32 +00:00
}
2016-03-02 20:52:12 +00:00
return allPodsLabeled , nil
2016-02-18 19:45:24 +00:00
}
2016-01-20 00:40:18 +00:00
// Returns the desired PodTemplateSpec for the new ReplicaSet corresponding to the given ReplicaSet.
2016-02-28 02:13:32 +00:00
func GetNewReplicaSetTemplate ( deployment * extensions . Deployment ) api . PodTemplateSpec {
2016-01-20 00:40:18 +00:00
// newRS will have the same template as in deployment spec, plus a unique label in some cases.
newRSTemplate := api . PodTemplateSpec {
2015-09-18 20:35:56 +00:00
ObjectMeta : deployment . Spec . Template . ObjectMeta ,
Spec : deployment . Spec . Template . Spec ,
}
2016-01-20 00:40:18 +00:00
newRSTemplate . ObjectMeta . Labels = labelsutil . CloneAndAddLabel (
2015-10-07 22:28:39 +00:00
deployment . Spec . Template . ObjectMeta . Labels ,
2016-02-05 22:45:05 +00:00
extensions . DefaultDeploymentUniqueLabelKey ,
2016-01-20 00:40:18 +00:00
podutil . GetPodTemplateSpecHash ( newRSTemplate ) )
return newRSTemplate
2015-09-18 20:35:56 +00:00
}
2016-01-20 00:40:18 +00:00
// SetFromReplicaSetTemplate sets the desired PodTemplateSpec from a replica set template to the given deployment.
func SetFromReplicaSetTemplate ( deployment * extensions . Deployment , template api . PodTemplateSpec ) * extensions . Deployment {
2016-01-15 02:04:05 +00:00
deployment . Spec . Template . ObjectMeta = template . ObjectMeta
deployment . Spec . Template . Spec = template . Spec
deployment . Spec . Template . ObjectMeta . Labels = labelsutil . CloneAndRemoveLabel (
deployment . Spec . Template . ObjectMeta . Labels ,
2016-02-05 22:45:05 +00:00
extensions . DefaultDeploymentUniqueLabelKey )
2016-01-15 02:04:05 +00:00
return deployment
}
2016-01-20 00:40:18 +00:00
// Returns the sum of Replicas of the given replica sets.
2016-04-27 04:35:14 +00:00
func GetReplicaCountForReplicaSets ( replicaSets [ ] * extensions . ReplicaSet ) int32 {
totalReplicaCount := int32 ( 0 )
2016-01-20 00:40:18 +00:00
for _ , rs := range replicaSets {
2016-02-25 00:09:20 +00:00
if rs != nil {
totalReplicaCount += rs . Spec . Replicas
}
2015-09-29 23:55:06 +00:00
}
return totalReplicaCount
}
2016-02-22 22:28:28 +00:00
// GetActualReplicaCountForReplicaSets returns the sum of actual replicas of the given replica sets.
2016-04-27 04:35:14 +00:00
func GetActualReplicaCountForReplicaSets ( replicaSets [ ] * extensions . ReplicaSet ) int32 {
totalReplicaCount := int32 ( 0 )
2016-02-22 22:28:28 +00:00
for _ , rs := range replicaSets {
2016-02-25 00:09:20 +00:00
if rs != nil {
totalReplicaCount += rs . Status . Replicas
}
2016-02-22 22:28:28 +00:00
}
return totalReplicaCount
}
2016-01-20 00:40:18 +00:00
// Returns the number of available pods corresponding to the given replica sets.
2016-04-27 04:35:14 +00:00
func GetAvailablePodsForReplicaSets ( c clientset . Interface , rss [ ] * extensions . ReplicaSet , minReadySeconds int32 ) ( int32 , error ) {
2016-02-19 23:30:40 +00:00
allPods , err := GetPodsForReplicaSets ( c , rss )
2015-09-29 23:55:06 +00:00
if err != nil {
return 0 , err
}
2015-11-11 23:22:57 +00:00
return getReadyPodsCount ( allPods , minReadySeconds ) , nil
}
2016-04-27 04:35:14 +00:00
func getReadyPodsCount ( pods [ ] api . Pod , minReadySeconds int32 ) int32 {
readyPodCount := int32 ( 0 )
2015-11-11 23:22:57 +00:00
for _ , pod := range pods {
2016-02-19 23:30:40 +00:00
if IsPodAvailable ( & pod , minReadySeconds ) {
readyPodCount ++
2015-09-29 23:55:06 +00:00
}
}
2015-11-11 23:22:57 +00:00
return readyPodCount
2015-09-29 23:55:06 +00:00
}
2016-04-27 04:35:14 +00:00
func IsPodAvailable ( pod * api . Pod , minReadySeconds int32 ) bool {
2016-03-05 00:32:32 +00:00
if ! controller . IsPodActive ( * pod ) {
return false
}
2016-02-19 23:30:40 +00:00
// Check if we've passed minReadySeconds since LastTransitionTime
// If so, this pod is ready
for _ , c := range pod . Status . Conditions {
// we only care about pod ready conditions
2016-02-19 23:53:34 +00:00
if c . Type == api . PodReady && c . Status == api . ConditionTrue {
2016-02-19 23:30:40 +00:00
// 2 cases that this ready condition is valid (passed minReadySeconds, i.e. the pod is ready):
// 1. minReadySeconds <= 0
// 2. LastTransitionTime (is set) + minReadySeconds (>0) < current time
minReadySecondsDuration := time . Duration ( minReadySeconds ) * time . Second
if minReadySeconds <= 0 || ! c . LastTransitionTime . IsZero ( ) && c . LastTransitionTime . Add ( minReadySecondsDuration ) . Before ( time . Now ( ) ) {
return true
}
}
}
return false
}
func GetPodsForReplicaSets ( c clientset . Interface , replicaSets [ ] * extensions . ReplicaSet ) ( [ ] api . Pod , error ) {
2016-02-24 18:59:51 +00:00
allPods := map [ string ] api . Pod { }
2016-01-20 00:40:18 +00:00
for _ , rs := range replicaSets {
2016-02-25 00:09:20 +00:00
if rs != nil {
selector , err := unversioned . LabelSelectorAsSelector ( rs . Spec . Selector )
if err != nil {
return nil , fmt . Errorf ( "invalid label selector: %v" , err )
}
options := api . ListOptions { LabelSelector : selector }
podList , err := c . Core ( ) . Pods ( rs . ObjectMeta . Namespace ) . List ( options )
if err != nil {
return nil , fmt . Errorf ( "error listing pods: %v" , err )
}
for _ , pod := range podList . Items {
allPods [ pod . Name ] = pod
}
2016-02-24 18:59:51 +00:00
}
}
requiredPods := [ ] api . Pod { }
for _ , pod := range allPods {
requiredPods = append ( requiredPods , pod )
2015-09-29 23:55:06 +00:00
}
2016-02-24 18:59:51 +00:00
return requiredPods , nil
2015-09-29 23:55:06 +00:00
}
2016-01-20 23:48:52 +00:00
2016-01-20 00:40:18 +00:00
// Revision returns the revision number of the input replica set
func Revision ( rs * extensions . ReplicaSet ) ( int64 , error ) {
v , ok := rs . Annotations [ RevisionAnnotation ]
2016-01-20 23:48:52 +00:00
if ! ok {
return 0 , nil
}
return strconv . ParseInt ( v , 10 , 64 )
}
2016-02-05 02:05:38 +00:00
func IsRollingUpdate ( deployment * extensions . Deployment ) bool {
return deployment . Spec . Strategy . Type == extensions . RollingUpdateDeploymentStrategyType
}
// NewRSNewReplicas calculates the number of replicas a deployment's new RS should have.
// When one of the followings is true, we're rolling out the deployment; otherwise, we're scaling it.
// 1) The new RS is saturated: newRS's replicas == deployment's replicas
// 2) Max number of pods allowed is reached: deployment's replicas + maxSurge == all RSs' replicas
2016-04-27 04:35:14 +00:00
func NewRSNewReplicas ( deployment * extensions . Deployment , allRSs [ ] * extensions . ReplicaSet , newRS * extensions . ReplicaSet ) ( int32 , error ) {
2016-02-05 02:05:38 +00:00
switch deployment . Spec . Strategy . Type {
case extensions . RollingUpdateDeploymentStrategyType :
// Check if we can scale up.
2016-04-27 04:35:14 +00:00
maxSurge , err := intstrutil . GetValueFromIntOrPercent ( & deployment . Spec . Strategy . RollingUpdate . MaxSurge , int ( deployment . Spec . Replicas ) , true )
2016-02-05 02:05:38 +00:00
if err != nil {
return 0 , err
}
// Find the total number of pods
currentPodCount := GetReplicaCountForReplicaSets ( allRSs )
2016-04-27 04:35:14 +00:00
maxTotalPods := deployment . Spec . Replicas + int32 ( maxSurge )
2016-02-05 02:05:38 +00:00
if currentPodCount >= maxTotalPods {
// Cannot scale up.
return newRS . Spec . Replicas , nil
}
// Scale up.
scaleUpCount := maxTotalPods - currentPodCount
// Do not exceed the number of desired replicas.
2016-04-27 04:35:14 +00:00
scaleUpCount = int32 ( integer . IntMin ( int ( scaleUpCount ) , int ( deployment . Spec . Replicas - newRS . Spec . Replicas ) ) )
2016-02-05 02:05:38 +00:00
return newRS . Spec . Replicas + scaleUpCount , nil
case extensions . RecreateDeploymentStrategyType :
return deployment . Spec . Replicas , nil
default :
return 0 , fmt . Errorf ( "deployment type %v isn't supported" , deployment . Spec . Strategy . Type )
}
}
2016-02-24 04:27:24 +00:00
// Polls for deployment to be updated so that deployment.Status.ObservedGeneration >= desiredGeneration.
// Returns error if polling timesout.
func WaitForObservedDeployment ( getDeploymentFunc func ( ) ( * extensions . Deployment , error ) , desiredGeneration int64 , interval , timeout time . Duration ) error {
// TODO: This should take clientset.Interface when all code is updated to use clientset. Keeping it this way allows the function to be used by callers who have client.Interface.
return wait . Poll ( interval , timeout , func ( ) ( bool , error ) {
deployment , err := getDeploymentFunc ( )
if err != nil {
return false , err
}
return deployment . Status . ObservedGeneration >= desiredGeneration , nil
} )
}
2016-03-04 10:29:55 +00:00
// ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one
// step. For example:
//
// 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1)
// 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1)
// 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
// 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1)
// 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
// 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1)
2016-04-27 04:35:14 +00:00
func ResolveFenceposts ( maxSurge , maxUnavailable * intstrutil . IntOrString , desired int32 ) ( int32 , int32 , error ) {
surge , err := intstrutil . GetValueFromIntOrPercent ( maxSurge , int ( desired ) , true )
2016-03-04 10:29:55 +00:00
if err != nil {
return 0 , 0 , err
}
2016-04-27 04:35:14 +00:00
unavailable , err := intstrutil . GetValueFromIntOrPercent ( maxUnavailable , int ( desired ) , false )
2016-03-04 10:29:55 +00:00
if err != nil {
return 0 , 0 , err
}
if surge == 0 && unavailable == 0 {
// Validation should never allow the user to explicitly use zero values for both maxSurge
// maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero.
// If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the
// theory that surge might not work due to quota.
unavailable = 1
}
2016-04-27 04:35:14 +00:00
return int32 ( surge ) , int32 ( unavailable ) , nil
2016-03-04 10:29:55 +00:00
}