sigs.k8s.io/cluster-api-provider-azure@v1.17.0/azure/scope/strategies/machinepool_deployments/machinepool_deployment_strategy_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 machinepool 18 19 import ( 20 "context" 21 "testing" 22 "time" 23 24 . "github.com/onsi/gomega" 25 "github.com/onsi/gomega/types" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/intstr" 28 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 29 infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" 30 "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomega" 31 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 32 ) 33 34 func TestMachinePoolRollingUpdateStrategy_Type(t *testing.T) { 35 g := NewWithT(t) 36 strategy := NewMachinePoolDeploymentStrategy(infrav1exp.AzureMachinePoolDeploymentStrategy{ 37 Type: infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType, 38 }) 39 g.Expect(strategy.Type()).To(Equal(infrav1exp.RollingUpdateAzureMachinePoolDeploymentStrategyType)) 40 } 41 42 func TestMachinePoolRollingUpdateStrategy_Surge(t *testing.T) { 43 var ( 44 two = intstr.FromInt(2) 45 twentyPercent = intstr.FromString("20%") 46 ) 47 48 tests := []struct { 49 name string 50 strategy Surger 51 desiredReplicas int 52 want int 53 errStr string 54 }{ 55 { 56 name: "Strategy is empty", 57 strategy: &rollingUpdateStrategy{}, 58 want: 1, 59 }, 60 { 61 name: "MaxSurge is set to 2", 62 strategy: &rollingUpdateStrategy{ 63 MachineRollingUpdateDeployment: infrav1exp.MachineRollingUpdateDeployment{ 64 MaxSurge: &two, 65 }, 66 }, 67 want: 2, 68 }, 69 { 70 name: "MaxSurge is set to 20% and desiredReplicas is 20", 71 strategy: &rollingUpdateStrategy{ 72 MachineRollingUpdateDeployment: infrav1exp.MachineRollingUpdateDeployment{ 73 MaxSurge: &twentyPercent, 74 }, 75 }, 76 desiredReplicas: 20, 77 want: 4, 78 }, 79 { 80 name: "MaxSurge is set to 20% and desiredReplicas is 21; rounds up", 81 strategy: &rollingUpdateStrategy{ 82 MachineRollingUpdateDeployment: infrav1exp.MachineRollingUpdateDeployment{ 83 MaxSurge: &twentyPercent, 84 }, 85 }, 86 desiredReplicas: 21, 87 want: 5, 88 }, 89 } 90 91 for _, tt := range tests { 92 t.Run(tt.name, func(t *testing.T) { 93 g := NewWithT(t) 94 got, err := tt.strategy.Surge(tt.desiredReplicas) 95 if tt.errStr == "" { 96 g.Expect(err).To(Succeed()) 97 g.Expect(got).To(Equal(tt.want)) 98 } else { 99 g.Expect(err).To(MatchError(tt.errStr)) 100 } 101 }) 102 } 103 } 104 105 func TestMachinePoolScope_maxUnavailable(t *testing.T) { 106 var ( 107 two = intstr.FromInt(2) 108 twentyPercent = intstr.FromString("20%") 109 ) 110 111 tests := []struct { 112 name string 113 strategy *rollingUpdateStrategy 114 desiredReplicas int 115 want int 116 errStr string 117 }{ 118 { 119 name: "Strategy is empty", 120 strategy: &rollingUpdateStrategy{}, 121 }, 122 { 123 name: "MaxUnavailable is nil", 124 strategy: &rollingUpdateStrategy{ 125 MachineRollingUpdateDeployment: infrav1exp.MachineRollingUpdateDeployment{}, 126 }, 127 want: 0, 128 }, 129 { 130 name: "MaxUnavailable is set to 2", 131 strategy: &rollingUpdateStrategy{ 132 MachineRollingUpdateDeployment: infrav1exp.MachineRollingUpdateDeployment{ 133 MaxUnavailable: &two, 134 }, 135 }, 136 want: 2, 137 }, 138 { 139 name: "MaxUnavailable is set to 20%", 140 strategy: &rollingUpdateStrategy{ 141 MachineRollingUpdateDeployment: infrav1exp.MachineRollingUpdateDeployment{ 142 MaxUnavailable: &twentyPercent, 143 }, 144 }, 145 desiredReplicas: 20, 146 want: 4, 147 }, 148 { 149 name: "MaxUnavailable is set to 20% and it rounds down", 150 strategy: &rollingUpdateStrategy{ 151 MachineRollingUpdateDeployment: infrav1exp.MachineRollingUpdateDeployment{ 152 MaxUnavailable: &twentyPercent, 153 }, 154 }, 155 desiredReplicas: 21, 156 want: 4, 157 }, 158 } 159 160 for _, tt := range tests { 161 t.Run(tt.name, func(t *testing.T) { 162 g := NewWithT(t) 163 got, err := tt.strategy.maxUnavailable(tt.desiredReplicas) 164 if tt.errStr == "" { 165 g.Expect(err).To(Succeed()) 166 g.Expect(got).To(Equal(tt.want)) 167 } else { 168 g.Expect(err).To(MatchError(tt.errStr)) 169 } 170 }) 171 } 172 } 173 174 func TestMachinePoolRollingUpdateStrategy_SelectMachinesToDelete(t *testing.T) { 175 var ( 176 one = intstr.FromInt(1) 177 two = intstr.FromInt(2) 178 fortyFivePercent = intstr.FromString("45%") 179 thirtyPercent = intstr.FromString("30%") 180 succeeded = infrav1.Succeeded 181 baseTime = time.Now().Add(-24 * time.Hour).Truncate(time.Microsecond) 182 deleteTime = metav1.NewTime(time.Now()) 183 ) 184 185 tests := []struct { 186 name string 187 strategy DeleteSelector 188 input map[string]infrav1exp.AzureMachinePoolMachine 189 desiredReplicas int32 190 want types.GomegaMatcher 191 errStr string 192 }{ 193 { 194 name: "should not select machines to delete if less than desired replica count", 195 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{}), 196 desiredReplicas: 1, 197 input: map[string]infrav1exp.AzureMachinePoolMachine{ 198 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 199 }, 200 want: Equal([]infrav1exp.AzureMachinePoolMachine{}), 201 }, 202 { 203 name: "if over-provisioned, select a machine with an out-of-date model", 204 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{}), 205 desiredReplicas: 2, 206 input: map[string]infrav1exp.AzureMachinePoolMachine{ 207 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 208 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 209 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 210 }, 211 want: Equal([]infrav1exp.AzureMachinePoolMachine{ 212 makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 213 }), 214 }, 215 { 216 name: "if over-provisioned, select a machine with an out-of-date model when using Random Delete Policy", 217 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.RandomDeletePolicyType}), 218 desiredReplicas: 2, 219 input: map[string]infrav1exp.AzureMachinePoolMachine{ 220 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 221 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 222 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 223 }, 224 want: Equal([]infrav1exp.AzureMachinePoolMachine{ 225 makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 226 }), 227 }, 228 { 229 name: "if over-provisioned, select the oldest machine", 230 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 231 desiredReplicas: 2, 232 input: map[string]infrav1exp.AzureMachinePoolMachine{ 233 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 234 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 235 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 236 }, 237 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 238 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 239 }), 240 }, 241 { 242 name: "if machine has delete machine annotation, select those machines first", 243 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 244 desiredReplicas: 2, 245 input: map[string]infrav1exp.AzureMachinePoolMachine{ 246 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 247 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour)), HasDeleteMachineAnnotation: true}), 248 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 249 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 250 }, 251 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 252 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour)), HasDeleteMachineAnnotation: true}), 253 }), 254 }, 255 { 256 name: "if over-provisioned, select machines ordered by creation date", 257 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 258 desiredReplicas: 2, 259 input: map[string]infrav1exp.AzureMachinePoolMachine{ 260 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 261 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 262 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 263 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 264 }, 265 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 266 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 267 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 268 }), 269 }, 270 { 271 name: "if over-provisioned, select machines ordered by newest first", 272 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.NewestDeletePolicyType}), 273 desiredReplicas: 2, 274 input: map[string]infrav1exp.AzureMachinePoolMachine{ 275 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 276 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 277 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 278 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 279 }, 280 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 281 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 282 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 283 }), 284 }, 285 { 286 name: "if over-provisioned but with an equivalent number marked for deletion, nothing to do; this is the case where Azure has not yet caught up to capz", 287 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 288 desiredReplicas: 2, 289 input: map[string]infrav1exp.AzureMachinePoolMachine{ 290 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 291 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 292 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 293 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 294 }, 295 want: BeEmpty(), 296 }, 297 { 298 name: "if Azure is deleting 2 machines, but we have already marked their AzureMachinePoolMachine equivalents for deletion, nothing to do; this is the case where capz has not yet caught up to Azure", 299 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 300 desiredReplicas: 2, 301 input: map[string]infrav1exp.AzureMachinePoolMachine{ 302 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 303 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 304 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 305 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 306 }, 307 want: BeEmpty(), 308 }, 309 { 310 name: "if Azure is deleting 2 machines, we want to delete their AzureMachinePoolMachine equivalents", 311 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 312 desiredReplicas: 2, 313 input: map[string]infrav1exp.AzureMachinePoolMachine{ 314 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 315 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 316 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: nil, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 317 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: nil, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 318 }, 319 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 320 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: nil, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 321 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: nil, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 322 }), 323 }, 324 { 325 name: "if Azure is deleting 1 machine, pick another candidate for deletion", 326 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 327 desiredReplicas: 2, 328 input: map[string]infrav1exp.AzureMachinePoolMachine{ 329 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 330 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 331 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 332 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 333 }, 334 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 335 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 336 }), 337 }, 338 { 339 name: "if maxUnavailable is 1, and 1 is not the latest model, delete it.", 340 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &one}), 341 desiredReplicas: 3, 342 input: map[string]infrav1exp.AzureMachinePoolMachine{ 343 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 344 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 345 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 346 }, 347 want: Equal([]infrav1exp.AzureMachinePoolMachine{ 348 makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 349 }), 350 }, 351 { 352 name: "if maxUnavailable is 1, and all are the latest model, delete nothing.", 353 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &one}), 354 desiredReplicas: 3, 355 input: map[string]infrav1exp.AzureMachinePoolMachine{ 356 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 357 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 358 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 359 }, 360 want: BeEmpty(), 361 }, 362 { 363 name: "if maxUnavailable is 2, and there are 2 with the latest model == false, delete 2.", 364 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &two}), 365 desiredReplicas: 3, 366 input: map[string]infrav1exp.AzureMachinePoolMachine{ 367 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 368 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 369 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 370 }, 371 want: Equal([]infrav1exp.AzureMachinePoolMachine{ 372 makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 373 makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 374 }), 375 }, 376 { 377 name: "if maxUnavailable is 45%, and there are 2 with the latest model == false, delete 1.", 378 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &fortyFivePercent}), 379 desiredReplicas: 3, 380 input: map[string]infrav1exp.AzureMachinePoolMachine{ 381 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 382 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 383 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 384 }, 385 want: HaveLen(1), 386 }, 387 { 388 name: "if maxUnavailable is 30%, and there are 2 with the latest model == false, delete 0.", 389 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &thirtyPercent}), 390 desiredReplicas: 3, 391 input: map[string]infrav1exp.AzureMachinePoolMachine{ 392 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 393 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 394 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 395 }, 396 want: BeEmpty(), 397 }, 398 } 399 400 for _, tt := range tests { 401 t.Run(tt.name, func(t *testing.T) { 402 g := NewWithT(t) 403 got, err := tt.strategy.SelectMachinesToDelete(context.Background(), tt.desiredReplicas, tt.input) 404 if tt.errStr == "" { 405 g.Expect(err).To(Succeed()) 406 g.Expect(got).To(tt.want) 407 } else { 408 g.Expect(err).To(MatchError(tt.errStr)) 409 } 410 }) 411 } 412 } 413 414 func makeRollingUpdateStrategy(rolling infrav1exp.MachineRollingUpdateDeployment) *rollingUpdateStrategy { 415 return &rollingUpdateStrategy{ 416 MachineRollingUpdateDeployment: rolling, 417 } 418 } 419 420 type ampmOptions struct { 421 Ready bool 422 LatestModel bool 423 ProvisioningState infrav1.ProvisioningState 424 CreationTime metav1.Time 425 DeletionTime *metav1.Time 426 HasDeleteMachineAnnotation bool 427 } 428 429 func makeAMPM(opts ampmOptions) infrav1exp.AzureMachinePoolMachine { 430 ampm := infrav1exp.AzureMachinePoolMachine{ 431 ObjectMeta: metav1.ObjectMeta{ 432 CreationTimestamp: opts.CreationTime, 433 DeletionTimestamp: opts.DeletionTime, 434 Annotations: map[string]string{}, 435 }, 436 Status: infrav1exp.AzureMachinePoolMachineStatus{ 437 Ready: opts.Ready, 438 LatestModelApplied: opts.LatestModel, 439 ProvisioningState: &opts.ProvisioningState, 440 }, 441 } 442 443 if opts.HasDeleteMachineAnnotation { 444 ampm.Annotations[clusterv1.DeleteMachineAnnotation] = "true" 445 } 446 447 return ampm 448 }