agones.dev/agones@v1.54.0/pkg/gameserversets/controller_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 gameserversets 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "math/rand" 22 "net/http" 23 "strconv" 24 "testing" 25 "time" 26 27 "agones.dev/agones/pkg/apis" 28 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 29 "agones.dev/agones/pkg/cloudproduct/generic" 30 "agones.dev/agones/pkg/gameservers" 31 agtesting "agones.dev/agones/pkg/testing" 32 utilruntime "agones.dev/agones/pkg/util/runtime" 33 "agones.dev/agones/pkg/util/webhooks" 34 "github.com/heptiolabs/healthcheck" 35 "github.com/pkg/errors" 36 "github.com/sirupsen/logrus" 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39 admissionv1 "k8s.io/api/admission/v1" 40 corev1 "k8s.io/api/core/v1" 41 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 42 "k8s.io/apimachinery/pkg/runtime" 43 "k8s.io/apimachinery/pkg/watch" 44 k8stesting "k8s.io/client-go/testing" 45 "k8s.io/client-go/tools/cache" 46 ) 47 48 func gsWithState(st agonesv1.GameServerState) *agonesv1.GameServer { 49 return &agonesv1.GameServer{Status: agonesv1.GameServerStatus{State: st}} 50 } 51 52 func gsPendingDeletionWithState(st agonesv1.GameServerState) *agonesv1.GameServer { 53 return &agonesv1.GameServer{ 54 ObjectMeta: metav1.ObjectMeta{ 55 DeletionTimestamp: &deletionTime, 56 }, 57 Status: agonesv1.GameServerStatus{State: st}, 58 } 59 } 60 61 const ( 62 maxTestCreationsPerBatch = 3 63 maxTestDeletionsPerBatch = 3 64 maxTestPendingPerBatch = 3 65 ) 66 67 func TestComputeReconciliationAction(t *testing.T) { 68 t.Parallel() 69 70 cases := []struct { 71 desc string 72 list []*agonesv1.GameServer 73 targetReplicaCount int 74 wantNumServersToAdd int 75 wantNumServersToDelete int 76 wantIsPartial bool 77 priorities []agonesv1.Priority 78 }{ 79 { 80 desc: "Empty", 81 }, 82 { 83 desc: "AddServers", 84 list: []*agonesv1.GameServer{ 85 gsWithState(agonesv1.GameServerStateReady), 86 }, 87 targetReplicaCount: 3, 88 wantNumServersToAdd: 2, 89 }, 90 { 91 // 1 ready servers, target is 30 but we can only create 3 at a time. 92 desc: "AddServersPartial", 93 list: []*agonesv1.GameServer{ 94 gsWithState(agonesv1.GameServerStateReady), 95 }, 96 targetReplicaCount: 30, 97 wantNumServersToAdd: 3, 98 wantIsPartial: true, // max 3 creations per action 99 }, 100 { 101 // 0 ready servers, target is 30 but we can only have 3 in-flight. 102 desc: "AddServersExceedsInFlightLimit", 103 list: []*agonesv1.GameServer{ 104 gsWithState(agonesv1.GameServerStateCreating), 105 gsWithState(agonesv1.GameServerStatePortAllocation), 106 }, 107 targetReplicaCount: 30, 108 wantNumServersToAdd: 1, 109 wantIsPartial: true, 110 }, { 111 desc: "DeleteServers", 112 list: []*agonesv1.GameServer{ 113 gsWithState(agonesv1.GameServerStateReady), 114 gsWithState(agonesv1.GameServerStateReserved), 115 gsWithState(agonesv1.GameServerStateReady), 116 }, 117 targetReplicaCount: 1, 118 wantNumServersToDelete: 2, 119 }, 120 { 121 // 6 ready servers, target is 1 but we can only delete 3 at a time. 122 desc: "DeleteServerPartial", 123 list: []*agonesv1.GameServer{ 124 gsWithState(agonesv1.GameServerStateReady), 125 gsWithState(agonesv1.GameServerStateReady), 126 gsWithState(agonesv1.GameServerStateReady), 127 gsWithState(agonesv1.GameServerStateReady), 128 gsWithState(agonesv1.GameServerStateReady), 129 gsWithState(agonesv1.GameServerStateReady), 130 }, 131 targetReplicaCount: 1, 132 wantNumServersToDelete: 3, 133 wantIsPartial: true, // max 3 deletions per action 134 }, 135 { 136 desc: "DeleteIgnoresAllocatedServers", 137 list: []*agonesv1.GameServer{ 138 gsWithState(agonesv1.GameServerStateReady), 139 gsWithState(agonesv1.GameServerStateAllocated), 140 gsWithState(agonesv1.GameServerStateAllocated), 141 }, 142 targetReplicaCount: 0, 143 wantNumServersToDelete: 1, 144 }, 145 { 146 desc: "DeleteIgnoresReservedServers", 147 list: []*agonesv1.GameServer{ 148 gsWithState(agonesv1.GameServerStateReady), 149 gsWithState(agonesv1.GameServerStateReserved), 150 gsWithState(agonesv1.GameServerStateReserved), 151 }, 152 targetReplicaCount: 0, 153 wantNumServersToDelete: 1, 154 }, 155 { 156 desc: "CreateWhileDeletionsPending", 157 list: []*agonesv1.GameServer{ 158 // 2 being deleted, one ready, target is 4, we add 3 more. 159 gsPendingDeletionWithState(agonesv1.GameServerStateUnhealthy), 160 gsPendingDeletionWithState(agonesv1.GameServerStateUnhealthy), 161 gsWithState(agonesv1.GameServerStateReady), 162 }, 163 targetReplicaCount: 4, 164 wantNumServersToAdd: 3, 165 }, 166 { 167 desc: "PendingDeletionsCountTowardsTargetReplicaCount", 168 list: []*agonesv1.GameServer{ 169 // 6 being deleted now, we want 10 but that would exceed in-flight limit by a lot. 170 gsWithState(agonesv1.GameServerStateCreating), 171 gsWithState(agonesv1.GameServerStatePortAllocation), 172 gsWithState(agonesv1.GameServerStateCreating), 173 gsWithState(agonesv1.GameServerStatePortAllocation), 174 gsWithState(agonesv1.GameServerStateCreating), 175 gsWithState(agonesv1.GameServerStatePortAllocation), 176 }, 177 targetReplicaCount: 10, 178 wantNumServersToAdd: 0, 179 wantIsPartial: true, 180 }, 181 { 182 desc: "DeletingUnhealthyGameServers", 183 list: []*agonesv1.GameServer{ 184 gsWithState(agonesv1.GameServerStateReady), 185 gsWithState(agonesv1.GameServerStateUnhealthy), 186 gsWithState(agonesv1.GameServerStateUnhealthy), 187 }, 188 targetReplicaCount: 3, 189 wantNumServersToAdd: 2, 190 wantNumServersToDelete: 2, 191 }, 192 { 193 desc: "DeletingErrorGameServers", 194 list: []*agonesv1.GameServer{ 195 gsWithState(agonesv1.GameServerStateReady), 196 gsWithState(agonesv1.GameServerStateError), 197 gsWithState(agonesv1.GameServerStateError), 198 }, 199 targetReplicaCount: 3, 200 wantNumServersToAdd: 2, 201 wantNumServersToDelete: 2, 202 }, 203 { 204 desc: "DeletingPartialGameServers", 205 list: []*agonesv1.GameServer{ 206 gsWithState(agonesv1.GameServerStateReady), 207 gsWithState(agonesv1.GameServerStateUnhealthy), 208 gsWithState(agonesv1.GameServerStateError), 209 gsWithState(agonesv1.GameServerStateUnhealthy), 210 gsWithState(agonesv1.GameServerStateError), 211 gsWithState(agonesv1.GameServerStateUnhealthy), 212 gsWithState(agonesv1.GameServerStateError), 213 }, 214 targetReplicaCount: 3, 215 wantNumServersToAdd: 2, 216 wantNumServersToDelete: 3, 217 wantIsPartial: true, 218 }, 219 } 220 221 for _, tc := range cases { 222 t.Run(tc.desc, func(t *testing.T) { 223 toAdd, toDelete, isPartial := computeReconciliationAction(apis.Distributed, tc.list, map[string]gameservers.NodeCount{}, 224 tc.targetReplicaCount, maxTestCreationsPerBatch, maxTestDeletionsPerBatch, maxTestPendingPerBatch, tc.priorities) 225 226 assert.Equal(t, tc.wantNumServersToAdd, toAdd, "# of GameServers to add") 227 assert.Len(t, toDelete, tc.wantNumServersToDelete, "# of GameServers to delete") 228 assert.Equal(t, tc.wantIsPartial, isPartial, "is partial reconciliation") 229 }) 230 } 231 232 t.Run("test packed scale down", func(t *testing.T) { 233 list := []*agonesv1.GameServer{ 234 {ObjectMeta: metav1.ObjectMeta{Name: "gs1"}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, NodeName: "node3"}}, 235 {ObjectMeta: metav1.ObjectMeta{Name: "gs2"}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, NodeName: "node1"}}, 236 {ObjectMeta: metav1.ObjectMeta{Name: "gs3"}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, NodeName: "node3"}}, 237 {ObjectMeta: metav1.ObjectMeta{Name: "gs4"}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, NodeName: ""}}, 238 } 239 240 counts := map[string]gameservers.NodeCount{"node1": {Ready: 1}, "node3": {Ready: 2}} 241 toAdd, toDelete, isPartial := computeReconciliationAction(apis.Packed, list, counts, 2, 242 1000, 1000, 1000, nil) 243 244 assert.Empty(t, toAdd) 245 assert.False(t, isPartial, "shouldn't be partial") 246 247 assert.Len(t, toDelete, 2) 248 assert.Equal(t, "gs4", toDelete[0].ObjectMeta.Name) 249 assert.Equal(t, "gs2", toDelete[1].ObjectMeta.Name) 250 }) 251 252 t.Run("test distributed scale down", func(t *testing.T) { 253 now := metav1.Now() 254 255 list := []*agonesv1.GameServer{ 256 {ObjectMeta: metav1.ObjectMeta{Name: "gs1", 257 CreationTimestamp: metav1.Time{Time: now.Add(10 * time.Second)}}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}}, 258 {ObjectMeta: metav1.ObjectMeta{Name: "gs2", 259 CreationTimestamp: now}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}}, 260 {ObjectMeta: metav1.ObjectMeta{Name: "gs3", 261 CreationTimestamp: metav1.Time{Time: now.Add(40 * time.Second)}}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}}, 262 {ObjectMeta: metav1.ObjectMeta{Name: "gs4", 263 CreationTimestamp: metav1.Time{Time: now.Add(30 * time.Second)}}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}}, 264 } 265 266 toAdd, toDelete, isPartial := computeReconciliationAction(apis.Distributed, list, map[string]gameservers.NodeCount{}, 267 2, 1000, 1000, 1000, nil) 268 269 assert.Empty(t, toAdd) 270 assert.False(t, isPartial, "shouldn't be partial") 271 272 assert.Len(t, toDelete, 2) 273 assert.Equal(t, "gs2", toDelete[0].ObjectMeta.Name) 274 assert.Equal(t, "gs1", toDelete[1].ObjectMeta.Name) 275 }) 276 } 277 278 func TestComputeStatus(t *testing.T) { 279 t.Parallel() 280 281 t.Run("compute status", func(t *testing.T) { 282 utilruntime.FeatureTestMutex.Lock() 283 defer utilruntime.FeatureTestMutex.Unlock() 284 285 require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=false", utilruntime.FeatureCountsAndLists))) 286 gsSet := defaultFixture() 287 cases := []struct { 288 list []*agonesv1.GameServer 289 wantStatus agonesv1.GameServerSetStatus 290 }{ 291 {[]*agonesv1.GameServer{}, agonesv1.GameServerSetStatus{}}, 292 {[]*agonesv1.GameServer{ 293 gsWithState(agonesv1.GameServerStateCreating), 294 gsWithState(agonesv1.GameServerStateReady), 295 }, agonesv1.GameServerSetStatus{ReadyReplicas: 1, Replicas: 2}}, 296 {[]*agonesv1.GameServer{ 297 gsWithState(agonesv1.GameServerStateAllocated), 298 gsWithState(agonesv1.GameServerStateAllocated), 299 gsWithState(agonesv1.GameServerStateCreating), 300 gsWithState(agonesv1.GameServerStateReady), 301 }, agonesv1.GameServerSetStatus{ReadyReplicas: 1, AllocatedReplicas: 2, Replicas: 4}}, 302 { 303 list: []*agonesv1.GameServer{ 304 gsWithState(agonesv1.GameServerStateReserved), 305 gsWithState(agonesv1.GameServerStateReserved), 306 gsWithState(agonesv1.GameServerStateReady), 307 }, 308 wantStatus: agonesv1.GameServerSetStatus{Replicas: 3, ReadyReplicas: 1, ReservedReplicas: 2}, 309 }, 310 } 311 312 for _, tc := range cases { 313 assert.Equal(t, tc.wantStatus, computeStatus(gsSet, tc.list)) 314 } 315 }) 316 317 t.Run("player tracking", func(t *testing.T) { 318 utilruntime.FeatureTestMutex.Lock() 319 defer utilruntime.FeatureTestMutex.Unlock() 320 321 require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeaturePlayerTracking))) 322 323 gsSet := defaultFixture() 324 var list []*agonesv1.GameServer 325 gs1 := gsWithState(agonesv1.GameServerStateAllocated) 326 gs1.Status.Players = &agonesv1.PlayerStatus{Count: 5, Capacity: 10} 327 gs2 := gsWithState(agonesv1.GameServerStateReserved) 328 gs2.Status.Players = &agonesv1.PlayerStatus{Count: 10, Capacity: 15} 329 gs3 := gsWithState(agonesv1.GameServerStateCreating) 330 gs3.Status.Players = &agonesv1.PlayerStatus{Count: 20, Capacity: 30} 331 gs4 := gsWithState(agonesv1.GameServerStateReady) 332 gs4.Status.Players = &agonesv1.PlayerStatus{Count: 15, Capacity: 30} 333 list = append(list, gs1, gs2, gs3, gs4) 334 335 expected := agonesv1.GameServerSetStatus{ 336 Replicas: 4, 337 ReadyReplicas: 1, 338 ReservedReplicas: 1, 339 AllocatedReplicas: 1, 340 Players: &agonesv1.AggregatedPlayerStatus{ 341 Count: 30, 342 Capacity: 55, 343 }, 344 Counters: map[string]agonesv1.AggregatedCounterStatus{}, 345 Lists: map[string]agonesv1.AggregatedListStatus{}, 346 } 347 348 assert.Equal(t, expected, computeStatus(gsSet, list)) 349 }) 350 351 t.Run("counters", func(t *testing.T) { 352 utilruntime.FeatureTestMutex.Lock() 353 defer utilruntime.FeatureTestMutex.Unlock() 354 355 require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists))) 356 357 gsSet := defaultFixture() 358 var list []*agonesv1.GameServer 359 gs1 := gsWithState(agonesv1.GameServerStateAllocated) 360 gs1.Status.Counters = map[string]agonesv1.CounterStatus{ 361 "firstCounter": {Count: 5, Capacity: 10}, 362 "secondCounter": {Count: 100, Capacity: 1000}, 363 "fullCounter": {Count: 9223372036854775807, Capacity: 9223372036854775807}, 364 } 365 gs2 := gsWithState(agonesv1.GameServerStateReserved) 366 gs2.Status.Counters = map[string]agonesv1.CounterStatus{ 367 "firstCounter": {Count: 10, Capacity: 15}, 368 "fullCounter": {Count: 9223372036854775807, Capacity: 9223372036854775807}, 369 } 370 gs3 := gsWithState(agonesv1.GameServerStateCreating) 371 gs3.Status.Counters = map[string]agonesv1.CounterStatus{ 372 "firstCounter": {Count: 20, Capacity: 30}, 373 "secondCounter": {Count: 100, Capacity: 1000}, 374 "fullCounter": {Count: 9223372036854775807, Capacity: 9223372036854775807}, 375 } 376 gs4 := gsWithState(agonesv1.GameServerStateReady) 377 gs4.Status.Counters = map[string]agonesv1.CounterStatus{ 378 "firstCounter": {Count: 15, Capacity: 30}, 379 "secondCounter": {Count: 20, Capacity: 200}, 380 "fullCounter": {Count: 9223372036854775807, Capacity: 9223372036854775807}, 381 } 382 list = append(list, gs1, gs2, gs3, gs4) 383 384 expected := agonesv1.GameServerSetStatus{ 385 Replicas: 4, 386 ReadyReplicas: 1, 387 ReservedReplicas: 1, 388 AllocatedReplicas: 1, 389 Counters: map[string]agonesv1.AggregatedCounterStatus{ 390 "firstCounter": { 391 AllocatedCount: 5, 392 AllocatedCapacity: 10, 393 Count: 50, 394 Capacity: 85, 395 }, 396 "secondCounter": { 397 AllocatedCount: 100, 398 AllocatedCapacity: 1000, 399 Count: 220, 400 Capacity: 2200, 401 }, 402 "fullCounter": { 403 AllocatedCount: 9223372036854775807, 404 AllocatedCapacity: 9223372036854775807, 405 Count: 9223372036854775807, 406 Capacity: 9223372036854775807, 407 }, 408 }, 409 Lists: map[string]agonesv1.AggregatedListStatus{}, 410 } 411 412 assert.Equal(t, expected, computeStatus(gsSet, list)) 413 }) 414 415 t.Run("counters with no gameservers", func(t *testing.T) { 416 utilruntime.FeatureTestMutex.Lock() 417 defer utilruntime.FeatureTestMutex.Unlock() 418 419 require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists))) 420 421 gsSet := defaultFixture() 422 gsSet.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{ 423 "firstCounter": {Capacity: 10, Count: 1}, 424 "secondCounter": {Capacity: 10, Count: 1}, 425 } 426 var list []*agonesv1.GameServer 427 428 expected := agonesv1.GameServerSetStatus{ 429 Replicas: 0, 430 ReadyReplicas: 0, 431 ReservedReplicas: 0, 432 AllocatedReplicas: 0, 433 Lists: map[string]agonesv1.AggregatedListStatus{}, 434 Counters: map[string]agonesv1.AggregatedCounterStatus{ 435 "firstCounter": { 436 AllocatedCount: 0, 437 AllocatedCapacity: 0, 438 Capacity: 0, 439 Count: 0, 440 }, 441 "secondCounter": { 442 AllocatedCount: 0, 443 AllocatedCapacity: 0, 444 Capacity: 0, 445 Count: 0, 446 }, 447 }, 448 } 449 450 assert.Equal(t, expected, computeStatus(gsSet, list)) 451 }) 452 453 t.Run("lists", func(t *testing.T) { 454 utilruntime.FeatureTestMutex.Lock() 455 defer utilruntime.FeatureTestMutex.Unlock() 456 457 require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists))) 458 459 gsSet := defaultFixture() 460 var list []*agonesv1.GameServer 461 gs1 := gsWithState(agonesv1.GameServerStateAllocated) 462 gs1.Status.Lists = map[string]agonesv1.ListStatus{ 463 "firstList": {Capacity: 10, Values: []string{"a", "b"}}, 464 "secondList": {Capacity: 1000, Values: []string{"1", "2"}}, 465 } 466 gs2 := gsWithState(agonesv1.GameServerStateReserved) 467 gs2.Status.Lists = map[string]agonesv1.ListStatus{ 468 "firstList": {Capacity: 15, Values: []string{"c"}}, 469 } 470 gs3 := gsWithState(agonesv1.GameServerStateCreating) 471 gs3.Status.Lists = map[string]agonesv1.ListStatus{ 472 "firstList": {Capacity: 30, Values: []string{"d"}}, 473 "secondList": {Capacity: 1000, Values: []string{"3"}}, 474 } 475 gs4 := gsWithState(agonesv1.GameServerStateReady) 476 gs4.Status.Lists = map[string]agonesv1.ListStatus{ 477 "firstList": {Capacity: 30}, 478 "secondList": {Capacity: 100, Values: []string{"4", "5", "6"}}, 479 } 480 list = append(list, gs1, gs2, gs3, gs4) 481 482 expected := agonesv1.GameServerSetStatus{ 483 Replicas: 4, 484 ReadyReplicas: 1, 485 ReservedReplicas: 1, 486 AllocatedReplicas: 1, 487 Counters: map[string]agonesv1.AggregatedCounterStatus{}, 488 Lists: map[string]agonesv1.AggregatedListStatus{ 489 "firstList": { 490 AllocatedCount: 2, 491 AllocatedCapacity: 10, 492 Capacity: 85, 493 Count: 4, 494 }, 495 "secondList": { 496 AllocatedCount: 2, 497 AllocatedCapacity: 1000, 498 Capacity: 2100, 499 Count: 6, 500 }, 501 }, 502 } 503 504 assert.Equal(t, expected, computeStatus(gsSet, list)) 505 }) 506 507 t.Run("lists with no gameservers", func(t *testing.T) { 508 utilruntime.FeatureTestMutex.Lock() 509 defer utilruntime.FeatureTestMutex.Unlock() 510 511 require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists))) 512 513 gsSet := defaultFixture() 514 gsSet.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{ 515 "firstList": {Capacity: 10, Values: []string{"a", "b"}}, 516 "secondList": {Capacity: 1000, Values: []string{"1", "2"}}, 517 } 518 var list []*agonesv1.GameServer 519 520 expected := agonesv1.GameServerSetStatus{ 521 Replicas: 0, 522 ReadyReplicas: 0, 523 ReservedReplicas: 0, 524 AllocatedReplicas: 0, 525 Counters: map[string]agonesv1.AggregatedCounterStatus{}, 526 Lists: map[string]agonesv1.AggregatedListStatus{ 527 "firstList": { 528 AllocatedCount: 0, 529 AllocatedCapacity: 0, 530 Capacity: 0, 531 Count: 0, 532 }, 533 "secondList": { 534 AllocatedCount: 0, 535 AllocatedCapacity: 0, 536 Capacity: 0, 537 Count: 0, 538 }, 539 }, 540 } 541 542 assert.Equal(t, expected, computeStatus(gsSet, list)) 543 }) 544 } 545 546 // Test that the aggregated Counters and Lists are removed from the Game Server Set status if the 547 // FeatureCountsAndLists flag is set to false. 548 func TestGameServerSetDropCountsAndListsStatus(t *testing.T) { 549 t.Parallel() 550 utilruntime.FeatureTestMutex.Lock() 551 defer utilruntime.FeatureTestMutex.Unlock() 552 553 gss := defaultFixture() 554 c, m := newFakeController() 555 556 list := createGameServers(gss, 2) 557 list[0].Status.Counters = map[string]agonesv1.CounterStatus{ 558 "firstCounter": {Count: 5, Capacity: 10}, 559 } 560 list[1].Status.Lists = map[string]agonesv1.ListStatus{ 561 "firstList": {Capacity: 100, Values: []string{"4", "5", "6"}}, 562 } 563 gsList := []*agonesv1.GameServer{&list[0], &list[1]} 564 565 expectedCounterStatus := map[string]agonesv1.AggregatedCounterStatus{ 566 "firstCounter": { 567 AllocatedCount: 0, 568 AllocatedCapacity: 0, 569 Capacity: 10, 570 Count: 5, 571 }, 572 } 573 expectedListStatus := map[string]agonesv1.AggregatedListStatus{ 574 "firstList": { 575 AllocatedCount: 0, 576 AllocatedCapacity: 0, 577 Capacity: 100, 578 Count: 3, 579 }, 580 } 581 582 flag := "" 583 updated := false 584 585 m.AgonesClient.AddReactor("update", "gameserversets", 586 func(action k8stesting.Action) (bool, runtime.Object, error) { 587 updated = true 588 ua := action.(k8stesting.UpdateAction) 589 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 590 591 switch flag { 592 case string(utilruntime.FeatureCountsAndLists) + "=true": 593 assert.Equal(t, expectedCounterStatus, gsSet.Status.Counters) 594 assert.Equal(t, expectedListStatus, gsSet.Status.Lists) 595 case string(utilruntime.FeatureCountsAndLists) + "=false": 596 assert.Nil(t, gsSet.Status.Counters) 597 assert.Nil(t, gsSet.Status.Lists) 598 default: 599 return false, nil, errors.Errorf("Flag string(utilruntime.FeatureCountsAndLists) should be set") 600 } 601 602 return true, gsSet, nil 603 }) 604 605 // Expect starting fleet to have Aggregated Counter and List Statuses 606 flag = string(utilruntime.FeatureCountsAndLists) + "=true" 607 require.NoError(t, utilruntime.ParseFeatures(flag)) 608 err := c.syncGameServerSetStatus(context.Background(), gss, gsList) 609 assert.Nil(t, err) 610 assert.True(t, updated) 611 612 updated = false 613 flag = string(utilruntime.FeatureCountsAndLists) + "=false" 614 require.NoError(t, utilruntime.ParseFeatures(flag)) 615 err = c.syncGameServerSetStatus(context.Background(), gss, gsList) 616 assert.Nil(t, err) 617 assert.True(t, updated) 618 619 updated = false 620 flag = string(utilruntime.FeatureCountsAndLists) + "=true" 621 require.NoError(t, utilruntime.ParseFeatures(flag)) 622 err = c.syncGameServerSetStatus(context.Background(), gss, gsList) 623 assert.Nil(t, err) 624 assert.True(t, updated) 625 } 626 627 func TestControllerWatchGameServers(t *testing.T) { 628 t.Parallel() 629 utilruntime.FeatureTestMutex.Lock() 630 defer utilruntime.FeatureTestMutex.Unlock() 631 632 gsSet := defaultFixture() 633 634 c, m := newFakeController() 635 636 received := make(chan string) 637 defer close(received) 638 639 m.ExtClient.AddReactor("get", "customresourcedefinitions", func(_ k8stesting.Action) (bool, runtime.Object, error) { 640 return true, agtesting.NewEstablishedCRD(), nil 641 }) 642 gsSetWatch := watch.NewFake() 643 m.AgonesClient.AddWatchReactor("gameserversets", k8stesting.DefaultWatchReactor(gsSetWatch, nil)) 644 gsWatch := watch.NewFake() 645 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) 646 647 c.workerqueue.SyncHandler = func(_ context.Context, name string) error { 648 received <- name 649 return nil 650 } 651 652 ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced) 653 defer cancel() 654 655 go func() { 656 err := c.Run(ctx, 1) 657 assert.Nil(t, err) 658 }() 659 660 f := func() string { 661 select { 662 case result := <-received: 663 return result 664 case <-time.After(3 * time.Second): 665 assert.FailNow(t, "timeout occurred") 666 } 667 return "" 668 } 669 670 expected, err := cache.MetaNamespaceKeyFunc(gsSet) 671 require.NoError(t, err) 672 673 // gsSet add 674 logrus.Info("adding gsSet") 675 gsSetWatch.Add(gsSet.DeepCopy()) 676 677 assert.Equal(t, expected, f()) 678 // gsSet update 679 logrus.Info("modify gsSet") 680 gsSetCopy := gsSet.DeepCopy() 681 gsSetCopy.Spec.Replicas = 5 682 gsSetWatch.Modify(gsSetCopy) 683 assert.Equal(t, expected, f()) 684 685 gs := gsSet.GameServer() 686 gs.ObjectMeta.Name = "test-gs" 687 // gs add 688 logrus.Info("add gs") 689 gsWatch.Add(gs.DeepCopy()) 690 assert.Equal(t, expected, f()) 691 692 // gs update 693 gsCopy := gs.DeepCopy() 694 now := metav1.Now() 695 gsCopy.ObjectMeta.DeletionTimestamp = &now 696 697 logrus.Info("modify gs - noop") 698 gsWatch.Modify(gsCopy.DeepCopy()) 699 select { 700 case <-received: 701 assert.Fail(t, "Should be no value") 702 case <-time.After(time.Second): 703 } 704 705 gsCopy = gs.DeepCopy() 706 gsCopy.Status.State = agonesv1.GameServerStateUnhealthy 707 logrus.Info("modify gs - unhealthy") 708 gsWatch.Modify(gsCopy.DeepCopy()) 709 assert.Equal(t, expected, f()) 710 711 // gs delete 712 logrus.Info("delete gs") 713 gsWatch.Delete(gsCopy.DeepCopy()) 714 assert.Equal(t, expected, f()) 715 } 716 717 func TestSyncGameServerSet(t *testing.T) { 718 t.Parallel() 719 720 t.Run("gameservers are not recreated when set is marked for deletion", func(t *testing.T) { 721 gsSet := defaultFixture() 722 gsSet.DeletionTimestamp = &metav1.Time{ 723 Time: time.Now(), 724 } 725 list := createGameServers(gsSet, 5) 726 727 // mark some as shutdown 728 list[0].Status.State = agonesv1.GameServerStateShutdown 729 list[1].Status.State = agonesv1.GameServerStateShutdown 730 731 c, m := newFakeController() 732 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 733 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 734 }) 735 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 736 return true, &agonesv1.GameServerList{Items: list}, nil 737 }) 738 m.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 739 assert.FailNow(t, "gameserver should not update") 740 return false, nil, nil 741 }) 742 m.AgonesClient.AddReactor("create", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 743 assert.FailNow(t, "new gameservers should not be created") 744 745 return false, nil, nil 746 }) 747 748 ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced) 749 defer cancel() 750 751 c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck 752 }) 753 754 t.Run("adding and deleting unhealthy gameservers", func(t *testing.T) { 755 gsSet := defaultFixture() 756 list := createGameServers(gsSet, 5) 757 758 // make some as unhealthy 759 list[0].Status.State = agonesv1.GameServerStateUnhealthy 760 761 updated := false 762 count := 0 763 764 c, m := newFakeController() 765 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 766 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 767 }) 768 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 769 return true, &agonesv1.GameServerList{Items: list}, nil 770 }) 771 772 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 773 ua := action.(k8stesting.UpdateAction) 774 gs := ua.GetObject().(*agonesv1.GameServer) 775 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown) 776 777 updated = true 778 assert.Equal(t, "test-0", gs.GetName()) 779 return true, nil, nil 780 }) 781 m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 782 ca := action.(k8stesting.CreateAction) 783 gs := ca.GetObject().(*agonesv1.GameServer) 784 785 assert.True(t, metav1.IsControlledBy(gs, gsSet)) 786 count++ 787 return true, gs, nil 788 }) 789 790 ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced) 791 defer cancel() 792 793 c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck 794 795 assert.Equal(t, 6, count) 796 assert.True(t, updated, "A game servers should have been updated") 797 }) 798 799 t.Run("adding and deleting errored gameservers", func(t *testing.T) { 800 gsSet := defaultFixture() 801 list := createGameServers(gsSet, 5) 802 803 // make some as unhealthy 804 list[0].Annotations = map[string]string{agonesv1.GameServerErroredAtAnnotation: time.Now().Add(-30 * time.Second).UTC().Format(time.RFC3339)} 805 list[0].Status.State = agonesv1.GameServerStateError 806 807 updated := false 808 count := 0 809 810 c, m := newFakeController() 811 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 812 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 813 }) 814 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 815 return true, &agonesv1.GameServerList{Items: list}, nil 816 }) 817 818 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 819 ua := action.(k8stesting.UpdateAction) 820 gs := ua.GetObject().(*agonesv1.GameServer) 821 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown) 822 823 updated = true 824 assert.Equal(t, "test-0", gs.GetName()) 825 return true, nil, nil 826 }) 827 m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 828 ca := action.(k8stesting.CreateAction) 829 gs := ca.GetObject().(*agonesv1.GameServer) 830 831 assert.True(t, metav1.IsControlledBy(gs, gsSet)) 832 count++ 833 return true, gs, nil 834 }) 835 836 ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced) 837 defer cancel() 838 839 c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck 840 841 assert.Equal(t, 6, count) 842 assert.True(t, updated, "A game servers should have been updated") 843 }) 844 845 t.Run("adding and delay deleting errored gameservers", func(t *testing.T) { 846 gsSet := defaultFixture() 847 list := createGameServers(gsSet, 5) 848 849 // make some as unhealthy 850 list[0].Annotations = map[string]string{agonesv1.GameServerErroredAtAnnotation: time.Now().UTC().Format(time.RFC3339)} 851 list[0].Status.State = agonesv1.GameServerStateError 852 853 updated := false 854 count := 0 855 856 c, m := newFakeController() 857 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 858 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 859 }) 860 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 861 return true, &agonesv1.GameServerList{Items: list}, nil 862 }) 863 864 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 865 ua := action.(k8stesting.UpdateAction) 866 gs := ua.GetObject().(*agonesv1.GameServer) 867 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown) 868 869 updated = true 870 assert.Equal(t, "test-0", gs.GetName()) 871 return true, nil, nil 872 }) 873 m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 874 ca := action.(k8stesting.CreateAction) 875 gs := ca.GetObject().(*agonesv1.GameServer) 876 877 assert.True(t, metav1.IsControlledBy(gs, gsSet)) 878 count++ 879 return true, gs, nil 880 }) 881 882 ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced) 883 defer cancel() 884 885 c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck 886 887 assert.Equal(t, 5, count) 888 assert.False(t, updated, "A game servers should not have been updated") 889 }) 890 891 t.Run("removing gamservers", func(t *testing.T) { 892 gsSet := defaultFixture() 893 list := createGameServers(gsSet, 15) 894 count := 0 895 896 c, m := newFakeController() 897 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 898 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 899 }) 900 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 901 return true, &agonesv1.GameServerList{Items: list}, nil 902 }) 903 m.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 904 count++ 905 return true, nil, nil 906 }) 907 908 ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced) 909 defer cancel() 910 911 c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck 912 913 assert.Equal(t, 5, count) 914 }) 915 916 t.Run("Starting GameServers get deleted first", func(t *testing.T) { 917 gsSet := defaultFixture() 918 list := createGameServers(gsSet, 12) 919 920 list[0].Status.State = agonesv1.GameServerStateStarting 921 list[1].Status.State = agonesv1.GameServerStateCreating 922 923 rand.Shuffle(len(list), func(i, j int) { 924 list[i], list[j] = list[j], list[i] 925 }) 926 927 var deleted []string 928 929 c, m := newFakeController() 930 m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) { 931 return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil 932 }) 933 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 934 return true, &agonesv1.GameServerList{Items: list}, nil 935 }) 936 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 937 ua := action.(k8stesting.UpdateAction) 938 gs := ua.GetObject().(*agonesv1.GameServer) 939 require.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown) 940 941 deleted = append(deleted, gs.ObjectMeta.Name) 942 return true, nil, nil 943 }) 944 945 ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced) 946 defer cancel() 947 require.NoError(t, c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name)) 948 949 require.Len(t, deleted, 2) 950 require.ElementsMatchf(t, []string{"test-0", "test-1"}, deleted, "should be the non-ready GameServers") 951 }) 952 } 953 954 func TestControllerSyncUnhealthyGameServers(t *testing.T) { 955 t.Parallel() 956 957 gsSet := defaultFixture() 958 959 gs1 := gsSet.GameServer() 960 gs1.ObjectMeta.Name = "test-1" 961 gs1.Status = agonesv1.GameServerStatus{State: agonesv1.GameServerStateUnhealthy} 962 963 gs2 := gsSet.GameServer() 964 gs2.ObjectMeta.Name = "test-2" 965 gs2.Status = agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady} 966 967 gs3 := gsSet.GameServer() 968 gs3.ObjectMeta.Name = "test-3" 969 now := metav1.Now() 970 gs3.ObjectMeta.DeletionTimestamp = &now 971 gs3.Status = agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady} 972 973 t.Run("valid case", func(t *testing.T) { 974 var updatedCount int 975 c, m := newFakeController() 976 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 977 ua := action.(k8stesting.UpdateAction) 978 gs := ua.GetObject().(*agonesv1.GameServer) 979 980 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown) 981 982 updatedCount++ 983 return true, nil, nil 984 }) 985 986 ctx, cancel := agtesting.StartInformers(m) 987 defer cancel() 988 989 err := c.deleteGameServers(ctx, gsSet, []*agonesv1.GameServer{gs1, gs2, gs3}) 990 assert.Nil(t, err) 991 992 assert.Equal(t, 3, updatedCount, "Updates should have occurred") 993 }) 994 995 t.Run("error on update step", func(t *testing.T) { 996 c, m := newFakeController() 997 m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 998 ua := action.(k8stesting.UpdateAction) 999 gs := ua.GetObject().(*agonesv1.GameServer) 1000 1001 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown) 1002 1003 return true, nil, errors.New("update-err") 1004 }) 1005 1006 ctx, cancel := agtesting.StartInformers(m) 1007 defer cancel() 1008 1009 err := c.deleteGameServers(ctx, gsSet, []*agonesv1.GameServer{gs1, gs2, gs3}) 1010 require.Error(t, err) 1011 assert.Contains(t, err.Error(), "error updating gameserver") 1012 }) 1013 } 1014 1015 func TestSyncMoreGameServers(t *testing.T) { 1016 t.Parallel() 1017 gsSet := defaultFixture() 1018 1019 t.Run("valid case", func(t *testing.T) { 1020 1021 c, m := newFakeController() 1022 expected := 10 1023 count := 0 1024 1025 m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1026 ca := action.(k8stesting.CreateAction) 1027 gs := ca.GetObject().(*agonesv1.GameServer) 1028 1029 assert.True(t, metav1.IsControlledBy(gs, gsSet)) 1030 count++ 1031 1032 return true, gs, nil 1033 }) 1034 1035 ctx, cancel := agtesting.StartInformers(m) 1036 defer cancel() 1037 1038 err := c.addMoreGameServers(ctx, gsSet, expected) 1039 assert.Nil(t, err) 1040 assert.Equal(t, expected, count) 1041 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "SuccessfulCreate") 1042 }) 1043 1044 t.Run("error on create step", func(t *testing.T) { 1045 gsSet := defaultFixture() 1046 c, m := newFakeController() 1047 expected := 10 1048 m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1049 ca := action.(k8stesting.CreateAction) 1050 gs := ca.GetObject().(*agonesv1.GameServer) 1051 1052 assert.True(t, metav1.IsControlledBy(gs, gsSet)) 1053 1054 return true, gs, errors.New("create-err") 1055 }) 1056 1057 ctx, cancel := agtesting.StartInformers(m) 1058 defer cancel() 1059 1060 err := c.addMoreGameServers(ctx, gsSet, expected) 1061 require.Error(t, err) 1062 assert.Equal(t, "error creating gameserver for gameserverset test: create-err", err.Error()) 1063 }) 1064 } 1065 1066 func TestControllerSyncGameServerSetStatus(t *testing.T) { 1067 t.Parallel() 1068 1069 t.Run("all ready list", func(t *testing.T) { 1070 gsSet := defaultFixture() 1071 c, m := newFakeController() 1072 1073 updated := false 1074 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1075 updated = true 1076 ua := action.(k8stesting.UpdateAction) 1077 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 1078 1079 assert.Equal(t, int32(1), gsSet.Status.Replicas) 1080 assert.Equal(t, int32(1), gsSet.Status.ReadyReplicas) 1081 assert.Equal(t, int32(0), gsSet.Status.AllocatedReplicas) 1082 1083 return true, nil, nil 1084 }) 1085 1086 list := []*agonesv1.GameServer{{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}}} 1087 err := c.syncGameServerSetStatus(context.Background(), gsSet, list) 1088 assert.Nil(t, err) 1089 assert.True(t, updated) 1090 }) 1091 1092 t.Run("only some ready list", func(t *testing.T) { 1093 gsSet := defaultFixture() 1094 c, m := newFakeController() 1095 1096 updated := false 1097 m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { 1098 updated = true 1099 ua := action.(k8stesting.UpdateAction) 1100 gsSet := ua.GetObject().(*agonesv1.GameServerSet) 1101 1102 assert.Equal(t, int32(8), gsSet.Status.Replicas) 1103 assert.Equal(t, int32(1), gsSet.Status.ReadyReplicas) 1104 assert.Equal(t, int32(2), gsSet.Status.AllocatedReplicas) 1105 1106 return true, nil, nil 1107 }) 1108 1109 list := []*agonesv1.GameServer{ 1110 {Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}}, 1111 {Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateStarting}}, 1112 {Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateUnhealthy}}, 1113 {Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStatePortAllocation}}, 1114 {Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateError}}, 1115 {Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}, 1116 {Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateAllocated}}, 1117 {Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateAllocated}}, 1118 } 1119 err := c.syncGameServerSetStatus(context.Background(), gsSet, list) 1120 assert.Nil(t, err) 1121 assert.True(t, updated) 1122 }) 1123 } 1124 1125 func TestControllerUpdateValidationHandler(t *testing.T) { 1126 t.Parallel() 1127 1128 ext := newFakeExtensions() 1129 gvk := metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("GameServerSet")) 1130 fixture := &agonesv1.GameServerSet{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}, 1131 Spec: agonesv1.GameServerSetSpec{Replicas: 5}, 1132 } 1133 raw, err := json.Marshal(fixture) 1134 require.NoError(t, err) 1135 1136 t.Run("valid gameserverset update", func(t *testing.T) { 1137 newGSS := fixture.DeepCopy() 1138 newGSS.Spec.Replicas = 10 1139 newRaw, err := json.Marshal(newGSS) 1140 require.NoError(t, err) 1141 1142 review := admissionv1.AdmissionReview{ 1143 Request: &admissionv1.AdmissionRequest{ 1144 Kind: gvk, 1145 Operation: admissionv1.Create, 1146 Object: runtime.RawExtension{ 1147 Raw: newRaw, 1148 }, 1149 OldObject: runtime.RawExtension{ 1150 Raw: raw, 1151 }, 1152 }, 1153 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1154 } 1155 1156 result, err := ext.updateValidationHandler(review) 1157 require.NoError(t, err) 1158 if !assert.True(t, result.Response.Allowed) { 1159 // show the reason of the failure 1160 require.NotNil(t, result.Response.Result) 1161 require.NotNil(t, result.Response.Result.Details) 1162 require.NotEmpty(t, result.Response.Result.Details.Causes) 1163 } 1164 }) 1165 1166 t.Run("new object is nil, err excpected", func(t *testing.T) { 1167 review := admissionv1.AdmissionReview{ 1168 Request: &admissionv1.AdmissionRequest{ 1169 Kind: gvk, 1170 Operation: admissionv1.Create, 1171 Object: runtime.RawExtension{ 1172 Raw: nil, 1173 }, 1174 OldObject: runtime.RawExtension{ 1175 Raw: raw, 1176 }, 1177 }, 1178 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1179 } 1180 1181 _, err := ext.updateValidationHandler(review) 1182 require.Error(t, err) 1183 assert.Equal(t, "error unmarshalling new GameServerSet json: : unexpected end of JSON input", err.Error()) 1184 }) 1185 1186 t.Run("old object is nil, err excpected", func(t *testing.T) { 1187 newGSS := fixture.DeepCopy() 1188 newGSS.Spec.Replicas = 10 1189 newRaw, err := json.Marshal(newGSS) 1190 require.NoError(t, err) 1191 1192 review := admissionv1.AdmissionReview{ 1193 Request: &admissionv1.AdmissionRequest{ 1194 Kind: gvk, 1195 Operation: admissionv1.Create, 1196 Object: runtime.RawExtension{ 1197 Raw: newRaw, 1198 }, 1199 OldObject: runtime.RawExtension{ 1200 Raw: nil, 1201 }, 1202 }, 1203 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1204 } 1205 1206 _, err = ext.updateValidationHandler(review) 1207 require.Error(t, err) 1208 assert.Equal(t, "error unmarshalling old GameServerSet json: : unexpected end of JSON input", err.Error()) 1209 }) 1210 1211 t.Run("invalid gameserverset update", func(t *testing.T) { 1212 newGSS := fixture.DeepCopy() 1213 newGSS.Spec.Template = agonesv1.GameServerTemplateSpec{ 1214 Spec: agonesv1.GameServerSpec{ 1215 Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Static}}, 1216 }, 1217 } 1218 newRaw, err := json.Marshal(newGSS) 1219 require.NoError(t, err) 1220 1221 assert.NotEqual(t, string(raw), string(newRaw)) 1222 1223 review := admissionv1.AdmissionReview{ 1224 Request: &admissionv1.AdmissionRequest{ 1225 Kind: gvk, 1226 Operation: admissionv1.Create, 1227 Object: runtime.RawExtension{ 1228 Raw: newRaw, 1229 }, 1230 OldObject: runtime.RawExtension{ 1231 Raw: raw, 1232 }, 1233 }, 1234 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1235 } 1236 1237 result, err := ext.updateValidationHandler(review) 1238 require.NoError(t, err) 1239 require.NotNil(t, result.Response) 1240 require.NotNil(t, result.Response.Result) 1241 require.NotNil(t, result.Response.Result.Details) 1242 assert.False(t, result.Response.Allowed) 1243 assert.NotEmpty(t, result.Response.Result.Details.Causes) 1244 assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status) 1245 assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason) 1246 assert.Contains(t, result.Response.Result.Message, "GameServerSet.agones.dev \"\" is invalid") 1247 }) 1248 } 1249 1250 func TestCreationValidationHandler(t *testing.T) { 1251 t.Parallel() 1252 1253 ext := newFakeExtensions() 1254 1255 gvk := metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("GameServerSet")) 1256 fixture := &agonesv1.GameServerSet{ObjectMeta: metav1.ObjectMeta{Name: "c1", Namespace: "default"}, 1257 Spec: agonesv1.GameServerSetSpec{ 1258 Replicas: 5, 1259 Template: agonesv1.GameServerTemplateSpec{ 1260 Spec: agonesv1.GameServerSpec{Container: "test", 1261 Template: corev1.PodTemplateSpec{ 1262 Spec: corev1.PodSpec{ 1263 Containers: []corev1.Container{{Name: "c1"}}, 1264 }, 1265 }, 1266 }, 1267 }, 1268 }, 1269 } 1270 raw, err := json.Marshal(fixture) 1271 require.NoError(t, err) 1272 1273 t.Run("valid gameserverset create", func(t *testing.T) { 1274 newGSS := fixture.DeepCopy() 1275 newGSS.Spec.Replicas = 10 1276 newRaw, err := json.Marshal(newGSS) 1277 require.NoError(t, err) 1278 1279 review := admissionv1.AdmissionReview{ 1280 Request: &admissionv1.AdmissionRequest{ 1281 Kind: gvk, 1282 Operation: admissionv1.Create, 1283 Object: runtime.RawExtension{ 1284 Raw: newRaw, 1285 }, 1286 }, 1287 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1288 } 1289 1290 result, err := ext.creationValidationHandler(review) 1291 require.NoError(t, err) 1292 if !assert.True(t, result.Response.Allowed) { 1293 // show the reason of the failure 1294 require.NotNil(t, result.Response.Result) 1295 require.NotNil(t, result.Response.Result.Details) 1296 require.NotEmpty(t, result.Response.Result.Details.Causes) 1297 } 1298 }) 1299 1300 t.Run("object is nil, err excpected", func(t *testing.T) { 1301 review := admissionv1.AdmissionReview{ 1302 Request: &admissionv1.AdmissionRequest{ 1303 Kind: gvk, 1304 Operation: admissionv1.Create, 1305 Object: runtime.RawExtension{ 1306 Raw: nil, 1307 }, 1308 }, 1309 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1310 } 1311 1312 _, err := ext.creationValidationHandler(review) 1313 require.Error(t, err) 1314 assert.Equal(t, "error unmarshalling GameServerSet json after schema validation: : unexpected end of JSON input", err.Error()) 1315 }) 1316 1317 t.Run("invalid gameserverset create", func(t *testing.T) { 1318 newGSS := fixture.DeepCopy() 1319 newGSS.Spec.Template = agonesv1.GameServerTemplateSpec{ 1320 Spec: agonesv1.GameServerSpec{ 1321 Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Static}}, 1322 }, 1323 } 1324 newRaw, err := json.Marshal(newGSS) 1325 require.NoError(t, err) 1326 1327 assert.NotEqual(t, string(raw), string(newRaw)) 1328 1329 review := admissionv1.AdmissionReview{ 1330 Request: &admissionv1.AdmissionRequest{ 1331 Kind: gvk, 1332 Operation: admissionv1.Create, 1333 Object: runtime.RawExtension{ 1334 Raw: newRaw, 1335 }, 1336 }, 1337 Response: &admissionv1.AdmissionResponse{Allowed: true}, 1338 } 1339 1340 result, err := ext.creationValidationHandler(review) 1341 require.NoError(t, err) 1342 require.NotNil(t, result.Response) 1343 require.NotNil(t, result.Response.Result) 1344 require.NotNil(t, result.Response.Result.Details) 1345 assert.False(t, result.Response.Allowed) 1346 assert.NotEmpty(t, result.Response.Result.Details.Causes) 1347 assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status) 1348 assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason) 1349 assert.Contains(t, result.Response.Result.Message, "GameServerSet.agones.dev \"\" is invalid") 1350 }) 1351 } 1352 1353 // defaultFixture creates the default GameServerSet fixture 1354 func defaultFixture() *agonesv1.GameServerSet { 1355 gsSet := &agonesv1.GameServerSet{ 1356 ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "test", UID: "1234"}, 1357 Spec: agonesv1.GameServerSetSpec{ 1358 Replicas: 10, 1359 Scheduling: apis.Packed, 1360 Template: agonesv1.GameServerTemplateSpec{}, 1361 }, 1362 } 1363 return gsSet 1364 } 1365 1366 // createGameServers create an array of GameServers from the GameServerSet 1367 func createGameServers(gsSet *agonesv1.GameServerSet, size int) []agonesv1.GameServer { 1368 var list []agonesv1.GameServer 1369 for i := 0; i < size; i++ { 1370 gs := gsSet.GameServer() 1371 gs.Name = gs.GenerateName + strconv.Itoa(i) 1372 gs.Status = agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady} 1373 list = append(list, *gs) 1374 } 1375 return list 1376 } 1377 1378 // newFakeController returns a controller, backed by the fake Clientset 1379 func newFakeController() (*Controller, agtesting.Mocks) { 1380 m := agtesting.NewMocks() 1381 counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory) 1382 c := NewController(healthcheck.NewHandler(), counter, m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory, 16, 64, 64, 64, 5000) 1383 c.recorder = m.FakeRecorder 1384 return c, m 1385 } 1386 1387 // newFakeExtensions returns an extensions struct 1388 func newFakeExtensions() *Extensions { 1389 return NewExtensions(generic.New(), webhooks.NewWebHook(http.NewServeMux())) 1390 }