k8s.io/kubernetes@v1.29.3/pkg/controller/deployment/rolling_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 "context" 21 "testing" 22 23 apps "k8s.io/api/apps/v1" 24 "k8s.io/apimachinery/pkg/util/intstr" 25 "k8s.io/client-go/kubernetes/fake" 26 core "k8s.io/client-go/testing" 27 "k8s.io/client-go/tools/record" 28 "k8s.io/klog/v2/ktesting" 29 ) 30 31 func TestDeploymentController_reconcileNewReplicaSet(t *testing.T) { 32 tests := []struct { 33 deploymentReplicas int32 34 maxSurge intstr.IntOrString 35 oldReplicas int32 36 newReplicas int32 37 scaleExpected bool 38 expectedNewReplicas int32 39 }{ 40 { 41 // Should not scale up. 42 deploymentReplicas: 10, 43 maxSurge: intstr.FromInt32(0), 44 oldReplicas: 10, 45 newReplicas: 0, 46 scaleExpected: false, 47 }, 48 { 49 deploymentReplicas: 10, 50 maxSurge: intstr.FromInt32(2), 51 oldReplicas: 10, 52 newReplicas: 0, 53 scaleExpected: true, 54 expectedNewReplicas: 2, 55 }, 56 { 57 deploymentReplicas: 10, 58 maxSurge: intstr.FromInt32(2), 59 oldReplicas: 5, 60 newReplicas: 0, 61 scaleExpected: true, 62 expectedNewReplicas: 7, 63 }, 64 { 65 deploymentReplicas: 10, 66 maxSurge: intstr.FromInt32(2), 67 oldReplicas: 10, 68 newReplicas: 2, 69 scaleExpected: false, 70 }, 71 { 72 // Should scale down. 73 deploymentReplicas: 10, 74 maxSurge: intstr.FromInt32(2), 75 oldReplicas: 2, 76 newReplicas: 11, 77 scaleExpected: true, 78 expectedNewReplicas: 10, 79 }, 80 } 81 82 for i := range tests { 83 test := tests[i] 84 t.Logf("executing scenario %d", i) 85 newRS := rs("foo-v2", test.newReplicas, nil, noTimestamp) 86 oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp) 87 allRSs := []*apps.ReplicaSet{newRS, oldRS} 88 maxUnavailable := intstr.FromInt32(0) 89 deployment := newDeployment("foo", test.deploymentReplicas, nil, &test.maxSurge, &maxUnavailable, map[string]string{"foo": "bar"}) 90 fake := fake.Clientset{} 91 controller := &DeploymentController{ 92 client: &fake, 93 eventRecorder: &record.FakeRecorder{}, 94 } 95 _, ctx := ktesting.NewTestContext(t) 96 ctx, cancel := context.WithCancel(ctx) 97 defer cancel() 98 scaled, err := controller.reconcileNewReplicaSet(ctx, allRSs, newRS, deployment) 99 if err != nil { 100 t.Errorf("unexpected error: %v", err) 101 continue 102 } 103 if !test.scaleExpected { 104 if scaled || len(fake.Actions()) > 0 { 105 t.Errorf("unexpected scaling: %v", fake.Actions()) 106 } 107 continue 108 } 109 if test.scaleExpected && !scaled { 110 t.Errorf("expected scaling to occur") 111 continue 112 } 113 if len(fake.Actions()) != 1 { 114 t.Errorf("expected 1 action during scale, got: %v", fake.Actions()) 115 continue 116 } 117 updated := fake.Actions()[0].(core.UpdateAction).GetObject().(*apps.ReplicaSet) 118 if e, a := test.expectedNewReplicas, *(updated.Spec.Replicas); e != a { 119 t.Errorf("expected update to %d replicas, got %d", e, a) 120 } 121 } 122 } 123 124 func TestDeploymentController_reconcileOldReplicaSets(t *testing.T) { 125 tests := []struct { 126 deploymentReplicas int32 127 maxUnavailable intstr.IntOrString 128 oldReplicas int32 129 newReplicas int32 130 readyPodsFromOldRS int 131 readyPodsFromNewRS int 132 scaleExpected bool 133 expectedOldReplicas int32 134 }{ 135 { 136 deploymentReplicas: 10, 137 maxUnavailable: intstr.FromInt32(0), 138 oldReplicas: 10, 139 newReplicas: 0, 140 readyPodsFromOldRS: 10, 141 readyPodsFromNewRS: 0, 142 scaleExpected: true, 143 expectedOldReplicas: 9, 144 }, 145 { 146 deploymentReplicas: 10, 147 maxUnavailable: intstr.FromInt32(2), 148 oldReplicas: 10, 149 newReplicas: 0, 150 readyPodsFromOldRS: 10, 151 readyPodsFromNewRS: 0, 152 scaleExpected: true, 153 expectedOldReplicas: 8, 154 }, 155 { // expect unhealthy replicas from old replica sets been cleaned up 156 deploymentReplicas: 10, 157 maxUnavailable: intstr.FromInt32(2), 158 oldReplicas: 10, 159 newReplicas: 0, 160 readyPodsFromOldRS: 8, 161 readyPodsFromNewRS: 0, 162 scaleExpected: true, 163 expectedOldReplicas: 8, 164 }, 165 { // expect 1 unhealthy replica from old replica sets been cleaned up, and 1 ready pod been scaled down 166 deploymentReplicas: 10, 167 maxUnavailable: intstr.FromInt32(2), 168 oldReplicas: 10, 169 newReplicas: 0, 170 readyPodsFromOldRS: 9, 171 readyPodsFromNewRS: 0, 172 scaleExpected: true, 173 expectedOldReplicas: 8, 174 }, 175 { // the unavailable pods from the newRS would not make us scale down old RSs in a further step 176 deploymentReplicas: 10, 177 maxUnavailable: intstr.FromInt32(2), 178 oldReplicas: 8, 179 newReplicas: 2, 180 readyPodsFromOldRS: 8, 181 readyPodsFromNewRS: 0, 182 scaleExpected: false, 183 }, 184 } 185 for i := range tests { 186 test := tests[i] 187 t.Logf("executing scenario %d", i) 188 189 newSelector := map[string]string{"foo": "new"} 190 oldSelector := map[string]string{"foo": "old"} 191 newRS := rs("foo-new", test.newReplicas, newSelector, noTimestamp) 192 newRS.Status.AvailableReplicas = int32(test.readyPodsFromNewRS) 193 oldRS := rs("foo-old", test.oldReplicas, oldSelector, noTimestamp) 194 oldRS.Status.AvailableReplicas = int32(test.readyPodsFromOldRS) 195 oldRSs := []*apps.ReplicaSet{oldRS} 196 allRSs := []*apps.ReplicaSet{oldRS, newRS} 197 maxSurge := intstr.FromInt32(0) 198 deployment := newDeployment("foo", test.deploymentReplicas, nil, &maxSurge, &test.maxUnavailable, newSelector) 199 fakeClientset := fake.Clientset{} 200 controller := &DeploymentController{ 201 client: &fakeClientset, 202 eventRecorder: &record.FakeRecorder{}, 203 } 204 _, ctx := ktesting.NewTestContext(t) 205 scaled, err := controller.reconcileOldReplicaSets(ctx, allRSs, oldRSs, newRS, deployment) 206 if err != nil { 207 t.Errorf("unexpected error: %v", err) 208 continue 209 } 210 if !test.scaleExpected && scaled { 211 t.Errorf("unexpected scaling: %v", fakeClientset.Actions()) 212 } 213 if test.scaleExpected && !scaled { 214 t.Errorf("expected scaling to occur") 215 continue 216 } 217 continue 218 } 219 } 220 221 func TestDeploymentController_cleanupUnhealthyReplicas(t *testing.T) { 222 tests := []struct { 223 oldReplicas int32 224 readyPods int 225 unHealthyPods int 226 maxCleanupCount int 227 cleanupCountExpected int 228 }{ 229 { 230 oldReplicas: 10, 231 readyPods: 8, 232 unHealthyPods: 2, 233 maxCleanupCount: 1, 234 cleanupCountExpected: 1, 235 }, 236 { 237 oldReplicas: 10, 238 readyPods: 8, 239 unHealthyPods: 2, 240 maxCleanupCount: 3, 241 cleanupCountExpected: 2, 242 }, 243 { 244 oldReplicas: 10, 245 readyPods: 8, 246 unHealthyPods: 2, 247 maxCleanupCount: 0, 248 cleanupCountExpected: 0, 249 }, 250 { 251 oldReplicas: 10, 252 readyPods: 10, 253 unHealthyPods: 0, 254 maxCleanupCount: 3, 255 cleanupCountExpected: 0, 256 }, 257 } 258 259 for i, test := range tests { 260 t.Logf("executing scenario %d", i) 261 oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp) 262 oldRS.Status.AvailableReplicas = int32(test.readyPods) 263 oldRSs := []*apps.ReplicaSet{oldRS} 264 maxSurge := intstr.FromInt32(2) 265 maxUnavailable := intstr.FromInt32(2) 266 deployment := newDeployment("foo", 10, nil, &maxSurge, &maxUnavailable, nil) 267 fakeClientset := fake.Clientset{} 268 269 controller := &DeploymentController{ 270 client: &fakeClientset, 271 eventRecorder: &record.FakeRecorder{}, 272 } 273 _, ctx := ktesting.NewTestContext(t) 274 _, cleanupCount, err := controller.cleanupUnhealthyReplicas(ctx, oldRSs, deployment, int32(test.maxCleanupCount)) 275 if err != nil { 276 t.Errorf("unexpected error: %v", err) 277 continue 278 } 279 if int(cleanupCount) != test.cleanupCountExpected { 280 t.Errorf("expected %v unhealthy replicas been cleaned up, got %v", test.cleanupCountExpected, cleanupCount) 281 continue 282 } 283 } 284 } 285 286 func TestDeploymentController_scaleDownOldReplicaSetsForRollingUpdate(t *testing.T) { 287 tests := []struct { 288 deploymentReplicas int32 289 maxUnavailable intstr.IntOrString 290 readyPods int 291 oldReplicas int32 292 scaleExpected bool 293 expectedOldReplicas int32 294 }{ 295 { 296 deploymentReplicas: 10, 297 maxUnavailable: intstr.FromInt32(0), 298 readyPods: 10, 299 oldReplicas: 10, 300 scaleExpected: true, 301 expectedOldReplicas: 9, 302 }, 303 { 304 deploymentReplicas: 10, 305 maxUnavailable: intstr.FromInt32(2), 306 readyPods: 10, 307 oldReplicas: 10, 308 scaleExpected: true, 309 expectedOldReplicas: 8, 310 }, 311 { 312 deploymentReplicas: 10, 313 maxUnavailable: intstr.FromInt32(2), 314 readyPods: 8, 315 oldReplicas: 10, 316 scaleExpected: false, 317 }, 318 { 319 deploymentReplicas: 10, 320 maxUnavailable: intstr.FromInt32(2), 321 readyPods: 10, 322 oldReplicas: 0, 323 scaleExpected: false, 324 }, 325 { 326 deploymentReplicas: 10, 327 maxUnavailable: intstr.FromInt32(2), 328 readyPods: 1, 329 oldReplicas: 10, 330 scaleExpected: false, 331 }, 332 } 333 334 for i := range tests { 335 test := tests[i] 336 t.Logf("executing scenario %d", i) 337 oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp) 338 oldRS.Status.AvailableReplicas = int32(test.readyPods) 339 allRSs := []*apps.ReplicaSet{oldRS} 340 oldRSs := []*apps.ReplicaSet{oldRS} 341 maxSurge := intstr.FromInt32(0) 342 deployment := newDeployment("foo", test.deploymentReplicas, nil, &maxSurge, &test.maxUnavailable, map[string]string{"foo": "bar"}) 343 fakeClientset := fake.Clientset{} 344 controller := &DeploymentController{ 345 client: &fakeClientset, 346 eventRecorder: &record.FakeRecorder{}, 347 } 348 _, ctx := ktesting.NewTestContext(t) 349 scaled, err := controller.scaleDownOldReplicaSetsForRollingUpdate(ctx, allRSs, oldRSs, deployment) 350 if err != nil { 351 t.Errorf("unexpected error: %v", err) 352 continue 353 } 354 if !test.scaleExpected { 355 if scaled != 0 { 356 t.Errorf("unexpected scaling: %v", fakeClientset.Actions()) 357 } 358 continue 359 } 360 if test.scaleExpected && scaled == 0 { 361 t.Errorf("expected scaling to occur; actions: %v", fakeClientset.Actions()) 362 continue 363 } 364 // There are both list and update actions logged, so extract the update 365 // action for verification. 366 var updateAction core.UpdateAction 367 for _, action := range fakeClientset.Actions() { 368 switch a := action.(type) { 369 case core.UpdateAction: 370 if updateAction != nil { 371 t.Errorf("expected only 1 update action; had %v and found %v", updateAction, a) 372 } else { 373 updateAction = a 374 } 375 } 376 } 377 if updateAction == nil { 378 t.Errorf("expected an update action") 379 continue 380 } 381 updated := updateAction.GetObject().(*apps.ReplicaSet) 382 if e, a := test.expectedOldReplicas, *(updated.Spec.Replicas); e != a { 383 t.Errorf("expected update to %d replicas, got %d", e, a) 384 } 385 } 386 }