2015-09-18 20:35:56 +00:00
/ *
2016-07-05 07:29:09 +00:00
Copyright 2016 The Kubernetes Authors .
2015-09-18 20:35:56 +00:00
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 .
* /
2016-07-05 07:29:09 +00:00
package util
2015-09-18 20:35:56 +00:00
import (
"fmt"
2016-07-05 07:29:09 +00:00
"sort"
2016-01-20 23:48:52 +00:00
"strconv"
2016-10-06 15:02:51 +00:00
"strings"
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-07-05 07:29:09 +00:00
"k8s.io/kubernetes/pkg/api/annotations"
2016-10-10 11:07:38 +00:00
"k8s.io/kubernetes/pkg/api/meta"
2016-11-18 20:50:17 +00:00
"k8s.io/kubernetes/pkg/api/v1"
internalextensions "k8s.io/kubernetes/pkg/apis/extensions"
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
2016-12-03 19:06:03 +00:00
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
2016-11-07 13:23:15 +00:00
"k8s.io/kubernetes/pkg/client/cache"
2016-11-18 20:50:17 +00:00
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
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-10-10 11:07:38 +00:00
"k8s.io/kubernetes/pkg/runtime"
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"
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-07-05 07:29:09 +00:00
// RevisionAnnotation is 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-10-06 15:02:51 +00:00
// RevisionHistoryAnnotation maintains the history of all old revisions that a replica set has served for a deployment.
RevisionHistoryAnnotation = "deployment.kubernetes.io/revision-history"
2016-01-28 16:35:14 +00:00
// DesiredReplicasAnnotation is the desired replicas for a deployment recorded as an annotation
// in its replica sets. Helps in separating scaling events from the rollout process and for
// determining if the new replica set for a deployment is really saturated.
DesiredReplicasAnnotation = "deployment.kubernetes.io/desired-replicas"
// MaxReplicasAnnotation is the maximum replicas a deployment can have at a given point, which
// is deployment.spec.replicas + maxSurge. Used by the underlying replica sets to estimate their
// proportions in case the deployment has surge replicas.
MaxReplicasAnnotation = "deployment.kubernetes.io/max-replicas"
2016-01-19 22:50:03 +00:00
2016-07-05 07:29:09 +00:00
// RollbackRevisionNotFound is not found rollback event reason
RollbackRevisionNotFound = "DeploymentRollbackRevisionNotFound"
// RollbackTemplateUnchanged is the template unchanged rollback event reason
2016-01-19 22:50:03 +00:00
RollbackTemplateUnchanged = "DeploymentRollbackTemplateUnchanged"
2016-07-05 07:29:09 +00:00
// RollbackDone is the done rollback event reason
RollbackDone = "DeploymentRollback"
2016-08-17 01:47:15 +00:00
// OverlapAnnotation marks deployments with overlapping selector with other deployments
2016-10-06 15:02:51 +00:00
// TODO: Delete this annotation when we gracefully handle overlapping selectors.
// See https://github.com/kubernetes/kubernetes/issues/2210
2016-08-17 01:47:15 +00:00
OverlapAnnotation = "deployment.kubernetes.io/error-selector-overlapping-with"
// SelectorUpdateAnnotation marks the last time deployment selector update
2016-10-06 15:02:51 +00:00
// TODO: Delete this annotation when we gracefully handle overlapping selectors.
// See https://github.com/kubernetes/kubernetes/issues/2210
2016-08-17 01:47:15 +00:00
SelectorUpdateAnnotation = "deployment.kubernetes.io/selector-updated-at"
2016-09-15 15:57:53 +00:00
// Reasons for deployment conditions
//
// Progressing:
//
// ReplicaSetUpdatedReason is added in a deployment when one of its replica sets is updated as part
// of the rollout process.
ReplicaSetUpdatedReason = "ReplicaSetUpdated"
// FailedRSCreateReason is added in a deployment when it cannot create a new replica set.
FailedRSCreateReason = "ReplicaSetCreateError"
// NewReplicaSetReason is added in a deployment when it creates a new replica set.
NewReplicaSetReason = "NewReplicaSetCreated"
// FoundNewRSReason is added in a deployment when it adopts an existing replica set.
FoundNewRSReason = "FoundNewReplicaSet"
// NewRSAvailableReason is added in a deployment when its newest replica set is made available
// ie. the number of new pods that have passed readiness checks and run for at least minReadySeconds
// is at least the minimum available pods that need to run for the deployment.
NewRSAvailableReason = "NewReplicaSetAvailable"
// TimedOutReason is added in a deployment when its newest replica set fails to show any progress
// within the given deadline (progressDeadlineSeconds).
TimedOutReason = "ProgressDeadlineExceeded"
// PausedDeployReason is added in a deployment when it is paused. Lack of progress shouldn't be
// estimated once a deployment is paused.
PausedDeployReason = "DeploymentPaused"
// ResumedDeployReason is added in a deployment when it is resumed. Useful for not failing accidentally
// deployments that paused amidst a rollout and are bounded by a deadline.
ResumedDeployReason = "DeploymentResumed"
//
// Available:
//
// MinimumReplicasAvailable is added in a deployment when it has its minimum replicas required available.
MinimumReplicasAvailable = "MinimumReplicasAvailable"
// MinimumReplicasUnavailable is added in a deployment when it doesn't have the minimum required replicas
// available.
MinimumReplicasUnavailable = "MinimumReplicasUnavailable"
2016-01-13 01:52:18 +00:00
)
2016-09-15 15:57:53 +00:00
// NewDeploymentCondition creates a new deployment condition.
2016-11-18 20:50:17 +00:00
func NewDeploymentCondition ( condType extensions . DeploymentConditionType , status v1 . ConditionStatus , reason , message string ) * extensions . DeploymentCondition {
2016-09-15 15:57:53 +00:00
return & extensions . DeploymentCondition {
Type : condType ,
Status : status ,
2016-12-03 18:57:26 +00:00
LastUpdateTime : metav1 . Now ( ) ,
LastTransitionTime : metav1 . Now ( ) ,
2016-09-15 15:57:53 +00:00
Reason : reason ,
Message : message ,
}
}
// GetDeploymentCondition returns the condition with the provided type.
func GetDeploymentCondition ( status extensions . DeploymentStatus , condType extensions . DeploymentConditionType ) * extensions . DeploymentCondition {
for i := range status . Conditions {
c := status . Conditions [ i ]
if c . Type == condType {
return & c
}
}
return nil
}
2016-11-18 20:58:22 +00:00
// TODO: remove the duplicate
// GetDeploymentConditionInternal returns the condition with the provided type.
func GetDeploymentConditionInternal ( status internalextensions . DeploymentStatus , condType internalextensions . DeploymentConditionType ) * internalextensions . DeploymentCondition {
for i := range status . Conditions {
c := status . Conditions [ i ]
if c . Type == condType {
return & c
}
}
return nil
}
2016-09-15 15:57:53 +00:00
// SetDeploymentCondition updates the deployment to include the provided condition. If the condition that
// we are about to add already exists and has the same status and reason then we are not going to update.
func SetDeploymentCondition ( status * extensions . DeploymentStatus , condition extensions . DeploymentCondition ) {
currentCond := GetDeploymentCondition ( * status , condition . Type )
if currentCond != nil && currentCond . Status == condition . Status && currentCond . Reason == condition . Reason {
return
}
// Do not update lastTransitionTime if the status of the condition doesn't change.
if currentCond != nil && currentCond . Status == condition . Status {
condition . LastTransitionTime = currentCond . LastTransitionTime
}
newConditions := filterOutCondition ( status . Conditions , condition . Type )
status . Conditions = append ( newConditions , condition )
}
// RemoveDeploymentCondition removes the deployment condition with the provided type.
func RemoveDeploymentCondition ( status * extensions . DeploymentStatus , condType extensions . DeploymentConditionType ) {
status . Conditions = filterOutCondition ( status . Conditions , condType )
}
// filterOutCondition returns a new slice of deployment conditions without conditions with the provided type.
func filterOutCondition ( conditions [ ] extensions . DeploymentCondition , condType extensions . DeploymentConditionType ) [ ] extensions . DeploymentCondition {
var newConditions [ ] extensions . DeploymentCondition
for _ , c := range conditions {
if c . Type == condType {
continue
}
newConditions = append ( newConditions , c )
}
return newConditions
}
// ReplicaSetToDeploymentCondition converts a replica set condition into a deployment condition.
// Useful for promoting replica set failure conditions into deployments.
func ReplicaSetToDeploymentCondition ( cond extensions . ReplicaSetCondition ) extensions . DeploymentCondition {
return extensions . DeploymentCondition {
Type : extensions . DeploymentConditionType ( cond . Type ) ,
Status : cond . Status ,
LastTransitionTime : cond . LastTransitionTime ,
LastUpdateTime : cond . LastTransitionTime ,
Reason : cond . Reason ,
Message : cond . Message ,
}
}
2016-10-06 15:02:51 +00:00
// SetDeploymentRevision updates the revision for a deployment.
func SetDeploymentRevision ( deployment * extensions . Deployment , revision string ) bool {
updated := false
if deployment . Annotations == nil {
deployment . Annotations = make ( map [ string ] string )
}
if deployment . Annotations [ RevisionAnnotation ] != revision {
deployment . Annotations [ RevisionAnnotation ] = revision
updated = true
}
return updated
}
2016-07-05 07:29:09 +00:00
// MaxRevision finds the highest revision in the replica sets
func MaxRevision ( allRSs [ ] * extensions . ReplicaSet ) int64 {
max := int64 ( 0 )
for _ , rs := range allRSs {
if v , err := Revision ( rs ) ; err != nil {
// Skip the replica sets when it failed to parse their revision information
glog . V ( 4 ) . Infof ( "Error: %v. Couldn't parse revision for replica set %#v, deployment controller will skip it when reconciling revisions." , err , rs )
} else if v > max {
max = v
}
}
return max
}
// LastRevision finds the second max revision number in all replica sets (the last revision)
func LastRevision ( allRSs [ ] * extensions . ReplicaSet ) int64 {
max , secMax := int64 ( 0 ) , int64 ( 0 )
for _ , rs := range allRSs {
if v , err := Revision ( rs ) ; err != nil {
// Skip the replica sets when it failed to parse their revision information
glog . V ( 4 ) . Infof ( "Error: %v. Couldn't parse revision for replica set %#v, deployment controller will skip it when reconciling revisions." , err , rs )
} else if v >= max {
secMax = max
max = v
} else if v > secMax {
secMax = v
}
}
return secMax
}
2016-10-10 11:07:38 +00:00
// Revision returns the revision number of the input object.
func Revision ( obj runtime . Object ) ( int64 , error ) {
acc , err := meta . Accessor ( obj )
if err != nil {
return 0 , err
}
v , ok := acc . GetAnnotations ( ) [ RevisionAnnotation ]
2016-10-06 15:02:51 +00:00
if ! ok {
return 0 , nil
}
return strconv . ParseInt ( v , 10 , 64 )
}
2016-07-05 07:29:09 +00:00
// SetNewReplicaSetAnnotations sets new replica set's annotations appropriately by updating its revision and
// copying required deployment annotations to it; it returns true if replica set's annotation is changed.
func SetNewReplicaSetAnnotations ( deployment * extensions . Deployment , newRS * extensions . ReplicaSet , newRevision string , exists bool ) bool {
// First, copy deployment's annotations (except for apply and revision annotations)
annotationChanged := copyDeploymentAnnotationsToReplicaSet ( deployment , newRS )
// Then, update replica set's revision annotation
if newRS . Annotations == nil {
newRS . Annotations = make ( map [ string ] string )
}
2016-10-06 15:02:51 +00:00
oldRevision , ok := newRS . Annotations [ RevisionAnnotation ]
2016-07-05 07:29:09 +00:00
// The newRS's revision should be the greatest among all RSes. Usually, its revision number is newRevision (the max revision number
// of all old RSes + 1). However, it's possible that some of the old RSes are deleted after the newRS revision being updated, and
// newRevision becomes smaller than newRS's revision. We should only update newRS revision when it's smaller than newRevision.
2016-10-06 15:02:51 +00:00
if oldRevision < newRevision {
2016-07-05 07:29:09 +00:00
newRS . Annotations [ RevisionAnnotation ] = newRevision
annotationChanged = true
glog . V ( 4 ) . Infof ( "Updating replica set %q revision to %s" , newRS . Name , newRevision )
}
2016-10-06 15:02:51 +00:00
// If a revision annotation already existed and this replica set was updated with a new revision
// then that means we are rolling back to this replica set. We need to preserve the old revisions
// for historical information.
if ok && annotationChanged {
revisionHistoryAnnotation := newRS . Annotations [ RevisionHistoryAnnotation ]
oldRevisions := strings . Split ( revisionHistoryAnnotation , "," )
if len ( oldRevisions [ 0 ] ) == 0 {
newRS . Annotations [ RevisionHistoryAnnotation ] = oldRevision
} else {
oldRevisions = append ( oldRevisions , oldRevision )
newRS . Annotations [ RevisionHistoryAnnotation ] = strings . Join ( oldRevisions , "," )
}
}
// If the new replica set is about to be created, we need to add replica annotations to it.
2016-11-18 20:50:17 +00:00
if ! exists && SetReplicasAnnotations ( newRS , * ( deployment . Spec . Replicas ) , * ( deployment . Spec . Replicas ) + MaxSurge ( * deployment ) ) {
2016-07-05 07:29:09 +00:00
annotationChanged = true
}
return annotationChanged
}
var annotationsToSkip = map [ string ] bool {
annotations . LastAppliedConfigAnnotation : true ,
RevisionAnnotation : true ,
2016-10-06 15:02:51 +00:00
RevisionHistoryAnnotation : true ,
2016-07-05 07:29:09 +00:00
DesiredReplicasAnnotation : true ,
MaxReplicasAnnotation : true ,
2016-08-29 15:51:05 +00:00
OverlapAnnotation : true ,
SelectorUpdateAnnotation : true ,
2016-07-05 07:29:09 +00:00
}
// skipCopyAnnotation returns true if we should skip copying the annotation with the given annotation key
// TODO: How to decide which annotations should / should not be copied?
// See https://github.com/kubernetes/kubernetes/pull/20035#issuecomment-179558615
func skipCopyAnnotation ( key string ) bool {
return annotationsToSkip [ key ]
}
// copyDeploymentAnnotationsToReplicaSet copies deployment's annotations to replica set's annotations,
// and returns true if replica set's annotation is changed.
// Note that apply and revision annotations are not copied.
func copyDeploymentAnnotationsToReplicaSet ( deployment * extensions . Deployment , rs * extensions . ReplicaSet ) bool {
rsAnnotationsChanged := false
if rs . Annotations == nil {
rs . Annotations = make ( map [ string ] string )
}
for k , v := range deployment . Annotations {
// newRS revision is updated automatically in getNewReplicaSet, and the deployment's revision number is then updated
// by copying its newRS revision number. We should not copy deployment's revision to its newRS, since the update of
// deployment revision number may fail (revision becomes stale) and the revision number in newRS is more reliable.
if skipCopyAnnotation ( k ) || rs . Annotations [ k ] == v {
continue
}
rs . Annotations [ k ] = v
rsAnnotationsChanged = true
}
return rsAnnotationsChanged
}
// SetDeploymentAnnotationsTo sets deployment's annotations as given RS's annotations.
// This action should be done if and only if the deployment is rolling back to this rs.
// Note that apply and revision annotations are not changed.
func SetDeploymentAnnotationsTo ( deployment * extensions . Deployment , rollbackToRS * extensions . ReplicaSet ) {
deployment . Annotations = getSkippedAnnotations ( deployment . Annotations )
for k , v := range rollbackToRS . Annotations {
if ! skipCopyAnnotation ( k ) {
deployment . Annotations [ k ] = v
}
}
}
func getSkippedAnnotations ( annotations map [ string ] string ) map [ string ] string {
skippedAnnotations := make ( map [ string ] string )
for k , v := range annotations {
if skipCopyAnnotation ( k ) {
skippedAnnotations [ k ] = v
}
}
return skippedAnnotations
}
// FindActiveOrLatest returns the only active or the latest replica set in case there is at most one active
// replica set. If there are more active replica sets, then we should proportionally scale them.
func FindActiveOrLatest ( newRS * extensions . ReplicaSet , oldRSs [ ] * extensions . ReplicaSet ) * extensions . ReplicaSet {
if newRS == nil && len ( oldRSs ) == 0 {
return nil
}
sort . Sort ( sort . Reverse ( controller . ReplicaSetsByCreationTimestamp ( oldRSs ) ) )
allRSs := controller . FilterActiveReplicaSets ( append ( oldRSs , newRS ) )
switch len ( allRSs ) {
case 0 :
// If there is no active replica set then we should return the newest.
if newRS != nil {
return newRS
}
return oldRSs [ 0 ]
case 1 :
return allRSs [ 0 ]
default :
return nil
}
}
// GetDesiredReplicasAnnotation returns the number of desired replicas
func GetDesiredReplicasAnnotation ( rs * extensions . ReplicaSet ) ( int32 , bool ) {
return getIntFromAnnotation ( rs , DesiredReplicasAnnotation )
}
func getMaxReplicasAnnotation ( rs * extensions . ReplicaSet ) ( int32 , bool ) {
return getIntFromAnnotation ( rs , MaxReplicasAnnotation )
}
func getIntFromAnnotation ( rs * extensions . ReplicaSet , annotationKey string ) ( int32 , bool ) {
annotationValue , ok := rs . Annotations [ annotationKey ]
if ! ok {
return int32 ( 0 ) , false
}
intValue , err := strconv . Atoi ( annotationValue )
if err != nil {
glog . Warningf ( "Cannot convert the value %q with annotation key %q for the replica set %q" ,
annotationValue , annotationKey , rs . Name )
return int32 ( 0 ) , false
}
return int32 ( intValue ) , true
}
// SetReplicasAnnotations sets the desiredReplicas and maxReplicas into the annotations
func SetReplicasAnnotations ( rs * extensions . ReplicaSet , desiredReplicas , maxReplicas int32 ) bool {
updated := false
if rs . Annotations == nil {
rs . Annotations = make ( map [ string ] string )
}
desiredString := fmt . Sprintf ( "%d" , desiredReplicas )
if hasString := rs . Annotations [ DesiredReplicasAnnotation ] ; hasString != desiredString {
rs . Annotations [ DesiredReplicasAnnotation ] = desiredString
updated = true
}
maxString := fmt . Sprintf ( "%d" , maxReplicas )
if hasString := rs . Annotations [ MaxReplicasAnnotation ] ; hasString != maxString {
rs . Annotations [ MaxReplicasAnnotation ] = maxString
updated = true
}
return updated
}
// MaxUnavailable returns the maximum unavailable pods a rolling deployment can take.
func MaxUnavailable ( deployment extensions . Deployment ) int32 {
if ! IsRollingUpdate ( & deployment ) {
return int32 ( 0 )
}
// Error caught by validation
2016-11-18 20:50:17 +00:00
_ , maxUnavailable , _ := ResolveFenceposts ( deployment . Spec . Strategy . RollingUpdate . MaxSurge , deployment . Spec . Strategy . RollingUpdate . MaxUnavailable , * ( deployment . Spec . Replicas ) )
2016-07-05 07:29:09 +00:00
return maxUnavailable
}
2016-11-29 10:20:09 +00:00
// MinAvailable returns the minimum available pods of a given deployment
2016-08-01 22:26:17 +00:00
func MinAvailable ( deployment * extensions . Deployment ) int32 {
if ! IsRollingUpdate ( deployment ) {
return int32 ( 0 )
}
2016-11-18 20:50:17 +00:00
return * ( deployment . Spec . Replicas ) - MaxUnavailable ( * deployment )
2016-08-01 22:26:17 +00:00
}
2016-07-05 07:29:09 +00:00
// MaxSurge returns the maximum surge pods a rolling deployment can take.
func MaxSurge ( deployment extensions . Deployment ) int32 {
if ! IsRollingUpdate ( & deployment ) {
return int32 ( 0 )
}
// Error caught by validation
2016-11-18 20:50:17 +00:00
maxSurge , _ , _ := ResolveFenceposts ( deployment . Spec . Strategy . RollingUpdate . MaxSurge , deployment . Spec . Strategy . RollingUpdate . MaxUnavailable , * ( deployment . Spec . Replicas ) )
2016-07-05 07:29:09 +00:00
return maxSurge
}
// GetProportion will estimate the proportion for the provided replica set using 1. the current size
// of the parent deployment, 2. the replica count that needs be added on the replica sets of the
// deployment, and 3. the total replicas added in the replica sets of the deployment so far.
func GetProportion ( rs * extensions . ReplicaSet , d extensions . Deployment , deploymentReplicasToAdd , deploymentReplicasAdded int32 ) int32 {
2016-11-18 20:50:17 +00:00
if rs == nil || * ( rs . Spec . Replicas ) == 0 || deploymentReplicasToAdd == 0 || deploymentReplicasToAdd == deploymentReplicasAdded {
2016-07-05 07:29:09 +00:00
return int32 ( 0 )
}
rsFraction := getReplicaSetFraction ( * rs , d )
allowed := deploymentReplicasToAdd - deploymentReplicasAdded
if deploymentReplicasToAdd > 0 {
// Use the minimum between the replica set fraction and the maximum allowed replicas
// when scaling up. This way we ensure we will not scale up more than the allowed
// replicas we can add.
return integer . Int32Min ( rsFraction , allowed )
}
// Use the maximum between the replica set fraction and the maximum allowed replicas
// when scaling down. This way we ensure we will not scale down more than the allowed
// replicas we can remove.
return integer . Int32Max ( rsFraction , allowed )
}
// getReplicaSetFraction estimates the fraction of replicas a replica set can have in
// 1. a scaling event during a rollout or 2. when scaling a paused deployment.
func getReplicaSetFraction ( rs extensions . ReplicaSet , d extensions . Deployment ) int32 {
// If we are scaling down to zero then the fraction of this replica set is its whole size (negative)
2016-11-18 20:50:17 +00:00
if * ( d . Spec . Replicas ) == int32 ( 0 ) {
return - * ( rs . Spec . Replicas )
2016-07-05 07:29:09 +00:00
}
2016-11-18 20:50:17 +00:00
deploymentReplicas := * ( d . Spec . Replicas ) + MaxSurge ( d )
2016-07-05 07:29:09 +00:00
annotatedReplicas , ok := getMaxReplicasAnnotation ( & rs )
if ! ok {
// If we cannot find the annotation then fallback to the current deployment size. Note that this
// will not be an accurate proportion estimation in case other replica sets have different values
// which means that the deployment was scaled at some point but we at least will stay in limits
// due to the min-max comparisons in getProportion.
annotatedReplicas = d . Status . Replicas
}
// We should never proportionally scale up from zero which means rs.spec.replicas and annotatedReplicas
// will never be zero here.
2016-11-18 20:50:17 +00:00
newRSsize := ( float64 ( * ( rs . Spec . Replicas ) * deploymentReplicas ) ) / float64 ( annotatedReplicas )
return integer . RoundToInt32 ( newRSsize ) - * ( rs . Spec . Replicas )
2016-07-05 07:29:09 +00:00
}
2016-06-01 21:14:40 +00:00
// GetAllReplicaSets returns the old and new replica sets targeted by the given Deployment. It gets 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.
// The third returned value is the new replica set, and it may be nil if it doesn't exist yet.
func GetAllReplicaSets ( deployment * extensions . Deployment , c clientset . Interface ) ( [ ] * extensions . ReplicaSet , [ ] * extensions . ReplicaSet , * extensions . ReplicaSet , error ) {
2016-06-01 22:00:29 +00:00
rsList , err := listReplicaSets ( deployment , c )
if err != nil {
return nil , nil , nil , err
}
podList , err := listPods ( deployment , c )
if err != nil {
return nil , nil , nil , err
}
oldRSes , allOldRSes , err := FindOldReplicaSets ( deployment , rsList , podList )
if err != nil {
return nil , nil , nil , err
}
newRS , err := FindNewReplicaSet ( deployment , rsList )
if err != nil {
return nil , nil , nil , err
}
return oldRSes , allOldRSes , newRS , nil
2016-06-01 21:14:40 +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-06-01 22:00:29 +00:00
rsList , err := listReplicaSets ( deployment , c )
if err != nil {
return nil , nil , err
}
podList , err := listPods ( deployment , c )
if err != nil {
return nil , nil , err
}
return FindOldReplicaSets ( deployment , rsList , podList )
2016-03-14 19:07:56 +00:00
}
// 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 ) {
2016-06-01 22:00:29 +00:00
rsList , err := listReplicaSets ( deployment , c )
if err != nil {
return nil , err
}
return FindNewReplicaSet ( deployment , rsList )
2016-06-01 21:14:40 +00:00
}
2016-06-01 22:00:29 +00:00
// listReplicaSets lists all RSes the given deployment targets with the given client interface.
2016-10-04 17:23:27 +00:00
func listReplicaSets ( deployment * extensions . Deployment , c clientset . Interface ) ( [ ] * extensions . ReplicaSet , error ) {
2016-06-01 22:00:29 +00:00
return ListReplicaSets ( deployment ,
2016-11-18 20:50:17 +00:00
func ( namespace string , options v1 . ListOptions ) ( [ ] * extensions . ReplicaSet , error ) {
2016-01-20 00:40:18 +00:00
rsList , err := c . Extensions ( ) . ReplicaSets ( namespace ) . List ( options )
2016-10-04 17:23:27 +00:00
if err != nil {
return nil , err
}
ret := [ ] * extensions . ReplicaSet { }
for i := range rsList . Items {
ret = append ( ret , & rsList . Items [ i ] )
}
return ret , err
2015-11-18 23:12:11 +00:00
} )
2016-06-01 22:00:29 +00:00
}
2016-06-01 21:14:40 +00:00
2016-06-01 22:00:29 +00:00
// listReplicaSets lists all Pods the given deployment targets with the given client interface.
2016-11-18 20:50:17 +00:00
func listPods ( deployment * extensions . Deployment , c clientset . Interface ) ( * v1 . PodList , error ) {
2016-06-01 22:00:29 +00:00
return ListPods ( deployment ,
2016-11-18 20:50:17 +00:00
func ( namespace string , options v1 . ListOptions ) ( * v1 . PodList , error ) {
2016-06-01 22:00:29 +00:00
return c . Core ( ) . Pods ( namespace ) . List ( options )
} )
2015-11-18 23:12:11 +00:00
}
2016-02-19 18:25:34 +00:00
// TODO: switch this to full namespacers
2016-11-18 20:50:17 +00:00
type rsListFunc func ( string , v1 . ListOptions ) ( [ ] * extensions . ReplicaSet , error )
type podListFunc func ( string , v1 . ListOptions ) ( * v1 . PodList , error )
2016-02-11 01:49:11 +00:00
2016-03-14 19:07:56 +00:00
// ListReplicaSets returns a slice of RSes the given deployment targets.
2016-10-04 17:23:27 +00:00
func ListReplicaSets ( deployment * extensions . Deployment , getRSList rsListFunc ) ( [ ] * extensions . ReplicaSet , error ) {
2016-03-14 19:07:56 +00:00
// 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
2016-12-03 18:57:26 +00:00
selector , err := metav1 . LabelSelectorAsSelector ( deployment . Spec . Selector )
2016-03-14 19:07:56 +00:00
if err != nil {
return nil , err
}
2016-11-18 20:50:17 +00:00
options := v1 . ListOptions { LabelSelector : selector . String ( ) }
2016-03-14 19:07:56 +00:00
return getRSList ( namespace , options )
}
// ListPods returns a list of pods the given deployment targets.
2016-11-18 20:50:17 +00:00
func ListPods ( deployment * extensions . Deployment , getPodList podListFunc ) ( * v1 . PodList , error ) {
2016-03-14 19:07:56 +00:00
namespace := deployment . Namespace
2016-12-03 18:57:26 +00:00
selector , err := metav1 . LabelSelectorAsSelector ( deployment . Spec . Selector )
2016-03-14 19:07:56 +00:00
if err != nil {
return nil , err
}
2016-11-18 20:50:17 +00:00
options := v1 . ListOptions { LabelSelector : selector . String ( ) }
2016-03-14 19:07:56 +00:00
return getPodList ( namespace , options )
}
2016-06-03 17:53:14 +00:00
// equalIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
// We ignore pod-template-hash because the hash result would be different upon podTemplateSpec API changes
// (e.g. the addition of a new field will cause the hash code to change)
// Note that we assume input podTemplateSpecs contain non-empty labels
2016-11-18 20:50:17 +00:00
func equalIgnoreHash ( template1 , template2 v1 . PodTemplateSpec ) ( bool , error ) {
2016-08-03 18:19:12 +00:00
// First, compare template.Labels (ignoring hash)
labels1 , labels2 := template1 . Labels , template2 . Labels
2016-06-03 17:53:14 +00:00
// The podTemplateSpec must have a non-empty label so that label selectors can find them.
// This is checked by validation (of resources contain a podTemplateSpec).
2016-08-03 18:19:12 +00:00
if len ( labels1 ) == 0 || len ( labels2 ) == 0 {
2016-06-03 17:53:14 +00:00
return false , fmt . Errorf ( "Unexpected empty labels found in given template" )
}
2016-08-03 18:19:12 +00:00
if len ( labels1 ) > len ( labels2 ) {
labels1 , labels2 = labels2 , labels1
}
// We make sure len(labels2) >= len(labels1)
for k , v := range labels2 {
if labels1 [ k ] != v && k != extensions . DefaultDeploymentUniqueLabelKey {
return false , nil
}
}
// Then, compare the templates without comparing their labels
template1 . Labels , template2 . Labels = nil , nil
2016-11-19 23:32:10 +00:00
result := api . Semantic . DeepEqual ( template1 , template2 )
2016-06-03 17:53:14 +00:00
return result , nil
}
2016-03-14 19:07:56 +00:00
// FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template).
2016-10-04 17:23:27 +00:00
func FindNewReplicaSet ( deployment * extensions . Deployment , rsList [ ] * extensions . ReplicaSet ) ( * extensions . ReplicaSet , error ) {
2016-03-14 19:07:56 +00:00
newRSTemplate := GetNewReplicaSetTemplate ( deployment )
for i := range rsList {
2016-06-03 17:53:14 +00:00
equal , err := equalIgnoreHash ( rsList [ i ] . Spec . Template , newRSTemplate )
if err != nil {
return nil , err
}
if equal {
2016-03-14 19:07:56 +00:00
// This is the new ReplicaSet.
2016-10-04 17:23:27 +00:00
return rsList [ i ] , nil
2016-03-14 19:07:56 +00:00
}
}
// 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-11-18 20:50:17 +00:00
func FindOldReplicaSets ( deployment * extensions . Deployment , rsList [ ] * extensions . ReplicaSet , podList * v1 . 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-10-04 17:23:27 +00:00
oldRSs := map [ string ] * extensions . ReplicaSet { }
allOldRSs := map [ string ] * extensions . ReplicaSet { }
2016-01-20 00:40:18 +00:00
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-12-03 18:57:26 +00:00
rsLabelsSelector , err := metav1 . LabelSelectorAsSelector ( rs . Spec . Selector )
2016-02-06 02:43:02 +00:00
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-06-03 17:53:14 +00:00
equal , err := equalIgnoreHash ( rs . Spec . Template , newRSTemplate )
if err != nil {
return nil , nil , err
}
if equal {
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 ]
2016-10-04 17:23:27 +00:00
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 ]
2016-10-04 17:23:27 +00:00
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-07-05 07:29:09 +00:00
// WaitForReplicaSetUpdated polls the replica set until it is updated.
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-07-05 07:29:09 +00:00
// WaitForPodsHashPopulated polls the replica set until updated and fully labeled.
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 &&
2016-11-18 20:50:17 +00:00
rs . Status . FullyLabeledReplicas == * ( rs . Spec . Replicas ) , nil
2016-03-11 18:34:13 +00:00
} )
}
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-11-07 13:23:15 +00:00
func LabelPodsWithHash ( podList * v1 . PodList , c clientset . Interface , podLister * cache . StoreToPodLister , namespace , name , hash string ) error {
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-11-07 13:23:15 +00:00
_ , err := UpdatePodWithRetries ( c . Core ( ) . Pods ( namespace ) , podLister , pod . Namespace , pod . Name ,
2016-11-18 20:50:17 +00:00
func ( podToUpdate * v1 . Pod ) error {
2016-03-03 23:48:56 +00:00
// 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-11-07 13:23:15 +00:00
} )
if err != nil {
return fmt . Errorf ( "error in adding template hash label %s to pod %q: %v" , hash , pod . Name , err )
2016-02-11 01:49:11 +00:00
}
2016-11-07 13:23:15 +00:00
glog . V ( 4 ) . Infof ( "Labeled pod %s/%s of ReplicaSet %s/%s with hash %s." , pod . Namespace , pod . Name , namespace , name , hash )
2016-02-11 01:49:11 +00:00
}
2016-03-01 02:28:32 +00:00
}
2016-11-07 13:23:15 +00:00
return nil
2016-02-18 19:45:24 +00:00
}
2016-07-05 07:29:09 +00:00
// GetNewReplicaSetTemplate returns the desired PodTemplateSpec for the new ReplicaSet corresponding to the given ReplicaSet.
2016-11-18 20:50:17 +00:00
func GetNewReplicaSetTemplate ( deployment * extensions . Deployment ) v1 . 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.
2016-11-18 20:50:17 +00:00
newRSTemplate := v1 . 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-11-07 13:23:15 +00:00
GetPodTemplateSpecHash ( newRSTemplate ) )
2016-01-20 00:40:18 +00:00
return newRSTemplate
2015-09-18 20:35:56 +00:00
}
2016-11-18 20:50:17 +00:00
// TODO: remove the duplicate
2016-11-18 20:58:22 +00:00
// GetNewReplicaSetTemplateInternal returns the desired PodTemplateSpec for the new ReplicaSet corresponding to the given ReplicaSet.
func GetNewReplicaSetTemplateInternal ( deployment * internalextensions . Deployment ) api . PodTemplateSpec {
2016-11-18 20:50:17 +00:00
// newRS will have the same template as in deployment spec, plus a unique label in some cases.
newRSTemplate := api . PodTemplateSpec {
ObjectMeta : deployment . Spec . Template . ObjectMeta ,
Spec : deployment . Spec . Template . Spec ,
}
newRSTemplate . ObjectMeta . Labels = labelsutil . CloneAndAddLabel (
deployment . Spec . Template . ObjectMeta . Labels ,
internalextensions . DefaultDeploymentUniqueLabelKey ,
2016-11-07 13:23:15 +00:00
GetInternalPodTemplateSpecHash ( newRSTemplate ) )
2016-11-18 20:50:17 +00:00
return newRSTemplate
}
2016-01-20 00:40:18 +00:00
// SetFromReplicaSetTemplate sets the desired PodTemplateSpec from a replica set template to the given deployment.
2016-11-18 20:50:17 +00:00
func SetFromReplicaSetTemplate ( deployment * extensions . Deployment , template v1 . 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-07-05 07:29:09 +00:00
// GetReplicaCountForReplicaSets returns the sum of Replicas of the given replica sets.
2016-04-27 04:35:14 +00:00
func GetReplicaCountForReplicaSets ( replicaSets [ ] * extensions . ReplicaSet ) int32 {
2016-10-11 14:37:39 +00:00
totalReplicas := 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 {
2016-11-18 20:50:17 +00:00
totalReplicas += * ( rs . Spec . Replicas )
2016-02-25 00:09:20 +00:00
}
2015-09-29 23:55:06 +00:00
}
2016-10-11 14:37:39 +00:00
return totalReplicas
2015-09-29 23:55:06 +00:00
}
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 {
2016-10-11 14:37:39 +00:00
totalActualReplicas := 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 {
2016-10-11 14:37:39 +00:00
totalActualReplicas += rs . Status . Replicas
2016-02-25 00:09:20 +00:00
}
2016-02-22 22:28:28 +00:00
}
2016-10-11 14:37:39 +00:00
return totalActualReplicas
2016-02-22 22:28:28 +00:00
}
2016-10-11 14:37:39 +00:00
// GetAvailableReplicaCountForReplicaSets returns the number of available pods corresponding to the given replica sets.
func GetAvailableReplicaCountForReplicaSets ( replicaSets [ ] * extensions . ReplicaSet ) int32 {
totalAvailableReplicas := int32 ( 0 )
for _ , rs := range replicaSets {
if rs != nil {
totalAvailableReplicas += rs . Status . AvailableReplicas
2015-09-29 23:55:06 +00:00
}
}
2016-10-11 14:37:39 +00:00
return totalAvailableReplicas
2015-09-29 23:55:06 +00:00
}
2016-07-05 07:29:09 +00:00
// IsPodAvailable return true if the pod is available.
2016-09-13 16:59:38 +00:00
// TODO: Remove this once we start using replica set status for calculating available pods
// for a deployment.
2016-11-18 20:50:17 +00:00
func IsPodAvailable ( pod * v1 . Pod , minReadySeconds int32 , now time . Time ) bool {
2016-08-17 20:05:37 +00:00
if ! controller . IsPodActive ( pod ) {
2016-03-05 00:32:32 +00:00
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-11-18 20:50:17 +00:00
if c . Type == v1 . PodReady && c . Status == v1 . ConditionTrue {
2016-07-19 21:08:28 +00:00
glog . V ( 4 ) . Infof ( "Comparing pod %s/%s ready condition last transition time %s + minReadySeconds %d with now %s." , pod . Namespace , pod . Name , c . LastTransitionTime . String ( ) , minReadySeconds , now . String ( ) )
2016-05-03 00:25:31 +00:00
// 2 cases that this ready condition is valid (passed minReadySeconds, i.e. the pod is available):
// 1. minReadySeconds == 0, or
2016-02-19 23:30:40 +00:00
// 2. LastTransitionTime (is set) + minReadySeconds (>0) < current time
minReadySecondsDuration := time . Duration ( minReadySeconds ) * time . Second
2016-06-27 12:30:14 +00:00
if minReadySeconds == 0 || ! c . LastTransitionTime . IsZero ( ) && c . LastTransitionTime . Add ( minReadySecondsDuration ) . Before ( now ) {
2016-02-19 23:30:40 +00:00
return true
}
}
}
return false
}
2016-07-05 07:29:09 +00:00
// IsRollingUpdate returns true if the strategy type is a rolling update.
2016-02-05 02:05:38 +00:00
func IsRollingUpdate ( deployment * extensions . Deployment ) bool {
return deployment . Spec . Strategy . Type == extensions . RollingUpdateDeploymentStrategyType
}
2016-09-15 15:57:53 +00:00
// DeploymentComplete considers a deployment to be complete once its desired replicas equals its
// updatedReplicas and it doesn't violate minimum availability.
func DeploymentComplete ( deployment * extensions . Deployment , newStatus * extensions . DeploymentStatus ) bool {
2016-11-18 20:50:17 +00:00
return newStatus . UpdatedReplicas == * ( deployment . Spec . Replicas ) &&
2016-11-10 16:59:30 +00:00
newStatus . AvailableReplicas >= * ( deployment . Spec . Replicas ) - MaxUnavailable ( * deployment ) &&
newStatus . ObservedGeneration >= deployment . Generation
2016-09-15 15:57:53 +00:00
}
// DeploymentProgressing reports progress for a deployment. Progress is estimated by comparing the
2016-11-10 16:59:30 +00:00
// current with the new status of the deployment that the controller is observing. More specifically,
// when new pods are scaled up or become available, or old pods are scaled down, then we consider the
// deployment is progressing.
2016-09-15 15:57:53 +00:00
func DeploymentProgressing ( deployment * extensions . Deployment , newStatus * extensions . DeploymentStatus ) bool {
oldStatus := deployment . Status
// Old replicas that need to be scaled down
oldStatusOldReplicas := oldStatus . Replicas - oldStatus . UpdatedReplicas
newStatusOldReplicas := newStatus . Replicas - newStatus . UpdatedReplicas
2016-11-10 16:59:30 +00:00
return ( newStatus . UpdatedReplicas > oldStatus . UpdatedReplicas ) ||
( newStatusOldReplicas < oldStatusOldReplicas ) ||
newStatus . AvailableReplicas > deployment . Status . AvailableReplicas
2016-09-15 15:57:53 +00:00
}
// used for unit testing
var nowFn = func ( ) time . Time { return time . Now ( ) }
// DeploymentTimedOut considers a deployment to have timed out once its condition that reports progress
// is older than progressDeadlineSeconds or a Progressing condition with a TimedOutReason reason already
// exists.
func DeploymentTimedOut ( deployment * extensions . Deployment , newStatus * extensions . DeploymentStatus ) bool {
if deployment . Spec . ProgressDeadlineSeconds == nil {
return false
}
// Look for the Progressing condition. If it doesn't exist, we have no base to estimate progress.
// If it's already set with a TimedOutReason reason, we have already timed out, no need to check
// again.
condition := GetDeploymentCondition ( * newStatus , extensions . DeploymentProgressing )
if condition == nil {
return false
}
if condition . Reason == TimedOutReason {
return true
}
// Look at the difference in seconds between now and the last time we reported any
// progress or tried to create a replica set, or resumed a paused deployment and
// compare against progressDeadlineSeconds.
2016-11-08 10:41:53 +00:00
from := condition . LastUpdateTime
2016-09-15 15:57:53 +00:00
delta := time . Duration ( * deployment . Spec . ProgressDeadlineSeconds ) * time . Second
return from . Add ( delta ) . Before ( nowFn ( ) )
}
2016-02-05 02:05:38 +00:00
// 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-11-18 20:50:17 +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-11-18 20:50:17 +00:00
maxTotalPods := * ( deployment . Spec . Replicas ) + int32 ( maxSurge )
2016-02-05 02:05:38 +00:00
if currentPodCount >= maxTotalPods {
// Cannot scale up.
2016-11-18 20:50:17 +00:00
return * ( newRS . Spec . Replicas ) , nil
2016-02-05 02:05:38 +00:00
}
// Scale up.
scaleUpCount := maxTotalPods - currentPodCount
// Do not exceed the number of desired replicas.
2016-11-18 20:50:17 +00:00
scaleUpCount = int32 ( integer . IntMin ( int ( scaleUpCount ) , int ( * ( deployment . Spec . Replicas ) - * ( newRS . Spec . Replicas ) ) ) )
return * ( newRS . Spec . Replicas ) + scaleUpCount , nil
2016-02-05 02:05:38 +00:00
case extensions . RecreateDeploymentStrategyType :
2016-11-18 20:50:17 +00:00
return * ( deployment . Spec . Replicas ) , nil
2016-02-05 02:05:38 +00:00
default :
return 0 , fmt . Errorf ( "deployment type %v isn't supported" , deployment . Spec . Strategy . Type )
}
}
2016-02-24 04:27:24 +00:00
2016-01-28 16:35:14 +00:00
// IsSaturated checks if the new replica set is saturated by comparing its size with its deployment size.
// Both the deployment and the replica set have to believe this replica set can own all of the desired
// replicas in the deployment and the annotation helps in achieving that.
func IsSaturated ( deployment * extensions . Deployment , rs * extensions . ReplicaSet ) bool {
if rs == nil {
return false
}
desiredString := rs . Annotations [ DesiredReplicasAnnotation ]
desired , err := strconv . Atoi ( desiredString )
if err != nil {
return false
}
2016-11-18 20:50:17 +00:00
return * ( rs . Spec . Replicas ) == * ( deployment . Spec . Replicas ) && int32 ( desired ) == * ( deployment . Spec . Replicas )
2016-01-28 16:35:14 +00:00
}
2016-07-05 07:29:09 +00:00
// WaitForObservedDeployment polls for deployment to be updated so that deployment.Status.ObservedGeneration >= desiredGeneration.
2016-02-24 04:27:24 +00:00
// 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
2016-11-18 20:58:22 +00:00
// TODO: remove the duplicate
// WaitForObservedInternalDeployment polls for deployment to be updated so that deployment.Status.ObservedGeneration >= desiredGeneration.
// Returns error if polling timesout.
func WaitForObservedDeploymentInternal ( getDeploymentFunc func ( ) ( * internalextensions . Deployment , error ) , desiredGeneration int64 , interval , timeout time . Duration ) error {
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
}
2016-07-08 12:48:38 +00:00
func DeploymentDeepCopy ( deployment * extensions . Deployment ) ( * extensions . Deployment , error ) {
objCopy , err := api . Scheme . DeepCopy ( deployment )
if err != nil {
return nil , err
}
copied , ok := objCopy . ( * extensions . Deployment )
if ! ok {
return nil , fmt . Errorf ( "expected Deployment, got %#v" , objCopy )
}
return copied , nil
}
2016-08-17 01:47:15 +00:00
// SelectorUpdatedBefore returns true if the former deployment's selector
// is updated before the latter, false otherwise
func SelectorUpdatedBefore ( d1 , d2 * extensions . Deployment ) bool {
t1 , t2 := LastSelectorUpdate ( d1 ) , LastSelectorUpdate ( d2 )
return t1 . Before ( t2 )
}
// LastSelectorUpdate returns the last time given deployment's selector is updated
2016-12-03 18:57:26 +00:00
func LastSelectorUpdate ( d * extensions . Deployment ) metav1 . Time {
2016-08-17 01:47:15 +00:00
t := d . Annotations [ SelectorUpdateAnnotation ]
if len ( t ) > 0 {
parsedTime , err := time . Parse ( t , time . RFC3339 )
// If failed to parse the time, use creation timestamp instead
if err != nil {
return d . CreationTimestamp
}
2016-12-03 18:57:26 +00:00
return metav1 . Time { Time : parsedTime }
2016-08-17 01:47:15 +00:00
}
// If it's never updated, use creation timestamp instead
return d . CreationTimestamp
}
// BySelectorLastUpdateTime sorts a list of deployments by the last update time of their selector,
// first using their creation timestamp and then their names as a tie breaker.
2016-10-04 17:11:07 +00:00
type BySelectorLastUpdateTime [ ] * extensions . Deployment
2016-08-17 01:47:15 +00:00
func ( o BySelectorLastUpdateTime ) Len ( ) int { return len ( o ) }
func ( o BySelectorLastUpdateTime ) Swap ( i , j int ) { o [ i ] , o [ j ] = o [ j ] , o [ i ] }
func ( o BySelectorLastUpdateTime ) Less ( i , j int ) bool {
2016-10-04 17:11:07 +00:00
ti , tj := LastSelectorUpdate ( o [ i ] ) , LastSelectorUpdate ( o [ j ] )
2016-08-17 01:47:15 +00:00
if ti . Equal ( tj ) {
if o [ i ] . CreationTimestamp . Equal ( o [ j ] . CreationTimestamp ) {
return o [ i ] . Name < o [ j ] . Name
}
return o [ i ] . CreationTimestamp . Before ( o [ j ] . CreationTimestamp )
}
return ti . Before ( tj )
}
2016-11-02 21:43:23 +00:00
// OverlapsWith returns true when two given deployments are different and overlap with each other
func OverlapsWith ( current , other * extensions . Deployment ) ( bool , error ) {
if current . UID == other . UID {
return false , nil
}
2016-12-03 18:57:26 +00:00
currentSelector , err := metav1 . LabelSelectorAsSelector ( current . Spec . Selector )
2016-11-02 21:43:23 +00:00
if err != nil {
return false , fmt . Errorf ( "deployment %s/%s has invalid label selector: %v" , current . Namespace , current . Name , err )
}
2016-12-03 18:57:26 +00:00
otherSelector , err := metav1 . LabelSelectorAsSelector ( other . Spec . Selector )
2016-11-02 21:43:23 +00:00
if err != nil {
// Broken selectors from other deployments shouldn't block current deployment. Just log the error and continue.
glog . V ( 2 ) . Infof ( "Skip overlapping check: deployment %s/%s has invalid label selector: %v" , other . Namespace , other . Name , err )
return false , nil
}
return ( ! currentSelector . Empty ( ) && currentSelector . Matches ( labels . Set ( other . Spec . Template . Labels ) ) ) ||
( ! otherSelector . Empty ( ) && otherSelector . Matches ( labels . Set ( current . Spec . Template . Labels ) ) ) , nil
}