123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278 |
- /*
- Copyright 2017 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 (
- "context"
- "fmt"
- "strings"
- "testing"
- apps "k8s.io/api/apps/v1"
- "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/intstr"
- "k8s.io/apimachinery/pkg/util/uuid"
- "k8s.io/apimachinery/pkg/util/wait"
- "k8s.io/client-go/util/retry"
- deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
- "k8s.io/kubernetes/test/integration/framework"
- "k8s.io/utils/pointer"
- )
- func TestNewDeployment(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-new-deployment"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- replicas := int32(20)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(name, ns.Name, replicas)}
- tester.deployment.Spec.MinReadySeconds = 4
- tester.deployment.Annotations = map[string]string{"test": "should-copy-to-replica-set", v1.LastAppliedConfigAnnotation: "should-not-copy-to-replica-set"}
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %s: %v", tester.deployment.Name, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Wait for the Deployment to be updated to revision 1
- if err := tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatal(err)
- }
- // Make sure the Deployment completes while manually marking Deployment pods as ready at the same time.
- // Use soft check because this deployment was just created and rolling update strategy might be violated.
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- // Check new RS annotations
- newRS, err := tester.expectNewReplicaSet()
- if err != nil {
- t.Fatal(err)
- }
- if newRS.Annotations["test"] != "should-copy-to-replica-set" {
- t.Errorf("expected new ReplicaSet annotations copied from Deployment %s, got: %v", tester.deployment.Name, newRS.Annotations)
- }
- if newRS.Annotations[v1.LastAppliedConfigAnnotation] != "" {
- t.Errorf("expected new ReplicaSet last-applied annotation not copied from Deployment %s", tester.deployment.Name)
- }
- // New RS should contain pod-template-hash in its selector, label, and template label
- rsHash, err := checkRSHashLabels(newRS)
- if err != nil {
- t.Error(err)
- }
- // All pods targeted by the deployment should contain pod-template-hash in their labels
- selector, err := metav1.LabelSelectorAsSelector(tester.deployment.Spec.Selector)
- if err != nil {
- t.Fatalf("failed to parse deployment %s selector: %v", name, err)
- }
- pods, err := c.CoreV1().Pods(ns.Name).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()})
- if err != nil {
- t.Fatalf("failed to list pods of deployment %s: %v", name, err)
- }
- if len(pods.Items) != int(replicas) {
- t.Errorf("expected %d pods, got %d pods", replicas, len(pods.Items))
- }
- podHash, err := checkPodsHashLabel(pods)
- if err != nil {
- t.Error(err)
- }
- if rsHash != podHash {
- t.Errorf("found mismatching pod-template-hash value: rs hash = %s whereas pod hash = %s", rsHash, podHash)
- }
- }
- // Deployments should support roll out, roll back, and roll over.
- // TODO: drop the rollback portions of this test when extensions/v1beta1 is no longer served
- // and rollback endpoint is no longer supported.
- func TestDeploymentRollingUpdate(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-rolling-update-deployment"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- replicas := int32(20)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(name, ns.Name, replicas)}
- tester.deployment.Spec.MinReadySeconds = 4
- quarter := intstr.FromString("25%")
- tester.deployment.Spec.Strategy.RollingUpdate = &apps.RollingUpdateDeployment{
- MaxUnavailable: &quarter,
- MaxSurge: &quarter,
- }
- // Create a deployment.
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %s: %v", tester.deployment.Name, err)
- }
- oriImage := tester.deployment.Spec.Template.Spec.Containers[0].Image
- if err := tester.waitForDeploymentRevisionAndImage("1", oriImage); err != nil {
- t.Fatal(err)
- }
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- // 1. Roll out a new image.
- image := "new-image"
- if oriImage == image {
- t.Fatalf("bad test setup, deployment %s roll out with the same image", tester.deployment.Name)
- }
- imageFn := func(update *apps.Deployment) {
- update.Spec.Template.Spec.Containers[0].Image = image
- }
- tester.deployment, err = tester.updateDeployment(imageFn)
- if err != nil {
- t.Fatalf("failed to update deployment %s: %v", tester.deployment.Name, err)
- }
- if err := tester.waitForDeploymentRevisionAndImage("2", image); err != nil {
- t.Fatal(err)
- }
- if err := tester.waitForDeploymentCompleteAndCheckRollingAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- // 2. Roll over a deployment before the previous rolling update finishes.
- image = "dont-finish"
- imageFn = func(update *apps.Deployment) {
- update.Spec.Template.Spec.Containers[0].Image = image
- }
- tester.deployment, err = tester.updateDeployment(imageFn)
- if err != nil {
- t.Fatalf("failed to update deployment %s: %v", tester.deployment.Name, err)
- }
- if err := tester.waitForDeploymentRevisionAndImage("3", image); err != nil {
- t.Fatal(err)
- }
- // We don't mark pods as ready so that rollout won't finish.
- // Before the rollout finishes, trigger another rollout.
- image = "rollover"
- imageFn = func(update *apps.Deployment) {
- update.Spec.Template.Spec.Containers[0].Image = image
- }
- tester.deployment, err = tester.updateDeployment(imageFn)
- if err != nil {
- t.Fatalf("failed to update deployment %s: %v", tester.deployment.Name, err)
- }
- if err := tester.waitForDeploymentRevisionAndImage("4", image); err != nil {
- t.Fatal(err)
- }
- if err := tester.waitForDeploymentCompleteAndCheckRollingAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- _, allOldRSs, err := deploymentutil.GetOldReplicaSets(tester.deployment, c.AppsV1())
- if err != nil {
- t.Fatalf("failed retrieving old replicasets of deployment %s: %v", tester.deployment.Name, err)
- }
- for _, oldRS := range allOldRSs {
- if *oldRS.Spec.Replicas != 0 {
- t.Errorf("expected old replicaset %s of deployment %s to have 0 replica, got %d", oldRS.Name, tester.deployment.Name, *oldRS.Spec.Replicas)
- }
- }
- }
- // selectors are IMMUTABLE for all API versions except apps/v1beta1 and extensions/v1beta1
- func TestDeploymentSelectorImmutability(t *testing.T) {
- s, closeFn, c := dcSimpleSetup(t)
- defer closeFn()
- name := "test-deployment-selector-immutability"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(name, ns.Name, int32(20))}
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create apps/v1 deployment %s: %v", tester.deployment.Name, err)
- }
- // test to ensure apps/v1 selector is immutable
- newSelectorLabels := map[string]string{"name_apps_v1beta1": "test_apps_v1beta1"}
- deploymentAppsV1, err := c.AppsV1().Deployments(ns.Name).Get(context.TODO(), name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to get apps/v1 deployment %s: %v", name, err)
- }
- newSelectorLabels = map[string]string{"name_apps_v1": "test_apps_v1"}
- deploymentAppsV1.Spec.Selector.MatchLabels = newSelectorLabels
- deploymentAppsV1.Spec.Template.Labels = newSelectorLabels
- _, err = c.AppsV1().Deployments(ns.Name).Update(context.TODO(), deploymentAppsV1, metav1.UpdateOptions{})
- if err == nil {
- t.Fatalf("failed to provide validation error when changing immutable selector when updating apps/v1 deployment %s", deploymentAppsV1.Name)
- }
- expectedErrType := "Invalid value"
- expectedErrDetail := "field is immutable"
- if !strings.Contains(err.Error(), expectedErrType) || !strings.Contains(err.Error(), expectedErrDetail) {
- t.Errorf("error message does not match, expected type: %s, expected detail: %s, got: %s", expectedErrType, expectedErrDetail, err.Error())
- }
- }
- // Paused deployment should not start new rollout
- func TestPausedDeployment(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-paused-deployment"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- replicas := int32(1)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(name, ns.Name, replicas)}
- tester.deployment.Spec.Paused = true
- tgps := int64(1)
- tester.deployment.Spec.Template.Spec.TerminationGracePeriodSeconds = &tgps
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %s: %v", tester.deployment.Name, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Verify that the paused deployment won't create new replica set.
- if err := tester.expectNoNewReplicaSet(); err != nil {
- t.Fatal(err)
- }
- // Resume the deployment
- tester.deployment, err = tester.updateDeployment(resumeFn)
- if err != nil {
- t.Fatalf("failed to resume deployment %s: %v", tester.deployment.Name, err)
- }
- // Wait for the controller to notice the resume.
- if err := tester.waitForObservedDeployment(tester.deployment.Generation); err != nil {
- t.Fatal(err)
- }
- // Wait for the Deployment to be updated to revision 1
- if err := tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatal(err)
- }
- // Make sure the Deployment completes while manually marking Deployment pods as ready at the same time.
- // Use soft check because this deployment was just created and rolling update strategy might be violated.
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- // A new replicaset should be created.
- if _, err := tester.expectNewReplicaSet(); err != nil {
- t.Fatal(err)
- }
- // Pause the deployment.
- // The paused deployment shouldn't trigger a new rollout.
- tester.deployment, err = tester.updateDeployment(pauseFn)
- if err != nil {
- t.Fatalf("failed to pause deployment %s: %v", tester.deployment.Name, err)
- }
- // Wait for the controller to notice the pause.
- if err := tester.waitForObservedDeployment(tester.deployment.Generation); err != nil {
- t.Fatal(err)
- }
- // Update the deployment template
- newTGPS := int64(0)
- tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
- update.Spec.Template.Spec.TerminationGracePeriodSeconds = &newTGPS
- })
- if err != nil {
- t.Fatalf("failed updating template of deployment %s: %v", tester.deployment.Name, err)
- }
- // Wait for the controller to notice the rollout.
- if err := tester.waitForObservedDeployment(tester.deployment.Generation); err != nil {
- t.Fatal(err)
- }
- // Verify that the paused deployment won't create new replica set.
- if err := tester.expectNoNewReplicaSet(); err != nil {
- t.Fatal(err)
- }
- _, allOldRs, err := deploymentutil.GetOldReplicaSets(tester.deployment, c.AppsV1())
- if err != nil {
- t.Fatalf("failed retrieving old replicasets of deployment %s: %v", tester.deployment.Name, err)
- }
- if len(allOldRs) != 1 {
- t.Errorf("expected an old replica set, got %v", allOldRs)
- }
- if *allOldRs[0].Spec.Template.Spec.TerminationGracePeriodSeconds == newTGPS {
- t.Errorf("TerminationGracePeriodSeconds on the replica set should be %d, got %d", tgps, newTGPS)
- }
- }
- // Paused deployment can be scaled
- func TestScalePausedDeployment(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-scale-paused-deployment"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- replicas := int32(1)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(name, ns.Name, replicas)}
- tgps := int64(1)
- tester.deployment.Spec.Template.Spec.TerminationGracePeriodSeconds = &tgps
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %s: %v", tester.deployment.Name, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Wait for the Deployment to be updated to revision 1
- if err := tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatal(err)
- }
- // Make sure the Deployment completes while manually marking Deployment pods as ready at the same time.
- // Use soft check because this deployment was just created and rolling update strategy might be violated.
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- // A new replicaset should be created.
- if _, err := tester.expectNewReplicaSet(); err != nil {
- t.Fatal(err)
- }
- // Pause the deployment.
- tester.deployment, err = tester.updateDeployment(pauseFn)
- if err != nil {
- t.Fatalf("failed to pause deployment %s: %v", tester.deployment.Name, err)
- }
- // Wait for the controller to notice the scale.
- if err := tester.waitForObservedDeployment(tester.deployment.Generation); err != nil {
- t.Fatal(err)
- }
- // Scale the paused deployment.
- newReplicas := int32(10)
- tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
- update.Spec.Replicas = &newReplicas
- })
- if err != nil {
- t.Fatalf("failed updating deployment %s: %v", tester.deployment.Name, err)
- }
- // Wait for the controller to notice the scale.
- if err := tester.waitForObservedDeployment(tester.deployment.Generation); err != nil {
- t.Fatal(err)
- }
- // Verify that the new replicaset is scaled.
- rs, err := tester.expectNewReplicaSet()
- if err != nil {
- t.Fatal(err)
- }
- if *rs.Spec.Replicas != newReplicas {
- t.Errorf("expected new replicaset replicas = %d, got %d", newReplicas, *rs.Spec.Replicas)
- }
- // Make sure the Deployment completes while manually marking Deployment pods as ready at the same time.
- // Use soft check because this deployment was just scaled and rolling update strategy might be violated.
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- }
- // Deployment rollout shouldn't be blocked on hash collisions
- func TestDeploymentHashCollision(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-hash-collision-deployment"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- replicas := int32(1)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(name, ns.Name, replicas)}
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %s: %v", tester.deployment.Name, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Wait for the Deployment to be updated to revision 1
- if err := tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatal(err)
- }
- // Mock a hash collision
- newRS, err := deploymentutil.GetNewReplicaSet(tester.deployment, c.AppsV1())
- if err != nil {
- t.Fatalf("failed getting new replicaset of deployment %s: %v", tester.deployment.Name, err)
- }
- if newRS == nil {
- t.Fatalf("unable to find new replicaset of deployment %s", tester.deployment.Name)
- }
- _, err = tester.updateReplicaSet(newRS.Name, func(update *apps.ReplicaSet) {
- *update.Spec.Template.Spec.TerminationGracePeriodSeconds = int64(5)
- })
- if err != nil {
- t.Fatalf("failed updating replicaset %s template: %v", newRS.Name, err)
- }
- // Expect deployment collision counter to increment
- if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
- d, err := c.AppsV1().Deployments(ns.Name).Get(context.TODO(), tester.deployment.Name, metav1.GetOptions{})
- if err != nil {
- return false, nil
- }
- return d.Status.CollisionCount != nil && *d.Status.CollisionCount == int32(1), nil
- }); err != nil {
- t.Fatalf("Failed to increment collision counter for deployment %q: %v", tester.deployment.Name, err)
- }
- // Expect a new ReplicaSet to be created
- if err := tester.waitForDeploymentRevisionAndImage("2", fakeImage); err != nil {
- t.Fatal(err)
- }
- }
- func checkRSHashLabels(rs *apps.ReplicaSet) (string, error) {
- hash := rs.Labels[apps.DefaultDeploymentUniqueLabelKey]
- selectorHash := rs.Spec.Selector.MatchLabels[apps.DefaultDeploymentUniqueLabelKey]
- templateLabelHash := rs.Spec.Template.Labels[apps.DefaultDeploymentUniqueLabelKey]
- if hash != selectorHash || selectorHash != templateLabelHash {
- return "", fmt.Errorf("mismatching hash value found in replicaset %s: %#v", rs.Name, rs)
- }
- if len(hash) == 0 {
- return "", fmt.Errorf("unexpected replicaset %s missing required pod-template-hash labels", rs.Name)
- }
- if !strings.HasSuffix(rs.Name, hash) {
- return "", fmt.Errorf("unexpected replicaset %s name suffix doesn't match hash %s", rs.Name, hash)
- }
- return hash, nil
- }
- func checkPodsHashLabel(pods *v1.PodList) (string, error) {
- if len(pods.Items) == 0 {
- return "", fmt.Errorf("no pods given")
- }
- var hash string
- for _, pod := range pods.Items {
- podHash := pod.Labels[apps.DefaultDeploymentUniqueLabelKey]
- if len(podHash) == 0 {
- return "", fmt.Errorf("found pod %s missing pod-template-hash label: %#v", pod.Name, pods)
- }
- // Save the first valid hash
- if len(hash) == 0 {
- hash = podHash
- }
- if podHash != hash {
- return "", fmt.Errorf("found pod %s with mismatching pod-template-hash value %s: %#v", pod.Name, podHash, pods)
- }
- }
- return hash, nil
- }
- // Deployment should have a timeout condition when it fails to progress after given deadline.
- func TestFailedDeployment(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-failed-deployment"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- deploymentName := "progress-check"
- replicas := int32(1)
- three := int32(3)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(deploymentName, ns.Name, replicas)}
- tester.deployment.Spec.ProgressDeadlineSeconds = &three
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %q: %v", deploymentName, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- if err = tester.waitForDeploymentUpdatedReplicasGTE(replicas); err != nil {
- t.Fatal(err)
- }
- // Pods are not marked as Ready, therefore the deployment progress will eventually timeout after progressDeadlineSeconds has passed.
- // Wait for the deployment to have a progress timeout condition.
- if err = tester.waitForDeploymentWithCondition(deploymentutil.TimedOutReason, apps.DeploymentProgressing); err != nil {
- t.Fatal(err)
- }
- // Manually mark pods as Ready and wait for deployment to complete.
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatalf("deployment %q fails to have its status becoming valid: %v", deploymentName, err)
- }
- // Wait for the deployment to have a progress complete condition.
- if err = tester.waitForDeploymentWithCondition(deploymentutil.NewRSAvailableReason, apps.DeploymentProgressing); err != nil {
- t.Fatal(err)
- }
- }
- func TestOverlappingDeployments(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-overlapping-deployments"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- replicas := int32(1)
- firstDeploymentName := "first-deployment"
- secondDeploymentName := "second-deployment"
- testers := []*deploymentTester{
- {t: t, c: c, deployment: newDeployment(firstDeploymentName, ns.Name, replicas)},
- {t: t, c: c, deployment: newDeployment(secondDeploymentName, ns.Name, replicas)},
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Create 2 deployments with overlapping selectors
- var err error
- var rss []*apps.ReplicaSet
- for _, tester := range testers {
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- dname := tester.deployment.Name
- if err != nil {
- t.Fatalf("failed to create deployment %q: %v", dname, err)
- }
- // Wait for the deployment to be updated to revision 1
- if err = tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatalf("failed to update deployment %q to revision 1: %v", dname, err)
- }
- // Make sure the deployment completes while manually marking its pods as ready at the same time
- if err = tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatalf("deployment %q failed to complete: %v", dname, err)
- }
- // Get replicaset of the deployment
- newRS, err := tester.getNewReplicaSet()
- if err != nil {
- t.Fatalf("failed to get new replicaset of deployment %q: %v", dname, err)
- }
- if newRS == nil {
- t.Fatalf("unable to find new replicaset of deployment %q", dname)
- }
- // Store the replicaset for future usage
- rss = append(rss, newRS)
- }
- // Both deployments should proceed independently, so their respective replicaset should not be the same replicaset
- if rss[0].UID == rss[1].UID {
- t.Fatalf("overlapping deployments should not share the same replicaset")
- }
- // Scale only the first deployment by 1
- newReplicas := replicas + 1
- testers[0].deployment, err = testers[0].updateDeployment(func(update *apps.Deployment) {
- update.Spec.Replicas = &newReplicas
- })
- if err != nil {
- t.Fatalf("failed updating deployment %q: %v", firstDeploymentName, err)
- }
- // Make sure the deployment completes after scaling
- if err := testers[0].waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatalf("deployment %q failed to complete after scaling: %v", firstDeploymentName, err)
- }
- // Verify replicaset of both deployments has updated number of replicas
- for i, tester := range testers {
- rs, err := c.AppsV1().ReplicaSets(ns.Name).Get(context.TODO(), rss[i].Name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to get replicaset %q: %v", rss[i].Name, err)
- }
- if *rs.Spec.Replicas != *tester.deployment.Spec.Replicas {
- t.Errorf("expected replicaset %q of deployment %q has %d replicas, but found %d replicas", rs.Name, firstDeploymentName, *tester.deployment.Spec.Replicas, *rs.Spec.Replicas)
- }
- }
- }
- // Deployment should not block rollout when updating spec replica number and template at the same time.
- func TestScaledRolloutDeployment(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-scaled-rollout-deployment"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Create a deployment with rolling update strategy, max surge = 3, and max unavailable = 2
- var err error
- replicas := int32(10)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(name, ns.Name, replicas)}
- tester.deployment.Spec.Strategy.RollingUpdate.MaxSurge = intOrStrP(3)
- tester.deployment.Spec.Strategy.RollingUpdate.MaxUnavailable = intOrStrP(2)
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %q: %v", name, err)
- }
- if err = tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatal(err)
- }
- if err = tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatalf("deployment %q failed to complete: %v", name, err)
- }
- // Record current replicaset before starting new rollout
- firstRS, err := tester.expectNewReplicaSet()
- if err != nil {
- t.Fatal(err)
- }
- // Update the deployment with another new image but do not mark the pods as ready to block new replicaset
- fakeImage2 := "fakeimage2"
- tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
- update.Spec.Template.Spec.Containers[0].Image = fakeImage2
- })
- if err != nil {
- t.Fatalf("failed updating deployment %q: %v", name, err)
- }
- if err = tester.waitForDeploymentRevisionAndImage("2", fakeImage2); err != nil {
- t.Fatal(err)
- }
- // Verify the deployment has minimum available replicas after 2nd rollout
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Get(context.TODO(), name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to get deployment %q: %v", name, err)
- }
- minAvailableReplicas := deploymentutil.MinAvailable(tester.deployment)
- if tester.deployment.Status.AvailableReplicas < minAvailableReplicas {
- t.Fatalf("deployment %q does not have minimum number of available replicas after 2nd rollout", name)
- }
- // Wait for old replicaset of 1st rollout to have desired replicas
- firstRS, err = c.AppsV1().ReplicaSets(ns.Name).Get(context.TODO(), firstRS.Name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to get replicaset %q: %v", firstRS.Name, err)
- }
- if err = tester.waitRSStable(firstRS); err != nil {
- t.Fatal(err)
- }
- // Wait for new replicaset of 2nd rollout to have desired replicas
- secondRS, err := tester.expectNewReplicaSet()
- if err != nil {
- t.Fatal(err)
- }
- if err = tester.waitRSStable(secondRS); err != nil {
- t.Fatal(err)
- }
- // Scale up the deployment and update its image to another new image simultaneously (this time marks all pods as ready)
- newReplicas := int32(20)
- fakeImage3 := "fakeimage3"
- tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
- update.Spec.Replicas = &newReplicas
- update.Spec.Template.Spec.Containers[0].Image = fakeImage3
- })
- if err != nil {
- t.Fatalf("failed updating deployment %q: %v", name, err)
- }
- if err = tester.waitForDeploymentRevisionAndImage("3", fakeImage3); err != nil {
- t.Fatal(err)
- }
- if err = tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatalf("deployment %q failed to complete: %v", name, err)
- }
- // Verify every replicaset has correct desiredReplicas annotation after 3rd rollout
- thirdRS, err := deploymentutil.GetNewReplicaSet(tester.deployment, c.AppsV1())
- if err != nil {
- t.Fatalf("failed getting new revision 3 replicaset for deployment %q: %v", name, err)
- }
- rss := []*apps.ReplicaSet{firstRS, secondRS, thirdRS}
- for _, curRS := range rss {
- curRS, err = c.AppsV1().ReplicaSets(ns.Name).Get(context.TODO(), curRS.Name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to get replicaset when checking desired replicas annotation: %v", err)
- }
- desired, ok := deploymentutil.GetDesiredReplicasAnnotation(curRS)
- if !ok {
- t.Fatalf("failed to retrieve desiredReplicas annotation for replicaset %q", curRS.Name)
- }
- if desired != *(tester.deployment.Spec.Replicas) {
- t.Fatalf("unexpected desiredReplicas annotation for replicaset %q: expected %d, got %d", curRS.Name, *(tester.deployment.Spec.Replicas), desired)
- }
- }
- // Update the deployment with another new image but do not mark the pods as ready to block new replicaset
- fakeImage4 := "fakeimage4"
- tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
- update.Spec.Template.Spec.Containers[0].Image = fakeImage4
- })
- if err != nil {
- t.Fatalf("failed updating deployment %q: %v", name, err)
- }
- if err = tester.waitForDeploymentRevisionAndImage("4", fakeImage4); err != nil {
- t.Fatal(err)
- }
- // Verify the deployment has minimum available replicas after 4th rollout
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Get(context.TODO(), name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to get deployment %q: %v", name, err)
- }
- minAvailableReplicas = deploymentutil.MinAvailable(tester.deployment)
- if tester.deployment.Status.AvailableReplicas < minAvailableReplicas {
- t.Fatalf("deployment %q does not have minimum number of available replicas after 4th rollout", name)
- }
- // Wait for old replicaset of 3rd rollout to have desired replicas
- thirdRS, err = c.AppsV1().ReplicaSets(ns.Name).Get(context.TODO(), thirdRS.Name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to get replicaset %q: %v", thirdRS.Name, err)
- }
- if err = tester.waitRSStable(thirdRS); err != nil {
- t.Fatal(err)
- }
- // Wait for new replicaset of 4th rollout to have desired replicas
- fourthRS, err := tester.expectNewReplicaSet()
- if err != nil {
- t.Fatal(err)
- }
- if err = tester.waitRSStable(fourthRS); err != nil {
- t.Fatal(err)
- }
- // Scale down the deployment and update its image to another new image simultaneously (this time marks all pods as ready)
- newReplicas = int32(5)
- fakeImage5 := "fakeimage5"
- tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
- update.Spec.Replicas = &newReplicas
- update.Spec.Template.Spec.Containers[0].Image = fakeImage5
- })
- if err != nil {
- t.Fatalf("failed updating deployment %q: %v", name, err)
- }
- if err = tester.waitForDeploymentRevisionAndImage("5", fakeImage5); err != nil {
- t.Fatal(err)
- }
- if err = tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatalf("deployment %q failed to complete: %v", name, err)
- }
- // Verify every replicaset has correct desiredReplicas annotation after 5th rollout
- fifthRS, err := deploymentutil.GetNewReplicaSet(tester.deployment, c.AppsV1())
- if err != nil {
- t.Fatalf("failed getting new revision 5 replicaset for deployment %q: %v", name, err)
- }
- rss = []*apps.ReplicaSet{thirdRS, fourthRS, fifthRS}
- for _, curRS := range rss {
- curRS, err = c.AppsV1().ReplicaSets(ns.Name).Get(context.TODO(), curRS.Name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to get replicaset when checking desired replicas annotation: %v", err)
- }
- desired, ok := deploymentutil.GetDesiredReplicasAnnotation(curRS)
- if !ok {
- t.Fatalf("failed to retrieve desiredReplicas annotation for replicaset %q", curRS.Name)
- }
- if desired != *(tester.deployment.Spec.Replicas) {
- t.Fatalf("unexpected desiredReplicas annotation for replicaset %q: expected %d, got %d", curRS.Name, *(tester.deployment.Spec.Replicas), desired)
- }
- }
- }
- func TestSpecReplicasChange(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-spec-replicas-change"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- deploymentName := "deployment"
- replicas := int32(1)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(deploymentName, ns.Name, replicas)}
- tester.deployment.Spec.Strategy.Type = apps.RecreateDeploymentStrategyType
- tester.deployment.Spec.Strategy.RollingUpdate = nil
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %q: %v", deploymentName, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Scale up/down deployment and verify its replicaset has matching .spec.replicas
- if err = tester.scaleDeployment(2); err != nil {
- t.Fatal(err)
- }
- if err = tester.scaleDeployment(0); err != nil {
- t.Fatal(err)
- }
- if err = tester.scaleDeployment(1); err != nil {
- t.Fatal(err)
- }
- // Add a template annotation change to test deployment's status does update
- // without .spec.replicas change
- var oldGeneration int64
- tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
- oldGeneration = update.Generation
- update.Spec.RevisionHistoryLimit = pointer.Int32Ptr(4)
- })
- if err != nil {
- t.Fatalf("failed updating deployment %q: %v", tester.deployment.Name, err)
- }
- savedGeneration := tester.deployment.Generation
- if savedGeneration == oldGeneration {
- t.Fatalf("Failed to verify .Generation has incremented for deployment %q", deploymentName)
- }
- if err = tester.waitForObservedDeployment(savedGeneration); err != nil {
- t.Fatal(err)
- }
- }
- func TestDeploymentAvailableCondition(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-deployment-available-condition"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- deploymentName := "deployment"
- replicas := int32(10)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(deploymentName, ns.Name, replicas)}
- // Assign a high value to the deployment's minReadySeconds
- tester.deployment.Spec.MinReadySeconds = 3600
- // progressDeadlineSeconds must be greater than minReadySeconds
- tester.deployment.Spec.ProgressDeadlineSeconds = pointer.Int32Ptr(7200)
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %q: %v", deploymentName, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Wait for the deployment to be observed by the controller and has at least specified number of updated replicas
- if err = tester.waitForDeploymentUpdatedReplicasGTE(replicas); err != nil {
- t.Fatal(err)
- }
- // Wait for the deployment to have MinimumReplicasUnavailable reason because the pods are not marked as ready
- if err = tester.waitForDeploymentWithCondition(deploymentutil.MinimumReplicasUnavailable, apps.DeploymentAvailable); err != nil {
- t.Fatal(err)
- }
- // Verify all replicas fields of DeploymentStatus have desired counts
- if err = tester.checkDeploymentStatusReplicasFields(10, 10, 0, 0, 10); err != nil {
- t.Fatal(err)
- }
- // Mark the pods as ready without waiting for the deployment to complete
- if err = tester.markUpdatedPodsReadyWithoutComplete(); err != nil {
- t.Fatal(err)
- }
- // Wait for number of ready replicas to equal number of replicas.
- if err = tester.waitForReadyReplicas(); err != nil {
- t.Fatal(err)
- }
- // Wait for the deployment to still have MinimumReplicasUnavailable reason within minReadySeconds period
- if err = tester.waitForDeploymentWithCondition(deploymentutil.MinimumReplicasUnavailable, apps.DeploymentAvailable); err != nil {
- t.Fatal(err)
- }
- // Verify all replicas fields of DeploymentStatus have desired counts
- if err = tester.checkDeploymentStatusReplicasFields(10, 10, 10, 0, 10); err != nil {
- t.Fatal(err)
- }
- // Update the deployment's minReadySeconds to a small value
- tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
- update.Spec.MinReadySeconds = 1
- })
- if err != nil {
- t.Fatalf("failed updating deployment %q: %v", deploymentName, err)
- }
- // Wait for the deployment to notice minReadySeconds has changed
- if err := tester.waitForObservedDeployment(tester.deployment.Generation); err != nil {
- t.Fatal(err)
- }
- // Wait for the deployment to have MinimumReplicasAvailable reason after minReadySeconds period
- if err = tester.waitForDeploymentWithCondition(deploymentutil.MinimumReplicasAvailable, apps.DeploymentAvailable); err != nil {
- t.Fatal(err)
- }
- // Verify all replicas fields of DeploymentStatus have desired counts
- if err = tester.checkDeploymentStatusReplicasFields(10, 10, 10, 10, 0); err != nil {
- t.Fatal(err)
- }
- }
- // Wait for deployment to automatically patch incorrect ControllerRef of RS
- func testRSControllerRefPatch(t *testing.T, tester *deploymentTester, rs *apps.ReplicaSet, ownerReference *metav1.OwnerReference, expectedOwnerReferenceNum int) {
- ns := rs.Namespace
- rsClient := tester.c.AppsV1().ReplicaSets(ns)
- rs, err := tester.updateReplicaSet(rs.Name, func(update *apps.ReplicaSet) {
- update.OwnerReferences = []metav1.OwnerReference{*ownerReference}
- })
- if err != nil {
- t.Fatalf("failed to update replicaset %q: %v", rs.Name, err)
- }
- if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
- newRS, err := rsClient.Get(context.TODO(), rs.Name, metav1.GetOptions{})
- if err != nil {
- return false, err
- }
- return metav1.GetControllerOf(newRS) != nil, nil
- }); err != nil {
- t.Fatalf("failed to wait for controllerRef of the replicaset %q to become nil: %v", rs.Name, err)
- }
- newRS, err := rsClient.Get(context.TODO(), rs.Name, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("failed to obtain replicaset %q: %v", rs.Name, err)
- }
- controllerRef := metav1.GetControllerOf(newRS)
- if controllerRef.UID != tester.deployment.UID {
- t.Fatalf("controllerRef of replicaset %q has a different UID: Expected %v, got %v", newRS.Name, tester.deployment.UID, controllerRef.UID)
- }
- ownerReferenceNum := len(newRS.GetOwnerReferences())
- if ownerReferenceNum != expectedOwnerReferenceNum {
- t.Fatalf("unexpected number of owner references for replicaset %q: Expected %d, got %d", newRS.Name, expectedOwnerReferenceNum, ownerReferenceNum)
- }
- }
- func TestGeneralReplicaSetAdoption(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-general-replicaset-adoption"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- deploymentName := "deployment"
- replicas := int32(1)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(deploymentName, ns.Name, replicas)}
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %q: %v", deploymentName, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Wait for the Deployment to be updated to revision 1
- if err := tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatal(err)
- }
- // Ensure the deployment completes while marking its pods as ready simultaneously
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- // Get replicaset of the deployment
- rs, err := deploymentutil.GetNewReplicaSet(tester.deployment, c.AppsV1())
- if err != nil {
- t.Fatalf("failed to get replicaset of deployment %q: %v", deploymentName, err)
- }
- if rs == nil {
- t.Fatalf("unable to find replicaset of deployment %q", deploymentName)
- }
- // When the only OwnerReference of the RS points to another type of API object such as statefulset
- // with Controller=false, the deployment should add a second OwnerReference (ControllerRef) pointing to itself
- // with Controller=true
- var falseVar = false
- ownerReference := metav1.OwnerReference{UID: uuid.NewUUID(), APIVersion: "apps/v1", Kind: "StatefulSet", Name: deploymentName, Controller: &falseVar}
- testRSControllerRefPatch(t, tester, rs, &ownerReference, 2)
- // When the only OwnerReference of the RS points to the deployment with Controller=false,
- // the deployment should set Controller=true for the only OwnerReference
- ownerReference = metav1.OwnerReference{UID: tester.deployment.UID, APIVersion: "apps/v1", Kind: "Deployment", Name: deploymentName, Controller: &falseVar}
- testRSControllerRefPatch(t, tester, rs, &ownerReference, 1)
- }
- func testScalingUsingScaleSubresource(t *testing.T, tester *deploymentTester, replicas int32) {
- ns := tester.deployment.Namespace
- deploymentName := tester.deployment.Name
- deploymentClient := tester.c.AppsV1().Deployments(ns)
- deployment, err := deploymentClient.Get(context.TODO(), deploymentName, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("Failed to obtain deployment %q: %v", deploymentName, err)
- }
- scale, err := tester.c.AppsV1().Deployments(ns).GetScale(context.TODO(), deploymentName, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("Failed to obtain scale subresource for deployment %q: %v", deploymentName, err)
- }
- if scale.Spec.Replicas != *deployment.Spec.Replicas {
- t.Fatalf("Scale subresource for deployment %q does not match .Spec.Replicas: expected %d, got %d", deploymentName, *deployment.Spec.Replicas, scale.Spec.Replicas)
- }
- if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
- scale, err := tester.c.AppsV1().Deployments(ns).GetScale(context.TODO(), deploymentName, metav1.GetOptions{})
- if err != nil {
- return err
- }
- scale.Spec.Replicas = replicas
- _, err = tester.c.AppsV1().Deployments(ns).UpdateScale(context.TODO(), deploymentName, scale, metav1.UpdateOptions{})
- return err
- }); err != nil {
- t.Fatalf("Failed to set .Spec.Replicas of scale subresource for deployment %q: %v", deploymentName, err)
- }
- deployment, err = deploymentClient.Get(context.TODO(), deploymentName, metav1.GetOptions{})
- if err != nil {
- t.Fatalf("Failed to obtain deployment %q: %v", deploymentName, err)
- }
- if *deployment.Spec.Replicas != replicas {
- t.Fatalf(".Spec.Replicas of deployment %q does not match its scale subresource: expected %d, got %d", deploymentName, replicas, *deployment.Spec.Replicas)
- }
- }
- func TestDeploymentScaleSubresource(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-deployment-scale-subresource"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- deploymentName := "deployment"
- replicas := int32(2)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(deploymentName, ns.Name, replicas)}
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %q: %v", deploymentName, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Wait for the Deployment to be updated to revision 1
- if err := tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatal(err)
- }
- // Ensure the deployment completes while marking its pods as ready simultaneously
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- // Use scale subresource to scale the deployment up to 3
- testScalingUsingScaleSubresource(t, tester, 3)
- // Use the scale subresource to scale the deployment down to 0
- testScalingUsingScaleSubresource(t, tester, 0)
- }
- // This test verifies that the Deployment does orphan a ReplicaSet when the ReplicaSet's
- // .Labels field is changed to no longer match the Deployment's selector. It also partially
- // verifies that collision avoidance mechanism is triggered when a Deployment's new ReplicaSet
- // is orphaned, even without PodTemplateSpec change. Refer comment below for more info:
- // https://github.com/kubernetes/kubernetes/pull/59212#discussion_r166465113
- func TestReplicaSetOrphaningAndAdoptionWhenLabelsChange(t *testing.T) {
- s, closeFn, rm, dc, informers, c := dcSetup(t)
- defer closeFn()
- name := "test-replicaset-orphaning-and-adoption-when-labels-change"
- ns := framework.CreateTestingNamespace(name, s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- deploymentName := "deployment"
- replicas := int32(1)
- tester := &deploymentTester{t: t, c: c, deployment: newDeployment(deploymentName, ns.Name, replicas)}
- var err error
- tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("failed to create deployment %q: %v", deploymentName, err)
- }
- // Start informer and controllers
- stopCh := make(chan struct{})
- defer close(stopCh)
- informers.Start(stopCh)
- go rm.Run(5, stopCh)
- go dc.Run(5, stopCh)
- // Wait for the Deployment to be updated to revision 1
- if err := tester.waitForDeploymentRevisionAndImage("1", fakeImage); err != nil {
- t.Fatal(err)
- }
- // Ensure the deployment completes while marking its pods as ready simultaneously
- if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
- t.Fatal(err)
- }
- // Orphaning: deployment should remove OwnerReference from a RS when the RS's labels change to not match its labels
- // Get replicaset of the deployment
- rs, err := deploymentutil.GetNewReplicaSet(tester.deployment, c.AppsV1())
- if err != nil {
- t.Fatalf("failed to get replicaset of deployment %q: %v", deploymentName, err)
- }
- if rs == nil {
- t.Fatalf("unable to find replicaset of deployment %q", deploymentName)
- }
- // Verify controllerRef of the replicaset is not nil and pointing to the deployment
- controllerRef := metav1.GetControllerOf(rs)
- if controllerRef == nil {
- t.Fatalf("controllerRef of replicaset %q is nil", rs.Name)
- }
- if controllerRef.UID != tester.deployment.UID {
- t.Fatalf("controllerRef of replicaset %q has a different UID: Expected %v, got %v", rs.Name, tester.deployment.UID, controllerRef.UID)
- }
- // Change the replicaset's labels to not match the deployment's labels
- labelMap := map[string]string{"new-name": "new-test"}
- rs, err = tester.updateReplicaSet(rs.Name, func(update *apps.ReplicaSet) {
- update.Labels = labelMap
- })
- if err != nil {
- t.Fatalf("failed to update replicaset %q: %v", rs.Name, err)
- }
- // Wait for the controllerRef of the replicaset to become nil
- rsClient := tester.c.AppsV1().ReplicaSets(ns.Name)
- if err = wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
- rs, err = rsClient.Get(context.TODO(), rs.Name, metav1.GetOptions{})
- if err != nil {
- return false, err
- }
- return metav1.GetControllerOf(rs) == nil, nil
- }); err != nil {
- t.Fatalf("failed to wait for controllerRef of replicaset %q to become nil: %v", rs.Name, err)
- }
- // Wait for the deployment to create a new replicaset
- // This will trigger collision avoidance due to deterministic nature of replicaset name
- // i.e., the new replicaset will have a name with different hash to preserve name uniqueness
- var newRS *apps.ReplicaSet
- if err = wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
- newRS, err = deploymentutil.GetNewReplicaSet(tester.deployment, c.AppsV1())
- if err != nil {
- return false, fmt.Errorf("failed to get new replicaset of deployment %q after orphaning: %v", deploymentName, err)
- }
- return newRS != nil, nil
- }); err != nil {
- t.Fatalf("failed to wait for deployment %q to create a new replicaset after orphaning: %v", deploymentName, err)
- }
- if newRS.UID == rs.UID {
- t.Fatalf("expect deployment %q to create a new replicaset different from the orphaned one, but it isn't", deploymentName)
- }
- // Adoption: deployment should add controllerRef to a RS when the RS's labels change to match its labels
- // Change the old replicaset's labels to match the deployment's labels
- rs, err = tester.updateReplicaSet(rs.Name, func(update *apps.ReplicaSet) {
- update.Labels = testLabels()
- })
- if err != nil {
- t.Fatalf("failed to update replicaset %q: %v", rs.Name, err)
- }
- // Wait for the deployment to adopt the old replicaset
- if err = wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
- rs, err := rsClient.Get(context.TODO(), rs.Name, metav1.GetOptions{})
- if err != nil {
- return false, err
- }
- controllerRef = metav1.GetControllerOf(rs)
- return controllerRef != nil && controllerRef.UID == tester.deployment.UID, nil
- }); err != nil {
- t.Fatalf("failed waiting for replicaset adoption by deployment %q to complete: %v", deploymentName, err)
- }
- }
|