sigs.k8s.io/cluster-api-provider-azure@v1.14.3/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 over-provisioned and has delete machine annotation, select machines those first and then by oldest", 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 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 254 }), 255 }, 256 { 257 name: "if over-provisioned, select machines ordered by creation date", 258 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 259 desiredReplicas: 2, 260 input: map[string]infrav1exp.AzureMachinePoolMachine{ 261 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 262 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 263 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 264 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 265 }, 266 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 267 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 268 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 269 }), 270 }, 271 { 272 name: "if over-provisioned and has delete machine annotation, prioritize those machines first over creation date", 273 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 274 desiredReplicas: 2, 275 input: map[string]infrav1exp.AzureMachinePoolMachine{ 276 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 277 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour)), HasDeleteMachineAnnotation: true}), 278 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 279 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 280 }, 281 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 282 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour)), HasDeleteMachineAnnotation: true}), 283 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 284 }), 285 }, 286 { 287 name: "if over-provisioned, select machines ordered by newest first", 288 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.NewestDeletePolicyType}), 289 desiredReplicas: 2, 290 input: map[string]infrav1exp.AzureMachinePoolMachine{ 291 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 292 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 293 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 294 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 295 }, 296 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 297 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 298 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 299 }), 300 }, 301 { 302 name: "if over-provisioned and has delete machine annotation, select those machines first followed by newest", 303 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.NewestDeletePolicyType}), 304 desiredReplicas: 2, 305 input: map[string]infrav1exp.AzureMachinePoolMachine{ 306 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 307 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 308 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 309 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour)), HasDeleteMachineAnnotation: true}), 310 }, 311 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 312 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour)), HasDeleteMachineAnnotation: true}), 313 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 314 }), 315 }, 316 { 317 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", 318 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 319 desiredReplicas: 2, 320 input: map[string]infrav1exp.AzureMachinePoolMachine{ 321 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 322 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 323 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 324 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 325 }, 326 want: BeEmpty(), 327 }, 328 { 329 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", 330 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 331 desiredReplicas: 2, 332 input: map[string]infrav1exp.AzureMachinePoolMachine{ 333 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 334 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 335 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 336 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 337 }, 338 want: BeEmpty(), 339 }, 340 { 341 name: "if Azure is deleting 2 machines, we want to delete their AzureMachinePoolMachine equivalents", 342 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 343 desiredReplicas: 2, 344 input: map[string]infrav1exp.AzureMachinePoolMachine{ 345 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 346 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 347 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: nil, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 348 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: nil, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 349 }, 350 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 351 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: nil, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 352 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: infrav1.Deleting, DeletionTime: nil, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 353 }), 354 }, 355 { 356 name: "if Azure is deleting 1 machine, pick another candidate for deletion", 357 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{DeletePolicy: infrav1exp.OldestDeletePolicyType}), 358 desiredReplicas: 2, 359 input: map[string]infrav1exp.AzureMachinePoolMachine{ 360 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(4 * time.Hour))}), 361 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(3 * time.Hour))}), 362 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 363 "bar": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, DeletionTime: &deleteTime, CreationTime: metav1.NewTime(baseTime.Add(1 * time.Hour))}), 364 }, 365 want: gomega.DiffEq([]infrav1exp.AzureMachinePoolMachine{ 366 makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded, CreationTime: metav1.NewTime(baseTime.Add(2 * time.Hour))}), 367 }), 368 }, 369 { 370 name: "if maxUnavailable is 1, and 1 is not the latest model, delete it.", 371 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &one}), 372 desiredReplicas: 3, 373 input: map[string]infrav1exp.AzureMachinePoolMachine{ 374 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 375 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 376 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 377 }, 378 want: Equal([]infrav1exp.AzureMachinePoolMachine{ 379 makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 380 }), 381 }, 382 { 383 name: "if maxUnavailable is 1, and all are the latest model, delete nothing.", 384 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &one}), 385 desiredReplicas: 3, 386 input: map[string]infrav1exp.AzureMachinePoolMachine{ 387 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 388 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 389 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 390 }, 391 want: BeEmpty(), 392 }, 393 { 394 name: "if maxUnavailable is 2, and there are 2 with the latest model == false, delete 2.", 395 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &two}), 396 desiredReplicas: 3, 397 input: map[string]infrav1exp.AzureMachinePoolMachine{ 398 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 399 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 400 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 401 }, 402 want: Equal([]infrav1exp.AzureMachinePoolMachine{ 403 makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 404 makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 405 }), 406 }, 407 { 408 name: "if maxUnavailable is 45%, and there are 2 with the latest model == false, delete 1.", 409 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &fortyFivePercent}), 410 desiredReplicas: 3, 411 input: map[string]infrav1exp.AzureMachinePoolMachine{ 412 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 413 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 414 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 415 }, 416 want: HaveLen(1), 417 }, 418 { 419 name: "if maxUnavailable is 30%, and there are 2 with the latest model == false, delete 0.", 420 strategy: makeRollingUpdateStrategy(infrav1exp.MachineRollingUpdateDeployment{MaxUnavailable: &thirtyPercent}), 421 desiredReplicas: 3, 422 input: map[string]infrav1exp.AzureMachinePoolMachine{ 423 "foo": makeAMPM(ampmOptions{Ready: true, LatestModel: true, ProvisioningState: succeeded}), 424 "bin": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 425 "baz": makeAMPM(ampmOptions{Ready: true, LatestModel: false, ProvisioningState: succeeded}), 426 }, 427 want: BeEmpty(), 428 }, 429 } 430 431 for _, tt := range tests { 432 t.Run(tt.name, func(t *testing.T) { 433 g := NewWithT(t) 434 got, err := tt.strategy.SelectMachinesToDelete(context.Background(), tt.desiredReplicas, tt.input) 435 if tt.errStr == "" { 436 g.Expect(err).To(Succeed()) 437 g.Expect(got).To(tt.want) 438 } else { 439 g.Expect(err).To(MatchError(tt.errStr)) 440 } 441 }) 442 } 443 } 444 445 func makeRollingUpdateStrategy(rolling infrav1exp.MachineRollingUpdateDeployment) *rollingUpdateStrategy { 446 return &rollingUpdateStrategy{ 447 MachineRollingUpdateDeployment: rolling, 448 } 449 } 450 451 type ampmOptions struct { 452 Ready bool 453 LatestModel bool 454 ProvisioningState infrav1.ProvisioningState 455 CreationTime metav1.Time 456 DeletionTime *metav1.Time 457 HasDeleteMachineAnnotation bool 458 } 459 460 func makeAMPM(opts ampmOptions) infrav1exp.AzureMachinePoolMachine { 461 ampm := infrav1exp.AzureMachinePoolMachine{ 462 ObjectMeta: metav1.ObjectMeta{ 463 CreationTimestamp: opts.CreationTime, 464 DeletionTimestamp: opts.DeletionTime, 465 Annotations: map[string]string{}, 466 }, 467 Status: infrav1exp.AzureMachinePoolMachineStatus{ 468 Ready: opts.Ready, 469 LatestModelApplied: opts.LatestModel, 470 ProvisioningState: &opts.ProvisioningState, 471 }, 472 } 473 474 if opts.HasDeleteMachineAnnotation { 475 ampm.Annotations[clusterv1.DeleteMachineAnnotation] = "true" 476 } 477 478 return ampm 479 }