agones.dev/agones@v1.54.0/pkg/apis/allocation/v1/gameserverallocation_test.go (about) 1 // Copyright 2019 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 "fmt" 19 "sort" 20 "testing" 21 22 "agones.dev/agones/pkg/apis" 23 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 24 "agones.dev/agones/pkg/util/runtime" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/util/validation/field" 29 ) 30 31 func TestGameServerAllocationApplyDefaults(t *testing.T) { 32 t.Parallel() 33 34 gsa := &GameServerAllocation{} 35 gsa.ApplyDefaults() 36 37 assert.Equal(t, apis.Packed, gsa.Spec.Scheduling) 38 39 priorities := []agonesv1.Priority{ 40 {Type: agonesv1.GameServerPriorityList}, 41 {Type: agonesv1.GameServerPriorityCounter}, 42 } 43 expectedPrioritiesWithDefault := []agonesv1.Priority{ 44 {Type: agonesv1.GameServerPriorityList, Order: agonesv1.GameServerPriorityAscending}, 45 {Type: agonesv1.GameServerPriorityCounter, Order: agonesv1.GameServerPriorityAscending}, 46 } 47 48 gsa = &GameServerAllocation{Spec: GameServerAllocationSpec{Scheduling: apis.Distributed, Priorities: priorities}} 49 gsa.ApplyDefaults() 50 assert.Equal(t, apis.Distributed, gsa.Spec.Scheduling) 51 assert.Equal(t, expectedPrioritiesWithDefault, gsa.Spec.Priorities) 52 53 runtime.FeatureTestMutex.Lock() 54 defer runtime.FeatureTestMutex.Unlock() 55 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true&%s=true", runtime.FeaturePlayerAllocationFilter, runtime.FeatureCountsAndLists))) 56 57 gsa = &GameServerAllocation{} 58 gsa.ApplyDefaults() 59 60 assert.Equal(t, agonesv1.GameServerStateReady, *gsa.Spec.Required.GameServerState) 61 assert.Equal(t, int64(0), gsa.Spec.Required.Players.MaxAvailable) 62 assert.Equal(t, int64(0), gsa.Spec.Required.Players.MinAvailable) 63 assert.Equal(t, []agonesv1.Priority(nil), gsa.Spec.Priorities) 64 assert.Nil(t, gsa.Spec.Priorities) 65 } 66 67 // nolint // Current lint duplicate threshold will consider this function is a duplication of the function TestGameServerAllocationSpecSelectors 68 func TestGameServerAllocationSpecPreferredSelectors(t *testing.T) { 69 t.Parallel() 70 71 gsas := &GameServerAllocationSpec{ 72 Preferred: []GameServerSelector{ 73 {LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"check": "blue"}}}, 74 {LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"check": "red"}}}, 75 }, 76 } 77 78 require.Len(t, gsas.Preferred, 2) 79 80 gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}} 81 82 for _, s := range gsas.Preferred { 83 assert.False(t, s.Matches(gs)) 84 } 85 86 gs.ObjectMeta.Labels["check"] = "blue" 87 assert.True(t, gsas.Preferred[0].Matches(gs)) 88 assert.False(t, gsas.Preferred[1].Matches(gs)) 89 90 gs.ObjectMeta.Labels["check"] = "red" 91 assert.False(t, gsas.Preferred[0].Matches(gs)) 92 assert.True(t, gsas.Preferred[1].Matches(gs)) 93 } 94 95 // nolint // Current lint duplicate threshold will consider this function is a duplication of the function TestGameServerAllocationSpecPreferredSelectors 96 func TestGameServerAllocationSpecSelectors(t *testing.T) { 97 t.Parallel() 98 99 gsas := &GameServerAllocationSpec{ 100 Selectors: []GameServerSelector{ 101 {LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"check": "blue"}}}, 102 {LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"check": "red"}}}, 103 }, 104 } 105 106 require.Len(t, gsas.Selectors, 2) 107 108 gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}} 109 110 for _, s := range gsas.Selectors { 111 assert.False(t, s.Matches(gs)) 112 } 113 114 gs.ObjectMeta.Labels["check"] = "blue" 115 assert.True(t, gsas.Selectors[0].Matches(gs)) 116 assert.False(t, gsas.Selectors[1].Matches(gs)) 117 118 gs.ObjectMeta.Labels["check"] = "red" 119 assert.False(t, gsas.Selectors[0].Matches(gs)) 120 assert.True(t, gsas.Selectors[1].Matches(gs)) 121 } 122 123 func TestGameServerSelectorApplyDefaults(t *testing.T) { 124 t.Parallel() 125 runtime.FeatureTestMutex.Lock() 126 defer runtime.FeatureTestMutex.Unlock() 127 128 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true&%s=true", 129 runtime.FeaturePlayerAllocationFilter, 130 runtime.FeatureCountsAndLists))) 131 132 s := &GameServerSelector{} 133 134 // no defaults 135 s.ApplyDefaults() 136 assert.Equal(t, agonesv1.GameServerStateReady, *s.GameServerState) 137 assert.Equal(t, int64(0), s.Players.MinAvailable) 138 assert.Equal(t, int64(0), s.Players.MaxAvailable) 139 assert.NotNil(t, s.Counters) 140 assert.NotNil(t, s.Lists) 141 142 // Test apply defaults is idempotent -- calling ApplyDefaults more than one time does not change the original result. 143 s.ApplyDefaults() 144 assert.Equal(t, agonesv1.GameServerStateReady, *s.GameServerState) 145 assert.Equal(t, int64(0), s.Players.MinAvailable) 146 assert.Equal(t, int64(0), s.Players.MaxAvailable) 147 assert.NotNil(t, s.Counters) 148 assert.NotNil(t, s.Lists) 149 150 state := agonesv1.GameServerStateAllocated 151 // set values 152 s = &GameServerSelector{ 153 GameServerState: &state, 154 Players: &PlayerSelector{MinAvailable: 10, MaxAvailable: 20}, 155 Counters: map[string]CounterSelector{"foo": {MinAvailable: 1, MaxAvailable: 10}}, 156 Lists: map[string]ListSelector{"bar": {MinAvailable: 2}}, 157 } 158 s.ApplyDefaults() 159 assert.Equal(t, state, *s.GameServerState) 160 assert.Equal(t, int64(10), s.Players.MinAvailable) 161 assert.Equal(t, int64(20), s.Players.MaxAvailable) 162 assert.Equal(t, int64(0), s.Counters["foo"].MinCount) 163 assert.Equal(t, int64(0), s.Counters["foo"].MaxCount) 164 assert.Equal(t, int64(1), s.Counters["foo"].MinAvailable) 165 assert.Equal(t, int64(10), s.Counters["foo"].MaxAvailable) 166 assert.Equal(t, int64(2), s.Lists["bar"].MinAvailable) 167 assert.Equal(t, int64(0), s.Lists["bar"].MaxAvailable) 168 assert.Equal(t, "", s.Lists["bar"].ContainsValue) 169 170 // Test apply defaults is idempotent -- calling ApplyDefaults more than one time does not change the original result. 171 s.ApplyDefaults() 172 assert.Equal(t, state, *s.GameServerState) 173 assert.Equal(t, int64(10), s.Players.MinAvailable) 174 assert.Equal(t, int64(20), s.Players.MaxAvailable) 175 assert.Equal(t, int64(0), s.Counters["foo"].MinCount) 176 assert.Equal(t, int64(0), s.Counters["foo"].MaxCount) 177 assert.Equal(t, int64(1), s.Counters["foo"].MinAvailable) 178 assert.Equal(t, int64(10), s.Counters["foo"].MaxAvailable) 179 assert.Equal(t, int64(2), s.Lists["bar"].MinAvailable) 180 assert.Equal(t, int64(0), s.Lists["bar"].MaxAvailable) 181 assert.Equal(t, "", s.Lists["bar"].ContainsValue) 182 } 183 184 func TestGameServerSelectorValidate(t *testing.T) { 185 t.Parallel() 186 187 runtime.FeatureTestMutex.Lock() 188 defer runtime.FeatureTestMutex.Unlock() 189 190 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true&%s=true", runtime.FeaturePlayerAllocationFilter, runtime.FeatureCountsAndLists))) 191 192 allocated := agonesv1.GameServerStateAllocated 193 starting := agonesv1.GameServerStateStarting 194 195 fixtures := map[string]struct { 196 selector *GameServerSelector 197 want field.ErrorList 198 }{ 199 "valid": { 200 selector: &GameServerSelector{GameServerState: &allocated, Players: &PlayerSelector{ 201 MinAvailable: 0, 202 MaxAvailable: 10, 203 }}, 204 }, 205 "nil values": { 206 selector: &GameServerSelector{}, 207 }, 208 "invalid state": { 209 selector: &GameServerSelector{ 210 GameServerState: &starting, 211 }, 212 want: field.ErrorList{ 213 field.Invalid(field.NewPath("fieldName.gameServerState"), starting, "GameServerState must be either Allocated or Ready"), 214 }, 215 }, 216 "invalid min value": { 217 selector: &GameServerSelector{ 218 Players: &PlayerSelector{ 219 MinAvailable: -10, 220 }, 221 }, 222 want: field.ErrorList{ 223 field.Invalid(field.NewPath("fieldName", "players", "minAvailable"), int64(-10), "must be greater than or equal to 0"), 224 }, 225 }, 226 "invalid max value": { 227 selector: &GameServerSelector{ 228 Players: &PlayerSelector{ 229 MinAvailable: -30, 230 MaxAvailable: -20, 231 }, 232 }, 233 want: field.ErrorList{ 234 field.Invalid(field.NewPath("fieldName", "players", "minAvailable"), int64(-30), "must be greater than or equal to 0"), 235 field.Invalid(field.NewPath("fieldName", "players", "maxAvailable"), int64(-20), "must be greater than or equal to 0"), 236 }, 237 }, 238 "invalid min/max value": { 239 selector: &GameServerSelector{ 240 Players: &PlayerSelector{ 241 MinAvailable: 10, 242 MaxAvailable: 5, 243 }, 244 }, 245 want: field.ErrorList{ 246 field.Invalid(field.NewPath("fieldName", "players", "minAvailable"), int64(10), "minAvailable cannot be greater than maxAvailable"), 247 }, 248 }, 249 "invalid label keys": { 250 selector: &GameServerSelector{ 251 LabelSelector: metav1.LabelSelector{ 252 MatchLabels: map[string]string{"$$$$": "true"}, 253 }, 254 }, 255 want: field.ErrorList{ 256 field.Invalid( 257 field.NewPath("fieldName", "labelSelector"), 258 metav1.LabelSelector{MatchLabels: map[string]string{"$$$$": "true"}}, 259 `Error converting label selector: key: Invalid value: "$$$$": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`, 260 ), 261 }, 262 }, 263 "invalid min/max Counter available value": { 264 selector: &GameServerSelector{ 265 Counters: map[string]CounterSelector{ 266 "counter": { 267 MinAvailable: -1, 268 MaxAvailable: -1, 269 }, 270 }, 271 }, 272 want: field.ErrorList{ 273 field.Invalid(field.NewPath("fieldName", "counters[counter]", "minAvailable"), int64(-1), "must be greater than or equal to 0"), 274 field.Invalid(field.NewPath("fieldName", "counters[counter]", "maxAvailable"), int64(-1), "must be greater than or equal to 0"), 275 }, 276 }, 277 "invalid max less than min Counter available value": { 278 selector: &GameServerSelector{ 279 Counters: map[string]CounterSelector{ 280 "foo": { 281 MinAvailable: 10, 282 MaxAvailable: 1, 283 }, 284 }, 285 }, 286 want: field.ErrorList{ 287 field.Invalid(field.NewPath("fieldName", "counters[foo]"), int64(1), "maxAvailable must zero or greater than minAvailable 10"), 288 }, 289 }, 290 "invalid min/max Counter count value": { 291 selector: &GameServerSelector{ 292 Counters: map[string]CounterSelector{ 293 "counter": { 294 MinCount: -1, 295 MaxCount: -1, 296 }, 297 }, 298 }, 299 want: field.ErrorList{ 300 field.Invalid(field.NewPath("fieldName", "counters[counter]", "minCount"), int64(-1), "must be greater than or equal to 0"), 301 field.Invalid(field.NewPath("fieldName", "counters[counter]", "maxCount"), int64(-1), "must be greater than or equal to 0"), 302 }, 303 }, 304 "invalid max less than min Counter count value": { 305 selector: &GameServerSelector{ 306 Counters: map[string]CounterSelector{ 307 "foo": { 308 MinCount: 10, 309 MaxCount: 1, 310 }, 311 }, 312 }, 313 want: field.ErrorList{ 314 field.Invalid(field.NewPath("fieldName", "counters[foo]"), int64(1), "maxCount must zero or greater than minCount 10"), 315 }, 316 }, 317 "invalid min/max List value": { 318 selector: &GameServerSelector{ 319 Lists: map[string]ListSelector{ 320 "list": { 321 MinAvailable: -11, 322 MaxAvailable: -11, 323 }, 324 }, 325 }, 326 want: field.ErrorList{ 327 field.Invalid(field.NewPath("fieldName", "lists[list]", "minAvailable"), int64(-11), "must be greater than or equal to 0"), 328 field.Invalid(field.NewPath("fieldName", "lists[list]", "maxAvailable"), int64(-11), "must be greater than or equal to 0"), 329 }, 330 }, 331 "invalid max less than min List value": { 332 selector: &GameServerSelector{ 333 Lists: map[string]ListSelector{ 334 "list": { 335 MinAvailable: 11, 336 MaxAvailable: 2, 337 }, 338 }, 339 }, 340 want: field.ErrorList{ 341 field.Invalid(field.NewPath("fieldName", "lists[list]"), int64(2), "maxAvailable must zero or greater than minAvailable 11"), 342 }, 343 }, 344 } 345 346 for k, v := range fixtures { 347 t.Run(k, func(t *testing.T) { 348 v.selector.ApplyDefaults() 349 allErrs := v.selector.Validate(field.NewPath("fieldName")) 350 assert.ElementsMatch(t, v.want, allErrs) 351 }) 352 } 353 } 354 355 func TestMetaPatchValidate(t *testing.T) { 356 t.Parallel() 357 358 // valid 359 mp := &MetaPatch{ 360 Labels: nil, 361 Annotations: nil, 362 } 363 path := field.NewPath("spec", "metadata") 364 allErrs := mp.Validate(path) 365 assert.Len(t, allErrs, 0) 366 367 mp.Labels = map[string]string{} 368 mp.Annotations = map[string]string{} 369 allErrs = mp.Validate(path) 370 assert.Len(t, allErrs, 0) 371 372 mp.Labels["foo"] = "bar" 373 mp.Annotations["bar"] = "foo" 374 allErrs = mp.Validate(path) 375 assert.Len(t, allErrs, 0) 376 377 // invalid label 378 invalid := mp.DeepCopy() 379 invalid.Labels["$$$$"] = "no" 380 allErrs = invalid.Validate(path) 381 assert.Len(t, allErrs, 1) 382 assert.Equal(t, "spec.metadata.labels", allErrs[0].Field) 383 384 // invalid annotation 385 invalid = mp.DeepCopy() 386 invalid.Annotations["$$$$"] = "no" 387 388 allErrs = invalid.Validate(path) 389 require.Len(t, allErrs, 1) 390 assert.Equal(t, "spec.metadata.annotations", allErrs[0].Field) 391 392 // invalid both 393 invalid.Labels["$$$$"] = "no" 394 allErrs = invalid.Validate(path) 395 require.Len(t, allErrs, 2) 396 assert.Equal(t, "spec.metadata.labels", allErrs[0].Field) 397 assert.Equal(t, "spec.metadata.annotations", allErrs[1].Field) 398 } 399 400 func TestGameServerSelectorMatches(t *testing.T) { 401 t.Parallel() 402 403 runtime.FeatureTestMutex.Lock() 404 defer runtime.FeatureTestMutex.Unlock() 405 406 blueSelector := metav1.LabelSelector{ 407 MatchLabels: map[string]string{"colour": "blue"}, 408 } 409 410 allocatedState := agonesv1.GameServerStateAllocated 411 fixtures := map[string]struct { 412 features string 413 selector *GameServerSelector 414 gameServer *agonesv1.GameServer 415 matches bool 416 }{ 417 "no labels, pass": { 418 selector: &GameServerSelector{}, 419 gameServer: &agonesv1.GameServer{}, 420 matches: true, 421 }, 422 423 "no labels, fail": { 424 selector: &GameServerSelector{ 425 LabelSelector: blueSelector, 426 }, 427 gameServer: &agonesv1.GameServer{}, 428 matches: false, 429 }, 430 "single label, match": { 431 selector: &GameServerSelector{ 432 LabelSelector: blueSelector, 433 }, 434 gameServer: &agonesv1.GameServer{ 435 ObjectMeta: metav1.ObjectMeta{ 436 Labels: map[string]string{"colour": "blue"}, 437 }, 438 }, 439 matches: true, 440 }, 441 "single label, fail": { 442 selector: &GameServerSelector{ 443 LabelSelector: blueSelector, 444 }, 445 gameServer: &agonesv1.GameServer{ 446 ObjectMeta: metav1.ObjectMeta{ 447 Labels: map[string]string{"colour": "purple"}, 448 }, 449 }, 450 matches: false, 451 }, 452 "two labels, pass": { 453 selector: &GameServerSelector{ 454 LabelSelector: metav1.LabelSelector{ 455 MatchLabels: map[string]string{"colour": "blue", "animal": "frog"}, 456 }, 457 }, 458 gameServer: &agonesv1.GameServer{ 459 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"colour": "blue", "animal": "frog"}}, 460 }, 461 matches: true, 462 }, 463 "two labels, fail": { 464 selector: &GameServerSelector{ 465 LabelSelector: metav1.LabelSelector{ 466 MatchLabels: map[string]string{"colour": "blue", "animal": "cat"}, 467 }, 468 }, 469 gameServer: &agonesv1.GameServer{ 470 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"colour": "blue", "animal": "frog"}}, 471 }, 472 matches: false, 473 }, 474 "state filter, pass": { 475 selector: &GameServerSelector{ 476 GameServerState: &allocatedState, 477 }, 478 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{State: allocatedState}}, 479 matches: true, 480 }, 481 "state filter, fail": { 482 selector: &GameServerSelector{ 483 GameServerState: &allocatedState, 484 }, 485 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}}, 486 matches: false, 487 }, 488 "player tracking, between, pass": { 489 features: string(runtime.FeaturePlayerAllocationFilter) + "=true", 490 selector: &GameServerSelector{Players: &PlayerSelector{ 491 MinAvailable: 10, 492 MaxAvailable: 20, 493 }}, 494 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 495 Players: &agonesv1.PlayerStatus{ 496 Count: 20, 497 Capacity: 35, 498 }, 499 }}, 500 matches: true, 501 }, 502 "player tracking, between, fail": { 503 features: string(runtime.FeaturePlayerAllocationFilter) + "=true", 504 selector: &GameServerSelector{Players: &PlayerSelector{ 505 MinAvailable: 10, 506 MaxAvailable: 20, 507 }}, 508 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 509 Players: &agonesv1.PlayerStatus{ 510 Count: 30, 511 Capacity: 35, 512 }, 513 }}, 514 matches: false, 515 }, 516 "player tracking, max, pass": { 517 features: string(runtime.FeaturePlayerAllocationFilter) + "=true", 518 selector: &GameServerSelector{Players: &PlayerSelector{ 519 MinAvailable: 10, 520 }}, 521 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 522 Players: &agonesv1.PlayerStatus{ 523 Count: 20, 524 Capacity: 35, 525 }, 526 }}, 527 matches: true, 528 }, 529 "player tracking, max, fail": { 530 features: string(runtime.FeaturePlayerAllocationFilter) + "=true", 531 selector: &GameServerSelector{Players: &PlayerSelector{ 532 MinAvailable: 10, 533 }}, 534 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 535 Players: &agonesv1.PlayerStatus{ 536 Count: 30, 537 Capacity: 35, 538 }, 539 }}, 540 matches: true, 541 }, 542 "combo": { 543 features: string(runtime.FeaturePlayerAllocationFilter) + "=true&", 544 selector: &GameServerSelector{ 545 LabelSelector: metav1.LabelSelector{ 546 MatchLabels: map[string]string{"colour": "blue"}, 547 }, 548 GameServerState: &allocatedState, 549 Players: &PlayerSelector{ 550 MinAvailable: 10, 551 MaxAvailable: 20, 552 }, 553 }, 554 gameServer: &agonesv1.GameServer{ 555 ObjectMeta: metav1.ObjectMeta{ 556 Labels: map[string]string{"colour": "blue"}, 557 }, 558 Status: agonesv1.GameServerStatus{ 559 State: allocatedState, 560 Players: &agonesv1.PlayerStatus{ 561 Count: 5, 562 Capacity: 25, 563 }, 564 }, 565 }, 566 matches: true, 567 }, 568 "Counter has available capacity": { 569 features: string(runtime.FeatureCountsAndLists) + "=true", 570 selector: &GameServerSelector{Counters: map[string]CounterSelector{ 571 "sessions": { 572 MinAvailable: 1, 573 MaxAvailable: 1000, 574 }, 575 }}, 576 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 577 Counters: map[string]agonesv1.CounterStatus{ 578 "sessions": { 579 Count: 10, 580 Capacity: 1000, 581 }, 582 }, 583 }}, 584 matches: true, 585 }, 586 "Counter has below minimum available capacity": { 587 features: string(runtime.FeatureCountsAndLists) + "=true", 588 selector: &GameServerSelector{Counters: map[string]CounterSelector{ 589 "players": { 590 MinAvailable: 100, 591 MaxAvailable: 0, 592 }, 593 }}, 594 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 595 Counters: map[string]agonesv1.CounterStatus{ 596 "players": { 597 Count: 999, 598 Capacity: 1000, 599 }, 600 }, 601 }}, 602 matches: false, 603 }, 604 "Counter has above maximum available capacity": { 605 features: string(runtime.FeatureCountsAndLists) + "=true", 606 selector: &GameServerSelector{Counters: map[string]CounterSelector{ 607 "animals": { 608 MinAvailable: 1, 609 MaxAvailable: 100, 610 }, 611 }}, 612 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 613 Counters: map[string]agonesv1.CounterStatus{ 614 "animals": { 615 Count: 0, 616 Capacity: 1000, 617 }, 618 }, 619 }}, 620 matches: false, 621 }, 622 "Counter has count in requested range (MaxCount undefined = 0 = unlimited)": { 623 features: string(runtime.FeatureCountsAndLists) + "=true", 624 selector: &GameServerSelector{Counters: map[string]CounterSelector{ 625 "games": { 626 MinCount: 1, 627 }, 628 }}, 629 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 630 Counters: map[string]agonesv1.CounterStatus{ 631 "games": { 632 Count: 10, 633 Capacity: 1000, 634 }, 635 }, 636 }}, 637 matches: true, 638 }, 639 "Counter has count below minimum": { 640 features: string(runtime.FeatureCountsAndLists) + "=true", 641 selector: &GameServerSelector{Counters: map[string]CounterSelector{ 642 "characters": { 643 MinCount: 1, 644 MaxCount: 0, 645 }, 646 }}, 647 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 648 Counters: map[string]agonesv1.CounterStatus{ 649 "characters": { 650 Count: 0, 651 Capacity: 100, 652 }, 653 }, 654 }}, 655 matches: false, 656 }, 657 "Counter has count above maximum": { 658 features: string(runtime.FeatureCountsAndLists) + "=true", 659 selector: &GameServerSelector{Counters: map[string]CounterSelector{ 660 "monsters": { 661 MinCount: 0, 662 MaxCount: 10, 663 }, 664 }}, 665 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 666 Counters: map[string]agonesv1.CounterStatus{ 667 "monsters": { 668 Count: 11, 669 Capacity: 100, 670 }, 671 }, 672 }}, 673 matches: false, 674 }, 675 "Counter does not exist": { 676 features: string(runtime.FeatureCountsAndLists) + "=true", 677 selector: &GameServerSelector{Counters: map[string]CounterSelector{ 678 "dragoons": { 679 MinCount: 1, 680 MaxCount: 10, 681 }, 682 }}, 683 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 684 Counters: map[string]agonesv1.CounterStatus{ 685 "dragons": { 686 Count: 1, 687 Capacity: 100, 688 }, 689 }, 690 }}, 691 matches: false, 692 }, 693 "GameServer does not have Counters": { 694 features: string(runtime.FeatureCountsAndLists) + "=true", 695 selector: &GameServerSelector{Counters: map[string]CounterSelector{ 696 "dragoons": { 697 MinCount: 1, 698 MaxCount: 10, 699 }, 700 }}, 701 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 702 Lists: map[string]agonesv1.ListStatus{ 703 "bazzles": { 704 Capacity: 3, 705 Values: []string{"baz1", "baz2", "baz3"}, 706 }, 707 }, 708 }}, 709 matches: false, 710 }, 711 "List has available capacity": { 712 features: string(runtime.FeatureCountsAndLists) + "=true", 713 selector: &GameServerSelector{Lists: map[string]ListSelector{ 714 "lobbies": { 715 MinAvailable: 1, 716 MaxAvailable: 3, 717 }, 718 }}, 719 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 720 Lists: map[string]agonesv1.ListStatus{ 721 "lobbies": { 722 Capacity: 3, 723 Values: []string{"lobby1", "lobby2"}, 724 }, 725 }, 726 }}, 727 matches: true, 728 }, 729 "List has below minimum available capacity": { 730 features: string(runtime.FeatureCountsAndLists) + "=true", 731 selector: &GameServerSelector{Lists: map[string]ListSelector{ 732 "avatars": { 733 MinAvailable: 1, 734 MaxAvailable: 1000, 735 }, 736 }}, 737 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 738 Lists: map[string]agonesv1.ListStatus{ 739 "avatars": { 740 Capacity: 3, 741 Values: []string{"avatar1", "avatar2", "avatar3"}, 742 }, 743 }, 744 }}, 745 matches: false, 746 }, 747 "List has above maximum available capacity": { 748 features: string(runtime.FeatureCountsAndLists) + "=true", 749 selector: &GameServerSelector{Lists: map[string]ListSelector{ 750 "things": { 751 MinAvailable: 1, 752 MaxAvailable: 10, 753 }, 754 }}, 755 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 756 Lists: map[string]agonesv1.ListStatus{ 757 "things": { 758 Capacity: 1000, 759 Values: []string{"thing1", "thing2", "thing3"}, 760 }, 761 }, 762 }}, 763 matches: false, 764 }, 765 "List does not exist": { 766 features: string(runtime.FeatureCountsAndLists) + "=true", 767 selector: &GameServerSelector{Lists: map[string]ListSelector{ 768 "thingamabobs": { 769 MinAvailable: 1, 770 MaxAvailable: 100, 771 }, 772 }}, 773 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 774 Lists: map[string]agonesv1.ListStatus{ 775 "thingamajigs": { 776 Capacity: 100, 777 Values: []string{"thingamajig1", "thingamajig2"}, 778 }, 779 }, 780 }}, 781 matches: false, 782 }, 783 "List contains value": { 784 features: string(runtime.FeatureCountsAndLists) + "=true", 785 selector: &GameServerSelector{Lists: map[string]ListSelector{ 786 "bazzles": { 787 ContainsValue: "baz1", 788 }, 789 }}, 790 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 791 Lists: map[string]agonesv1.ListStatus{ 792 "bazzles": { 793 Capacity: 3, 794 Values: []string{"baz1", "baz2", "baz3"}, 795 }, 796 }, 797 }}, 798 matches: true, 799 }, 800 "List does not contain value": { 801 features: string(runtime.FeatureCountsAndLists) + "=true", 802 selector: &GameServerSelector{Lists: map[string]ListSelector{ 803 "bazzles": { 804 ContainsValue: "BAZ1", 805 }, 806 }}, 807 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 808 Lists: map[string]agonesv1.ListStatus{ 809 "bazzles": { 810 Capacity: 3, 811 Values: []string{"baz1", "baz2", "baz3"}, 812 }, 813 }, 814 }}, 815 matches: false, 816 }, 817 "GameServer does not have Lists": { 818 features: string(runtime.FeatureCountsAndLists) + "=true", 819 selector: &GameServerSelector{Lists: map[string]ListSelector{ 820 "bazzles": { 821 ContainsValue: "BAZ1", 822 }, 823 }}, 824 gameServer: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 825 Counters: map[string]agonesv1.CounterStatus{ 826 "dragons": { 827 Count: 1, 828 Capacity: 100, 829 }, 830 }, 831 }}, 832 matches: false, 833 }, 834 } 835 836 for k, v := range fixtures { 837 t.Run(k, func(t *testing.T) { 838 if v.features != "" { 839 require.NoError(t, runtime.ParseFeatures(v.features)) 840 } 841 842 match := v.selector.Matches(v.gameServer) 843 assert.Equal(t, v.matches, match) 844 }) 845 } 846 } 847 848 // Helper function for creating int64 pointers 849 func int64Pointer(x int64) *int64 { 850 return &x 851 } 852 853 func TestGameServerCounterActions(t *testing.T) { 854 t.Parallel() 855 856 runtime.FeatureTestMutex.Lock() 857 defer runtime.FeatureTestMutex.Unlock() 858 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists))) 859 860 DECREMENT := "Decrement" 861 INCREMENT := "Increment" 862 863 testScenarios := map[string]struct { 864 ca CounterAction 865 counter string 866 gs *agonesv1.GameServer 867 want *agonesv1.GameServer 868 wantErr bool 869 }{ 870 "update counter capacity and count is set to capacity": { 871 ca: CounterAction{ 872 Capacity: int64Pointer(0), 873 }, 874 counter: "mages", 875 gs: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 876 Counters: map[string]agonesv1.CounterStatus{ 877 "mages": { 878 Count: 1, 879 Capacity: 100, 880 }}}}, 881 want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 882 Counters: map[string]agonesv1.CounterStatus{ 883 "mages": { 884 Count: 0, 885 Capacity: 0, 886 }}}}, 887 wantErr: false, 888 }, 889 "fail update counter capacity and truncate update count": { 890 ca: CounterAction{ 891 Action: &INCREMENT, 892 Amount: int64Pointer(10), 893 Capacity: int64Pointer(-1), 894 }, 895 counter: "sages", 896 gs: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 897 Counters: map[string]agonesv1.CounterStatus{ 898 "sages": { 899 Count: 99, 900 Capacity: 100, 901 }}}}, 902 want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 903 Counters: map[string]agonesv1.CounterStatus{ 904 "sages": { 905 Count: 100, 906 Capacity: 100, 907 }}}}, 908 wantErr: true, 909 }, 910 "update counter count": { 911 ca: CounterAction{ 912 Action: &INCREMENT, 913 Amount: int64Pointer(10), 914 }, 915 counter: "baddies", 916 gs: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 917 Counters: map[string]agonesv1.CounterStatus{ 918 "baddies": { 919 Count: 1, 920 Capacity: 100, 921 }}}}, 922 want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 923 Counters: map[string]agonesv1.CounterStatus{ 924 "baddies": { 925 Count: 11, 926 Capacity: 100, 927 }}}}, 928 wantErr: false, 929 }, 930 "update counter count and capacity": { 931 ca: CounterAction{ 932 Action: &DECREMENT, 933 Amount: int64Pointer(10), 934 Capacity: int64Pointer(10), 935 }, 936 counter: "heroes", 937 gs: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 938 Counters: map[string]agonesv1.CounterStatus{ 939 "heroes": { 940 Count: 11, 941 Capacity: 100, 942 }}}}, 943 want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 944 Counters: map[string]agonesv1.CounterStatus{ 945 "heroes": { 946 // Note: The Capacity is set first, and Count updated to not be greater than Capacity. 947 // Then the Count is decremented. See: gameserver.go/UpdateCounterCapacity 948 Count: 0, 949 Capacity: 10, 950 }}}}, 951 wantErr: false, 952 }, 953 } 954 955 for test, testScenario := range testScenarios { 956 t.Run(test, func(t *testing.T) { 957 errs := testScenario.ca.CounterActions(testScenario.counter, testScenario.gs) 958 if errs != nil { 959 assert.True(t, testScenario.wantErr) 960 } else { 961 assert.False(t, testScenario.wantErr) 962 } 963 assert.Equal(t, testScenario.want, testScenario.gs) 964 }) 965 } 966 } 967 968 func TestGameServerListActions(t *testing.T) { 969 t.Parallel() 970 971 runtime.FeatureTestMutex.Lock() 972 defer runtime.FeatureTestMutex.Unlock() 973 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists))) 974 975 testScenarios := map[string]struct { 976 la ListAction 977 list string 978 gs *agonesv1.GameServer 979 want *agonesv1.GameServer 980 wantErr bool 981 }{ 982 "update list capacity truncates list": { 983 la: ListAction{ 984 Capacity: int64Pointer(0), 985 }, 986 list: "pages", 987 gs: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 988 Lists: map[string]agonesv1.ListStatus{ 989 "pages": { 990 Values: []string{"page1", "page2"}, 991 Capacity: 100, 992 }}}}, 993 want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 994 Lists: map[string]agonesv1.ListStatus{ 995 "pages": { 996 Values: []string{}, 997 Capacity: 0, 998 }}}}, 999 wantErr: false, 1000 }, 1001 "update list values": { 1002 la: ListAction{ 1003 AddValues: []string{"sage1", "sage3"}, 1004 }, 1005 list: "sages", 1006 gs: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 1007 Lists: map[string]agonesv1.ListStatus{ 1008 "sages": { 1009 Values: []string{"sage1", "sage2"}, 1010 Capacity: 100, 1011 }}}}, 1012 want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 1013 Lists: map[string]agonesv1.ListStatus{ 1014 "sages": { 1015 Values: []string{"sage1", "sage2", "sage3"}, 1016 Capacity: 100, 1017 }}}}, 1018 wantErr: false, 1019 }, 1020 "update list values and capacity": { 1021 la: ListAction{ 1022 AddValues: []string{"magician1", "magician3"}, 1023 Capacity: int64Pointer(42), 1024 DeleteValues: []string{"magician2", "magician5", "magician6"}, 1025 }, 1026 list: "magicians", 1027 gs: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 1028 Lists: map[string]agonesv1.ListStatus{ 1029 "magicians": { 1030 Values: []string{"magician1", "magician2", "magician4", "magician5"}, 1031 Capacity: 100, 1032 }}}}, 1033 want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 1034 Lists: map[string]agonesv1.ListStatus{ 1035 "magicians": { 1036 Values: []string{"magician1", "magician4", "magician3"}, 1037 Capacity: 42, 1038 }}}}, 1039 wantErr: false, 1040 }, 1041 "update list values and capacity - value add truncates silently": { 1042 la: ListAction{ 1043 AddValues: []string{"fairy1", "fairy3"}, 1044 Capacity: int64Pointer(2), 1045 }, 1046 list: "fairies", 1047 gs: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 1048 Lists: map[string]agonesv1.ListStatus{ 1049 "fairies": { 1050 Values: []string{"fairy1", "fairy2"}, 1051 Capacity: 100, 1052 }}}}, 1053 want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{ 1054 Lists: map[string]agonesv1.ListStatus{ 1055 "fairies": { 1056 Values: []string{"fairy1", "fairy2"}, 1057 Capacity: 2, 1058 }}}}, 1059 wantErr: false, 1060 }, 1061 } 1062 1063 for test, testScenario := range testScenarios { 1064 t.Run(test, func(t *testing.T) { 1065 errs := testScenario.la.ListActions(testScenario.list, testScenario.gs) 1066 if errs != nil { 1067 assert.True(t, testScenario.wantErr) 1068 } else { 1069 assert.False(t, testScenario.wantErr) 1070 } 1071 assert.Equal(t, testScenario.want, testScenario.gs) 1072 }) 1073 } 1074 } 1075 1076 func TestValidatePriorities(t *testing.T) { 1077 t.Parallel() 1078 1079 runtime.FeatureTestMutex.Lock() 1080 defer runtime.FeatureTestMutex.Unlock() 1081 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists))) 1082 1083 fieldPath := field.NewPath("spec.Priorities") 1084 1085 testScenarios := map[string]struct { 1086 priorities []agonesv1.Priority 1087 wantErr bool 1088 }{ 1089 "Valid priorities": { 1090 priorities: []agonesv1.Priority{ 1091 { 1092 Type: agonesv1.GameServerPriorityList, 1093 Key: "test", 1094 Order: agonesv1.GameServerPriorityAscending, 1095 }, 1096 { 1097 Type: agonesv1.GameServerPriorityCounter, 1098 Key: "test", 1099 Order: agonesv1.GameServerPriorityDescending, 1100 }, 1101 }, 1102 wantErr: false, 1103 }, 1104 "No type": { 1105 priorities: []agonesv1.Priority{ 1106 { 1107 Key: "test", 1108 Order: agonesv1.GameServerPriorityDescending, 1109 }, 1110 }, 1111 wantErr: true, 1112 }, 1113 "Invalid type": { 1114 priorities: []agonesv1.Priority{ 1115 { 1116 Key: "test", 1117 Type: "invalid", 1118 Order: agonesv1.GameServerPriorityDescending, 1119 }, 1120 }, 1121 wantErr: true, 1122 }, 1123 "No Key": { 1124 priorities: []agonesv1.Priority{ 1125 { 1126 Type: agonesv1.GameServerPriorityCounter, 1127 Order: agonesv1.GameServerPriorityDescending, 1128 }, 1129 }, 1130 wantErr: true, 1131 }, 1132 "No Order": { 1133 priorities: []agonesv1.Priority{ 1134 { 1135 Type: agonesv1.GameServerPriorityList, 1136 Key: "test", 1137 }, 1138 }, 1139 wantErr: true, 1140 }, 1141 "Invalid Order": { 1142 priorities: []agonesv1.Priority{ 1143 { 1144 Type: agonesv1.GameServerPriorityList, 1145 Key: "test", 1146 Order: "invalid", 1147 }, 1148 }, 1149 wantErr: true, 1150 }, 1151 } 1152 1153 for test, testScenario := range testScenarios { 1154 t.Run(test, func(t *testing.T) { 1155 allErrs := validatePriorities(testScenario.priorities, fieldPath) 1156 if testScenario.wantErr { 1157 assert.NotNil(t, allErrs) 1158 } else { 1159 assert.Nil(t, allErrs) 1160 } 1161 }) 1162 } 1163 } 1164 1165 func TestValidateCounterActions(t *testing.T) { 1166 t.Parallel() 1167 1168 runtime.FeatureTestMutex.Lock() 1169 defer runtime.FeatureTestMutex.Unlock() 1170 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists))) 1171 1172 fieldPath := field.NewPath("spec.Counters") 1173 decrement := agonesv1.GameServerPriorityDecrement 1174 increment := agonesv1.GameServerPriorityIncrement 1175 1176 testScenarios := map[string]struct { 1177 counterActions map[string]CounterAction 1178 wantErr bool 1179 }{ 1180 "Valid CounterActions": { 1181 counterActions: map[string]CounterAction{ 1182 "foo": { 1183 Action: &increment, 1184 Amount: int64Pointer(10), 1185 }, 1186 "bar": { 1187 Capacity: int64Pointer(100), 1188 }, 1189 "baz": { 1190 Action: &decrement, 1191 Amount: int64Pointer(1000), 1192 Capacity: int64Pointer(0), 1193 }, 1194 }, 1195 wantErr: false, 1196 }, 1197 "Negative Amount": { 1198 counterActions: map[string]CounterAction{ 1199 "foo": { 1200 Action: &increment, 1201 Amount: int64Pointer(-1), 1202 }, 1203 }, 1204 wantErr: true, 1205 }, 1206 "Negative Capacity": { 1207 counterActions: map[string]CounterAction{ 1208 "foo": { 1209 Capacity: int64Pointer(-20), 1210 }, 1211 }, 1212 wantErr: true, 1213 }, 1214 "Amount but no Action": { 1215 counterActions: map[string]CounterAction{ 1216 "foo": { 1217 Amount: int64Pointer(10), 1218 }, 1219 }, 1220 wantErr: true, 1221 }, 1222 "Action but no Amount": { 1223 counterActions: map[string]CounterAction{ 1224 "foo": { 1225 Action: &decrement, 1226 }, 1227 }, 1228 wantErr: true, 1229 }, 1230 } 1231 1232 for test, testScenario := range testScenarios { 1233 t.Run(test, func(t *testing.T) { 1234 allErrs := validateCounterActions(testScenario.counterActions, fieldPath) 1235 if testScenario.wantErr { 1236 assert.NotNil(t, allErrs) 1237 } else { 1238 assert.Nil(t, allErrs) 1239 } 1240 }) 1241 } 1242 } 1243 1244 func TestValidateListActions(t *testing.T) { 1245 t.Parallel() 1246 1247 runtime.FeatureTestMutex.Lock() 1248 defer runtime.FeatureTestMutex.Unlock() 1249 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists))) 1250 1251 fieldPath := field.NewPath("spec.Lists") 1252 1253 testScenarios := map[string]struct { 1254 listActions map[string]ListAction 1255 wantErr bool 1256 }{ 1257 "Valid ListActions": { 1258 listActions: map[string]ListAction{ 1259 "foo": { 1260 AddValues: []string{"hello", "world"}, 1261 Capacity: int64Pointer(10), 1262 }, 1263 "bar": { 1264 Capacity: int64Pointer(0), 1265 }, 1266 "baz": { 1267 AddValues: []string{}, 1268 DeleteValues: []string{"good", "bye"}, 1269 }, 1270 }, 1271 wantErr: false, 1272 }, 1273 "Negative Capacity": { 1274 listActions: map[string]ListAction{ 1275 "foo": { 1276 Capacity: int64Pointer(-20), 1277 }, 1278 }, 1279 wantErr: true, 1280 }, 1281 } 1282 1283 for test, testScenario := range testScenarios { 1284 t.Run(test, func(t *testing.T) { 1285 allErrs := validateListActions(testScenario.listActions, fieldPath) 1286 if testScenario.wantErr { 1287 assert.NotNil(t, allErrs) 1288 } else { 1289 assert.Nil(t, allErrs) 1290 } 1291 }) 1292 } 1293 } 1294 1295 func TestGameServerAllocationValidate(t *testing.T) { 1296 t.Parallel() 1297 1298 runtime.FeatureTestMutex.Lock() 1299 defer runtime.FeatureTestMutex.Unlock() 1300 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true&%s=false", 1301 runtime.FeaturePlayerAllocationFilter, 1302 runtime.FeatureCountsAndLists))) 1303 1304 gsa := &GameServerAllocation{} 1305 gsa.ApplyDefaults() 1306 1307 allErrs := gsa.Validate() 1308 assert.Len(t, allErrs, 0) 1309 1310 gsa.Spec.Scheduling = "FLERG" 1311 1312 allErrs = gsa.Validate() 1313 assert.Len(t, allErrs, 1) 1314 1315 assert.Equal(t, field.ErrorTypeNotSupported, allErrs[0].Type) 1316 assert.Equal(t, "spec.scheduling", allErrs[0].Field) 1317 1318 // invalid player selection 1319 gsa = &GameServerAllocation{ 1320 Spec: GameServerAllocationSpec{ 1321 Required: GameServerSelector{ 1322 Players: &PlayerSelector{ 1323 MinAvailable: -10, 1324 }, 1325 }, 1326 Preferred: []GameServerSelector{ 1327 {Players: &PlayerSelector{MaxAvailable: -10}}, 1328 }, 1329 MetaPatch: MetaPatch{ 1330 Labels: map[string]string{"$$$": "foo"}, 1331 }, 1332 Priorities: []agonesv1.Priority{}, 1333 Counters: map[string]CounterAction{}, 1334 Lists: map[string]ListAction{}, 1335 }, 1336 } 1337 gsa.ApplyDefaults() 1338 1339 allErrs = gsa.Validate() 1340 sort.Slice(allErrs, func(i, j int) bool { 1341 return allErrs[i].Field > allErrs[j].Field 1342 }) 1343 assert.Len(t, allErrs, 7) 1344 assert.Equal(t, "spec.required.players.minAvailable", allErrs[0].Field) 1345 assert.Equal(t, "spec.priorities", allErrs[1].Field) 1346 assert.Equal(t, "spec.preferred[0].players.minAvailable", allErrs[2].Field) 1347 assert.Equal(t, "spec.preferred[0].players.maxAvailable", allErrs[3].Field) 1348 assert.Equal(t, "spec.metadata.labels", allErrs[4].Field) 1349 assert.Equal(t, "spec.lists", allErrs[5].Field) 1350 assert.Equal(t, "spec.counters", allErrs[6].Field) 1351 } 1352 1353 func TestGameServerAllocationConverter(t *testing.T) { 1354 t.Parallel() 1355 1356 gsa := &GameServerAllocation{ 1357 Spec: GameServerAllocationSpec{ 1358 Scheduling: "Packed", 1359 Required: GameServerSelector{ 1360 Players: &PlayerSelector{ 1361 MinAvailable: 5, 1362 MaxAvailable: 10, 1363 }, 1364 }, 1365 Preferred: []GameServerSelector{ 1366 {Players: &PlayerSelector{MinAvailable: 10, 1367 MaxAvailable: 20}}, 1368 }, 1369 }, 1370 } 1371 gsaExpected := &GameServerAllocation{ 1372 Spec: GameServerAllocationSpec{ 1373 Scheduling: "Packed", 1374 Required: GameServerSelector{ 1375 Players: &PlayerSelector{ 1376 MinAvailable: 5, 1377 MaxAvailable: 10, 1378 }, 1379 }, 1380 Preferred: []GameServerSelector{ 1381 {Players: &PlayerSelector{MinAvailable: 10, 1382 MaxAvailable: 20}}, 1383 }, 1384 Selectors: []GameServerSelector{ 1385 {Players: &PlayerSelector{MinAvailable: 10, 1386 MaxAvailable: 20}}, 1387 {Players: &PlayerSelector{ 1388 MinAvailable: 5, 1389 MaxAvailable: 10}}, 1390 }, 1391 }, 1392 } 1393 1394 gsa.Converter() 1395 assert.Equal(t, gsaExpected, gsa) 1396 } 1397 1398 func TestSortKey(t *testing.T) { 1399 t.Parallel() 1400 1401 runtime.FeatureTestMutex.Lock() 1402 defer runtime.FeatureTestMutex.Unlock() 1403 assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists))) 1404 1405 gameServerAllocation1 := &GameServerAllocation{ 1406 Spec: GameServerAllocationSpec{ 1407 Scheduling: "Packed", 1408 Priorities: []agonesv1.Priority{ 1409 { 1410 Type: "List", 1411 Key: "foo", 1412 Order: "Descending", 1413 }, 1414 }, 1415 }, 1416 } 1417 1418 gameServerAllocation2 := &GameServerAllocation{ 1419 Spec: GameServerAllocationSpec{ 1420 Selectors: []GameServerSelector{ 1421 {LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}}, 1422 }, 1423 Scheduling: "Packed", 1424 Priorities: []agonesv1.Priority{ 1425 { 1426 Type: "List", 1427 Key: "foo", 1428 Order: "Descending", 1429 }, 1430 }, 1431 }, 1432 } 1433 1434 gameServerAllocation3 := &GameServerAllocation{ 1435 Spec: GameServerAllocationSpec{ 1436 Scheduling: "Packed", 1437 Priorities: []agonesv1.Priority{ 1438 { 1439 Type: "Counter", 1440 Key: "foo", 1441 Order: "Descending", 1442 }, 1443 }, 1444 }, 1445 } 1446 1447 gameServerAllocation4 := &GameServerAllocation{ 1448 Spec: GameServerAllocationSpec{ 1449 Scheduling: "Distributed", 1450 Priorities: []agonesv1.Priority{ 1451 { 1452 Type: "List", 1453 Key: "foo", 1454 Order: "Descending", 1455 }, 1456 }, 1457 }, 1458 } 1459 1460 gameServerAllocation5 := &GameServerAllocation{} 1461 1462 gameServerAllocation6 := &GameServerAllocation{ 1463 Spec: GameServerAllocationSpec{ 1464 Priorities: []agonesv1.Priority{}, 1465 }, 1466 } 1467 1468 testScenarios := map[string]struct { 1469 gsa1 *GameServerAllocation 1470 gsa2 *GameServerAllocation 1471 wantEqual bool 1472 }{ 1473 "equivalent GameServerAllocation": { 1474 gsa1: gameServerAllocation1, 1475 gsa2: gameServerAllocation2, 1476 wantEqual: true, 1477 }, 1478 "different Scheduling GameServerAllocation": { 1479 gsa1: gameServerAllocation1, 1480 gsa2: gameServerAllocation4, 1481 wantEqual: false, 1482 }, 1483 "equivalent empty GameServerAllocation": { 1484 gsa1: gameServerAllocation5, 1485 gsa2: gameServerAllocation6, 1486 wantEqual: true, 1487 }, 1488 "different Priorities GameServerAllocation": { 1489 gsa1: gameServerAllocation1, 1490 gsa2: gameServerAllocation3, 1491 wantEqual: false, 1492 }, 1493 } 1494 1495 for test, testScenario := range testScenarios { 1496 t.Run(test, func(t *testing.T) { 1497 key1, err := testScenario.gsa1.SortKey() 1498 assert.NoError(t, err) 1499 key2, err := testScenario.gsa2.SortKey() 1500 assert.NoError(t, err) 1501 1502 if testScenario.wantEqual { 1503 assert.Equal(t, key1, key2) 1504 } else { 1505 assert.NotEqual(t, key1, key2) 1506 } 1507 }) 1508 } 1509 1510 }