sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machinedeployment/machinedeployment_rolling_test.go (about) 1 /* 2 Copyright 2021 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 machinedeployment 18 19 import ( 20 "strconv" 21 "testing" 22 23 . "github.com/onsi/gomega" 24 "github.com/pkg/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/client-go/tools/record" 27 "k8s.io/utils/ptr" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 "sigs.k8s.io/controller-runtime/pkg/client/fake" 30 31 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 32 "sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil" 33 ) 34 35 func TestReconcileNewMachineSet(t *testing.T) { 36 testCases := []struct { 37 name string 38 machineDeployment *clusterv1.MachineDeployment 39 newMachineSet *clusterv1.MachineSet 40 oldMachineSets []*clusterv1.MachineSet 41 expectedNewMachineSetReplicas int 42 error error 43 }{ 44 { 45 name: "It fails when machineDeployment has no replicas", 46 machineDeployment: &clusterv1.MachineDeployment{ 47 ObjectMeta: metav1.ObjectMeta{ 48 Namespace: "foo", 49 Name: "bar", 50 }, 51 }, 52 newMachineSet: &clusterv1.MachineSet{ 53 Spec: clusterv1.MachineSetSpec{ 54 Replicas: ptr.To[int32](2), 55 }, 56 }, 57 error: errors.Errorf("spec.replicas for MachineDeployment foo/bar is nil, this is unexpected"), 58 }, 59 { 60 name: "It fails when new machineSet has no replicas", 61 machineDeployment: &clusterv1.MachineDeployment{ 62 Spec: clusterv1.MachineDeploymentSpec{ 63 Replicas: ptr.To[int32](2), 64 }, 65 }, 66 newMachineSet: &clusterv1.MachineSet{ 67 ObjectMeta: metav1.ObjectMeta{ 68 Namespace: "foo", 69 Name: "bar", 70 }, 71 }, 72 error: errors.Errorf("spec.replicas for MachineSet foo/bar is nil, this is unexpected"), 73 }, 74 { 75 name: "RollingUpdate strategy: Scale up: 0 -> 2", 76 machineDeployment: &clusterv1.MachineDeployment{ 77 ObjectMeta: metav1.ObjectMeta{ 78 Namespace: "foo", 79 Name: "bar", 80 }, 81 Spec: clusterv1.MachineDeploymentSpec{ 82 Strategy: &clusterv1.MachineDeploymentStrategy{ 83 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 84 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 85 MaxUnavailable: intOrStrPtr(0), 86 MaxSurge: intOrStrPtr(2), 87 }, 88 }, 89 Replicas: ptr.To[int32](2), 90 }, 91 }, 92 newMachineSet: &clusterv1.MachineSet{ 93 ObjectMeta: metav1.ObjectMeta{ 94 Namespace: "foo", 95 Name: "bar", 96 }, 97 Spec: clusterv1.MachineSetSpec{ 98 Replicas: ptr.To[int32](0), 99 }, 100 }, 101 expectedNewMachineSetReplicas: 2, 102 }, 103 { 104 name: "RollingUpdate strategy: Scale down: 2 -> 0", 105 machineDeployment: &clusterv1.MachineDeployment{ 106 ObjectMeta: metav1.ObjectMeta{ 107 Namespace: "foo", 108 Name: "bar", 109 }, 110 Spec: clusterv1.MachineDeploymentSpec{ 111 Strategy: &clusterv1.MachineDeploymentStrategy{ 112 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 113 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 114 MaxUnavailable: intOrStrPtr(0), 115 MaxSurge: intOrStrPtr(2), 116 }, 117 }, 118 Replicas: ptr.To[int32](0), 119 }, 120 }, 121 newMachineSet: &clusterv1.MachineSet{ 122 ObjectMeta: metav1.ObjectMeta{ 123 Namespace: "foo", 124 Name: "bar", 125 }, 126 Spec: clusterv1.MachineSetSpec{ 127 Replicas: ptr.To[int32](2), 128 }, 129 }, 130 expectedNewMachineSetReplicas: 0, 131 }, 132 { 133 name: "RollingUpdate strategy: Scale up does not go above maxSurge (3+2)", 134 machineDeployment: &clusterv1.MachineDeployment{ 135 ObjectMeta: metav1.ObjectMeta{ 136 Namespace: "foo", 137 Name: "bar", 138 }, 139 Spec: clusterv1.MachineDeploymentSpec{ 140 Strategy: &clusterv1.MachineDeploymentStrategy{ 141 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 142 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 143 MaxUnavailable: intOrStrPtr(0), 144 MaxSurge: intOrStrPtr(2), 145 }, 146 }, 147 Replicas: ptr.To[int32](3), 148 }, 149 }, 150 newMachineSet: &clusterv1.MachineSet{ 151 ObjectMeta: metav1.ObjectMeta{ 152 Namespace: "foo", 153 Name: "bar", 154 }, 155 Spec: clusterv1.MachineSetSpec{ 156 Replicas: ptr.To[int32](1), 157 }, 158 }, 159 expectedNewMachineSetReplicas: 2, 160 oldMachineSets: []*clusterv1.MachineSet{ 161 { 162 ObjectMeta: metav1.ObjectMeta{ 163 Namespace: "foo", 164 Name: "3replicas", 165 }, 166 Spec: clusterv1.MachineSetSpec{ 167 Replicas: ptr.To[int32](3), 168 }, 169 Status: clusterv1.MachineSetStatus{ 170 Replicas: 3, 171 }, 172 }, 173 }, 174 error: nil, 175 }, 176 { 177 name: "RollingUpdate strategy: Scale up accounts for deleting Machines to honour maxSurge", 178 machineDeployment: &clusterv1.MachineDeployment{ 179 ObjectMeta: metav1.ObjectMeta{ 180 Namespace: "foo", 181 Name: "bar", 182 }, 183 Spec: clusterv1.MachineDeploymentSpec{ 184 Strategy: &clusterv1.MachineDeploymentStrategy{ 185 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 186 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 187 MaxUnavailable: intOrStrPtr(0), 188 MaxSurge: intOrStrPtr(0), 189 }, 190 }, 191 Replicas: ptr.To[int32](1), 192 }, 193 }, 194 newMachineSet: &clusterv1.MachineSet{ 195 ObjectMeta: metav1.ObjectMeta{ 196 Namespace: "foo", 197 Name: "bar", 198 }, 199 Spec: clusterv1.MachineSetSpec{ 200 Replicas: ptr.To[int32](0), 201 }, 202 }, 203 expectedNewMachineSetReplicas: 0, 204 oldMachineSets: []*clusterv1.MachineSet{ 205 { 206 ObjectMeta: metav1.ObjectMeta{ 207 Namespace: "foo", 208 Name: "machine-not-yet-deleted", 209 }, 210 Spec: clusterv1.MachineSetSpec{ 211 Replicas: ptr.To[int32](0), 212 }, 213 Status: clusterv1.MachineSetStatus{ 214 Replicas: 1, 215 }, 216 }, 217 }, 218 error: nil, 219 }, 220 } 221 222 for _, tc := range testCases { 223 t.Run(tc.name, func(t *testing.T) { 224 g := NewWithT(t) 225 226 resources := []client.Object{ 227 tc.machineDeployment, 228 } 229 230 allMachineSets := append(tc.oldMachineSets, tc.newMachineSet) 231 for key := range allMachineSets { 232 resources = append(resources, allMachineSets[key]) 233 } 234 235 r := &Reconciler{ 236 Client: fake.NewClientBuilder().WithObjects(resources...).Build(), 237 recorder: record.NewFakeRecorder(32), 238 } 239 240 err := r.reconcileNewMachineSet(ctx, allMachineSets, tc.newMachineSet, tc.machineDeployment) 241 if tc.error != nil { 242 g.Expect(err).To(HaveOccurred()) 243 g.Expect(err.Error()).To(BeEquivalentTo(tc.error.Error())) 244 return 245 } 246 247 g.Expect(err).ToNot(HaveOccurred()) 248 249 freshNewMachineSet := &clusterv1.MachineSet{} 250 err = r.Client.Get(ctx, client.ObjectKeyFromObject(tc.newMachineSet), freshNewMachineSet) 251 g.Expect(err).ToNot(HaveOccurred()) 252 253 g.Expect(*freshNewMachineSet.Spec.Replicas).To(BeEquivalentTo(tc.expectedNewMachineSetReplicas)) 254 255 desiredReplicasAnnotation, ok := freshNewMachineSet.GetAnnotations()[clusterv1.DesiredReplicasAnnotation] 256 g.Expect(ok).To(BeTrue()) 257 g.Expect(strconv.Atoi(desiredReplicasAnnotation)).To(BeEquivalentTo(*tc.machineDeployment.Spec.Replicas)) 258 259 maxReplicasAnnotation, ok := freshNewMachineSet.GetAnnotations()[clusterv1.MaxReplicasAnnotation] 260 g.Expect(ok).To(BeTrue()) 261 g.Expect(strconv.Atoi(maxReplicasAnnotation)).To(BeEquivalentTo(*tc.machineDeployment.Spec.Replicas + mdutil.MaxSurge(*tc.machineDeployment))) 262 }) 263 } 264 } 265 266 func TestReconcileOldMachineSets(t *testing.T) { 267 testCases := []struct { 268 name string 269 machineDeployment *clusterv1.MachineDeployment 270 newMachineSet *clusterv1.MachineSet 271 oldMachineSets []*clusterv1.MachineSet 272 expectedOldMachineSetsReplicas int 273 error error 274 }{ 275 { 276 name: "It fails when machineDeployment has no replicas", 277 machineDeployment: &clusterv1.MachineDeployment{ 278 ObjectMeta: metav1.ObjectMeta{ 279 Namespace: "foo", 280 Name: "bar", 281 }, 282 }, 283 newMachineSet: &clusterv1.MachineSet{ 284 Spec: clusterv1.MachineSetSpec{ 285 Replicas: ptr.To[int32](2), 286 }, 287 }, 288 error: errors.Errorf("spec.replicas for MachineDeployment foo/bar is nil, this is unexpected"), 289 }, 290 { 291 name: "It fails when new machineSet has no replicas", 292 machineDeployment: &clusterv1.MachineDeployment{ 293 Spec: clusterv1.MachineDeploymentSpec{ 294 Replicas: ptr.To[int32](2), 295 }, 296 }, 297 newMachineSet: &clusterv1.MachineSet{ 298 ObjectMeta: metav1.ObjectMeta{ 299 Namespace: "foo", 300 Name: "bar", 301 }, 302 }, 303 error: errors.Errorf("spec.replicas for MachineSet foo/bar is nil, this is unexpected"), 304 }, 305 { 306 name: "RollingUpdate strategy: Scale down old MachineSets when all new replicas are available", 307 machineDeployment: &clusterv1.MachineDeployment{ 308 ObjectMeta: metav1.ObjectMeta{ 309 Namespace: "foo", 310 Name: "bar", 311 }, 312 Spec: clusterv1.MachineDeploymentSpec{ 313 Strategy: &clusterv1.MachineDeploymentStrategy{ 314 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 315 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 316 MaxUnavailable: intOrStrPtr(1), 317 MaxSurge: intOrStrPtr(3), 318 }, 319 }, 320 Replicas: ptr.To[int32](2), 321 }, 322 }, 323 newMachineSet: &clusterv1.MachineSet{ 324 ObjectMeta: metav1.ObjectMeta{ 325 Namespace: "foo", 326 Name: "bar", 327 }, 328 Spec: clusterv1.MachineSetSpec{ 329 Replicas: ptr.To[int32](0), 330 }, 331 Status: clusterv1.MachineSetStatus{ 332 AvailableReplicas: 2, 333 }, 334 }, 335 oldMachineSets: []*clusterv1.MachineSet{ 336 { 337 ObjectMeta: metav1.ObjectMeta{ 338 Namespace: "foo", 339 Name: "2replicas", 340 }, 341 Spec: clusterv1.MachineSetSpec{ 342 Replicas: ptr.To[int32](2), 343 }, 344 Status: clusterv1.MachineSetStatus{ 345 AvailableReplicas: 2, 346 }, 347 }, 348 { 349 ObjectMeta: metav1.ObjectMeta{ 350 Namespace: "foo", 351 Name: "1replicas", 352 }, 353 Spec: clusterv1.MachineSetSpec{ 354 Replicas: ptr.To[int32](1), 355 }, 356 Status: clusterv1.MachineSetStatus{ 357 AvailableReplicas: 1, 358 }, 359 }, 360 }, 361 expectedOldMachineSetsReplicas: 0, 362 }, 363 { 364 name: "RollingUpdate strategy: It does not scale down old MachineSets when above maxUnavailable", 365 machineDeployment: &clusterv1.MachineDeployment{ 366 ObjectMeta: metav1.ObjectMeta{ 367 Namespace: "foo", 368 Name: "bar", 369 }, 370 Spec: clusterv1.MachineDeploymentSpec{ 371 Strategy: &clusterv1.MachineDeploymentStrategy{ 372 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 373 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 374 MaxUnavailable: intOrStrPtr(2), 375 MaxSurge: intOrStrPtr(3), 376 }, 377 }, 378 Replicas: ptr.To[int32](10), 379 }, 380 }, 381 newMachineSet: &clusterv1.MachineSet{ 382 ObjectMeta: metav1.ObjectMeta{ 383 Namespace: "foo", 384 Name: "bar", 385 }, 386 Spec: clusterv1.MachineSetSpec{ 387 Replicas: ptr.To[int32](5), 388 }, 389 Status: clusterv1.MachineSetStatus{ 390 Replicas: 5, 391 ReadyReplicas: 0, 392 AvailableReplicas: 0, 393 }, 394 }, 395 oldMachineSets: []*clusterv1.MachineSet{ 396 { 397 ObjectMeta: metav1.ObjectMeta{ 398 Namespace: "foo", 399 Name: "8replicas", 400 }, 401 Spec: clusterv1.MachineSetSpec{ 402 Replicas: ptr.To[int32](8), 403 }, 404 Status: clusterv1.MachineSetStatus{ 405 Replicas: 10, 406 ReadyReplicas: 8, 407 AvailableReplicas: 8, 408 }, 409 }, 410 }, 411 expectedOldMachineSetsReplicas: 8, 412 }, 413 } 414 for _, tc := range testCases { 415 t.Run(tc.name, func(t *testing.T) { 416 g := NewWithT(t) 417 418 resources := []client.Object{ 419 tc.machineDeployment, 420 } 421 422 allMachineSets := append(tc.oldMachineSets, tc.newMachineSet) 423 for key := range allMachineSets { 424 resources = append(resources, allMachineSets[key]) 425 } 426 427 r := &Reconciler{ 428 Client: fake.NewClientBuilder().WithObjects(resources...).Build(), 429 recorder: record.NewFakeRecorder(32), 430 } 431 432 err := r.reconcileOldMachineSets(ctx, allMachineSets, tc.oldMachineSets, tc.newMachineSet, tc.machineDeployment) 433 if tc.error != nil { 434 g.Expect(err).To(HaveOccurred()) 435 g.Expect(err.Error()).To(BeEquivalentTo(tc.error.Error())) 436 return 437 } 438 439 g.Expect(err).ToNot(HaveOccurred()) 440 for key := range tc.oldMachineSets { 441 freshOldMachineSet := &clusterv1.MachineSet{} 442 err = r.Client.Get(ctx, client.ObjectKeyFromObject(tc.oldMachineSets[key]), freshOldMachineSet) 443 g.Expect(err).ToNot(HaveOccurred()) 444 g.Expect(*freshOldMachineSet.Spec.Replicas).To(BeEquivalentTo(tc.expectedOldMachineSetsReplicas)) 445 } 446 }) 447 } 448 }