123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- /*
- Copyright 2016 The Kubernetes Authors.
- 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 (
- "math"
- "testing"
- "time"
- apps "k8s.io/api/apps/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/intstr"
- "k8s.io/client-go/informers"
- "k8s.io/client-go/kubernetes/fake"
- testclient "k8s.io/client-go/testing"
- "k8s.io/client-go/tools/record"
- "k8s.io/kubernetes/pkg/controller"
- deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
- )
- func intOrStrP(val int) *intstr.IntOrString {
- intOrStr := intstr.FromInt(val)
- return &intOrStr
- }
- func TestScale(t *testing.T) {
- newTimestamp := metav1.Date(2016, 5, 20, 2, 0, 0, 0, time.UTC)
- oldTimestamp := metav1.Date(2016, 5, 20, 1, 0, 0, 0, time.UTC)
- olderTimestamp := metav1.Date(2016, 5, 20, 0, 0, 0, 0, time.UTC)
- var updatedTemplate = func(replicas int) *apps.Deployment {
- d := newDeployment("foo", replicas, nil, nil, nil, map[string]string{"foo": "bar"})
- d.Spec.Template.Labels["another"] = "label"
- return d
- }
- tests := []struct {
- name string
- deployment *apps.Deployment
- oldDeployment *apps.Deployment
- newRS *apps.ReplicaSet
- oldRSs []*apps.ReplicaSet
- expectedNew *apps.ReplicaSet
- expectedOld []*apps.ReplicaSet
- wasntUpdated map[string]bool
- desiredReplicasAnnotations map[string]int32
- }{
- {
- name: "normal scaling event: 10 -> 12",
- deployment: newDeployment("foo", 12, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 10, nil, nil, nil, nil),
- newRS: rs("foo-v1", 10, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{},
- expectedNew: rs("foo-v1", 12, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{},
- },
- {
- name: "normal scaling event: 10 -> 5",
- deployment: newDeployment("foo", 5, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 10, nil, nil, nil, nil),
- newRS: rs("foo-v1", 10, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{},
- expectedNew: rs("foo-v1", 5, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{},
- },
- {
- name: "proportional scaling: 5 -> 10",
- deployment: newDeployment("foo", 10, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
- newRS: rs("foo-v2", 2, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v1", 3, nil, oldTimestamp)},
- expectedNew: rs("foo-v2", 4, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v1", 6, nil, oldTimestamp)},
- },
- {
- name: "proportional scaling: 5 -> 3",
- deployment: newDeployment("foo", 3, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
- newRS: rs("foo-v2", 2, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v1", 3, nil, oldTimestamp)},
- expectedNew: rs("foo-v2", 1, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v1", 2, nil, oldTimestamp)},
- },
- {
- name: "proportional scaling: 9 -> 4",
- deployment: newDeployment("foo", 4, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 9, nil, nil, nil, nil),
- newRS: rs("foo-v2", 8, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
- expectedNew: rs("foo-v2", 4, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v1", 0, nil, oldTimestamp)},
- },
- {
- name: "proportional scaling: 7 -> 10",
- deployment: newDeployment("foo", 10, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 7, nil, nil, nil, nil),
- newRS: rs("foo-v3", 2, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 3, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
- expectedNew: rs("foo-v3", 3, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 4, nil, oldTimestamp), rs("foo-v1", 3, nil, olderTimestamp)},
- },
- {
- name: "proportional scaling: 13 -> 8",
- deployment: newDeployment("foo", 8, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 13, nil, nil, nil, nil),
- newRS: rs("foo-v3", 2, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 8, nil, oldTimestamp), rs("foo-v1", 3, nil, olderTimestamp)},
- expectedNew: rs("foo-v3", 1, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 5, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
- },
- // Scales up the new replica set.
- {
- name: "leftover distribution: 3 -> 4",
- deployment: newDeployment("foo", 4, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 3, nil, nil, nil, nil),
- newRS: rs("foo-v3", 1, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
- expectedNew: rs("foo-v3", 2, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
- },
- // Scales down the older replica set.
- {
- name: "leftover distribution: 3 -> 2",
- deployment: newDeployment("foo", 2, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 3, nil, nil, nil, nil),
- newRS: rs("foo-v3", 1, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
- expectedNew: rs("foo-v3", 1, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
- },
- // Scales up the latest replica set first.
- {
- name: "proportional scaling (no new rs): 4 -> 5",
- deployment: newDeployment("foo", 5, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 4, nil, nil, nil, nil),
- newRS: nil,
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
- expectedNew: nil,
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 3, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
- },
- // Scales down to zero
- {
- name: "proportional scaling: 6 -> 0",
- deployment: newDeployment("foo", 0, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 6, nil, nil, nil, nil),
- newRS: rs("foo-v3", 3, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
- expectedNew: rs("foo-v3", 0, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
- },
- // Scales up from zero
- {
- name: "proportional scaling: 0 -> 6",
- deployment: newDeployment("foo", 6, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 6, nil, nil, nil, nil),
- newRS: rs("foo-v3", 0, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
- expectedNew: rs("foo-v3", 6, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
- wasntUpdated: map[string]bool{"foo-v2": true, "foo-v1": true},
- },
- // Scenario: deployment.spec.replicas == 3 ( foo-v1.spec.replicas == foo-v2.spec.replicas == foo-v3.spec.replicas == 1 )
- // Deployment is scaled to 5. foo-v3.spec.replicas and foo-v2.spec.replicas should increment by 1 but foo-v2 fails to
- // update.
- {
- name: "failed rs update",
- deployment: newDeployment("foo", 5, nil, nil, nil, nil),
- oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
- newRS: rs("foo-v3", 2, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
- expectedNew: rs("foo-v3", 2, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
- wasntUpdated: map[string]bool{"foo-v3": true, "foo-v1": true},
- desiredReplicasAnnotations: map[string]int32{"foo-v2": int32(3)},
- },
- {
- name: "deployment with surge pods",
- deployment: newDeployment("foo", 20, nil, intOrStrP(2), nil, nil),
- oldDeployment: newDeployment("foo", 10, nil, intOrStrP(2), nil, nil),
- newRS: rs("foo-v2", 6, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v1", 6, nil, oldTimestamp)},
- expectedNew: rs("foo-v2", 11, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v1", 11, nil, oldTimestamp)},
- },
- {
- name: "change both surge and size",
- deployment: newDeployment("foo", 50, nil, intOrStrP(6), nil, nil),
- oldDeployment: newDeployment("foo", 10, nil, intOrStrP(3), nil, nil),
- newRS: rs("foo-v2", 5, nil, newTimestamp),
- oldRSs: []*apps.ReplicaSet{rs("foo-v1", 8, nil, oldTimestamp)},
- expectedNew: rs("foo-v2", 22, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v1", 34, nil, oldTimestamp)},
- },
- {
- name: "change both size and template",
- deployment: updatedTemplate(14),
- oldDeployment: newDeployment("foo", 10, nil, nil, nil, map[string]string{"foo": "bar"}),
- newRS: nil,
- oldRSs: []*apps.ReplicaSet{rs("foo-v2", 7, nil, newTimestamp), rs("foo-v1", 3, nil, oldTimestamp)},
- expectedNew: nil,
- expectedOld: []*apps.ReplicaSet{rs("foo-v2", 10, nil, newTimestamp), rs("foo-v1", 4, nil, oldTimestamp)},
- },
- {
- name: "saturated but broken new replica set does not affect old pods",
- deployment: newDeployment("foo", 2, nil, intOrStrP(1), intOrStrP(1), nil),
- oldDeployment: newDeployment("foo", 2, nil, intOrStrP(1), intOrStrP(1), nil),
- newRS: func() *apps.ReplicaSet {
- rs := rs("foo-v2", 2, nil, newTimestamp)
- rs.Status.AvailableReplicas = 0
- return rs
- }(),
- oldRSs: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
- expectedNew: rs("foo-v2", 2, nil, newTimestamp),
- expectedOld: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- _ = olderTimestamp
- t.Log(test.name)
- fake := fake.Clientset{}
- dc := &DeploymentController{
- client: &fake,
- eventRecorder: &record.FakeRecorder{},
- }
- if test.newRS != nil {
- desiredReplicas := *(test.oldDeployment.Spec.Replicas)
- if desired, ok := test.desiredReplicasAnnotations[test.newRS.Name]; ok {
- desiredReplicas = desired
- }
- deploymentutil.SetReplicasAnnotations(test.newRS, desiredReplicas, desiredReplicas+deploymentutil.MaxSurge(*test.oldDeployment))
- }
- for i := range test.oldRSs {
- rs := test.oldRSs[i]
- if rs == nil {
- continue
- }
- desiredReplicas := *(test.oldDeployment.Spec.Replicas)
- if desired, ok := test.desiredReplicasAnnotations[rs.Name]; ok {
- desiredReplicas = desired
- }
- deploymentutil.SetReplicasAnnotations(rs, desiredReplicas, desiredReplicas+deploymentutil.MaxSurge(*test.oldDeployment))
- }
- if err := dc.scale(test.deployment, test.newRS, test.oldRSs); err != nil {
- t.Errorf("%s: unexpected error: %v", test.name, err)
- return
- }
- // Construct the nameToSize map that will hold all the sizes we got our of tests
- // Skip updating the map if the replica set wasn't updated since there will be
- // no update action for it.
- nameToSize := make(map[string]int32)
- if test.newRS != nil {
- nameToSize[test.newRS.Name] = *(test.newRS.Spec.Replicas)
- }
- for i := range test.oldRSs {
- rs := test.oldRSs[i]
- nameToSize[rs.Name] = *(rs.Spec.Replicas)
- }
- // Get all the UPDATE actions and update nameToSize with all the updated sizes.
- for _, action := range fake.Actions() {
- rs := action.(testclient.UpdateAction).GetObject().(*apps.ReplicaSet)
- if !test.wasntUpdated[rs.Name] {
- nameToSize[rs.Name] = *(rs.Spec.Replicas)
- }
- }
- if test.expectedNew != nil && test.newRS != nil && *(test.expectedNew.Spec.Replicas) != nameToSize[test.newRS.Name] {
- t.Errorf("%s: expected new replicas: %d, got: %d", test.name, *(test.expectedNew.Spec.Replicas), nameToSize[test.newRS.Name])
- return
- }
- if len(test.expectedOld) != len(test.oldRSs) {
- t.Errorf("%s: expected %d old replica sets, got %d", test.name, len(test.expectedOld), len(test.oldRSs))
- return
- }
- for n := range test.oldRSs {
- rs := test.oldRSs[n]
- expected := test.expectedOld[n]
- if *(expected.Spec.Replicas) != nameToSize[rs.Name] {
- t.Errorf("%s: expected old (%s) replicas: %d, got: %d", test.name, rs.Name, *(expected.Spec.Replicas), nameToSize[rs.Name])
- }
- }
- })
- }
- }
- func TestDeploymentController_cleanupDeployment(t *testing.T) {
- selector := map[string]string{"foo": "bar"}
- alreadyDeleted := newRSWithStatus("foo-1", 0, 0, selector)
- now := metav1.Now()
- alreadyDeleted.DeletionTimestamp = &now
- tests := []struct {
- oldRSs []*apps.ReplicaSet
- revisionHistoryLimit int32
- expectedDeletions int
- }{
- {
- oldRSs: []*apps.ReplicaSet{
- newRSWithStatus("foo-1", 0, 0, selector),
- newRSWithStatus("foo-2", 0, 0, selector),
- newRSWithStatus("foo-3", 0, 0, selector),
- },
- revisionHistoryLimit: 1,
- expectedDeletions: 2,
- },
- {
- // Only delete the replica set with Spec.Replicas = Status.Replicas = 0.
- oldRSs: []*apps.ReplicaSet{
- newRSWithStatus("foo-1", 0, 0, selector),
- newRSWithStatus("foo-2", 0, 1, selector),
- newRSWithStatus("foo-3", 1, 0, selector),
- newRSWithStatus("foo-4", 1, 1, selector),
- },
- revisionHistoryLimit: 0,
- expectedDeletions: 1,
- },
- {
- oldRSs: []*apps.ReplicaSet{
- newRSWithStatus("foo-1", 0, 0, selector),
- newRSWithStatus("foo-2", 0, 0, selector),
- },
- revisionHistoryLimit: 0,
- expectedDeletions: 2,
- },
- {
- oldRSs: []*apps.ReplicaSet{
- newRSWithStatus("foo-1", 1, 1, selector),
- newRSWithStatus("foo-2", 1, 1, selector),
- },
- revisionHistoryLimit: 0,
- expectedDeletions: 0,
- },
- {
- oldRSs: []*apps.ReplicaSet{
- alreadyDeleted,
- },
- revisionHistoryLimit: 0,
- expectedDeletions: 0,
- },
- {
- // with unlimited revisionHistoryLimit
- oldRSs: []*apps.ReplicaSet{
- newRSWithStatus("foo-1", 0, 0, selector),
- newRSWithStatus("foo-2", 0, 0, selector),
- newRSWithStatus("foo-3", 0, 0, selector),
- },
- revisionHistoryLimit: math.MaxInt32,
- expectedDeletions: 0,
- },
- }
- for i := range tests {
- test := tests[i]
- t.Logf("scenario %d", i)
- fake := &fake.Clientset{}
- informers := informers.NewSharedInformerFactory(fake, controller.NoResyncPeriodFunc())
- controller, err := NewDeploymentController(informers.Apps().V1().Deployments(), informers.Apps().V1().ReplicaSets(), informers.Core().V1().Pods(), fake)
- if err != nil {
- t.Fatalf("error creating Deployment controller: %v", err)
- }
- controller.eventRecorder = &record.FakeRecorder{}
- controller.dListerSynced = alwaysReady
- controller.rsListerSynced = alwaysReady
- controller.podListerSynced = alwaysReady
- for _, rs := range test.oldRSs {
- informers.Apps().V1().ReplicaSets().Informer().GetIndexer().Add(rs)
- }
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- t.Logf(" &test.revisionHistoryLimit: %d", test.revisionHistoryLimit)
- d := newDeployment("foo", 1, &test.revisionHistoryLimit, nil, nil, map[string]string{"foo": "bar"})
- controller.cleanupDeployment(test.oldRSs, d)
- gotDeletions := 0
- for _, action := range fake.Actions() {
- if action.GetVerb() == "delete" {
- gotDeletions++
- }
- }
- if gotDeletions != test.expectedDeletions {
- t.Errorf("expect %v old replica sets been deleted, but got %v", test.expectedDeletions, gotDeletions)
- continue
- }
- }
- }
|