agones.dev/agones@v1.53.0/pkg/apis/autoscaling/v1/fleetautoscaler_test.go (about) 1 // Copyright 2018 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package v1 16 17 import ( 18 "testing" 19 "time" 20 21 "agones.dev/agones/pkg/util/runtime" 22 "github.com/stretchr/testify/assert" 23 admregv1 "k8s.io/api/admissionregistration/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/util/intstr" 26 ) 27 28 func TestFleetAutoscalerValidateUpdate(t *testing.T) { 29 t.Parallel() 30 31 t.Run("bad buffer size", func(t *testing.T) { 32 fas := defaultFixture() 33 fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(0) 34 causes := fas.Validate() 35 36 assert.Len(t, causes, 1) 37 assert.Equal(t, "spec.policy.buffer.bufferSize", causes[0].Field) 38 }) 39 40 t.Run("bad min replicas", func(t *testing.T) { 41 fas := defaultFixture() 42 fas.Spec.Policy.Buffer.MinReplicas = 2 43 44 causes := fas.Validate() 45 46 assert.Len(t, causes, 1) 47 assert.Equal(t, "spec.policy.buffer.minReplicas", causes[0].Field) 48 }) 49 50 t.Run("bad max replicas", func(t *testing.T) { 51 fas := defaultFixture() 52 fas.Spec.Policy.Buffer.MaxReplicas = 2 53 causes := fas.Validate() 54 55 assert.Len(t, causes, 1) 56 assert.Equal(t, "spec.policy.buffer.maxReplicas", causes[0].Field) 57 }) 58 59 t.Run("minReplicas > maxReplicas", func(t *testing.T) { 60 fas := defaultFixture() 61 fas.Spec.Policy.Buffer.MinReplicas = 20 62 causes := fas.Validate() 63 64 assert.Len(t, causes, 1) 65 assert.Equal(t, "spec.policy.buffer.minReplicas", causes[0].Field) 66 }) 67 68 t.Run("bufferSize good percent", func(t *testing.T) { 69 fas := defaultFixture() 70 fas.Spec.Policy.Buffer.MinReplicas = 1 71 fas.Spec.Policy.Buffer.BufferSize = intstr.FromString("20%") 72 causes := fas.Validate() 73 74 assert.Len(t, causes, 0) 75 }) 76 77 t.Run("bufferSize bad percent", func(t *testing.T) { 78 fas := defaultFixture() 79 fas.Spec.Policy.Buffer.MinReplicas = 1 80 81 fasCopy := fas.DeepCopy() 82 fasCopy.Spec.Policy.Buffer.BufferSize = intstr.FromString("120%") 83 causes := fasCopy.Validate() 84 assert.Len(t, causes, 1) 85 assert.Equal(t, "spec.policy.buffer.bufferSize", causes[0].Field) 86 87 fasCopy = fas.DeepCopy() 88 fasCopy.Spec.Policy.Buffer.BufferSize = intstr.FromString("0%") 89 causes = fasCopy.Validate() 90 assert.Len(t, causes, 1) 91 assert.Equal(t, "spec.policy.buffer.bufferSize", causes[0].Field) 92 93 fasCopy = fas.DeepCopy() 94 fasCopy.Spec.Policy.Buffer.BufferSize = intstr.FromString("-10%") 95 causes = fasCopy.Validate() 96 assert.Len(t, causes, 1) 97 assert.Equal(t, "spec.policy.buffer.bufferSize", causes[0].Field) 98 fasCopy = fas.DeepCopy() 99 100 fasCopy.Spec.Policy.Buffer.BufferSize = intstr.FromString("notgood") 101 causes = fasCopy.Validate() 102 assert.Len(t, causes, 1) 103 assert.Equal(t, "spec.policy.buffer.bufferSize", causes[0].Field) 104 }) 105 106 t.Run("bad min replicas with percentage value of bufferSize", func(t *testing.T) { 107 fas := defaultFixture() 108 fas.Spec.Policy.Buffer.BufferSize = intstr.FromString("10%") 109 fas.Spec.Policy.Buffer.MinReplicas = 0 110 111 causes := fas.Validate() 112 113 assert.Len(t, causes, 1) 114 assert.Equal(t, "spec.policy.buffer.minReplicas", causes[0].Field) 115 }) 116 117 t.Run("bad sync interval seconds", func(t *testing.T) { 118 119 fas := defaultFixture() 120 fas.Spec.Sync.FixedInterval.Seconds = 0 121 122 causes := fas.Validate() 123 124 assert.Len(t, causes, 1) 125 assert.Equal(t, "spec.sync.fixedInterval.seconds", causes[0].Field) 126 }) 127 } 128 func TestFleetAutoscalerWebhookValidateUpdate(t *testing.T) { 129 t.Parallel() 130 131 t.Run("good service value", func(t *testing.T) { 132 fas := webhookFixture() 133 causes := fas.Validate() 134 135 assert.Len(t, causes, 0) 136 }) 137 138 t.Run("good url value", func(t *testing.T) { 139 fas := webhookFixture() 140 url := "http://good.example.com" 141 fas.Spec.Policy.Webhook.URL = &url 142 fas.Spec.Policy.Webhook.Service = nil 143 causes := fas.Validate() 144 145 assert.Len(t, causes, 0) 146 }) 147 148 t.Run("bad URL and service value", func(t *testing.T) { 149 fas := webhookFixture() 150 fas.Spec.Policy.Webhook.URL = nil 151 fas.Spec.Policy.Webhook.Service = nil 152 causes := fas.Validate() 153 154 assert.Len(t, causes, 1) 155 assert.Equal(t, "spec.policy.webhook", causes[0].Field) 156 }) 157 158 t.Run("both URL and service value are used - fail", func(t *testing.T) { 159 160 fas := webhookFixture() 161 url := "123" 162 fas.Spec.Policy.Webhook.URL = &url 163 164 causes := fas.Validate() 165 166 assert.Len(t, causes, 1) 167 assert.Equal(t, "spec.policy.webhook.url", causes[0].Field) 168 }) 169 170 goodCaBundle := "\n-----BEGIN CERTIFICATE-----\nMIIDXjCCAkYCCQDvT9MAXwnuqDANBgkqhkiG9w0BAQsFADBxMQswCQYDVQQGEwJV\nUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEP\nMA0GA1UECgwGQWdvbmVzMQ8wDQYDVQQLDAZBZ29uZXMxEzARBgNVBAMMCmFnb25l\ncy5kZXYwHhcNMTkwMTAzMTEwNTA0WhcNMjExMDIzMTEwNTA0WjBxMQswCQYDVQQG\nEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll\ndzEPMA0GA1UECgwGQWdvbmVzMQ8wDQYDVQQLDAZBZ29uZXMxEzARBgNVBAMMCmFn\nb25lcy5kZXYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFwohJp3xK\n4iORkJXNO2KEkdrVYK7xpXTrPvZqLzoyMBOXi9b+lOKILUPaKtZ33GIwola31bHp\ni7V97vh3irIQVpap6uncesRTX0qk5Y70f7T6lByMKDsxi5ddea3ztAftH+PMYSLn\nE7H9276R1lvX8HZ0E2T4ea63PcVcTldw74ueEQr7HFMVucO+hHjgNJXDsWFUNppv\nxqWOvlIEDRdQzB1UYd13orqX0t514Ikp5Y3oNigXftDH+lZPlrWGsknMIDWr4DKP\n7NB1BZMfLFu/HXTGI9dK5Zc4T4GG4DBZqlgDPdzAXSBUT9cRQvbLrZ5+tUjOZK5E\nzEEIqyUo1+QdAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABgtnWaWIDFCbKvhD8cF\nd5fvARFJcRl4dcChoqANeUXK4iNiCEPiDJb4xDGrLSVOeQ2IMbghqCwJfH93aqTr\n9kFQPvYbCt10TPQpmmh2//QjWGc7lxniFWR8pAVYdCGHqIAMvW2V2177quHsqc2I\nNTXyEUus0SDHLK8swLQxoCVw4fSq+kjMFW/3zOvMfh13rZH7Lo0gQyAUcuHM5U7g\nbhCZ3yVkDYpPxVv2vL0eyWUdLrQjYXyY7MWHPXvDozi3CtuBZlp6ulgeubi6jhHE\nIzuOM3qiLMJ/KG8MlIgGCwSX/x0vfO0/LtkZM7P1+yptSr/Se5QiZMtmpxC+DDWJ\n2xw=\n-----END CERTIFICATE-----" 171 t.Run("good url and CABundle value", func(t *testing.T) { 172 fas := webhookFixture() 173 url := "https://good.example.com" 174 fas.Spec.Policy.Webhook.URL = &url 175 fas.Spec.Policy.Webhook.Service = nil 176 fas.Spec.Policy.Webhook.CABundle = []byte(goodCaBundle) 177 178 causes := fas.Validate() 179 assert.Len(t, causes, 0) 180 }) 181 182 t.Run("https url and invalid CABundle value", func(t *testing.T) { 183 fas := webhookFixture() 184 url := "https://bad.example.com" 185 fas.Spec.Policy.Webhook.URL = &url 186 fas.Spec.Policy.Webhook.Service = nil 187 fas.Spec.Policy.Webhook.CABundle = []byte("SomeInvalidCABundle") 188 189 causes := fas.Validate() 190 assert.Len(t, causes, 1) 191 assert.Equal(t, "spec.policy.webhook.caBundle", causes[0].Field) 192 }) 193 194 t.Run("https url and missing CABundle value", func(t *testing.T) { 195 fas := webhookFixture() 196 url := "https://bad.example.com" 197 fas.Spec.Policy.Webhook.URL = &url 198 fas.Spec.Policy.Webhook.Service = nil 199 fas.Spec.Policy.Webhook.CABundle = nil 200 201 causes := fas.Validate() 202 assert.Len(t, causes, 1) 203 assert.Equal(t, "spec.policy.webhook.caBundle", causes[0].Field) 204 }) 205 206 t.Run("bad url value", func(t *testing.T) { 207 fas := webhookFixture() 208 url := "http:/bad.example.com%" 209 fas.Spec.Policy.Webhook.URL = &url 210 fas.Spec.Policy.Webhook.Service = nil 211 fas.Spec.Policy.Webhook.CABundle = []byte(goodCaBundle) 212 213 causes := fas.Validate() 214 assert.Len(t, causes, 1) 215 assert.Equal(t, "spec.policy.webhook.url", causes[0].Field) 216 }) 217 218 } 219 220 // nolint:dupl // Linter errors on lines are duplicate of TestFleetAutoscalerListValidateUpdate 221 func TestFleetAutoscalerCounterValidateUpdate(t *testing.T) { 222 t.Parallel() 223 224 modifiedFAS := func(f func(*FleetAutoscalerPolicy)) *FleetAutoscaler { 225 fas := counterFixture() 226 f(&fas.Spec.Policy) 227 return fas 228 } 229 230 testCases := map[string]struct { 231 fas *FleetAutoscaler 232 featureFlags string 233 wantLength int 234 wantField string 235 }{ 236 "feature gate not turned on": { 237 fas: counterFixture(), 238 featureFlags: string(runtime.FeatureCountsAndLists) + "=false", 239 wantLength: 1, 240 wantField: "spec.policy.counter", 241 }, 242 "nil parameters": { 243 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 244 fap.Counter = nil 245 }), 246 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 247 wantLength: 1, 248 wantField: "spec.policy.counter", 249 }, 250 "minCapacity size too large": { 251 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 252 fap.Counter.MinCapacity = int64(11) 253 }), 254 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 255 wantLength: 1, 256 wantField: "spec.policy.counter.minCapacity", 257 }, 258 "bufferSize size too small": { 259 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 260 fap.Counter.BufferSize = intstr.FromInt(0) 261 }), 262 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 263 wantLength: 1, 264 wantField: "spec.policy.counter.bufferSize", 265 }, 266 "maxCapacity size too small": { 267 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 268 fap.Counter.MaxCapacity = int64(4) 269 }), 270 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 271 wantLength: 1, 272 wantField: "spec.policy.counter.maxCapacity", 273 }, 274 "minCapacity size too small": { 275 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 276 fap.Counter.MinCapacity = int64(4) 277 }), 278 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 279 wantLength: 1, 280 wantField: "spec.policy.counter.minCapacity", 281 }, 282 "bufferSize percentage OK": { 283 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 284 fap.Counter.BufferSize.Type = intstr.String 285 fap.Counter.BufferSize = intstr.FromString("99%") 286 fap.Counter.MinCapacity = 10 287 }), 288 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 289 wantLength: 0, 290 }, 291 "bufferSize percentage can't parse": { 292 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 293 fap.Counter.BufferSize.Type = intstr.String 294 fap.Counter.BufferSize = intstr.FromString("99.0%") 295 fap.Counter.MinCapacity = 1 296 }), 297 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 298 wantLength: 1, 299 wantField: "spec.policy.counter.bufferSize", 300 }, 301 "bufferSize percentage and MinCapacity too small": { 302 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 303 fap.Counter.BufferSize.Type = intstr.String 304 fap.Counter.BufferSize = intstr.FromString("0%") 305 }), 306 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 307 wantLength: 2, 308 wantField: "spec.policy.counter.bufferSize", 309 }, 310 "bufferSize percentage too large": { 311 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 312 fap.Counter.BufferSize.Type = intstr.String 313 fap.Counter.BufferSize = intstr.FromString("100%") 314 fap.Counter.MinCapacity = 10 315 }), 316 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 317 wantLength: 1, 318 wantField: "spec.policy.counter.bufferSize", 319 }, 320 } 321 322 runtime.FeatureTestMutex.Lock() 323 defer runtime.FeatureTestMutex.Unlock() 324 325 for name, tc := range testCases { 326 t.Run(name, func(t *testing.T) { 327 err := runtime.ParseFeatures(tc.featureFlags) 328 assert.NoError(t, err) 329 330 causes := tc.fas.Validate() 331 332 assert.Len(t, causes, tc.wantLength) 333 if tc.wantLength > 0 { 334 assert.Equal(t, tc.wantField, causes[0].Field) 335 } 336 }) 337 } 338 } 339 340 // nolint:dupl // Linter errors on lines are duplicate of TestFleetAutoscalerCounterValidateUpdate 341 func TestFleetAutoscalerListValidateUpdate(t *testing.T) { 342 t.Parallel() 343 344 modifiedFAS := func(f func(*FleetAutoscalerPolicy)) *FleetAutoscaler { 345 fas := listFixture() 346 f(&fas.Spec.Policy) 347 return fas 348 } 349 350 testCases := map[string]struct { 351 fas *FleetAutoscaler 352 featureFlags string 353 wantLength int 354 wantField string 355 }{ 356 "feature gate not turned on": { 357 fas: listFixture(), 358 featureFlags: string(runtime.FeatureCountsAndLists) + "=false", 359 wantLength: 1, 360 wantField: "spec.policy.list", 361 }, 362 "nil parameters": { 363 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 364 fap.List = nil 365 }), 366 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 367 wantLength: 1, 368 wantField: "spec.policy.list", 369 }, 370 "minCapacity size too large": { 371 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 372 fap.List.MinCapacity = int64(11) 373 }), 374 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 375 wantLength: 1, 376 wantField: "spec.policy.list.minCapacity", 377 }, 378 "bufferSize size too small": { 379 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 380 fap.List.BufferSize = intstr.FromInt(0) 381 }), 382 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 383 wantLength: 1, 384 wantField: "spec.policy.list.bufferSize", 385 }, 386 "maxCapacity size too small": { 387 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 388 fap.List.MaxCapacity = int64(4) 389 }), 390 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 391 wantLength: 1, 392 wantField: "spec.policy.list.maxCapacity", 393 }, 394 "minCapacity size too small": { 395 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 396 fap.List.MinCapacity = int64(4) 397 }), 398 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 399 wantLength: 1, 400 wantField: "spec.policy.list.minCapacity", 401 }, 402 "bufferSize percentage OK": { 403 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 404 fap.List.BufferSize.Type = intstr.String 405 fap.List.BufferSize = intstr.FromString("99%") 406 fap.List.MinCapacity = 1 407 }), 408 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 409 wantLength: 0, 410 }, 411 "bufferSize percentage can't parse": { 412 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 413 fap.List.BufferSize.Type = intstr.String 414 fap.List.BufferSize = intstr.FromString("99.0%") 415 fap.List.MinCapacity = 1 416 }), 417 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 418 wantLength: 1, 419 wantField: "spec.policy.list.bufferSize", 420 }, 421 "bufferSize percentage and MinCapacity too small": { 422 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 423 fap.List.BufferSize.Type = intstr.String 424 fap.List.BufferSize = intstr.FromString("0%") 425 }), 426 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 427 wantLength: 2, 428 wantField: "spec.policy.list.bufferSize", 429 }, 430 "bufferSize percentage too large": { 431 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 432 fap.List.BufferSize.Type = intstr.String 433 fap.List.BufferSize = intstr.FromString("100%") 434 fap.List.MinCapacity = 1 435 }), 436 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 437 wantLength: 1, 438 wantField: "spec.policy.list.bufferSize", 439 }, 440 } 441 442 runtime.FeatureTestMutex.Lock() 443 defer runtime.FeatureTestMutex.Unlock() 444 445 for name, tc := range testCases { 446 t.Run(name, func(t *testing.T) { 447 err := runtime.ParseFeatures(tc.featureFlags) 448 assert.NoError(t, err) 449 450 causes := tc.fas.Validate() 451 452 assert.Len(t, causes, tc.wantLength) 453 if tc.wantLength > 0 { 454 assert.Equal(t, tc.wantField, causes[0].Field) 455 } 456 }) 457 } 458 } 459 460 func TestFleetAutoscalerScheduleValidateUpdate(t *testing.T) { 461 t.Parallel() 462 463 modifiedFAS := func(f func(*FleetAutoscalerPolicy)) *FleetAutoscaler { 464 fas := scheduleFixture() 465 f(&fas.Spec.Policy) 466 return fas 467 } 468 469 testCases := map[string]struct { 470 fas *FleetAutoscaler 471 featureFlags string 472 wantLength int 473 wantField string 474 }{ 475 "feature gate not turned on": { 476 fas: scheduleFixture(), 477 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=false", 478 wantLength: 1, 479 wantField: "spec.policy.schedule", 480 }, 481 "end time before current time": { 482 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 483 fap.Schedule.Between.End = mustParseDate("2024-07-03T15:59:59Z") 484 }), 485 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 486 wantLength: 1, 487 wantField: "spec.policy.schedule.between.end", 488 }, 489 "end time before start time": { 490 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 491 fap.Schedule.Between.Start = mustParseDate("3999-06-15T15:59:59Z") 492 fap.Schedule.Between.End = mustParseDate("3999-05-15T15:59:59Z") 493 }), 494 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 495 wantLength: 1, 496 wantField: "spec.policy.schedule.between", 497 }, 498 "invalid cron format": { 499 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 500 fap.Schedule.ActivePeriod.StartCron = "* * * * * * * *" 501 }), 502 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 503 wantLength: 1, 504 wantField: "spec.policy.schedule.activePeriod.startCron", 505 }, 506 "invalid cron format with tz specification": { 507 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 508 fap.Schedule.ActivePeriod.StartCron = "CRON_TZ=America/New_York * * * * *" 509 }), 510 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 511 wantLength: 1, 512 wantField: "spec.policy.schedule.activePeriod.startCron", 513 }, 514 } 515 516 runtime.FeatureTestMutex.Lock() 517 defer runtime.FeatureTestMutex.Unlock() 518 519 for name, tc := range testCases { 520 t.Run(name, func(t *testing.T) { 521 err := runtime.ParseFeatures(tc.featureFlags) 522 assert.NoError(t, err) 523 524 causes := tc.fas.Validate() 525 526 assert.Len(t, causes, tc.wantLength) 527 if tc.wantLength > 0 { 528 assert.Equal(t, tc.wantField, causes[0].Field) 529 } 530 }) 531 } 532 } 533 534 func TestFleetAutoscalerChainValidateUpdate(t *testing.T) { 535 t.Parallel() 536 537 modifiedFAS := func(f func(*FleetAutoscalerPolicy)) *FleetAutoscaler { 538 fas := chainFixture() 539 f(&fas.Spec.Policy) 540 return fas 541 } 542 543 testCases := map[string]struct { 544 fas *FleetAutoscaler 545 featureFlags string 546 wantLength int 547 wantField string 548 }{ 549 "feature gate not turned on": { 550 fas: chainFixture(), 551 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=false", 552 wantLength: 1, 553 wantField: "spec.policy.chain", 554 }, 555 "duplicate id": { 556 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 557 fap.Chain[1].ID = "weekends" 558 }), 559 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 560 wantLength: 1, 561 wantField: "spec.policy.chain", 562 }, 563 "missing policy": { 564 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 565 fap.Chain[0].FleetAutoscalerPolicy = FleetAutoscalerPolicy{} 566 }), 567 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 568 wantLength: 1, 569 wantField: "spec.policy.chain[0]", 570 }, 571 "nested chain policy not allowed": { 572 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 573 fap.Chain[1].FleetAutoscalerPolicy = FleetAutoscalerPolicy{ 574 Type: ChainPolicyType, 575 Chain: ChainPolicy{ 576 { 577 FleetAutoscalerPolicy: FleetAutoscalerPolicy{ 578 Type: BufferPolicyType, 579 Buffer: &BufferPolicy{ 580 BufferSize: intstr.FromInt(5), 581 MaxReplicas: 10, 582 }, 583 }, 584 }, 585 }, 586 } 587 }), 588 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 589 wantLength: 1, 590 wantField: "spec.policy.chain[1]", 591 }, 592 "invalid nested policy format": { 593 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 594 fap.Chain[1].FleetAutoscalerPolicy.Buffer.MinReplicas = 20 595 }), 596 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 597 wantLength: 1, 598 wantField: "spec.policy.chain[1].policy.buffer.minReplicas", 599 }, 600 } 601 602 runtime.FeatureTestMutex.Lock() 603 defer runtime.FeatureTestMutex.Unlock() 604 605 for name, tc := range testCases { 606 t.Run(name, func(t *testing.T) { 607 err := runtime.ParseFeatures(tc.featureFlags) 608 assert.NoError(t, err) 609 610 causes := tc.fas.Validate() 611 612 assert.Len(t, causes, tc.wantLength) 613 if tc.wantLength > 0 { 614 assert.Equal(t, tc.wantField, causes[0].Field) 615 } 616 }) 617 } 618 } 619 620 func TestFleetAutoscalerWasmValidateUpdate(t *testing.T) { 621 t.Parallel() 622 623 testCases := map[string]struct { 624 fas *FleetAutoscaler 625 featureFlags string 626 wantLength int 627 wantField string 628 }{ 629 "valid": { 630 fas: wasmFixture(), 631 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=true", 632 wantLength: 0, 633 }, 634 "feature gate not turned on": { 635 fas: wasmFixture(), 636 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=false", 637 wantLength: 1, 638 wantField: "spec.policy.wasm", 639 }, 640 "nil wasm policy": { 641 fas: func() *FleetAutoscaler { 642 fas := wasmFixture() 643 fas.Spec.Policy.Wasm = nil 644 return fas 645 }(), 646 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=true", 647 wantLength: 1, 648 wantField: "spec.policy.wasm", 649 }, 650 "nil from.url": { 651 fas: func() *FleetAutoscaler { 652 fas := wasmFixture() 653 fas.Spec.Policy.Wasm.From.URL = nil 654 return fas 655 }(), 656 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=true", 657 wantLength: 1, 658 wantField: "spec.policy.wasm.from", 659 }, 660 "invalid url": { 661 fas: func() *FleetAutoscaler { 662 fas := wasmFixture() 663 badURL := "://invalid-url" 664 fas.Spec.Policy.Wasm.From.URL.URL = &badURL 665 return fas 666 }(), 667 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=true", 668 wantLength: 1, 669 wantField: "spec.policy.wasm.from.url.url", 670 }, 671 } 672 673 runtime.FeatureTestMutex.Lock() 674 defer runtime.FeatureTestMutex.Unlock() 675 676 for name, tc := range testCases { 677 t.Run(name, func(t *testing.T) { 678 err := runtime.ParseFeatures(tc.featureFlags) 679 assert.NoError(t, err) 680 681 causes := tc.fas.Validate() 682 683 assert.Len(t, causes, tc.wantLength) 684 if tc.wantLength > 0 && len(causes) > 0 { 685 assert.Equal(t, tc.wantField, causes[0].Field) 686 } 687 }) 688 } 689 } 690 691 func TestFleetAutoscalerApplyDefaults(t *testing.T) { 692 fas := &FleetAutoscaler{} 693 694 // gate 695 assert.Nil(t, fas.Spec.Sync) 696 697 fas.ApplyDefaults() 698 assert.NotNil(t, fas.Spec.Sync) 699 assert.Equal(t, FixedIntervalSyncType, fas.Spec.Sync.Type) 700 assert.Equal(t, defaultIntervalSyncSeconds, fas.Spec.Sync.FixedInterval.Seconds) 701 702 // Test apply defaults is idempotent -- calling ApplyDefaults more than one time does not change the original result. 703 fas.ApplyDefaults() 704 assert.NotNil(t, fas.Spec.Sync) 705 assert.Equal(t, FixedIntervalSyncType, fas.Spec.Sync.Type) 706 assert.Equal(t, defaultIntervalSyncSeconds, fas.Spec.Sync.FixedInterval.Seconds) 707 } 708 709 func mustParseDate(timeStr string) metav1.Time { 710 t, _ := time.Parse(time.RFC3339, timeStr) 711 return metav1.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) 712 } 713 714 func defaultFixture() *FleetAutoscaler { 715 return customFixture(BufferPolicyType) 716 } 717 718 func webhookFixture() *FleetAutoscaler { 719 return customFixture(WebhookPolicyType) 720 } 721 722 func counterFixture() *FleetAutoscaler { 723 return customFixture(CounterPolicyType) 724 } 725 726 func listFixture() *FleetAutoscaler { 727 return customFixture(ListPolicyType) 728 } 729 730 func scheduleFixture() *FleetAutoscaler { 731 return customFixture(SchedulePolicyType) 732 } 733 734 func chainFixture() *FleetAutoscaler { 735 return customFixture(ChainPolicyType) 736 } 737 738 func wasmFixture() *FleetAutoscaler { 739 return customFixture(WasmPolicyType) 740 } 741 742 func customFixture(t FleetAutoscalerPolicyType) *FleetAutoscaler { 743 744 res := &FleetAutoscaler{ 745 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 746 Spec: FleetAutoscalerSpec{ 747 FleetName: "testing", 748 Policy: FleetAutoscalerPolicy{ 749 Type: BufferPolicyType, 750 Buffer: &BufferPolicy{ 751 BufferSize: intstr.FromInt(5), 752 MaxReplicas: 10, 753 }, 754 }, 755 Sync: &FleetAutoscalerSync{ 756 Type: FixedIntervalSyncType, 757 FixedInterval: FixedIntervalSync{ 758 Seconds: 30, 759 }, 760 }, 761 }, 762 } 763 switch t { 764 case BufferPolicyType: 765 case WebhookPolicyType: 766 res.Spec.Policy.Type = WebhookPolicyType 767 res.Spec.Policy.Buffer = nil 768 url := "/scale" 769 res.Spec.Policy.Webhook = &URLConfiguration{ 770 Service: &admregv1.ServiceReference{ 771 Name: "service1", 772 Namespace: "default", 773 Path: &url, 774 }, 775 } 776 case CounterPolicyType: 777 res.Spec.Policy.Type = CounterPolicyType 778 res.Spec.Policy.Buffer = nil 779 res.Spec.Policy.Counter = &CounterPolicy{ 780 BufferSize: intstr.FromInt(5), 781 MaxCapacity: 10, 782 } 783 case ListPolicyType: 784 res.Spec.Policy.Type = ListPolicyType 785 res.Spec.Policy.Buffer = nil 786 res.Spec.Policy.List = &ListPolicy{ 787 BufferSize: intstr.FromInt(5), 788 MaxCapacity: 10, 789 } 790 case SchedulePolicyType: 791 res.Spec.Policy.Type = SchedulePolicyType 792 res.Spec.Policy.Buffer = nil 793 res.Spec.Policy.Schedule = &SchedulePolicy{ 794 Between: Between{ 795 Start: mustParseDate("2024-07-01T15:59:59Z"), 796 End: mustParseDate("9999-07-03T15:59:59Z"), 797 }, 798 ActivePeriod: ActivePeriod{ 799 Timezone: "", 800 StartCron: "* * * * 1-5", 801 Duration: "", 802 }, 803 Policy: FleetAutoscalerPolicy{ 804 Type: BufferPolicyType, 805 Buffer: &BufferPolicy{ 806 BufferSize: intstr.FromInt(5), 807 MaxReplicas: 10, 808 }, 809 }, 810 } 811 case ChainPolicyType: 812 res.Spec.Policy.Type = ChainPolicyType 813 res.Spec.Policy.Buffer = nil 814 res.Spec.Policy.Chain = ChainPolicy{ 815 { 816 ID: "weekends", 817 FleetAutoscalerPolicy: FleetAutoscalerPolicy{ 818 Type: SchedulePolicyType, 819 Schedule: &SchedulePolicy{ 820 Between: Between{ 821 Start: mustParseDate("2024-07-04T15:59:59Z"), 822 End: mustParseDate("9999-07-05T15:59:59Z"), 823 }, 824 ActivePeriod: ActivePeriod{ 825 Timezone: "", 826 StartCron: "* * * * 5-6", 827 Duration: "", 828 }, 829 Policy: FleetAutoscalerPolicy{ 830 Type: CounterPolicyType, 831 Counter: &CounterPolicy{ 832 Key: "playerCount", 833 BufferSize: intstr.FromInt32(5), 834 MaxCapacity: 10, 835 }, 836 }, 837 }, 838 }, 839 }, 840 { 841 ID: "", 842 FleetAutoscalerPolicy: FleetAutoscalerPolicy{ 843 Type: BufferPolicyType, 844 Buffer: &BufferPolicy{ 845 BufferSize: intstr.FromInt32(5), 846 MaxReplicas: 10, 847 }, 848 }, 849 }, 850 } 851 case WasmPolicyType: 852 res.Spec.Policy.Type = WasmPolicyType 853 res.Spec.Policy.Buffer = nil 854 url := "http://example.com/wasm-module" 855 res.Spec.Policy.Wasm = &WasmPolicy{ 856 Function: "scale", 857 Config: map[string]string{ 858 "scale_buffer": "10", 859 }, 860 From: WasmFrom{ 861 URL: &URLConfiguration{ 862 URL: &url, 863 }, 864 }, 865 } 866 } 867 return res 868 }