agones.dev/agones@v1.54.0/test/e2e/gameserverallocation_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 e2e 16 17 import ( 18 "context" 19 "fmt" 20 "sync" 21 "testing" 22 "time" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/sirupsen/logrus" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/util/uuid" 31 "k8s.io/apimachinery/pkg/util/wait" 32 33 "agones.dev/agones/pkg/apis" 34 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 35 allocationv1 "agones.dev/agones/pkg/apis/allocation/v1" 36 multiclusterv1 "agones.dev/agones/pkg/apis/multicluster/v1" 37 "agones.dev/agones/pkg/util/runtime" 38 e2e "agones.dev/agones/test/e2e/framework" 39 ) 40 41 func TestCreateFleetAndGameServerAllocate(t *testing.T) { 42 t.Parallel() 43 44 fixtures := []apis.SchedulingStrategy{apis.Packed, apis.Distributed} 45 46 for _, strategy := range fixtures { 47 t.Run(string(strategy), func(t *testing.T) { 48 t.Parallel() 49 ctx := context.Background() 50 51 fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace) 52 fleet := defaultFleet(framework.Namespace) 53 fleet.Spec.Scheduling = strategy 54 flt, err := fleets.Create(ctx, fleet, metav1.CreateOptions{}) 55 if strategy != apis.Packed && framework.CloudProduct == "gke-autopilot" { 56 // test that Autopilot rejects anything but Packed and skip the rest of the test 57 assert.ErrorContains(t, err, "Invalid value") 58 return 59 } 60 if assert.NoError(t, err) { 61 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 62 } 63 64 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 65 66 gsaList := []*allocationv1.GameServerAllocation{ 67 {Spec: allocationv1.GameServerAllocationSpec{ 68 Scheduling: strategy, 69 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}}}, 70 }, 71 {Spec: allocationv1.GameServerAllocationSpec{ 72 Scheduling: strategy, 73 Required: allocationv1.GameServerSelector{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}}, 74 }, 75 } 76 77 for _, gsa := range gsaList { 78 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 79 if assert.NoError(t, err) { 80 assert.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(gsa.Status.State)) 81 } 82 } 83 }) 84 } 85 } 86 87 func TestCreateFleetAndGameServerStateFilterAllocation(t *testing.T) { 88 t.Parallel() 89 90 fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace) 91 fleet := defaultFleet(framework.Namespace) 92 ctx := context.Background() 93 94 flt, err := fleets.Create(ctx, fleet, metav1.CreateOptions{}) 95 require.NoError(t, err) 96 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 97 98 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 99 100 fleetSelector := metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}} 101 gsa := &allocationv1.GameServerAllocation{ 102 Spec: allocationv1.GameServerAllocationSpec{ 103 Selectors: []allocationv1.GameServerSelector{{LabelSelector: fleetSelector}}, 104 }} 105 106 // standard allocation 107 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 108 require.NoError(t, err) 109 assert.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(gsa.Status.State)) 110 111 gs1, err := framework.AgonesClient.AgonesV1().GameServers(fleet.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 112 require.NoError(t, err) 113 assert.Equal(t, agonesv1.GameServerStateAllocated, gs1.Status.State) 114 assert.NotNil(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"]) 115 116 // now let's get it back again 117 gsa = gsa.DeepCopy() 118 allocated := agonesv1.GameServerStateAllocated 119 gsa.Spec.Selectors[0].GameServerState = &allocated 120 121 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 122 require.NoError(t, err) 123 assert.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(gsa.Status.State)) 124 assert.Equal(t, gs1.ObjectMeta.Name, gsa.Status.GameServerName) 125 126 gs2, err := framework.AgonesClient.AgonesV1().GameServers(fleet.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 127 require.NoError(t, err) 128 assert.Equal(t, agonesv1.GameServerStateAllocated, gs2.Status.State) 129 130 require.Equal(t, gs1.ObjectMeta.Name, gs2.ObjectMeta.Name) 131 require.NotEqual(t, gs1.ObjectMeta.ResourceVersion, gs2.ObjectMeta.ResourceVersion) 132 require.NotEqual(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"], gs2.ObjectMeta.Annotations["agones.dev/last-allocated"]) 133 } 134 135 func TestHighDensityGameServerFlow(t *testing.T) { 136 t.Parallel() 137 log := e2e.TestLogger(t) 138 ctx := context.Background() 139 140 fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace) 141 fleet := defaultFleet(framework.Namespace) 142 lockLabel := "agones.dev/sdk-available" 143 // to start they are all available 144 fleet.Spec.Template.ObjectMeta.Labels = map[string]string{lockLabel: "true"} 145 146 flt, err := fleets.Create(ctx, fleet, metav1.CreateOptions{}) 147 require.NoError(t, err) 148 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 149 150 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 151 152 fleetSelector := metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}} 153 allocatedSelector := fleetSelector.DeepCopy() 154 155 allocated := agonesv1.GameServerStateAllocated 156 allocatedSelector.MatchLabels[lockLabel] = "true" 157 gsa := &allocationv1.GameServerAllocation{ 158 Spec: allocationv1.GameServerAllocationSpec{ 159 MetaPatch: allocationv1.MetaPatch{Labels: map[string]string{lockLabel: "false"}}, 160 Selectors: []allocationv1.GameServerSelector{ 161 {LabelSelector: *allocatedSelector, GameServerState: &allocated}, 162 {LabelSelector: fleetSelector}, 163 }, 164 }} 165 166 // standard allocation 167 result, err := framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 168 require.NoError(t, err) 169 require.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(result.Status.State)) 170 171 gs, err := framework.AgonesClient.AgonesV1().GameServers(fleet.ObjectMeta.Namespace).Get(ctx, result.Status.GameServerName, metav1.GetOptions{}) 172 require.NoError(t, err) 173 require.Equal(t, allocated, gs.Status.State) 174 175 // set the label to being available again 176 _, err = framework.SendGameServerUDP(t, gs, "LABEL available true") 177 require.NoError(t, err) 178 179 // wait for the label to be applied! 180 require.Eventuallyf(t, func() bool { 181 gs, err := framework.AgonesClient.AgonesV1().GameServers(fleet.ObjectMeta.Namespace).Get(ctx, result.Status.GameServerName, metav1.GetOptions{}) 182 require.NoError(t, err) 183 log.WithField("labels", gs.ObjectMeta.Labels).Info("checking labels") 184 return gs.ObjectMeta.Labels[lockLabel] == "true" 185 }, time.Minute, time.Second, "GameServer did not unlock") 186 187 // Run the same allocation again, we should get back the preferred item. 188 expected := result.Status.GameServerName 189 190 // we will run this as an Eventually, as caches are eventually consistent 191 require.Eventuallyf(t, func() bool { 192 result, err = framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 193 require.NoError(t, err) 194 require.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(result.Status.State)) 195 196 if expected != result.Status.GameServerName { 197 log.WithField("expected", expected).WithField("gsa", result).Info("Re-allocation attempt failed. Retrying.") 198 return false 199 } 200 201 return true 202 }, time.Minute, time.Second, "Could not re-allocation") 203 } 204 205 func TestCreateFleetAndGameServerPlayerCapacityAllocation(t *testing.T) { 206 if !(runtime.FeatureEnabled(runtime.FeaturePlayerAllocationFilter)) { 207 t.SkipNow() 208 } 209 t.Parallel() 210 211 fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace) 212 fleet := defaultFleet(framework.Namespace) 213 fleet.Spec.Template.Spec.Players = &agonesv1.PlayersSpec{InitialCapacity: 10} 214 ctx := context.Background() 215 216 flt, err := fleets.Create(ctx, fleet, metav1.CreateOptions{}) 217 require.NoError(t, err) 218 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 219 220 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 221 222 fleetSelector := metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}} 223 allocated := agonesv1.GameServerStateAllocated 224 gsa := &allocationv1.GameServerAllocation{ 225 Spec: allocationv1.GameServerAllocationSpec{ 226 Selectors: []allocationv1.GameServerSelector{ 227 { 228 LabelSelector: fleetSelector, 229 GameServerState: &allocated, 230 Players: &allocationv1.PlayerSelector{ 231 MinAvailable: 1, 232 MaxAvailable: 99, 233 }, 234 }, 235 {LabelSelector: fleetSelector, Players: &allocationv1.PlayerSelector{MinAvailable: 5, MaxAvailable: 10}}, 236 }, 237 }} 238 239 // first try should give me a Ready->Allocated server 240 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 241 require.NoError(t, err) 242 assert.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(gsa.Status.State)) 243 244 gs1, err := framework.AgonesClient.AgonesV1().GameServers(fleet.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 245 require.NoError(t, err) 246 assert.Equal(t, agonesv1.GameServerStateAllocated, gs1.Status.State) 247 assert.NotNil(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"]) 248 249 // second try should give me the same allocated server 250 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 251 require.NoError(t, err) 252 assert.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(gsa.Status.State)) 253 assert.Equal(t, gs1.ObjectMeta.Name, gsa.Status.GameServerName) 254 255 gs2, err := framework.AgonesClient.AgonesV1().GameServers(fleet.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 256 require.NoError(t, err) 257 assert.Equal(t, agonesv1.GameServerStateAllocated, gs2.Status.State) 258 259 require.Equal(t, gs1.ObjectMeta.Name, gs2.ObjectMeta.Name) 260 require.NotEqual(t, gs1.ObjectMeta.ResourceVersion, gs2.ObjectMeta.ResourceVersion) 261 require.NotEqual(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"], gs2.ObjectMeta.Annotations["agones.dev/last-allocated"]) 262 } 263 264 func TestCounterAndListGameServerAllocation(t *testing.T) { 265 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 266 t.SkipNow() 267 } 268 t.Parallel() 269 ctx := context.Background() 270 client := framework.AgonesClient.AgonesV1() 271 272 flt := defaultFleet(framework.Namespace) 273 initialCounters := map[string]agonesv1.CounterStatus{ 274 "games": { 275 Count: 2, 276 Capacity: 10, 277 }, 278 } 279 initialLists := map[string]agonesv1.ListStatus{ 280 "players": { 281 Values: []string{"player0"}, 282 Capacity: 10, 283 }, 284 } 285 flt.Spec.Template.Spec.Counters = initialCounters 286 flt.Spec.Template.Spec.Lists = initialLists 287 288 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 289 require.NoError(t, err) 290 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 291 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 292 293 // Need fleetSelector to get the correct fleet, otherwise GSA will return game servers from any fleet in the namespace. 294 fleetSelector := metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}} 295 stateAllocated := agonesv1.GameServerStateAllocated 296 ready := agonesv1.GameServerStateReady 297 allocated := allocationv1.GameServerAllocationAllocated 298 unallocated := allocationv1.GameServerAllocationUnAllocated 299 300 testCases := map[string]struct { 301 gsa allocationv1.GameServerAllocation 302 wantGsaErr bool // For invalid GSA 303 wantAllocated allocationv1.GameServerAllocationState // For a valid GSA: "allocated" if you expect the GSA to succed in allocating a GameServer, "unallocated" if not 304 wantState agonesv1.GameServerState 305 }{ 306 "Counter Allocate to same GameServer MinAvailable (available capacity)": { 307 gsa: allocationv1.GameServerAllocation{ 308 Spec: allocationv1.GameServerAllocationSpec{ 309 Selectors: []allocationv1.GameServerSelector{ 310 { 311 LabelSelector: fleetSelector, 312 GameServerState: &stateAllocated, 313 Counters: map[string]allocationv1.CounterSelector{ 314 "games": { 315 MinAvailable: 5, 316 }}}, { 317 LabelSelector: fleetSelector, 318 GameServerState: &ready, 319 Counters: map[string]allocationv1.CounterSelector{ 320 "games": { 321 MinAvailable: 5, 322 }}}}}}, 323 wantGsaErr: false, 324 wantAllocated: allocated, 325 wantState: stateAllocated, 326 }, 327 "List Allocate to same GameServer MinAvailable (available capacity)": { 328 gsa: allocationv1.GameServerAllocation{ 329 Spec: allocationv1.GameServerAllocationSpec{ 330 Selectors: []allocationv1.GameServerSelector{ 331 { 332 LabelSelector: fleetSelector, 333 GameServerState: &stateAllocated, 334 Lists: map[string]allocationv1.ListSelector{ 335 "players": { 336 MinAvailable: 9, 337 }}}, { 338 LabelSelector: fleetSelector, 339 GameServerState: &ready, 340 Lists: map[string]allocationv1.ListSelector{ 341 "players": { 342 MinAvailable: 9, 343 }}}}}}, 344 wantGsaErr: false, 345 wantAllocated: allocated, 346 wantState: stateAllocated, 347 }, 348 "Counter Allocate to same GameServer MaxAvailable": { 349 gsa: allocationv1.GameServerAllocation{ 350 Spec: allocationv1.GameServerAllocationSpec{ 351 Selectors: []allocationv1.GameServerSelector{ 352 { 353 LabelSelector: fleetSelector, 354 GameServerState: &stateAllocated, 355 Counters: map[string]allocationv1.CounterSelector{ 356 "games": { 357 MaxAvailable: 10, 358 }}}, { 359 LabelSelector: fleetSelector, 360 GameServerState: &ready, 361 Counters: map[string]allocationv1.CounterSelector{ 362 "games": { 363 MaxAvailable: 10, 364 }}}}}}, 365 wantGsaErr: false, 366 wantAllocated: allocated, 367 wantState: stateAllocated, 368 }, 369 "List Allocate to same GameServer MaxAvailable": { 370 gsa: allocationv1.GameServerAllocation{ 371 Spec: allocationv1.GameServerAllocationSpec{ 372 Selectors: []allocationv1.GameServerSelector{ 373 { 374 LabelSelector: fleetSelector, 375 GameServerState: &stateAllocated, 376 Lists: map[string]allocationv1.ListSelector{ 377 "players": { 378 MaxAvailable: 9, 379 }}}, { 380 LabelSelector: fleetSelector, 381 GameServerState: &ready, 382 Lists: map[string]allocationv1.ListSelector{ 383 "players": { 384 MaxAvailable: 9, 385 }}}}}}, 386 wantGsaErr: false, 387 wantAllocated: allocated, 388 wantState: stateAllocated, 389 }, 390 "Allocate to same GameServer MinCount (count value)": { 391 gsa: allocationv1.GameServerAllocation{ 392 Spec: allocationv1.GameServerAllocationSpec{ 393 Selectors: []allocationv1.GameServerSelector{ 394 { 395 LabelSelector: fleetSelector, 396 GameServerState: &stateAllocated, 397 Counters: map[string]allocationv1.CounterSelector{ 398 "games": { 399 MinCount: 2, 400 }}}, { 401 LabelSelector: fleetSelector, 402 GameServerState: &ready, 403 Counters: map[string]allocationv1.CounterSelector{ 404 "games": { 405 MinCount: 1, 406 }}}}}}, 407 wantGsaErr: false, 408 wantAllocated: allocated, 409 wantState: stateAllocated, 410 }, 411 "Allocate to same GameServer MaxCount (count value)": { 412 gsa: allocationv1.GameServerAllocation{ 413 Spec: allocationv1.GameServerAllocationSpec{ 414 Selectors: []allocationv1.GameServerSelector{ 415 { 416 LabelSelector: fleetSelector, 417 GameServerState: &stateAllocated, 418 Counters: map[string]allocationv1.CounterSelector{ 419 "games": { 420 MaxCount: 3, 421 }}}, { 422 LabelSelector: fleetSelector, 423 GameServerState: &ready, 424 Counters: map[string]allocationv1.CounterSelector{ 425 "games": { 426 MaxCount: 2, 427 }}}}}}, 428 wantGsaErr: false, 429 wantAllocated: allocated, 430 wantState: stateAllocated, 431 }, 432 "List Allocate to same GameServer Contains Value": { 433 gsa: allocationv1.GameServerAllocation{ 434 Spec: allocationv1.GameServerAllocationSpec{ 435 Selectors: []allocationv1.GameServerSelector{ 436 { 437 LabelSelector: fleetSelector, 438 GameServerState: &stateAllocated, 439 Lists: map[string]allocationv1.ListSelector{ 440 "players": { 441 ContainsValue: "player0", 442 }}}, { 443 LabelSelector: fleetSelector, 444 GameServerState: &ready, 445 Lists: map[string]allocationv1.ListSelector{ 446 "players": { 447 ContainsValue: "player0", 448 }}}}}}, 449 wantGsaErr: false, 450 wantAllocated: allocated, 451 wantState: stateAllocated, 452 }, 453 // 0 for MaxCount or MaxAvailable means unlimited maximum. Default for all fields: 0 454 "Allocate to same GameServer no Counter values": { 455 gsa: allocationv1.GameServerAllocation{ 456 Spec: allocationv1.GameServerAllocationSpec{ 457 Selectors: []allocationv1.GameServerSelector{ 458 { 459 LabelSelector: fleetSelector, 460 GameServerState: &stateAllocated, 461 Counters: map[string]allocationv1.CounterSelector{ 462 "games": {}}}, { 463 LabelSelector: fleetSelector, 464 GameServerState: &ready, 465 Counters: map[string]allocationv1.CounterSelector{ 466 "games": {}}}}}}, 467 wantGsaErr: false, 468 wantAllocated: allocated, 469 wantState: stateAllocated, 470 }, 471 "Allocate to same GameServer no List values": { 472 gsa: allocationv1.GameServerAllocation{ 473 Spec: allocationv1.GameServerAllocationSpec{ 474 Selectors: []allocationv1.GameServerSelector{ 475 { 476 LabelSelector: fleetSelector, 477 GameServerState: &stateAllocated, 478 Lists: map[string]allocationv1.ListSelector{ 479 "players": {}}}, { 480 LabelSelector: fleetSelector, 481 GameServerState: &ready, 482 Lists: map[string]allocationv1.ListSelector{ 483 "players": {}}}}}}, 484 wantGsaErr: false, 485 wantAllocated: allocated, 486 wantState: stateAllocated, 487 }, 488 "Counter does not exist": { 489 gsa: allocationv1.GameServerAllocation{ 490 Spec: allocationv1.GameServerAllocationSpec{ 491 Selectors: []allocationv1.GameServerSelector{{ 492 LabelSelector: fleetSelector, 493 GameServerState: &ready, 494 Counters: map[string]allocationv1.CounterSelector{ 495 "players": { 496 MinAvailable: 1, 497 }}}}}}, 498 wantGsaErr: false, 499 wantAllocated: unallocated, 500 }, 501 "List does not exist": { 502 gsa: allocationv1.GameServerAllocation{ 503 Spec: allocationv1.GameServerAllocationSpec{ 504 Selectors: []allocationv1.GameServerSelector{{ 505 LabelSelector: fleetSelector, 506 GameServerState: &ready, 507 Lists: map[string]allocationv1.ListSelector{ 508 "games": { 509 MinAvailable: 1, 510 }}}}}}, 511 wantGsaErr: false, 512 wantAllocated: unallocated, 513 }, 514 "Counter MaxAvailable < MinAvailable": { 515 gsa: allocationv1.GameServerAllocation{ 516 Spec: allocationv1.GameServerAllocationSpec{ 517 Selectors: []allocationv1.GameServerSelector{{ 518 LabelSelector: fleetSelector, 519 GameServerState: &ready, 520 Counters: map[string]allocationv1.CounterSelector{ 521 "games": { 522 MaxAvailable: 1, 523 MinAvailable: 2, 524 }}}}}}, 525 wantGsaErr: true, 526 }, 527 "List MaxAvailable < MinAvailable": { 528 gsa: allocationv1.GameServerAllocation{ 529 Spec: allocationv1.GameServerAllocationSpec{ 530 Selectors: []allocationv1.GameServerSelector{{ 531 LabelSelector: fleetSelector, 532 GameServerState: &ready, 533 Lists: map[string]allocationv1.ListSelector{ 534 "players": { 535 MaxAvailable: 8, 536 MinAvailable: 9, 537 }}}}}}, 538 wantGsaErr: true, 539 }, 540 "Counter Maxcount < MinCount": { 541 gsa: allocationv1.GameServerAllocation{ 542 Spec: allocationv1.GameServerAllocationSpec{ 543 Selectors: []allocationv1.GameServerSelector{{ 544 LabelSelector: fleetSelector, 545 GameServerState: &ready, 546 Counters: map[string]allocationv1.CounterSelector{ 547 "games": { 548 MaxCount: 1, 549 MinCount: 2, 550 }}}}}}, 551 wantGsaErr: true, 552 }, 553 "List Value does not exist": { 554 gsa: allocationv1.GameServerAllocation{ 555 Spec: allocationv1.GameServerAllocationSpec{ 556 Selectors: []allocationv1.GameServerSelector{{ 557 LabelSelector: fleetSelector, 558 GameServerState: &ready, 559 Lists: map[string]allocationv1.ListSelector{ 560 "players": { 561 ContainsValue: "player1", 562 }}}}}}, 563 wantGsaErr: false, 564 wantAllocated: unallocated, 565 }, 566 "Negative values for MinCount, MaxCount, MaxAvailable, MinAvailable": { 567 gsa: allocationv1.GameServerAllocation{ 568 Spec: allocationv1.GameServerAllocationSpec{ 569 Selectors: []allocationv1.GameServerSelector{{ 570 LabelSelector: fleetSelector, 571 GameServerState: &ready, 572 Counters: map[string]allocationv1.CounterSelector{ 573 "games": { 574 MaxCount: -1, 575 MinCount: -2, 576 MaxAvailable: -10, 577 MinAvailable: -1, 578 }}}}}}, 579 wantGsaErr: true, 580 }, 581 } 582 583 for name, testCase := range testCases { 584 t.Run(name, func(t *testing.T) { 585 586 // First allocation 587 gsa, err := framework.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(ctx, testCase.gsa.DeepCopy(), metav1.CreateOptions{}) 588 if testCase.wantGsaErr { 589 require.Error(t, err) 590 return 591 } 592 require.NoError(t, err) 593 assert.Equal(t, string(testCase.wantAllocated), string(gsa.Status.State)) 594 if gsa.Status.State == allocated { 595 assert.Equal(t, initialCounters, gsa.Status.Counters) 596 assert.Equal(t, initialLists, gsa.Status.Lists) 597 } 598 599 gs1, err := framework.AgonesClient.AgonesV1().GameServers(flt.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 600 if testCase.wantAllocated == unallocated { 601 require.Error(t, err) 602 return 603 } 604 require.NoError(t, err) 605 assert.Equal(t, testCase.wantState, gs1.Status.State) 606 assert.NotNil(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"]) 607 608 // Second allocation 609 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 610 require.NoError(t, err) 611 assert.Equal(t, string(testCase.wantAllocated), string(gsa.Status.State)) 612 assert.Equal(t, gs1.ObjectMeta.Name, gsa.Status.GameServerName) 613 if gsa.Status.State == allocated { 614 assert.Equal(t, initialCounters, gsa.Status.Counters) 615 assert.Equal(t, initialLists, gsa.Status.Lists) 616 } 617 618 gs2, err := framework.AgonesClient.AgonesV1().GameServers(flt.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 619 require.NoError(t, err) 620 assert.Equal(t, testCase.wantState, gs2.Status.State) 621 622 // Confirm allocated to the same GameServer (gs2 == gs1) 623 require.Equal(t, gs1.ObjectMeta.Name, gs2.ObjectMeta.Name) 624 require.NotEqual(t, gs1.ObjectMeta.ResourceVersion, gs2.ObjectMeta.ResourceVersion) 625 require.NotEqual(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"], gs2.ObjectMeta.Annotations["agones.dev/last-allocated"]) 626 627 // Reset any GameServers in state Allocated -> Ready. Note: This does not reset any changes to Counters or Lists. 628 list, err := framework.ListGameServersFromFleet(flt) 629 require.NoError(t, err) 630 for _, gs := range list { 631 if gs.Status.State == ready { 632 continue 633 } 634 gsCopy := gs.DeepCopy() 635 gsCopy.Status.State = ready 636 reqReadyGs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{}) 637 require.NoError(t, err) 638 require.Equal(t, ready, reqReadyGs.Status.State) 639 } 640 }) 641 } 642 } 643 644 func TestCounterGameServerAllocationActions(t *testing.T) { 645 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 646 t.SkipNow() 647 } 648 t.Parallel() 649 ctx := context.Background() 650 client := framework.AgonesClient.AgonesV1() 651 652 counters := map[string]agonesv1.CounterStatus{} 653 counters["games"] = agonesv1.CounterStatus{ 654 Count: 5, 655 Capacity: 10, 656 } 657 658 flt := defaultFleet(framework.Namespace) 659 flt.Spec.Template.Spec.Counters = counters 660 661 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 662 require.NoError(t, err) 663 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 664 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 665 666 fleetSelector := metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}} 667 allocated := agonesv1.GameServerStateAllocated 668 ready := agonesv1.GameServerStateReady 669 increment := agonesv1.GameServerPriorityIncrement 670 decrement := agonesv1.GameServerPriorityDecrement 671 zero := int64(0) 672 one := int64(1) 673 five := int64(5) 674 six := int64(6) 675 ten := int64(10) 676 negativeOne := int64(-1) 677 678 testCases := map[string]struct { 679 gsa allocationv1.GameServerAllocation 680 wantGsaErr bool 681 wantCount *int64 682 wantCapacity *int64 683 }{ 684 "increment": { 685 gsa: allocationv1.GameServerAllocation{ 686 Spec: allocationv1.GameServerAllocationSpec{ 687 Selectors: []allocationv1.GameServerSelector{ 688 {LabelSelector: fleetSelector}, 689 }, 690 Counters: map[string]allocationv1.CounterAction{ 691 "games": { 692 Action: &increment, 693 Amount: &one, 694 }}}}, 695 wantGsaErr: false, 696 wantCount: &six, 697 wantCapacity: &ten, 698 }, 699 "decrement": { 700 gsa: allocationv1.GameServerAllocation{ 701 Spec: allocationv1.GameServerAllocationSpec{ 702 Selectors: []allocationv1.GameServerSelector{ 703 {LabelSelector: fleetSelector}, 704 }, 705 Counters: map[string]allocationv1.CounterAction{ 706 "games": { 707 Action: &decrement, 708 Amount: &five, 709 }}}}, 710 wantGsaErr: false, 711 wantCount: &zero, 712 wantCapacity: &ten, 713 }, 714 "change capacity to less than count also updates count": { 715 gsa: allocationv1.GameServerAllocation{ 716 Spec: allocationv1.GameServerAllocationSpec{ 717 Selectors: []allocationv1.GameServerSelector{ 718 {LabelSelector: fleetSelector}, 719 }, 720 Counters: map[string]allocationv1.CounterAction{ 721 "games": { 722 Capacity: &zero, 723 }}}}, 724 wantGsaErr: false, 725 wantCount: &zero, 726 wantCapacity: &zero, 727 }, 728 "decrement past zero truncated": { 729 gsa: allocationv1.GameServerAllocation{ 730 Spec: allocationv1.GameServerAllocationSpec{ 731 Selectors: []allocationv1.GameServerSelector{ 732 {LabelSelector: fleetSelector}, 733 }, 734 Counters: map[string]allocationv1.CounterAction{ 735 "games": { 736 Action: &decrement, 737 Amount: &six, 738 }}}}, 739 wantGsaErr: false, 740 wantCount: &zero, 741 wantCapacity: &ten, 742 }, 743 "decrement negative": { 744 gsa: allocationv1.GameServerAllocation{ 745 Spec: allocationv1.GameServerAllocationSpec{ 746 Selectors: []allocationv1.GameServerSelector{ 747 {LabelSelector: fleetSelector}, 748 }, 749 Counters: map[string]allocationv1.CounterAction{ 750 "games": { 751 Action: &decrement, 752 Amount: &negativeOne, 753 }}}}, 754 wantGsaErr: true, 755 }, 756 "increment past capacity truncated": { 757 gsa: allocationv1.GameServerAllocation{ 758 Spec: allocationv1.GameServerAllocationSpec{ 759 Selectors: []allocationv1.GameServerSelector{ 760 {LabelSelector: fleetSelector}, 761 }, 762 Counters: map[string]allocationv1.CounterAction{ 763 "games": { 764 Action: &increment, 765 Amount: &six, 766 }}}}, 767 wantGsaErr: false, 768 wantCount: &ten, 769 wantCapacity: &ten, 770 }, 771 "increment negative": { 772 gsa: allocationv1.GameServerAllocation{ 773 Spec: allocationv1.GameServerAllocationSpec{ 774 Selectors: []allocationv1.GameServerSelector{ 775 {LabelSelector: fleetSelector}, 776 }, 777 Counters: map[string]allocationv1.CounterAction{ 778 "games": { 779 Action: &increment, 780 Amount: &negativeOne, 781 }}}}, 782 wantGsaErr: true, 783 }, 784 "change capacity negative": { 785 gsa: allocationv1.GameServerAllocation{ 786 Spec: allocationv1.GameServerAllocationSpec{ 787 Selectors: []allocationv1.GameServerSelector{ 788 {LabelSelector: fleetSelector}, 789 }, 790 Counters: map[string]allocationv1.CounterAction{ 791 "games": { 792 Capacity: &negativeOne, 793 }}}}, 794 wantGsaErr: true, 795 }, 796 // Note: a gameserver is still allocated even though the counter does not exist (and thus the 797 // action cannot be performed). gsa.Validate() is not able to see the state of Counters in the 798 // fleet, so the GSA is not able to validate the existence of a Counter. Use the 799 // GameServerSelector to filter the Counters. 800 "Counter does not exist": { 801 gsa: allocationv1.GameServerAllocation{ 802 Spec: allocationv1.GameServerAllocationSpec{ 803 Selectors: []allocationv1.GameServerSelector{ 804 {LabelSelector: fleetSelector}, 805 }, 806 Counters: map[string]allocationv1.CounterAction{ 807 "lames": { 808 Action: &increment, 809 Amount: &one, 810 }}}}, 811 wantGsaErr: false, 812 }, 813 } 814 815 for name, testCase := range testCases { 816 t.Run(name, func(t *testing.T) { 817 818 gsa, err := framework.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(ctx, testCase.gsa.DeepCopy(), metav1.CreateOptions{}) 819 if testCase.wantGsaErr { 820 require.Error(t, err) 821 return 822 } 823 require.NoError(t, err) 824 assert.Equal(t, string(allocated), string(gsa.Status.State)) 825 counterStatus, ok := gsa.Status.Counters["games"] 826 assert.True(t, ok) 827 if testCase.wantCount != nil { 828 assert.Equal(t, *testCase.wantCount, counterStatus.Count) 829 } 830 if testCase.wantCapacity != nil { 831 assert.Equal(t, *testCase.wantCapacity, counterStatus.Capacity) 832 } 833 834 gs1, err := framework.AgonesClient.AgonesV1().GameServers(flt.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 835 require.NoError(t, err) 836 assert.Equal(t, allocated, gs1.Status.State) 837 assert.NotNil(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"]) 838 839 counter, ok := gs1.Status.Counters["games"] 840 assert.True(t, ok) 841 if testCase.wantCount != nil { 842 assert.Equal(t, *testCase.wantCount, counter.Count) 843 } 844 if testCase.wantCapacity != nil { 845 assert.Equal(t, *testCase.wantCapacity, counter.Capacity) 846 } 847 848 // Reset any GameServers in state Allocated -> Ready, and reset any changes to Counters. 849 gsList, err := framework.ListGameServersFromFleet(flt) 850 require.NoError(t, err) 851 for _, gs := range gsList { 852 if gs.Status.State == ready && cmp.Equal(gs.Status.Counters, counters) { 853 continue 854 } 855 gsCopy := gs.DeepCopy() 856 gsCopy.Status.State = ready 857 gsCopy.Status.Counters = counters 858 reqReadyGs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{}) 859 require.NoError(t, err) 860 require.Equal(t, ready, reqReadyGs.Status.State) 861 } 862 }) 863 } 864 } 865 866 func TestListGameServerAllocationActions(t *testing.T) { 867 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 868 t.SkipNow() 869 } 870 t.Parallel() 871 ctx := context.Background() 872 client := framework.AgonesClient.AgonesV1() 873 874 lists := map[string]agonesv1.ListStatus{} 875 lists["players"] = agonesv1.ListStatus{ 876 Values: []string{"player0", "player1", "player2"}, 877 Capacity: 8, 878 } 879 lists["rooms"] = agonesv1.ListStatus{ 880 Values: []string{"room0", "room1", "room2"}, 881 Capacity: 20, 882 } 883 884 flt := defaultFleet(framework.Namespace) 885 flt.Spec.Template.Spec.Lists = lists 886 887 flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{}) 888 require.NoError(t, err) 889 defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 890 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 891 892 fleetSelector := metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}} 893 allocated := agonesv1.GameServerStateAllocated 894 ready := agonesv1.GameServerStateReady 895 one := int64(1) 896 897 testCases := map[string]struct { 898 gsa allocationv1.GameServerAllocation 899 listName string 900 wantGsaErr bool 901 wantCapacity *int64 902 wantValues []string 903 }{ 904 "add List values": { 905 gsa: allocationv1.GameServerAllocation{ 906 Spec: allocationv1.GameServerAllocationSpec{ 907 Selectors: []allocationv1.GameServerSelector{ 908 {LabelSelector: fleetSelector}, 909 }, 910 Lists: map[string]allocationv1.ListAction{ 911 "players": { 912 AddValues: []string{"player3", "player4", "player5"}, 913 }}}}, 914 listName: "players", 915 wantGsaErr: false, 916 wantValues: []string{"player0", "player1", "player2", "player3", "player4", "player5"}, 917 }, 918 "change List capacity truncates": { 919 gsa: allocationv1.GameServerAllocation{ 920 Spec: allocationv1.GameServerAllocationSpec{ 921 Selectors: []allocationv1.GameServerSelector{ 922 {LabelSelector: fleetSelector}, 923 }, 924 Lists: map[string]allocationv1.ListAction{ 925 "players": { 926 Capacity: &one, 927 }}}}, 928 listName: "players", 929 wantGsaErr: false, 930 wantValues: []string{"player0"}, 931 wantCapacity: &one, 932 }, 933 "delete List values": { 934 gsa: allocationv1.GameServerAllocation{ 935 Spec: allocationv1.GameServerAllocationSpec{ 936 Selectors: []allocationv1.GameServerSelector{ 937 {LabelSelector: fleetSelector}, 938 }, 939 Lists: map[string]allocationv1.ListAction{ 940 "rooms": { 941 DeleteValues: []string{"room1", "room0"}, 942 }}}}, 943 listName: "rooms", 944 wantGsaErr: false, 945 wantValues: []string{"room2"}, 946 }, 947 } 948 949 for name, testCase := range testCases { 950 t.Run(name, func(t *testing.T) { 951 952 gsa, err := framework.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(ctx, testCase.gsa.DeepCopy(), metav1.CreateOptions{}) 953 if testCase.wantGsaErr { 954 require.Error(t, err) 955 return 956 } 957 require.NoError(t, err) 958 assert.Equal(t, string(allocated), string(gsa.Status.State)) 959 listStatus, ok := gsa.Status.Lists[testCase.listName] 960 assert.True(t, ok) 961 if testCase.wantCapacity != nil { 962 assert.Equal(t, *testCase.wantCapacity, listStatus.Capacity) 963 } 964 assert.Equal(t, testCase.wantValues, listStatus.Values) 965 966 gs1, err := framework.AgonesClient.AgonesV1().GameServers(flt.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 967 require.NoError(t, err) 968 assert.Equal(t, allocated, gs1.Status.State) 969 assert.NotNil(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"]) 970 971 list, ok := gs1.Status.Lists[testCase.listName] 972 assert.True(t, ok) 973 if testCase.wantCapacity != nil { 974 assert.Equal(t, *testCase.wantCapacity, list.Capacity) 975 } 976 assert.Equal(t, testCase.wantValues, list.Values) 977 978 // Reset any GameServers in state Allocated -> Ready, and reset any changes to Lists. 979 gsList, err := framework.ListGameServersFromFleet(flt) 980 require.NoError(t, err) 981 for _, gs := range gsList { 982 if gs.Status.State == ready && cmp.Equal(gs.Status.Lists, lists) { 983 continue 984 } 985 gsCopy := gs.DeepCopy() 986 gsCopy.Status.State = ready 987 gsCopy.Status.Lists = lists 988 reqReadyGs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{}) 989 require.NoError(t, err) 990 require.Equal(t, ready, reqReadyGs.Status.State) 991 } 992 }) 993 } 994 } 995 996 func TestMultiClusterAllocationOnLocalCluster(t *testing.T) { 997 t.Parallel() 998 999 fixtures := []apis.SchedulingStrategy{apis.Packed, apis.Distributed} 1000 for _, strategy := range fixtures { 1001 t.Run(string(strategy), func(t *testing.T) { 1002 if strategy == apis.Distributed { 1003 framework.SkipOnCloudProduct(t, "gke-autopilot", "Autopilot does not support Distributed scheduling") 1004 } 1005 t.Parallel() 1006 ctx := context.Background() 1007 1008 namespace := fmt.Sprintf("gsa-multicluster-local-%s", uuid.NewUUID()) 1009 err := framework.CreateNamespace(namespace) 1010 if !assert.Nil(t, err) { 1011 return 1012 } 1013 defer func() { 1014 if derr := framework.DeleteNamespace(namespace); derr != nil { 1015 t.Error(derr) 1016 } 1017 }() 1018 1019 fleets := framework.AgonesClient.AgonesV1().Fleets(namespace) 1020 fleet := defaultFleet(namespace) 1021 fleet.Spec.Scheduling = strategy 1022 flt, err := fleets.Create(ctx, fleet, metav1.CreateOptions{}) 1023 if assert.Nil(t, err) { 1024 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1025 } 1026 1027 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1028 1029 // Allocation Policy #1: local cluster with desired label. 1030 // This policy allocates locally on the cluster due to matching namespace with gsa and not setting AllocationEndpoints. 1031 mca := &multiclusterv1.GameServerAllocationPolicy{ 1032 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 1033 Priority: 1, 1034 Weight: 100, 1035 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 1036 ClusterName: "multicluster1", 1037 SecretName: "sec1", 1038 Namespace: namespace, 1039 }, 1040 }, 1041 ObjectMeta: metav1.ObjectMeta{ 1042 Labels: map[string]string{"cluster": "onprem"}, 1043 GenerateName: "allocationpolicy-", 1044 }, 1045 } 1046 resp, err := framework.AgonesClient.MulticlusterV1().GameServerAllocationPolicies(fleet.ObjectMeta.Namespace).Create(ctx, mca, metav1.CreateOptions{}) 1047 if !assert.Nil(t, err) { 1048 assert.FailNowf(t, "GameServerAllocationPolicies(%v).Create(ctx, %v, metav1.CreateOptions{})", fleet.ObjectMeta.Namespace, mca) 1049 } 1050 assert.Equal(t, mca.Spec, resp.Spec) 1051 1052 // Allocation Policy #2: another cluster with desired label, but lower priority. 1053 // If the policy is selected due to a bug the request fails as it cannot find the secret. 1054 mca = &multiclusterv1.GameServerAllocationPolicy{ 1055 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 1056 Priority: 2, 1057 Weight: 100, 1058 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 1059 AllocationEndpoints: []string{"another-endpoint"}, 1060 ClusterName: "multicluster2", 1061 SecretName: "sec2", 1062 Namespace: namespace, 1063 }, 1064 }, 1065 ObjectMeta: metav1.ObjectMeta{ 1066 Labels: map[string]string{"cluster": "onprem"}, 1067 GenerateName: "allocationpolicy-", 1068 }, 1069 } 1070 resp, err = framework.AgonesClient.MulticlusterV1().GameServerAllocationPolicies(fleet.ObjectMeta.Namespace).Create(ctx, mca, metav1.CreateOptions{}) 1071 if assert.Nil(t, err) { 1072 assert.Equal(t, mca.Spec, resp.Spec) 1073 } 1074 1075 // Allocation Policy #3: another cluster with highest priority, but missing desired label (will not be selected) 1076 mca = &multiclusterv1.GameServerAllocationPolicy{ 1077 Spec: multiclusterv1.GameServerAllocationPolicySpec{ 1078 Priority: 1, 1079 Weight: 10, 1080 ConnectionInfo: multiclusterv1.ClusterConnectionInfo{ 1081 AllocationEndpoints: []string{"another-endpoint"}, 1082 ClusterName: "multicluster3", 1083 SecretName: "sec3", 1084 Namespace: namespace, 1085 }, 1086 }, 1087 ObjectMeta: metav1.ObjectMeta{ 1088 GenerateName: "allocationpolicy-", 1089 }, 1090 } 1091 resp, err = framework.AgonesClient.MulticlusterV1().GameServerAllocationPolicies(fleet.ObjectMeta.Namespace).Create(ctx, mca, metav1.CreateOptions{}) 1092 if assert.Nil(t, err) { 1093 assert.Equal(t, mca.Spec, resp.Spec) 1094 } 1095 1096 gsa := &allocationv1.GameServerAllocation{ 1097 Spec: allocationv1.GameServerAllocationSpec{ 1098 Scheduling: strategy, 1099 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}}, 1100 MultiClusterSetting: allocationv1.MultiClusterSetting{ 1101 Enabled: true, 1102 PolicySelector: metav1.LabelSelector{ 1103 MatchLabels: map[string]string{ 1104 "cluster": "onprem", 1105 }, 1106 }, 1107 }, 1108 }, 1109 ObjectMeta: metav1.ObjectMeta{ 1110 GenerateName: "allocation-", 1111 Namespace: namespace, 1112 }, 1113 } 1114 1115 // wait for the allocation policies to be added. 1116 err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) { 1117 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 1118 if err != nil { 1119 t.Logf("GameServerAllocations(%v).Create(ctx, %v, metav1.CreateOptions{}) failed: %s", fleet.ObjectMeta.Namespace, gsa, err) 1120 return false, nil 1121 } 1122 1123 assert.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(gsa.Status.State)) 1124 return true, nil 1125 }) 1126 1127 assert.NoError(t, err) 1128 }) 1129 } 1130 } 1131 1132 // Can't allocate more GameServers if a fleet is fully used. 1133 func TestCreateFullFleetAndCantGameServerAllocate(t *testing.T) { 1134 t.Parallel() 1135 1136 fixtures := []apis.SchedulingStrategy{apis.Packed, apis.Distributed} 1137 1138 for _, strategy := range fixtures { 1139 t.Run(string(strategy), func(t *testing.T) { 1140 if strategy == apis.Distributed { 1141 framework.SkipOnCloudProduct(t, "gke-autopilot", "Autopilot does not support Distributed scheduling") 1142 } 1143 t.Parallel() 1144 ctx := context.Background() 1145 1146 fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace) 1147 fleet := defaultFleet(framework.Namespace) 1148 fleet.Spec.Scheduling = strategy 1149 flt, err := fleets.Create(ctx, fleet, metav1.CreateOptions{}) 1150 if assert.Nil(t, err) { 1151 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1152 } 1153 1154 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1155 1156 gsa := &allocationv1.GameServerAllocation{ 1157 Spec: allocationv1.GameServerAllocationSpec{ 1158 Scheduling: strategy, 1159 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}}, 1160 }} 1161 1162 for i := 0; i < replicasCount; i++ { 1163 var gsa2 *allocationv1.GameServerAllocation 1164 gsa2, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1165 if assert.Nil(t, err) { 1166 assert.Equal(t, allocationv1.GameServerAllocationAllocated, gsa2.Status.State) 1167 } 1168 } 1169 1170 framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool { 1171 return fleet.Status.AllocatedReplicas == replicasCount 1172 }) 1173 1174 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1175 if assert.Nil(t, err) { 1176 assert.Equal(t, string(allocationv1.GameServerAllocationUnAllocated), string(gsa.Status.State)) 1177 } 1178 }) 1179 } 1180 } 1181 1182 func TestGameServerAllocationMetaDataPatch(t *testing.T) { 1183 t.Parallel() 1184 ctx := context.Background() 1185 1186 log := e2e.TestLogger(t) 1187 createAndAllocate := func(input *allocationv1.GameServerAllocation) *allocationv1.GameServerAllocation { 1188 gs := framework.DefaultGameServer(framework.Namespace) 1189 gs.ObjectMeta.Labels = map[string]string{"test": t.Name()} 1190 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1191 require.NoError(t, err) 1192 1193 log.WithField("gs", gs.ObjectMeta.Name).Info("👍 created and ready") 1194 1195 // poll, as it may take a moment for the allocation cache to be populated 1196 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) { 1197 input, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, input, metav1.CreateOptions{}) 1198 if err != nil { 1199 log.WithError(err).Info("Failed, trying again...") 1200 return false, err 1201 } 1202 1203 return allocationv1.GameServerAllocationAllocated == input.Status.State, nil 1204 }) 1205 require.NoError(t, err) 1206 return input 1207 } 1208 1209 // two standard labels 1210 gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"}, 1211 Spec: allocationv1.GameServerAllocationSpec{ 1212 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"test": t.Name()}}}}, 1213 MetaPatch: allocationv1.MetaPatch{ 1214 Labels: map[string]string{"red": "blue"}, 1215 Annotations: map[string]string{"dog": "good"}, 1216 }, 1217 }} 1218 result := createAndAllocate(gsa) 1219 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, result.Status.GameServerName, metav1.DeleteOptions{}) // nolint: errcheck 1220 1221 gs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, result.Status.GameServerName, metav1.GetOptions{}) 1222 require.NoError(t, err) 1223 assert.Equal(t, "blue", gs.ObjectMeta.Labels["red"]) 1224 assert.Equal(t, "good", gs.ObjectMeta.Annotations["dog"]) 1225 1226 // use special characters that are valid 1227 gsa.Spec.MetaPatch = allocationv1.MetaPatch{Labels: map[string]string{"blue-frog.fred_thing": "test"}} 1228 result = createAndAllocate(gsa) 1229 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, result.Status.GameServerName, metav1.DeleteOptions{}) // nolint: errcheck 1230 1231 gs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, result.Status.GameServerName, metav1.GetOptions{}) 1232 require.NoError(t, err) 1233 assert.Equal(t, "test", gs.ObjectMeta.Labels["blue-frog.fred_thing"]) 1234 1235 // throw something invalid at it. 1236 gsa.Spec.MetaPatch = allocationv1.MetaPatch{Labels: map[string]string{"$$$$$$$": "test"}} 1237 result, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1238 log.WithField("result", result).WithError(err).Info("Failed allocation") 1239 require.Error(t, err) 1240 require.Contains(t, err.Error(), `GameServerAllocation.allocation.agones.dev "" is invalid`) 1241 } 1242 1243 func TestGameServerAllocationPreferredSelection(t *testing.T) { 1244 t.Parallel() 1245 ctx := context.Background() 1246 1247 fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace) 1248 gameServers := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 1249 label := map[string]string{"role": t.Name()} 1250 1251 preferred := defaultFleet(framework.Namespace) 1252 preferred.ObjectMeta.GenerateName = "preferred-" 1253 preferred.Spec.Replicas = 1 1254 preferred.Spec.Template.ObjectMeta.Labels = label 1255 preferred, err := fleets.Create(ctx, preferred, metav1.CreateOptions{}) 1256 if assert.Nil(t, err) { 1257 defer fleets.Delete(ctx, preferred.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1258 } else { 1259 assert.FailNow(t, "could not create first fleet") 1260 } 1261 1262 required := defaultFleet(framework.Namespace) 1263 required.ObjectMeta.GenerateName = "required-" 1264 required.Spec.Replicas = 2 1265 required.Spec.Template.ObjectMeta.Labels = label 1266 required, err = fleets.Create(ctx, required, metav1.CreateOptions{}) 1267 if assert.Nil(t, err) { 1268 defer fleets.Delete(ctx, required.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1269 } else { 1270 assert.FailNow(t, "could not create second fleet") 1271 } 1272 1273 framework.AssertFleetCondition(t, preferred, e2e.FleetReadyCount(preferred.Spec.Replicas)) 1274 framework.AssertFleetCondition(t, required, e2e.FleetReadyCount(required.Spec.Replicas)) 1275 1276 gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"}, 1277 Spec: allocationv1.GameServerAllocationSpec{ 1278 Selectors: []allocationv1.GameServerSelector{ 1279 {LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: preferred.ObjectMeta.Name}}}, 1280 {LabelSelector: metav1.LabelSelector{MatchLabels: label}}, 1281 }, 1282 }} 1283 1284 gsa1, err := framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1285 if assert.Nil(t, err) { 1286 assert.Equal(t, allocationv1.GameServerAllocationAllocated, gsa1.Status.State) 1287 gs, err := gameServers.Get(ctx, gsa1.Status.GameServerName, metav1.GetOptions{}) 1288 assert.Nil(t, err) 1289 assert.Equal(t, preferred.ObjectMeta.Name, gs.ObjectMeta.Labels[agonesv1.FleetNameLabel]) 1290 } else { 1291 assert.FailNow(t, "could not completed gsa1 allocation") 1292 } 1293 1294 gs2, err := framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1295 if assert.Nil(t, err) { 1296 assert.Equal(t, allocationv1.GameServerAllocationAllocated, gs2.Status.State) 1297 gs, err := gameServers.Get(ctx, gs2.Status.GameServerName, metav1.GetOptions{}) 1298 assert.Nil(t, err) 1299 assert.Equal(t, required.ObjectMeta.Name, gs.ObjectMeta.Labels[agonesv1.FleetNameLabel]) 1300 } else { 1301 assert.FailNow(t, "could not completed gs2 allocation") 1302 } 1303 1304 // delete the preferred gameserver, and then let's try allocating again, make sure it goes back to the 1305 // preferred one 1306 err = gameServers.Delete(ctx, gsa1.Status.GameServerName, metav1.DeleteOptions{}) 1307 if !assert.Nil(t, err) { 1308 assert.FailNow(t, "could not delete gameserver") 1309 } 1310 1311 // wait until the game server is deleted 1312 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) { 1313 _, err = gameServers.Get(ctx, gsa1.Status.GameServerName, metav1.GetOptions{}) 1314 1315 if err != nil && errors.IsNotFound(err) { 1316 return true, nil 1317 } 1318 1319 return false, err 1320 }) 1321 assert.Nil(t, err) 1322 1323 // now wait for another one to come along 1324 framework.AssertFleetCondition(t, preferred, e2e.FleetReadyCount(preferred.Spec.Replicas)) 1325 1326 gsa3, err := framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1327 if assert.Nil(t, err) { 1328 assert.Equal(t, allocationv1.GameServerAllocationAllocated, gsa3.Status.State) 1329 gs, err := gameServers.Get(ctx, gsa3.Status.GameServerName, metav1.GetOptions{}) 1330 assert.Nil(t, err) 1331 assert.Equal(t, preferred.ObjectMeta.Name, gs.ObjectMeta.Labels[agonesv1.FleetNameLabel]) 1332 } 1333 } 1334 1335 func TestGameServerAllocationReturnLabels(t *testing.T) { 1336 t.Parallel() 1337 ctx := context.Background() 1338 1339 fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace) 1340 gameServers := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 1341 role := "role" 1342 label := map[string]string{role: t.Name()} 1343 annotationKey := "someAnnotation" 1344 annotationValue := "someValue" 1345 annotations := map[string]string{annotationKey: annotationValue} 1346 1347 flt := defaultFleet(framework.Namespace) 1348 flt.Spec.Replicas = 1 1349 flt.Spec.Template.ObjectMeta.Labels = label 1350 flt.Spec.Template.ObjectMeta.Annotations = annotations 1351 flt, err := fleets.Create(ctx, flt, metav1.CreateOptions{}) 1352 defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1353 require.NoError(t, err) 1354 1355 framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) 1356 1357 gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"}, 1358 Spec: allocationv1.GameServerAllocationSpec{ 1359 Selectors: []allocationv1.GameServerSelector{ 1360 {LabelSelector: metav1.LabelSelector{MatchLabels: label}}, 1361 }, 1362 }} 1363 1364 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1365 require.NoError(t, err) 1366 1367 assert.Equal(t, allocationv1.GameServerAllocationAllocated, gsa.Status.State) 1368 assert.Equal(t, t.Name(), gsa.Status.Metadata.Labels[role]) 1369 assert.Equal(t, flt.ObjectMeta.Name, gsa.Status.Metadata.Labels[agonesv1.FleetNameLabel]) 1370 assert.Equal(t, annotationValue, gsa.Status.Metadata.Annotations[annotationKey]) 1371 gs, err := gameServers.Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{}) 1372 require.NoError(t, err) 1373 assert.Equal(t, flt.ObjectMeta.Name, gs.ObjectMeta.Labels[agonesv1.FleetNameLabel]) 1374 } 1375 1376 func TestGameServerAllocationDeletionOnUnAllocate(t *testing.T) { 1377 t.Parallel() 1378 ctx := context.Background() 1379 1380 allocations := framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace) 1381 1382 gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"}, 1383 Spec: allocationv1.GameServerAllocationSpec{ 1384 Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"never": "goingtohappen"}}}}, 1385 }} 1386 1387 gsa, err := allocations.Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1388 if assert.Nil(t, err) { 1389 assert.Equal(t, allocationv1.GameServerAllocationUnAllocated, gsa.Status.State) 1390 } 1391 } 1392 1393 func TestGameServerAllocationDuringMultipleAllocationClients(t *testing.T) { 1394 t.Parallel() 1395 log := e2e.TestLogger(t) 1396 ctx := context.Background() 1397 1398 fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace) 1399 label := map[string]string{"role": t.Name()} 1400 1401 preferred := defaultFleet(framework.Namespace) 1402 preferred.ObjectMeta.GenerateName = "preferred-" 1403 preferred.Spec.Replicas = 150 1404 preferred.Spec.Template.ObjectMeta.Labels = label 1405 preferred, err := fleets.Create(ctx, preferred, metav1.CreateOptions{}) 1406 if assert.Nil(t, err) { 1407 defer fleets.Delete(ctx, preferred.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck 1408 } else { 1409 assert.FailNow(t, "could not create first fleet") 1410 } 1411 1412 framework.AssertFleetCondition(t, preferred, e2e.FleetReadyCount(preferred.Spec.Replicas)) 1413 1414 // scale down before starting allocation 1415 preferred = scaleFleetPatch(ctx, t, preferred, preferred.Spec.Replicas-20) 1416 1417 gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"}, 1418 Spec: allocationv1.GameServerAllocationSpec{ 1419 Selectors: []allocationv1.GameServerSelector{ 1420 {LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: "preferred"}}}, 1421 {LabelSelector: metav1.LabelSelector{MatchLabels: label}}, 1422 }, 1423 }} 1424 1425 allocatedGS := sync.Map{} 1426 1427 log.Info("Starting 100 allocation attempts") 1428 var wg sync.WaitGroup 1429 1430 // Allocate GS by 10 clients in parallel while the fleet is scaling down 1431 for i := 0; i < 10; i++ { 1432 wg.Add(1) 1433 1434 go func() { 1435 defer wg.Done() 1436 for j := 0; j < 10; j++ { 1437 gsa1, err := framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{}) 1438 if err == nil { 1439 allocatedGS.LoadOrStore(gsa1.Status.GameServerName, true) 1440 } else { 1441 log.Infof("Allocation error: %v", err) 1442 } 1443 } 1444 }() 1445 } 1446 1447 // scale down further while allocating 1448 time.Sleep(1 * time.Second) 1449 log.Infof("Scaling Fleet down by 10 replicas") 1450 scaleFleetPatch(ctx, t, preferred, preferred.Spec.Replicas-10) 1451 1452 wg.Wait() 1453 log.Infof("Finished allocation attempts") 1454 1455 // count the number of unique game servers allocated 1456 // there should not be any duplicate 1457 uniqueAllocatedGSs := 0 1458 allocatedGS.Range(func(_, _ interface{}) bool { 1459 uniqueAllocatedGSs++ 1460 return true 1461 }) 1462 1463 // TODO: Compromising on the expected allocation count to be between 98 to 100 due to a known allocation issue. Please check: [https://github.com/googleforgames/agones/issues/3553] 1464 switch { 1465 case uniqueAllocatedGSs < 98: 1466 t.Errorf("Test failed: Less than 98 GameServers were allocated. Allocated: %d", uniqueAllocatedGSs) 1467 case uniqueAllocatedGSs < 100: 1468 t.Logf("Number of GameServers Allocated: %d. This might be due to a known allocation issue. Please check: [https://github.com/googleforgames/agones/issues/3553]", uniqueAllocatedGSs) 1469 default: 1470 t.Logf("Number of GameServers allocated: %d. This matches the expected outcome.", uniqueAllocatedGSs) 1471 } 1472 }