Commit f3d2e3ff authored by Michail Kargakis's avatar Michail Kargakis

controller: proportionally scale paused and rolling deployments

Enable paused and rolling deployments to be proportionally scaled. Also have cleanup policy work for paused deployments.
parent a098d9fd
...@@ -1772,35 +1772,35 @@ __EOF__ ...@@ -1772,35 +1772,35 @@ __EOF__
# Command # Command
# Create a deployment (revision 1) # Create a deployment (revision 1)
kubectl create -f hack/testdata/deployment-revision1.yaml "${kube_flags[@]}" kubectl create -f hack/testdata/deployment-revision1.yaml "${kube_flags[@]}"
kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" 'nginx-deployment:' kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" 'nginx:'
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:" kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
# Rollback to revision 1 - should be no-op # Rollback to revision 1 - should be no-op
kubectl rollout undo deployment nginx-deployment --to-revision=1 "${kube_flags[@]}" kubectl rollout undo deployment nginx --to-revision=1 "${kube_flags[@]}"
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:" kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
# Update the deployment (revision 2) # Update the deployment (revision 2)
kubectl apply -f hack/testdata/deployment-revision2.yaml "${kube_flags[@]}" kubectl apply -f hack/testdata/deployment-revision2.yaml "${kube_flags[@]}"
kube::test::get_object_assert deployment.extensions "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:" kube::test::get_object_assert deployment.extensions "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:"
# Rollback to revision 1 # Rollback to revision 1
kubectl rollout undo deployment nginx-deployment --to-revision=1 "${kube_flags[@]}" kubectl rollout undo deployment nginx --to-revision=1 "${kube_flags[@]}"
sleep 1 sleep 1
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:" kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
# Rollback to revision 1000000 - should be no-op # Rollback to revision 1000000 - should be no-op
kubectl rollout undo deployment nginx-deployment --to-revision=1000000 "${kube_flags[@]}" kubectl rollout undo deployment nginx --to-revision=1000000 "${kube_flags[@]}"
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:" kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
# Rollback to last revision # Rollback to last revision
kubectl rollout undo deployment nginx-deployment "${kube_flags[@]}" kubectl rollout undo deployment nginx "${kube_flags[@]}"
sleep 1 sleep 1
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:" kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:"
# Pause the deployment # Pause the deployment
kubectl-with-retry rollout pause deployment nginx-deployment "${kube_flags[@]}" kubectl-with-retry rollout pause deployment nginx "${kube_flags[@]}"
# A paused deployment cannot be rolled back # A paused deployment cannot be rolled back
! kubectl rollout undo deployment nginx-deployment "${kube_flags[@]}" ! kubectl rollout undo deployment nginx "${kube_flags[@]}"
# Resume the deployment # Resume the deployment
kubectl-with-retry rollout resume deployment nginx-deployment "${kube_flags[@]}" kubectl-with-retry rollout resume deployment nginx "${kube_flags[@]}"
# The resumed deployment can now be rolled back # The resumed deployment can now be rolled back
kubectl rollout undo deployment nginx-deployment "${kube_flags[@]}" kubectl rollout undo deployment nginx "${kube_flags[@]}"
# Clean up # Clean up
kubectl delete deployment nginx-deployment "${kube_flags[@]}" kubectl delete deployment nginx "${kube_flags[@]}"
### Set image of a deployment ### Set image of a deployment
# Pre-condition: no deployment exists # Pre-condition: no deployment exists
......
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Deployment kind: Deployment
metadata: metadata:
name: nginx-deployment name: nginx
labels: labels:
name: nginx-deployment name: nginx-undo
spec: spec:
replicas: 3 replicas: 3
selector: selector:
matchLabels: matchLabels:
name: nginx name: nginx-undo
template: template:
metadata: metadata:
labels: labels:
name: nginx name: nginx-undo
spec: spec:
containers: containers:
- name: nginx - name: nginx
......
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Deployment kind: Deployment
metadata: metadata:
name: nginx-deployment name: nginx
labels: labels:
name: nginx-deployment name: nginx-undo
spec: spec:
replicas: 3 replicas: 3
selector: selector:
matchLabels: matchLabels:
name: nginx name: nginx-undo
template: template:
metadata: metadata:
labels: labels:
name: nginx name: nginx-undo
spec: spec:
containers: containers:
- name: nginx - name: nginx
......
...@@ -147,12 +147,15 @@ var ValidateDeploymentName = apivalidation.NameIsDNSSubdomain ...@@ -147,12 +147,15 @@ var ValidateDeploymentName = apivalidation.NameIsDNSSubdomain
func ValidatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fldPath *field.Path) field.ErrorList { func ValidatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if intOrPercent.Type == intstr.String { switch intOrPercent.Type {
case intstr.String:
if !validation.IsValidPercent(intOrPercent.StrVal) { if !validation.IsValidPercent(intOrPercent.StrVal) {
allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, "must be an integer or percentage (e.g '5%')")) allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, "must be an integer or percentage (e.g '5%%')"))
} }
} else if intOrPercent.Type == intstr.Int { case intstr.Int:
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(intOrPercent.IntValue()), fldPath)...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(intOrPercent.IntValue()), fldPath)...)
default:
allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, "must be an integer or percentage (e.g '5%%')"))
} }
return allErrs return allErrs
} }
......
...@@ -619,7 +619,9 @@ func IsPodActive(p api.Pod) bool { ...@@ -619,7 +619,9 @@ func IsPodActive(p api.Pod) bool {
func FilterActiveReplicaSets(replicaSets []*extensions.ReplicaSet) []*extensions.ReplicaSet { func FilterActiveReplicaSets(replicaSets []*extensions.ReplicaSet) []*extensions.ReplicaSet {
active := []*extensions.ReplicaSet{} active := []*extensions.ReplicaSet{}
for i := range replicaSets { for i := range replicaSets {
if replicaSets[i].Spec.Replicas > 0 { rs := replicaSets[i]
if rs != nil && rs.Spec.Replicas > 0 {
active = append(active, replicaSets[i]) active = append(active, replicaSets[i])
} }
} }
...@@ -639,7 +641,6 @@ type ControllersByCreationTimestamp []*api.ReplicationController ...@@ -639,7 +641,6 @@ type ControllersByCreationTimestamp []*api.ReplicationController
func (o ControllersByCreationTimestamp) Len() int { return len(o) } func (o ControllersByCreationTimestamp) Len() int { return len(o) }
func (o ControllersByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } func (o ControllersByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o ControllersByCreationTimestamp) Less(i, j int) bool { func (o ControllersByCreationTimestamp) Less(i, j int) bool {
if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) {
return o[i].Name < o[j].Name return o[i].Name < o[j].Name
...@@ -647,15 +648,40 @@ func (o ControllersByCreationTimestamp) Less(i, j int) bool { ...@@ -647,15 +648,40 @@ func (o ControllersByCreationTimestamp) Less(i, j int) bool {
return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) return o[i].CreationTimestamp.Before(o[j].CreationTimestamp)
} }
// ReplicaSetsByCreationTimestamp sorts a list of ReplicationSets by creation timestamp, using their names as a tie breaker. // ReplicaSetsByCreationTimestamp sorts a list of ReplicaSet by creation timestamp, using their names as a tie breaker.
type ReplicaSetsByCreationTimestamp []*extensions.ReplicaSet type ReplicaSetsByCreationTimestamp []*extensions.ReplicaSet
func (o ReplicaSetsByCreationTimestamp) Len() int { return len(o) } func (o ReplicaSetsByCreationTimestamp) Len() int { return len(o) }
func (o ReplicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } func (o ReplicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o ReplicaSetsByCreationTimestamp) Less(i, j int) bool { func (o ReplicaSetsByCreationTimestamp) Less(i, j int) bool {
if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) {
return o[i].Name < o[j].Name return o[i].Name < o[j].Name
} }
return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) return o[i].CreationTimestamp.Before(o[j].CreationTimestamp)
} }
// ReplicaSetsBySizeOlder sorts a list of ReplicaSet by size in descending order, using their creation timestamp or name as a tie breaker.
// By using the creation timestamp, this sorts from old to new replica sets.
type ReplicaSetsBySizeOlder []*extensions.ReplicaSet
func (o ReplicaSetsBySizeOlder) Len() int { return len(o) }
func (o ReplicaSetsBySizeOlder) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o ReplicaSetsBySizeOlder) Less(i, j int) bool {
if o[i].Spec.Replicas == o[j].Spec.Replicas {
return ReplicaSetsByCreationTimestamp(o).Less(i, j)
}
return o[i].Spec.Replicas > o[j].Spec.Replicas
}
// ReplicaSetsBySizeNewer sorts a list of ReplicaSet by size in descending order, using their creation timestamp or name as a tie breaker.
// By using the creation timestamp, this sorts from new to old replica sets.
type ReplicaSetsBySizeNewer []*extensions.ReplicaSet
func (o ReplicaSetsBySizeNewer) Len() int { return len(o) }
func (o ReplicaSetsBySizeNewer) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o ReplicaSetsBySizeNewer) Less(i, j int) bool {
if o[i].Spec.Replicas == o[j].Spec.Replicas {
return ReplicaSetsByCreationTimestamp(o).Less(j, i)
}
return o[i].Spec.Replicas > o[j].Spec.Replicas
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package deployment
import (
"fmt"
"sort"
"strconv"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/controller"
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
"k8s.io/kubernetes/pkg/util/integer"
)
// 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
}
}
func getDesiredReplicasAnnotation(rs *extensions.ReplicaSet) (int32, bool) {
return getIntFromAnnotation(rs, deploymentutil.DesiredReplicasAnnotation)
}
func getMaxReplicasAnnotation(rs *extensions.ReplicaSet) (int32, bool) {
return getIntFromAnnotation(rs, deploymentutil.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
}
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[deploymentutil.DesiredReplicasAnnotation]; hasString != desiredString {
rs.Annotations[deploymentutil.DesiredReplicasAnnotation] = desiredString
updated = true
}
maxString := fmt.Sprintf("%d", maxReplicas)
if hasString := rs.Annotations[deploymentutil.MaxReplicasAnnotation]; hasString != maxString {
rs.Annotations[deploymentutil.MaxReplicasAnnotation] = maxString
updated = true
}
return updated
}
// maxUnavailable returns the maximum unavailable pods a rolling deployment can take.
func maxUnavailable(deployment extensions.Deployment) int32 {
if !deploymentutil.IsRollingUpdate(&deployment) {
return int32(0)
}
// Error caught by validation
_, maxUnavailable, _ := deploymentutil.ResolveFenceposts(&deployment.Spec.Strategy.RollingUpdate.MaxSurge, &deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, deployment.Spec.Replicas)
return maxUnavailable
}
// maxSurge returns the maximum surge pods a rolling deployment can take.
func maxSurge(deployment extensions.Deployment) int32 {
if !deploymentutil.IsRollingUpdate(&deployment) {
return int32(0)
}
// Error caught by validation
maxSurge, _, _ := deploymentutil.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
}
...@@ -41,6 +41,14 @@ import ( ...@@ -41,6 +41,14 @@ import (
const ( const (
// The revision annotation of a deployment's replica sets which records its rollout sequence // The revision annotation of a deployment's replica sets which records its rollout sequence
RevisionAnnotation = "deployment.kubernetes.io/revision" RevisionAnnotation = "deployment.kubernetes.io/revision"
// 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"
// Here are the possible rollback event reasons // Here are the possible rollback event reasons
RollbackRevisionNotFound = "DeploymentRollbackRevisionNotFound" RollbackRevisionNotFound = "DeploymentRollbackRevisionNotFound"
...@@ -434,6 +442,21 @@ func NewRSNewReplicas(deployment *extensions.Deployment, allRSs []*extensions.Re ...@@ -434,6 +442,21 @@ func NewRSNewReplicas(deployment *extensions.Deployment, allRSs []*extensions.Re
} }
} }
// 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
}
// Polls for deployment to be updated so that deployment.Status.ObservedGeneration >= desiredGeneration. // Polls for deployment to be updated so that deployment.Status.ObservedGeneration >= desiredGeneration.
// Returns error if polling timesout. // Returns error if polling timesout.
func WaitForObservedDeployment(getDeploymentFunc func() (*extensions.Deployment, error), desiredGeneration int64, interval, timeout time.Duration) error { func WaitForObservedDeployment(getDeploymentFunc func() (*extensions.Deployment, error), desiredGeneration int64, interval, timeout time.Duration) error {
......
...@@ -144,5 +144,5 @@ func getIntOrPercentValue(intOrStr *IntOrString) (int, bool, error) { ...@@ -144,5 +144,5 @@ func getIntOrPercentValue(intOrStr *IntOrString) (int, bool, error) {
} }
return int(v), true, nil return int(v), true, nil
} }
return 0, false, fmt.Errorf("invalid value: neither int nor percentage") return 0, false, fmt.Errorf("invalid type: neither int nor percentage")
} }
...@@ -80,6 +80,9 @@ var _ = framework.KubeDescribe("Deployment", func() { ...@@ -80,6 +80,9 @@ var _ = framework.KubeDescribe("Deployment", func() {
It("deployment should label adopted RSs and pods", func() { It("deployment should label adopted RSs and pods", func() {
testDeploymentLabelAdopted(f) testDeploymentLabelAdopted(f)
}) })
It("paused deployment should be able to scale", func() {
testScalePausedDeployment(f)
})
}) })
func newRS(rsName string, replicas int32, rsPodLabels map[string]string, imageName string, image string) *extensions.ReplicaSet { func newRS(rsName string, replicas int32, rsPodLabels map[string]string, imageName string, image string) *extensions.ReplicaSet {
...@@ -569,6 +572,8 @@ func testPausedDeployment(f *framework.Framework) { ...@@ -569,6 +572,8 @@ func testPausedDeployment(f *framework.Framework) {
podLabels := map[string]string{"name": nginxImageName} podLabels := map[string]string{"name": nginxImageName}
d := newDeployment(deploymentName, 1, podLabels, nginxImageName, nginxImage, extensions.RollingUpdateDeploymentStrategyType, nil) d := newDeployment(deploymentName, 1, podLabels, nginxImageName, nginxImage, extensions.RollingUpdateDeploymentStrategyType, nil)
d.Spec.Paused = true d.Spec.Paused = true
tgps := int64(20)
d.Spec.Template.Spec.TerminationGracePeriodSeconds = &tgps
framework.Logf("Creating paused deployment %s", deploymentName) framework.Logf("Creating paused deployment %s", deploymentName)
_, err := c.Extensions().Deployments(ns).Create(d) _, err := c.Extensions().Deployments(ns).Create(d)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
...@@ -622,21 +627,34 @@ func testPausedDeployment(f *framework.Framework) { ...@@ -622,21 +627,34 @@ func testPausedDeployment(f *framework.Framework) {
err = framework.WaitForObservedDeployment(c, ns, deploymentName, deployment.Generation) err = framework.WaitForObservedDeployment(c, ns, deploymentName, deployment.Generation)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
newRS, err := deploymentutil.GetNewReplicaSet(deployment, c) // Update the deployment template - the new replicaset should stay the same
framework.Logf("Updating paused deployment %q", deploymentName)
newTGPS := int64(40)
deployment, err = framework.UpdateDeploymentWithRetries(c, ns, d.Name, func(update *extensions.Deployment) {
update.Spec.Template.Spec.TerminationGracePeriodSeconds = &newTGPS
})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(framework.DeleteReplicaSet(unversionedClient, ns, newRS.Name)).NotTo(HaveOccurred())
deployment, err = c.Extensions().Deployments(ns).Get(deploymentName) err = framework.WaitForObservedDeployment(c, ns, deploymentName, deployment.Generation)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
if !deployment.Spec.Paused { framework.Logf("Looking for new replicaset for paused deployment %q (there should be none)", deploymentName)
err = fmt.Errorf("deployment %q should be paused", deployment.Name) newRS, err := deploymentutil.GetNewReplicaSet(deployment, c)
Expect(err).NotTo(HaveOccurred())
if newRS != nil {
err = fmt.Errorf("No replica set should match the deployment template but there is %q", newRS.Name)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
shouldBeNil, err := deploymentutil.GetNewReplicaSet(deployment, c)
_, allOldRs, err := deploymentutil.GetOldReplicaSets(deployment, c)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
if shouldBeNil != nil { if len(allOldRs) != 1 {
err = fmt.Errorf("deployment %q shouldn't have a replica set but there is %q", deployment.Name, shouldBeNil.Name) err = fmt.Errorf("expected an old replica set")
Expect(err).NotTo(HaveOccurred())
}
framework.Logf("Comparing deployment diff with old replica set %q", allOldRs[0].Name)
if *allOldRs[0].Spec.Template.Spec.TerminationGracePeriodSeconds == newTGPS {
err = fmt.Errorf("TerminationGracePeriodSeconds on the replica set should be %d but is %d", tgps, newTGPS)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
} }
...@@ -944,3 +962,54 @@ func testDeploymentLabelAdopted(f *framework.Framework) { ...@@ -944,3 +962,54 @@ func testDeploymentLabelAdopted(f *framework.Framework) {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(int32(len(pods.Items))).Should(Equal(replicas)) Expect(int32(len(pods.Items))).Should(Equal(replicas))
} }
func testScalePausedDeployment(f *framework.Framework) {
ns := f.Namespace.Name
c := adapter.FromUnversionedClient(f.Client)
podLabels := map[string]string{"name": nginxImageName}
replicas := int32(3)
// Create a nginx deployment.
deploymentName := "nginx-deployment"
d := newDeployment(deploymentName, replicas, podLabels, nginxImageName, nginxImage, extensions.RollingUpdateDeploymentStrategyType, nil)
framework.Logf("Creating deployment %q", deploymentName)
_, err := c.Extensions().Deployments(ns).Create(d)
Expect(err).NotTo(HaveOccurred())
defer stopDeployment(c, f.Client, ns, deploymentName)
// Check that deployment is created fine.
deployment, err := c.Extensions().Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
err = framework.WaitForObservedDeployment(c, ns, deploymentName, deployment.Generation)
Expect(err).NotTo(HaveOccurred())
rs, err := deploymentutil.GetNewReplicaSet(deployment, c)
Expect(err).NotTo(HaveOccurred())
// Pause the deployment and try to scale it.
deployment, err = framework.UpdateDeploymentWithRetries(c, ns, d.Name, func(update *extensions.Deployment) {
update.Spec.Paused = true
})
Expect(err).NotTo(HaveOccurred())
// Scale the paused deployment.
framework.Logf("Scaling up the paused deployment %q", deploymentName)
newReplicas := int32(5)
deployment, err = framework.UpdateDeploymentWithRetries(c, ns, deployment.Name, func(update *extensions.Deployment) {
update.Spec.Replicas = newReplicas
})
Expect(err).NotTo(HaveOccurred())
err = framework.WaitForObservedDeployment(c, ns, deploymentName, deployment.Generation)
Expect(err).NotTo(HaveOccurred())
rs, err = deploymentutil.GetNewReplicaSet(deployment, c)
Expect(err).NotTo(HaveOccurred())
if rs.Spec.Replicas != newReplicas {
err = fmt.Errorf("Expected %d replicas for the new replica set, got %d", newReplicas, rs.Spec.Replicas)
Expect(err).NotTo(HaveOccurred())
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment