k8s.io/kubernetes@v1.29.3/pkg/controller/deployment/sync_test.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package deployment
    18  
    19  import (
    20  	"math"
    21  	"testing"
    22  	"time"
    23  
    24  	apps "k8s.io/api/apps/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/intstr"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	"k8s.io/client-go/informers"
    29  	"k8s.io/client-go/kubernetes/fake"
    30  	testclient "k8s.io/client-go/testing"
    31  	"k8s.io/client-go/tools/record"
    32  	"k8s.io/klog/v2/ktesting"
    33  	"k8s.io/kubernetes/pkg/controller"
    34  	deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
    35  	"k8s.io/utils/ptr"
    36  )
    37  
    38  func TestScale(t *testing.T) {
    39  	newTimestamp := metav1.Date(2016, 5, 20, 2, 0, 0, 0, time.UTC)
    40  	oldTimestamp := metav1.Date(2016, 5, 20, 1, 0, 0, 0, time.UTC)
    41  	olderTimestamp := metav1.Date(2016, 5, 20, 0, 0, 0, 0, time.UTC)
    42  
    43  	var updatedTemplate = func(replicas int32) *apps.Deployment {
    44  		d := newDeployment("foo", replicas, nil, nil, nil, map[string]string{"foo": "bar"})
    45  		d.Spec.Template.Labels["another"] = "label"
    46  		return d
    47  	}
    48  
    49  	tests := []struct {
    50  		name          string
    51  		deployment    *apps.Deployment
    52  		oldDeployment *apps.Deployment
    53  
    54  		newRS  *apps.ReplicaSet
    55  		oldRSs []*apps.ReplicaSet
    56  
    57  		expectedNew  *apps.ReplicaSet
    58  		expectedOld  []*apps.ReplicaSet
    59  		wasntUpdated map[string]bool
    60  
    61  		desiredReplicasAnnotations map[string]int32
    62  	}{
    63  		{
    64  			name:          "normal scaling event: 10 -> 12",
    65  			deployment:    newDeployment("foo", 12, nil, nil, nil, nil),
    66  			oldDeployment: newDeployment("foo", 10, nil, nil, nil, nil),
    67  
    68  			newRS:  rs("foo-v1", 10, nil, newTimestamp),
    69  			oldRSs: []*apps.ReplicaSet{},
    70  
    71  			expectedNew: rs("foo-v1", 12, nil, newTimestamp),
    72  			expectedOld: []*apps.ReplicaSet{},
    73  		},
    74  		{
    75  			name:          "normal scaling event: 10 -> 5",
    76  			deployment:    newDeployment("foo", 5, nil, nil, nil, nil),
    77  			oldDeployment: newDeployment("foo", 10, nil, nil, nil, nil),
    78  
    79  			newRS:  rs("foo-v1", 10, nil, newTimestamp),
    80  			oldRSs: []*apps.ReplicaSet{},
    81  
    82  			expectedNew: rs("foo-v1", 5, nil, newTimestamp),
    83  			expectedOld: []*apps.ReplicaSet{},
    84  		},
    85  		{
    86  			name:          "proportional scaling: 5 -> 10",
    87  			deployment:    newDeployment("foo", 10, nil, nil, nil, nil),
    88  			oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
    89  
    90  			newRS:  rs("foo-v2", 2, nil, newTimestamp),
    91  			oldRSs: []*apps.ReplicaSet{rs("foo-v1", 3, nil, oldTimestamp)},
    92  
    93  			expectedNew: rs("foo-v2", 4, nil, newTimestamp),
    94  			expectedOld: []*apps.ReplicaSet{rs("foo-v1", 6, nil, oldTimestamp)},
    95  		},
    96  		{
    97  			name:          "proportional scaling: 5 -> 3",
    98  			deployment:    newDeployment("foo", 3, nil, nil, nil, nil),
    99  			oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
   100  
   101  			newRS:  rs("foo-v2", 2, nil, newTimestamp),
   102  			oldRSs: []*apps.ReplicaSet{rs("foo-v1", 3, nil, oldTimestamp)},
   103  
   104  			expectedNew: rs("foo-v2", 1, nil, newTimestamp),
   105  			expectedOld: []*apps.ReplicaSet{rs("foo-v1", 2, nil, oldTimestamp)},
   106  		},
   107  		{
   108  			name:          "proportional scaling: 9 -> 4",
   109  			deployment:    newDeployment("foo", 4, nil, nil, nil, nil),
   110  			oldDeployment: newDeployment("foo", 9, nil, nil, nil, nil),
   111  
   112  			newRS:  rs("foo-v2", 8, nil, newTimestamp),
   113  			oldRSs: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
   114  
   115  			expectedNew: rs("foo-v2", 4, nil, newTimestamp),
   116  			expectedOld: []*apps.ReplicaSet{rs("foo-v1", 0, nil, oldTimestamp)},
   117  		},
   118  		{
   119  			name:          "proportional scaling: 7 -> 10",
   120  			deployment:    newDeployment("foo", 10, nil, nil, nil, nil),
   121  			oldDeployment: newDeployment("foo", 7, nil, nil, nil, nil),
   122  
   123  			newRS:  rs("foo-v3", 2, nil, newTimestamp),
   124  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 3, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
   125  
   126  			expectedNew: rs("foo-v3", 3, nil, newTimestamp),
   127  			expectedOld: []*apps.ReplicaSet{rs("foo-v2", 4, nil, oldTimestamp), rs("foo-v1", 3, nil, olderTimestamp)},
   128  		},
   129  		{
   130  			name:          "proportional scaling: 13 -> 8",
   131  			deployment:    newDeployment("foo", 8, nil, nil, nil, nil),
   132  			oldDeployment: newDeployment("foo", 13, nil, nil, nil, nil),
   133  
   134  			newRS:  rs("foo-v3", 2, nil, newTimestamp),
   135  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 8, nil, oldTimestamp), rs("foo-v1", 3, nil, olderTimestamp)},
   136  
   137  			expectedNew: rs("foo-v3", 1, nil, newTimestamp),
   138  			expectedOld: []*apps.ReplicaSet{rs("foo-v2", 5, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
   139  		},
   140  		// Scales up the new replica set.
   141  		{
   142  			name:          "leftover distribution: 3 -> 4",
   143  			deployment:    newDeployment("foo", 4, nil, nil, nil, nil),
   144  			oldDeployment: newDeployment("foo", 3, nil, nil, nil, nil),
   145  
   146  			newRS:  rs("foo-v3", 1, nil, newTimestamp),
   147  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
   148  
   149  			expectedNew: rs("foo-v3", 2, nil, newTimestamp),
   150  			expectedOld: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
   151  		},
   152  		// Scales down the older replica set.
   153  		{
   154  			name:          "leftover distribution: 3 -> 2",
   155  			deployment:    newDeployment("foo", 2, nil, nil, nil, nil),
   156  			oldDeployment: newDeployment("foo", 3, nil, nil, nil, nil),
   157  
   158  			newRS:  rs("foo-v3", 1, nil, newTimestamp),
   159  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
   160  
   161  			expectedNew: rs("foo-v3", 1, nil, newTimestamp),
   162  			expectedOld: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
   163  		},
   164  		// Scales up the latest replica set first.
   165  		{
   166  			name:          "proportional scaling (no new rs): 4 -> 5",
   167  			deployment:    newDeployment("foo", 5, nil, nil, nil, nil),
   168  			oldDeployment: newDeployment("foo", 4, nil, nil, nil, nil),
   169  
   170  			newRS:  nil,
   171  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
   172  
   173  			expectedNew: nil,
   174  			expectedOld: []*apps.ReplicaSet{rs("foo-v2", 3, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
   175  		},
   176  		// Scales down to zero
   177  		{
   178  			name:          "proportional scaling: 6 -> 0",
   179  			deployment:    newDeployment("foo", 0, nil, nil, nil, nil),
   180  			oldDeployment: newDeployment("foo", 6, nil, nil, nil, nil),
   181  
   182  			newRS:  rs("foo-v3", 3, nil, newTimestamp),
   183  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
   184  
   185  			expectedNew: rs("foo-v3", 0, nil, newTimestamp),
   186  			expectedOld: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
   187  		},
   188  		// Scales up from zero
   189  		{
   190  			name:          "proportional scaling: 0 -> 6",
   191  			deployment:    newDeployment("foo", 6, nil, nil, nil, nil),
   192  			oldDeployment: newDeployment("foo", 6, nil, nil, nil, nil),
   193  
   194  			newRS:  rs("foo-v3", 0, nil, newTimestamp),
   195  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
   196  
   197  			expectedNew:  rs("foo-v3", 6, nil, newTimestamp),
   198  			expectedOld:  []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
   199  			wasntUpdated: map[string]bool{"foo-v2": true, "foo-v1": true},
   200  		},
   201  		// Scenario: deployment.spec.replicas == 3 ( foo-v1.spec.replicas == foo-v2.spec.replicas == foo-v3.spec.replicas == 1 )
   202  		// Deployment is scaled to 5. foo-v3.spec.replicas and foo-v2.spec.replicas should increment by 1 but foo-v2 fails to
   203  		// update.
   204  		{
   205  			name:          "failed rs update",
   206  			deployment:    newDeployment("foo", 5, nil, nil, nil, nil),
   207  			oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
   208  
   209  			newRS:  rs("foo-v3", 2, nil, newTimestamp),
   210  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
   211  
   212  			expectedNew:  rs("foo-v3", 2, nil, newTimestamp),
   213  			expectedOld:  []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
   214  			wasntUpdated: map[string]bool{"foo-v3": true, "foo-v1": true},
   215  
   216  			desiredReplicasAnnotations: map[string]int32{"foo-v2": int32(3)},
   217  		},
   218  		{
   219  			name:          "deployment with surge pods",
   220  			deployment:    newDeployment("foo", 20, nil, ptr.To(intstr.FromInt32(2)), nil, nil),
   221  			oldDeployment: newDeployment("foo", 10, nil, ptr.To(intstr.FromInt32(2)), nil, nil),
   222  
   223  			newRS:  rs("foo-v2", 6, nil, newTimestamp),
   224  			oldRSs: []*apps.ReplicaSet{rs("foo-v1", 6, nil, oldTimestamp)},
   225  
   226  			expectedNew: rs("foo-v2", 11, nil, newTimestamp),
   227  			expectedOld: []*apps.ReplicaSet{rs("foo-v1", 11, nil, oldTimestamp)},
   228  		},
   229  		{
   230  			name:          "change both surge and size",
   231  			deployment:    newDeployment("foo", 50, nil, ptr.To(intstr.FromInt32(6)), nil, nil),
   232  			oldDeployment: newDeployment("foo", 10, nil, ptr.To(intstr.FromInt32(3)), nil, nil),
   233  
   234  			newRS:  rs("foo-v2", 5, nil, newTimestamp),
   235  			oldRSs: []*apps.ReplicaSet{rs("foo-v1", 8, nil, oldTimestamp)},
   236  
   237  			expectedNew: rs("foo-v2", 22, nil, newTimestamp),
   238  			expectedOld: []*apps.ReplicaSet{rs("foo-v1", 34, nil, oldTimestamp)},
   239  		},
   240  		{
   241  			name:          "change both size and template",
   242  			deployment:    updatedTemplate(14),
   243  			oldDeployment: newDeployment("foo", 10, nil, nil, nil, map[string]string{"foo": "bar"}),
   244  
   245  			newRS:  nil,
   246  			oldRSs: []*apps.ReplicaSet{rs("foo-v2", 7, nil, newTimestamp), rs("foo-v1", 3, nil, oldTimestamp)},
   247  
   248  			expectedNew: nil,
   249  			expectedOld: []*apps.ReplicaSet{rs("foo-v2", 10, nil, newTimestamp), rs("foo-v1", 4, nil, oldTimestamp)},
   250  		},
   251  		{
   252  			name:          "saturated but broken new replica set does not affect old pods",
   253  			deployment:    newDeployment("foo", 2, nil, ptr.To(intstr.FromInt32(1)), ptr.To(intstr.FromInt32(1)), nil),
   254  			oldDeployment: newDeployment("foo", 2, nil, ptr.To(intstr.FromInt32(1)), ptr.To(intstr.FromInt32(1)), nil),
   255  
   256  			newRS: func() *apps.ReplicaSet {
   257  				rs := rs("foo-v2", 2, nil, newTimestamp)
   258  				rs.Status.AvailableReplicas = 0
   259  				return rs
   260  			}(),
   261  			oldRSs: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
   262  
   263  			expectedNew: rs("foo-v2", 2, nil, newTimestamp),
   264  			expectedOld: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
   265  		},
   266  	}
   267  
   268  	for _, test := range tests {
   269  		t.Run(test.name, func(t *testing.T) {
   270  			_ = olderTimestamp
   271  			t.Log(test.name)
   272  			fake := fake.Clientset{}
   273  			dc := &DeploymentController{
   274  				client:        &fake,
   275  				eventRecorder: &record.FakeRecorder{},
   276  			}
   277  
   278  			if test.newRS != nil {
   279  				desiredReplicas := *(test.oldDeployment.Spec.Replicas)
   280  				if desired, ok := test.desiredReplicasAnnotations[test.newRS.Name]; ok {
   281  					desiredReplicas = desired
   282  				}
   283  				deploymentutil.SetReplicasAnnotations(test.newRS, desiredReplicas, desiredReplicas+deploymentutil.MaxSurge(*test.oldDeployment))
   284  			}
   285  			for i := range test.oldRSs {
   286  				rs := test.oldRSs[i]
   287  				if rs == nil {
   288  					continue
   289  				}
   290  				desiredReplicas := *(test.oldDeployment.Spec.Replicas)
   291  				if desired, ok := test.desiredReplicasAnnotations[rs.Name]; ok {
   292  					desiredReplicas = desired
   293  				}
   294  				deploymentutil.SetReplicasAnnotations(rs, desiredReplicas, desiredReplicas+deploymentutil.MaxSurge(*test.oldDeployment))
   295  			}
   296  
   297  			_, ctx := ktesting.NewTestContext(t)
   298  
   299  			if err := dc.scale(ctx, test.deployment, test.newRS, test.oldRSs); err != nil {
   300  				t.Errorf("%s: unexpected error: %v", test.name, err)
   301  				return
   302  			}
   303  
   304  			// Construct the nameToSize map that will hold all the sizes we got our of tests
   305  			// Skip updating the map if the replica set wasn't updated since there will be
   306  			// no update action for it.
   307  			nameToSize := make(map[string]int32)
   308  			if test.newRS != nil {
   309  				nameToSize[test.newRS.Name] = *(test.newRS.Spec.Replicas)
   310  			}
   311  			for i := range test.oldRSs {
   312  				rs := test.oldRSs[i]
   313  				nameToSize[rs.Name] = *(rs.Spec.Replicas)
   314  			}
   315  			// Get all the UPDATE actions and update nameToSize with all the updated sizes.
   316  			for _, action := range fake.Actions() {
   317  				rs := action.(testclient.UpdateAction).GetObject().(*apps.ReplicaSet)
   318  				if !test.wasntUpdated[rs.Name] {
   319  					nameToSize[rs.Name] = *(rs.Spec.Replicas)
   320  				}
   321  			}
   322  
   323  			if test.expectedNew != nil && test.newRS != nil && *(test.expectedNew.Spec.Replicas) != nameToSize[test.newRS.Name] {
   324  				t.Errorf("%s: expected new replicas: %d, got: %d", test.name, *(test.expectedNew.Spec.Replicas), nameToSize[test.newRS.Name])
   325  				return
   326  			}
   327  			if len(test.expectedOld) != len(test.oldRSs) {
   328  				t.Errorf("%s: expected %d old replica sets, got %d", test.name, len(test.expectedOld), len(test.oldRSs))
   329  				return
   330  			}
   331  			for n := range test.oldRSs {
   332  				rs := test.oldRSs[n]
   333  				expected := test.expectedOld[n]
   334  				if *(expected.Spec.Replicas) != nameToSize[rs.Name] {
   335  					t.Errorf("%s: expected old (%s) replicas: %d, got: %d", test.name, rs.Name, *(expected.Spec.Replicas), nameToSize[rs.Name])
   336  				}
   337  			}
   338  		})
   339  	}
   340  }
   341  
   342  func TestDeploymentController_cleanupDeployment(t *testing.T) {
   343  	selector := map[string]string{"foo": "bar"}
   344  	alreadyDeleted := newRSWithStatus("foo-1", 0, 0, selector)
   345  	now := metav1.Now()
   346  	alreadyDeleted.DeletionTimestamp = &now
   347  
   348  	tests := []struct {
   349  		oldRSs               []*apps.ReplicaSet
   350  		revisionHistoryLimit int32
   351  		expectedDeletions    int
   352  	}{
   353  		{
   354  			oldRSs: []*apps.ReplicaSet{
   355  				newRSWithStatus("foo-1", 0, 0, selector),
   356  				newRSWithStatus("foo-2", 0, 0, selector),
   357  				newRSWithStatus("foo-3", 0, 0, selector),
   358  			},
   359  			revisionHistoryLimit: 1,
   360  			expectedDeletions:    2,
   361  		},
   362  		{
   363  			// Only delete the replica set with Spec.Replicas = Status.Replicas = 0.
   364  			oldRSs: []*apps.ReplicaSet{
   365  				newRSWithStatus("foo-1", 0, 0, selector),
   366  				newRSWithStatus("foo-2", 0, 1, selector),
   367  				newRSWithStatus("foo-3", 1, 0, selector),
   368  				newRSWithStatus("foo-4", 1, 1, selector),
   369  			},
   370  			revisionHistoryLimit: 0,
   371  			expectedDeletions:    1,
   372  		},
   373  
   374  		{
   375  			oldRSs: []*apps.ReplicaSet{
   376  				newRSWithStatus("foo-1", 0, 0, selector),
   377  				newRSWithStatus("foo-2", 0, 0, selector),
   378  			},
   379  			revisionHistoryLimit: 0,
   380  			expectedDeletions:    2,
   381  		},
   382  		{
   383  			oldRSs: []*apps.ReplicaSet{
   384  				newRSWithStatus("foo-1", 1, 1, selector),
   385  				newRSWithStatus("foo-2", 1, 1, selector),
   386  			},
   387  			revisionHistoryLimit: 0,
   388  			expectedDeletions:    0,
   389  		},
   390  		{
   391  			oldRSs: []*apps.ReplicaSet{
   392  				alreadyDeleted,
   393  			},
   394  			revisionHistoryLimit: 0,
   395  			expectedDeletions:    0,
   396  		},
   397  		{
   398  			// with unlimited revisionHistoryLimit
   399  			oldRSs: []*apps.ReplicaSet{
   400  				newRSWithStatus("foo-1", 0, 0, selector),
   401  				newRSWithStatus("foo-2", 0, 0, selector),
   402  				newRSWithStatus("foo-3", 0, 0, selector),
   403  			},
   404  			revisionHistoryLimit: math.MaxInt32,
   405  			expectedDeletions:    0,
   406  		},
   407  	}
   408  
   409  	for i := range tests {
   410  		test := tests[i]
   411  		t.Logf("scenario %d", i)
   412  
   413  		_, ctx := ktesting.NewTestContext(t)
   414  
   415  		fake := &fake.Clientset{}
   416  		informers := informers.NewSharedInformerFactory(fake, controller.NoResyncPeriodFunc())
   417  		controller, err := NewDeploymentController(ctx, informers.Apps().V1().Deployments(), informers.Apps().V1().ReplicaSets(), informers.Core().V1().Pods(), fake)
   418  		if err != nil {
   419  			t.Fatalf("error creating Deployment controller: %v", err)
   420  		}
   421  
   422  		controller.eventRecorder = &record.FakeRecorder{}
   423  		controller.dListerSynced = alwaysReady
   424  		controller.rsListerSynced = alwaysReady
   425  		controller.podListerSynced = alwaysReady
   426  		for _, rs := range test.oldRSs {
   427  			informers.Apps().V1().ReplicaSets().Informer().GetIndexer().Add(rs)
   428  		}
   429  
   430  		stopCh := make(chan struct{})
   431  		defer close(stopCh)
   432  		informers.Start(stopCh)
   433  		informers.WaitForCacheSync(stopCh)
   434  
   435  		t.Logf(" &test.revisionHistoryLimit: %d", test.revisionHistoryLimit)
   436  		d := newDeployment("foo", 1, &test.revisionHistoryLimit, nil, nil, map[string]string{"foo": "bar"})
   437  		controller.cleanupDeployment(ctx, test.oldRSs, d)
   438  
   439  		gotDeletions := 0
   440  		for _, action := range fake.Actions() {
   441  			if action.GetVerb() == "delete" {
   442  				gotDeletions++
   443  			}
   444  		}
   445  		if gotDeletions != test.expectedDeletions {
   446  			t.Errorf("expect %v old replica sets been deleted, but got %v", test.expectedDeletions, gotDeletions)
   447  			continue
   448  		}
   449  	}
   450  }
   451  
   452  func TestDeploymentController_cleanupDeploymentOrder(t *testing.T) {
   453  	selector := map[string]string{"foo": "bar"}
   454  	now := metav1.Now()
   455  	duration := time.Minute
   456  
   457  	newRSWithRevisionAndCreationTimestamp := func(name string, replicas int32, selector map[string]string, timestamp time.Time, revision string) *apps.ReplicaSet {
   458  		rs := rs(name, replicas, selector, metav1.NewTime(timestamp))
   459  		if revision != "" {
   460  			rs.Annotations = map[string]string{
   461  				deploymentutil.RevisionAnnotation: revision,
   462  			}
   463  		}
   464  		rs.Status = apps.ReplicaSetStatus{
   465  			Replicas: int32(replicas),
   466  		}
   467  		return rs
   468  	}
   469  
   470  	// for all test cases, creationTimestamp order keeps as: rs1 < rs2 < rs3 < r4
   471  	tests := []struct {
   472  		oldRSs               []*apps.ReplicaSet
   473  		revisionHistoryLimit int32
   474  		expectedDeletedRSs   sets.String
   475  	}{
   476  		{
   477  			// revision order: rs1 < rs2, delete rs1
   478  			oldRSs: []*apps.ReplicaSet{
   479  				newRSWithRevisionAndCreationTimestamp("foo-1", 0, selector, now.Add(-1*duration), "1"),
   480  				newRSWithRevisionAndCreationTimestamp("foo-2", 0, selector, now.Time, "2"),
   481  			},
   482  			revisionHistoryLimit: 1,
   483  			expectedDeletedRSs:   sets.NewString("foo-1"),
   484  		},
   485  		{
   486  			// revision order: rs2 < rs1, delete rs2
   487  			oldRSs: []*apps.ReplicaSet{
   488  				newRSWithRevisionAndCreationTimestamp("foo-1", 0, selector, now.Add(-1*duration), "2"),
   489  				newRSWithRevisionAndCreationTimestamp("foo-2", 0, selector, now.Time, "1"),
   490  			},
   491  			revisionHistoryLimit: 1,
   492  			expectedDeletedRSs:   sets.NewString("foo-2"),
   493  		},
   494  		{
   495  			// rs1 has revision but rs2 doesn't have revision, delete rs2
   496  			oldRSs: []*apps.ReplicaSet{
   497  				newRSWithRevisionAndCreationTimestamp("foo-1", 0, selector, now.Add(-1*duration), "1"),
   498  				newRSWithRevisionAndCreationTimestamp("foo-2", 0, selector, now.Time, ""),
   499  			},
   500  			revisionHistoryLimit: 1,
   501  			expectedDeletedRSs:   sets.NewString("foo-2"),
   502  		},
   503  		{
   504  			// rs1 doesn't have revision while rs2 has revision, delete rs1
   505  			oldRSs: []*apps.ReplicaSet{
   506  				newRSWithRevisionAndCreationTimestamp("foo-1", 0, selector, now.Add(-1*duration), ""),
   507  				newRSWithRevisionAndCreationTimestamp("foo-2", 0, selector, now.Time, "2"),
   508  			},
   509  			revisionHistoryLimit: 1,
   510  			expectedDeletedRSs:   sets.NewString("foo-1"),
   511  		},
   512  		{
   513  			// revision order: rs1 < rs2 < r3, but rs1 has replicas, delete rs2
   514  			oldRSs: []*apps.ReplicaSet{
   515  				newRSWithRevisionAndCreationTimestamp("foo-1", 1, selector, now.Add(-1*duration), "1"),
   516  				newRSWithRevisionAndCreationTimestamp("foo-2", 0, selector, now.Time, "2"),
   517  				newRSWithRevisionAndCreationTimestamp("foo-3", 0, selector, now.Add(duration), "3"),
   518  			},
   519  			revisionHistoryLimit: 1,
   520  			expectedDeletedRSs:   sets.NewString("foo-2"),
   521  		},
   522  		{
   523  			// revision order: rs1 < rs2 < r3, both rs1 && rs2 have replicas, don't delete
   524  			oldRSs: []*apps.ReplicaSet{
   525  				newRSWithRevisionAndCreationTimestamp("foo-1", 1, selector, now.Add(-1*duration), "1"),
   526  				newRSWithRevisionAndCreationTimestamp("foo-2", 1, selector, now.Time, "2"),
   527  				newRSWithRevisionAndCreationTimestamp("foo-3", 0, selector, now.Add(duration), "3"),
   528  			},
   529  			revisionHistoryLimit: 1,
   530  			expectedDeletedRSs:   sets.NewString(),
   531  		},
   532  		{
   533  			// revision order: rs2 < rs4 < rs1 < rs3, delete rs2 && rs4
   534  			oldRSs: []*apps.ReplicaSet{
   535  				newRSWithRevisionAndCreationTimestamp("foo-1", 0, selector, now.Add(-1*duration), "3"),
   536  				newRSWithRevisionAndCreationTimestamp("foo-2", 0, selector, now.Time, "1"),
   537  				newRSWithRevisionAndCreationTimestamp("foo-3", 0, selector, now.Add(duration), "4"),
   538  				newRSWithRevisionAndCreationTimestamp("foo-4", 0, selector, now.Add(2*duration), "2"),
   539  			},
   540  			revisionHistoryLimit: 2,
   541  			expectedDeletedRSs:   sets.NewString("foo-2", "foo-4"),
   542  		},
   543  	}
   544  
   545  	for i := range tests {
   546  		test := tests[i]
   547  		t.Logf("scenario %d", i)
   548  
   549  		_, ctx := ktesting.NewTestContext(t)
   550  
   551  		fake := &fake.Clientset{}
   552  		informers := informers.NewSharedInformerFactory(fake, controller.NoResyncPeriodFunc())
   553  		controller, err := NewDeploymentController(ctx, informers.Apps().V1().Deployments(), informers.Apps().V1().ReplicaSets(), informers.Core().V1().Pods(), fake)
   554  		if err != nil {
   555  			t.Fatalf("error creating Deployment controller: %v", err)
   556  		}
   557  
   558  		controller.eventRecorder = &record.FakeRecorder{}
   559  		controller.dListerSynced = alwaysReady
   560  		controller.rsListerSynced = alwaysReady
   561  		controller.podListerSynced = alwaysReady
   562  		for _, rs := range test.oldRSs {
   563  			informers.Apps().V1().ReplicaSets().Informer().GetIndexer().Add(rs)
   564  		}
   565  
   566  		stopCh := make(chan struct{})
   567  		defer close(stopCh)
   568  		informers.Start(stopCh)
   569  
   570  		d := newDeployment("foo", 1, &test.revisionHistoryLimit, nil, nil, map[string]string{"foo": "bar"})
   571  		controller.cleanupDeployment(ctx, test.oldRSs, d)
   572  
   573  		deletedRSs := sets.String{}
   574  		for _, action := range fake.Actions() {
   575  			deleteAction, ok := action.(testclient.DeleteActionImpl)
   576  			if !ok {
   577  				t.Logf("Found not-delete action with verb %v. Ignoring.", action.GetVerb())
   578  				continue
   579  			}
   580  
   581  			if deleteAction.GetResource().Resource != "replicasets" {
   582  				continue
   583  			}
   584  
   585  			deletedRSs.Insert(deleteAction.GetName())
   586  		}
   587  		t.Logf("&test.revisionHistoryLimit: %d, &test.deletedReplicaSets: %v", test.revisionHistoryLimit, deletedRSs)
   588  
   589  		if !test.expectedDeletedRSs.Equal(deletedRSs) {
   590  			t.Errorf("expect to delete old replica sets %v, but got %v", test.expectedDeletedRSs, deletedRSs)
   591  			continue
   592  		}
   593  	}
   594  }