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-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-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"
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-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-01-13 01:52:18 +00:00
)
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-07-05 07:29:09 +00:00
if ! exists && SetReplicasAnnotations ( newRS , deployment . Spec . Replicas , deployment . Spec . Replicas + MaxSurge ( * deployment ) ) {
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
_ , maxUnavailable , _ := ResolveFenceposts ( & deployment . Spec . Strategy . RollingUpdate . MaxSurge , & deployment . Spec . Strategy . RollingUpdate . MaxUnavailable , deployment . Spec . Replicas )
return maxUnavailable
}
2016-08-01 22:26:17 +00:00
// MinAvailable returns the minimum vailable pods of a given deployment
func MinAvailable ( deployment * extensions . Deployment ) int32 {
if ! IsRollingUpdate ( deployment ) {
return int32 ( 0 )
}
return deployment . Spec . Replicas - MaxUnavailable ( * deployment )
}
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
maxSurge , _ , _ := ResolveFenceposts ( & deployment . Spec . Strategy . RollingUpdate . MaxSurge , & deployment . Spec . Strategy . RollingUpdate . MaxUnavailable , deployment . Spec . Replicas )
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 {
if rs == nil || rs . Spec . Replicas == 0 || deploymentReplicasToAdd == 0 || deploymentReplicasToAdd == deploymentReplicasAdded {
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)
if d . Spec . Replicas == int32 ( 0 ) {
return - rs . Spec . Replicas
}
deploymentReplicas := d . Spec . Replicas + MaxSurge ( d )
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.
newRSsize := ( float64 ( rs . Spec . Replicas * deploymentReplicas ) ) / float64 ( annotatedReplicas )
return integer . RoundToInt32 ( newRSsize ) - rs . Spec . Replicas
}
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-10-04 17:23:27 +00:00
func ( namespace string , options api . 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.
func listPods ( deployment * extensions . Deployment , c clientset . Interface ) ( * api . PodList , error ) {
return ListPods ( deployment ,
func ( namespace string , options api . ListOptions ) ( * api . PodList , error ) {
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-10-04 17:23:27 +00:00
type rsListFunc func ( string , api . ListOptions ) ( [ ] * extensions . ReplicaSet , error )
2016-02-11 01:49:11 +00:00
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.
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
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 )
}
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
func equalIgnoreHash ( template1 , template2 api . 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-06-03 17:53:14 +00:00
result := api . Semantic . DeepEqual ( template1 , template2 )
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-10-04 17:23:27 +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-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-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-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 &&
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-07-05 07:29:09 +00:00
// GetNewReplicaSetTemplate 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-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-10-11 14:37:39 +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-06-27 12:30:14 +00:00
func IsPodAvailable ( pod * api . 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-02-19 23:53:34 +00:00
if c . Type == api . PodReady && c . Status == api . 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
}
// 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
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
}
return rs . Spec . Replicas == deployment . Spec . Replicas && int32 ( desired ) == deployment . Spec . Replicas
}
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
// 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
func LastSelectorUpdate ( d * extensions . Deployment ) unversioned . Time {
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
}
return unversioned . Time { Time : parsedTime }
}
// 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 )
}