agones.dev/agones@v1.54.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, 0) 203 }) 204 205 t.Run("bad url value", func(t *testing.T) { 206 fas := webhookFixture() 207 url := "http:/bad.example.com%" 208 fas.Spec.Policy.Webhook.URL = &url 209 fas.Spec.Policy.Webhook.Service = nil 210 fas.Spec.Policy.Webhook.CABundle = []byte(goodCaBundle) 211 212 causes := fas.Validate() 213 assert.Len(t, causes, 1) 214 assert.Equal(t, "spec.policy.webhook.url", causes[0].Field) 215 }) 216 217 } 218 219 // nolint:dupl // Linter errors on lines are duplicate of TestFleetAutoscalerListValidateUpdate 220 func TestFleetAutoscalerCounterValidateUpdate(t *testing.T) { 221 t.Parallel() 222 223 modifiedFAS := func(f func(*FleetAutoscalerPolicy)) *FleetAutoscaler { 224 fas := counterFixture() 225 f(&fas.Spec.Policy) 226 return fas 227 } 228 229 testCases := map[string]struct { 230 fas *FleetAutoscaler 231 featureFlags string 232 wantLength int 233 wantField string 234 }{ 235 "feature gate not turned on": { 236 fas: counterFixture(), 237 featureFlags: string(runtime.FeatureCountsAndLists) + "=false", 238 wantLength: 1, 239 wantField: "spec.policy.counter", 240 }, 241 "nil parameters": { 242 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 243 fap.Counter = nil 244 }), 245 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 246 wantLength: 1, 247 wantField: "spec.policy.counter", 248 }, 249 "minCapacity size too large": { 250 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 251 fap.Counter.MinCapacity = int64(11) 252 }), 253 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 254 wantLength: 1, 255 wantField: "spec.policy.counter.minCapacity", 256 }, 257 "bufferSize size too small": { 258 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 259 fap.Counter.BufferSize = intstr.FromInt(0) 260 }), 261 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 262 wantLength: 1, 263 wantField: "spec.policy.counter.bufferSize", 264 }, 265 "maxCapacity size too small": { 266 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 267 fap.Counter.MaxCapacity = int64(4) 268 }), 269 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 270 wantLength: 1, 271 wantField: "spec.policy.counter.maxCapacity", 272 }, 273 "minCapacity size too small": { 274 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 275 fap.Counter.MinCapacity = int64(4) 276 }), 277 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 278 wantLength: 1, 279 wantField: "spec.policy.counter.minCapacity", 280 }, 281 "bufferSize percentage OK": { 282 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 283 fap.Counter.BufferSize.Type = intstr.String 284 fap.Counter.BufferSize = intstr.FromString("99%") 285 fap.Counter.MinCapacity = 10 286 }), 287 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 288 wantLength: 0, 289 }, 290 "bufferSize percentage can't parse": { 291 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 292 fap.Counter.BufferSize.Type = intstr.String 293 fap.Counter.BufferSize = intstr.FromString("99.0%") 294 fap.Counter.MinCapacity = 1 295 }), 296 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 297 wantLength: 1, 298 wantField: "spec.policy.counter.bufferSize", 299 }, 300 "bufferSize percentage and MinCapacity too small": { 301 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 302 fap.Counter.BufferSize.Type = intstr.String 303 fap.Counter.BufferSize = intstr.FromString("0%") 304 }), 305 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 306 wantLength: 2, 307 wantField: "spec.policy.counter.bufferSize", 308 }, 309 "bufferSize percentage too large": { 310 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 311 fap.Counter.BufferSize.Type = intstr.String 312 fap.Counter.BufferSize = intstr.FromString("100%") 313 fap.Counter.MinCapacity = 10 314 }), 315 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 316 wantLength: 1, 317 wantField: "spec.policy.counter.bufferSize", 318 }, 319 } 320 321 runtime.FeatureTestMutex.Lock() 322 defer runtime.FeatureTestMutex.Unlock() 323 324 for name, tc := range testCases { 325 t.Run(name, func(t *testing.T) { 326 err := runtime.ParseFeatures(tc.featureFlags) 327 assert.NoError(t, err) 328 329 causes := tc.fas.Validate() 330 331 assert.Len(t, causes, tc.wantLength) 332 if tc.wantLength > 0 { 333 assert.Equal(t, tc.wantField, causes[0].Field) 334 } 335 }) 336 } 337 } 338 339 // nolint:dupl // Linter errors on lines are duplicate of TestFleetAutoscalerCounterValidateUpdate 340 func TestFleetAutoscalerListValidateUpdate(t *testing.T) { 341 t.Parallel() 342 343 modifiedFAS := func(f func(*FleetAutoscalerPolicy)) *FleetAutoscaler { 344 fas := listFixture() 345 f(&fas.Spec.Policy) 346 return fas 347 } 348 349 testCases := map[string]struct { 350 fas *FleetAutoscaler 351 featureFlags string 352 wantLength int 353 wantField string 354 }{ 355 "feature gate not turned on": { 356 fas: listFixture(), 357 featureFlags: string(runtime.FeatureCountsAndLists) + "=false", 358 wantLength: 1, 359 wantField: "spec.policy.list", 360 }, 361 "nil parameters": { 362 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 363 fap.List = nil 364 }), 365 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 366 wantLength: 1, 367 wantField: "spec.policy.list", 368 }, 369 "minCapacity size too large": { 370 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 371 fap.List.MinCapacity = int64(11) 372 }), 373 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 374 wantLength: 1, 375 wantField: "spec.policy.list.minCapacity", 376 }, 377 "bufferSize size too small": { 378 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 379 fap.List.BufferSize = intstr.FromInt(0) 380 }), 381 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 382 wantLength: 1, 383 wantField: "spec.policy.list.bufferSize", 384 }, 385 "maxCapacity size too small": { 386 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 387 fap.List.MaxCapacity = int64(4) 388 }), 389 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 390 wantLength: 1, 391 wantField: "spec.policy.list.maxCapacity", 392 }, 393 "minCapacity size too small": { 394 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 395 fap.List.MinCapacity = int64(4) 396 }), 397 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 398 wantLength: 1, 399 wantField: "spec.policy.list.minCapacity", 400 }, 401 "bufferSize percentage OK": { 402 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 403 fap.List.BufferSize.Type = intstr.String 404 fap.List.BufferSize = intstr.FromString("99%") 405 fap.List.MinCapacity = 1 406 }), 407 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 408 wantLength: 0, 409 }, 410 "bufferSize percentage can't parse": { 411 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 412 fap.List.BufferSize.Type = intstr.String 413 fap.List.BufferSize = intstr.FromString("99.0%") 414 fap.List.MinCapacity = 1 415 }), 416 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 417 wantLength: 1, 418 wantField: "spec.policy.list.bufferSize", 419 }, 420 "bufferSize percentage and MinCapacity too small": { 421 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 422 fap.List.BufferSize.Type = intstr.String 423 fap.List.BufferSize = intstr.FromString("0%") 424 }), 425 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 426 wantLength: 2, 427 wantField: "spec.policy.list.bufferSize", 428 }, 429 "bufferSize percentage too large": { 430 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 431 fap.List.BufferSize.Type = intstr.String 432 fap.List.BufferSize = intstr.FromString("100%") 433 fap.List.MinCapacity = 1 434 }), 435 featureFlags: string(runtime.FeatureCountsAndLists) + "=true", 436 wantLength: 1, 437 wantField: "spec.policy.list.bufferSize", 438 }, 439 } 440 441 runtime.FeatureTestMutex.Lock() 442 defer runtime.FeatureTestMutex.Unlock() 443 444 for name, tc := range testCases { 445 t.Run(name, func(t *testing.T) { 446 err := runtime.ParseFeatures(tc.featureFlags) 447 assert.NoError(t, err) 448 449 causes := tc.fas.Validate() 450 451 assert.Len(t, causes, tc.wantLength) 452 if tc.wantLength > 0 { 453 assert.Equal(t, tc.wantField, causes[0].Field) 454 } 455 }) 456 } 457 } 458 459 func TestFleetAutoscalerScheduleValidateUpdate(t *testing.T) { 460 t.Parallel() 461 462 modifiedFAS := func(f func(*FleetAutoscalerPolicy)) *FleetAutoscaler { 463 fas := scheduleFixture() 464 f(&fas.Spec.Policy) 465 return fas 466 } 467 468 testCases := map[string]struct { 469 fas *FleetAutoscaler 470 featureFlags string 471 wantLength int 472 wantField string 473 }{ 474 "feature gate not turned on": { 475 fas: scheduleFixture(), 476 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=false", 477 wantLength: 1, 478 wantField: "spec.policy.schedule", 479 }, 480 "end time before current time": { 481 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 482 fap.Schedule.Between.End = mustParseDate("2024-07-03T15:59:59Z") 483 }), 484 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 485 wantLength: 1, 486 wantField: "spec.policy.schedule.between.end", 487 }, 488 "end time before start time": { 489 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 490 fap.Schedule.Between.Start = mustParseDate("3999-06-15T15:59:59Z") 491 fap.Schedule.Between.End = mustParseDate("3999-05-15T15:59:59Z") 492 }), 493 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 494 wantLength: 1, 495 wantField: "spec.policy.schedule.between", 496 }, 497 "invalid cron format": { 498 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 499 fap.Schedule.ActivePeriod.StartCron = "* * * * * * * *" 500 }), 501 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 502 wantLength: 1, 503 wantField: "spec.policy.schedule.activePeriod.startCron", 504 }, 505 "invalid cron format with tz specification": { 506 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 507 fap.Schedule.ActivePeriod.StartCron = "CRON_TZ=America/New_York * * * * *" 508 }), 509 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 510 wantLength: 1, 511 wantField: "spec.policy.schedule.activePeriod.startCron", 512 }, 513 } 514 515 runtime.FeatureTestMutex.Lock() 516 defer runtime.FeatureTestMutex.Unlock() 517 518 for name, tc := range testCases { 519 t.Run(name, func(t *testing.T) { 520 err := runtime.ParseFeatures(tc.featureFlags) 521 assert.NoError(t, err) 522 523 causes := tc.fas.Validate() 524 525 assert.Len(t, causes, tc.wantLength) 526 if tc.wantLength > 0 { 527 assert.Equal(t, tc.wantField, causes[0].Field) 528 } 529 }) 530 } 531 } 532 533 func TestFleetAutoscalerChainValidateUpdate(t *testing.T) { 534 t.Parallel() 535 536 modifiedFAS := func(f func(*FleetAutoscalerPolicy)) *FleetAutoscaler { 537 fas := chainFixture() 538 f(&fas.Spec.Policy) 539 return fas 540 } 541 542 testCases := map[string]struct { 543 fas *FleetAutoscaler 544 featureFlags string 545 wantLength int 546 wantField string 547 }{ 548 "feature gate not turned on": { 549 fas: chainFixture(), 550 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=false", 551 wantLength: 1, 552 wantField: "spec.policy.chain", 553 }, 554 "duplicate id": { 555 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 556 fap.Chain[1].ID = "weekends" 557 }), 558 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 559 wantLength: 1, 560 wantField: "spec.policy.chain", 561 }, 562 "missing policy": { 563 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 564 fap.Chain[0].FleetAutoscalerPolicy = FleetAutoscalerPolicy{} 565 }), 566 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 567 wantLength: 1, 568 wantField: "spec.policy.chain[0]", 569 }, 570 "nested chain policy not allowed": { 571 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 572 fap.Chain[1].FleetAutoscalerPolicy = FleetAutoscalerPolicy{ 573 Type: ChainPolicyType, 574 Chain: ChainPolicy{ 575 { 576 FleetAutoscalerPolicy: FleetAutoscalerPolicy{ 577 Type: BufferPolicyType, 578 Buffer: &BufferPolicy{ 579 BufferSize: intstr.FromInt(5), 580 MaxReplicas: 10, 581 }, 582 }, 583 }, 584 }, 585 } 586 }), 587 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 588 wantLength: 1, 589 wantField: "spec.policy.chain[1]", 590 }, 591 "invalid nested policy format": { 592 fas: modifiedFAS(func(fap *FleetAutoscalerPolicy) { 593 fap.Chain[1].FleetAutoscalerPolicy.Buffer.MinReplicas = 20 594 }), 595 featureFlags: string(runtime.FeatureScheduledAutoscaler) + "=true", 596 wantLength: 1, 597 wantField: "spec.policy.chain[1].policy.buffer.minReplicas", 598 }, 599 } 600 601 runtime.FeatureTestMutex.Lock() 602 defer runtime.FeatureTestMutex.Unlock() 603 604 for name, tc := range testCases { 605 t.Run(name, func(t *testing.T) { 606 err := runtime.ParseFeatures(tc.featureFlags) 607 assert.NoError(t, err) 608 609 causes := tc.fas.Validate() 610 611 assert.Len(t, causes, tc.wantLength) 612 if tc.wantLength > 0 { 613 assert.Equal(t, tc.wantField, causes[0].Field) 614 } 615 }) 616 } 617 } 618 619 func TestFleetAutoscalerWasmValidateUpdate(t *testing.T) { 620 t.Parallel() 621 622 testCases := map[string]struct { 623 fas *FleetAutoscaler 624 featureFlags string 625 wantLength int 626 wantField string 627 }{ 628 "valid": { 629 fas: wasmFixture(), 630 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=true", 631 wantLength: 0, 632 }, 633 "feature gate not turned on": { 634 fas: wasmFixture(), 635 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=false", 636 wantLength: 1, 637 wantField: "spec.policy.wasm", 638 }, 639 "nil wasm policy": { 640 fas: func() *FleetAutoscaler { 641 fas := wasmFixture() 642 fas.Spec.Policy.Wasm = nil 643 return fas 644 }(), 645 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=true", 646 wantLength: 1, 647 wantField: "spec.policy.wasm", 648 }, 649 "nil from.url": { 650 fas: func() *FleetAutoscaler { 651 fas := wasmFixture() 652 fas.Spec.Policy.Wasm.From.URL = nil 653 return fas 654 }(), 655 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=true", 656 wantLength: 1, 657 wantField: "spec.policy.wasm.from", 658 }, 659 "invalid url": { 660 fas: func() *FleetAutoscaler { 661 fas := wasmFixture() 662 badURL := "://invalid-url" 663 fas.Spec.Policy.Wasm.From.URL.URL = &badURL 664 return fas 665 }(), 666 featureFlags: string(runtime.FeatureWasmAutoscaler) + "=true", 667 wantLength: 1, 668 wantField: "spec.policy.wasm.from.url.url", 669 }, 670 } 671 672 runtime.FeatureTestMutex.Lock() 673 defer runtime.FeatureTestMutex.Unlock() 674 675 for name, tc := range testCases { 676 t.Run(name, func(t *testing.T) { 677 err := runtime.ParseFeatures(tc.featureFlags) 678 assert.NoError(t, err) 679 680 causes := tc.fas.Validate() 681 682 assert.Len(t, causes, tc.wantLength) 683 if tc.wantLength > 0 && len(causes) > 0 { 684 assert.Equal(t, tc.wantField, causes[0].Field) 685 } 686 }) 687 } 688 } 689 690 func TestFleetAutoscalerApplyDefaults(t *testing.T) { 691 fas := &FleetAutoscaler{} 692 693 // gate 694 assert.Nil(t, fas.Spec.Sync) 695 696 fas.ApplyDefaults() 697 assert.NotNil(t, fas.Spec.Sync) 698 assert.Equal(t, FixedIntervalSyncType, fas.Spec.Sync.Type) 699 assert.Equal(t, defaultIntervalSyncSeconds, fas.Spec.Sync.FixedInterval.Seconds) 700 701 // Test apply defaults is idempotent -- calling ApplyDefaults more than one time does not change the original result. 702 fas.ApplyDefaults() 703 assert.NotNil(t, fas.Spec.Sync) 704 assert.Equal(t, FixedIntervalSyncType, fas.Spec.Sync.Type) 705 assert.Equal(t, defaultIntervalSyncSeconds, fas.Spec.Sync.FixedInterval.Seconds) 706 } 707 708 func mustParseDate(timeStr string) metav1.Time { 709 t, _ := time.Parse(time.RFC3339, timeStr) 710 return metav1.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) 711 } 712 713 func defaultFixture() *FleetAutoscaler { 714 return customFixture(BufferPolicyType) 715 } 716 717 func webhookFixture() *FleetAutoscaler { 718 return customFixture(WebhookPolicyType) 719 } 720 721 func counterFixture() *FleetAutoscaler { 722 return customFixture(CounterPolicyType) 723 } 724 725 func listFixture() *FleetAutoscaler { 726 return customFixture(ListPolicyType) 727 } 728 729 func scheduleFixture() *FleetAutoscaler { 730 return customFixture(SchedulePolicyType) 731 } 732 733 func chainFixture() *FleetAutoscaler { 734 return customFixture(ChainPolicyType) 735 } 736 737 func wasmFixture() *FleetAutoscaler { 738 return customFixture(WasmPolicyType) 739 } 740 741 func customFixture(t FleetAutoscalerPolicyType) *FleetAutoscaler { 742 743 res := &FleetAutoscaler{ 744 ObjectMeta: metav1.ObjectMeta{Name: "test"}, 745 Spec: FleetAutoscalerSpec{ 746 FleetName: "testing", 747 Policy: FleetAutoscalerPolicy{ 748 Type: BufferPolicyType, 749 Buffer: &BufferPolicy{ 750 BufferSize: intstr.FromInt(5), 751 MaxReplicas: 10, 752 }, 753 }, 754 Sync: &FleetAutoscalerSync{ 755 Type: FixedIntervalSyncType, 756 FixedInterval: FixedIntervalSync{ 757 Seconds: 30, 758 }, 759 }, 760 }, 761 } 762 switch t { 763 case BufferPolicyType: 764 case WebhookPolicyType: 765 res.Spec.Policy.Type = WebhookPolicyType 766 res.Spec.Policy.Buffer = nil 767 url := "/scale" 768 res.Spec.Policy.Webhook = &URLConfiguration{ 769 Service: &admregv1.ServiceReference{ 770 Name: "service1", 771 Namespace: "default", 772 Path: &url, 773 }, 774 } 775 case CounterPolicyType: 776 res.Spec.Policy.Type = CounterPolicyType 777 res.Spec.Policy.Buffer = nil 778 res.Spec.Policy.Counter = &CounterPolicy{ 779 BufferSize: intstr.FromInt(5), 780 MaxCapacity: 10, 781 } 782 case ListPolicyType: 783 res.Spec.Policy.Type = ListPolicyType 784 res.Spec.Policy.Buffer = nil 785 res.Spec.Policy.List = &ListPolicy{ 786 BufferSize: intstr.FromInt(5), 787 MaxCapacity: 10, 788 } 789 case SchedulePolicyType: 790 res.Spec.Policy.Type = SchedulePolicyType 791 res.Spec.Policy.Buffer = nil 792 res.Spec.Policy.Schedule = &SchedulePolicy{ 793 Between: Between{ 794 Start: mustParseDate("2024-07-01T15:59:59Z"), 795 End: mustParseDate("9999-07-03T15:59:59Z"), 796 }, 797 ActivePeriod: ActivePeriod{ 798 Timezone: "", 799 StartCron: "* * * * 1-5", 800 Duration: "", 801 }, 802 Policy: FleetAutoscalerPolicy{ 803 Type: BufferPolicyType, 804 Buffer: &BufferPolicy{ 805 BufferSize: intstr.FromInt(5), 806 MaxReplicas: 10, 807 }, 808 }, 809 } 810 case ChainPolicyType: 811 res.Spec.Policy.Type = ChainPolicyType 812 res.Spec.Policy.Buffer = nil 813 res.Spec.Policy.Chain = ChainPolicy{ 814 { 815 ID: "weekends", 816 FleetAutoscalerPolicy: FleetAutoscalerPolicy{ 817 Type: SchedulePolicyType, 818 Schedule: &SchedulePolicy{ 819 Between: Between{ 820 Start: mustParseDate("2024-07-04T15:59:59Z"), 821 End: mustParseDate("9999-07-05T15:59:59Z"), 822 }, 823 ActivePeriod: ActivePeriod{ 824 Timezone: "", 825 StartCron: "* * * * 5-6", 826 Duration: "", 827 }, 828 Policy: FleetAutoscalerPolicy{ 829 Type: CounterPolicyType, 830 Counter: &CounterPolicy{ 831 Key: "playerCount", 832 BufferSize: intstr.FromInt32(5), 833 MaxCapacity: 10, 834 }, 835 }, 836 }, 837 }, 838 }, 839 { 840 ID: "", 841 FleetAutoscalerPolicy: FleetAutoscalerPolicy{ 842 Type: BufferPolicyType, 843 Buffer: &BufferPolicy{ 844 BufferSize: intstr.FromInt32(5), 845 MaxReplicas: 10, 846 }, 847 }, 848 }, 849 } 850 case WasmPolicyType: 851 res.Spec.Policy.Type = WasmPolicyType 852 res.Spec.Policy.Buffer = nil 853 url := "http://example.com/wasm-module" 854 res.Spec.Policy.Wasm = &WasmPolicy{ 855 Function: "scale", 856 Config: map[string]string{ 857 "scale_buffer": "10", 858 }, 859 From: WasmFrom{ 860 URL: &URLConfiguration{ 861 URL: &url, 862 }, 863 }, 864 } 865 } 866 return res 867 }