agones.dev/agones@v1.54.0/pkg/sdkserver/sdkserver_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 sdkserver 16 17 import ( 18 "context" 19 "encoding/json" 20 "net/http" 21 "strconv" 22 "sync" 23 "testing" 24 "time" 25 26 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 27 "agones.dev/agones/pkg/gameserverallocations" 28 "agones.dev/agones/pkg/sdk" 29 "agones.dev/agones/pkg/sdk/alpha" 30 "agones.dev/agones/pkg/sdk/beta" 31 agtesting "agones.dev/agones/pkg/testing" 32 agruntime "agones.dev/agones/pkg/util/runtime" 33 jsonpatch "github.com/evanphx/json-patch" 34 "github.com/google/go-cmp/cmp" 35 "github.com/sirupsen/logrus" 36 "github.com/stretchr/testify/assert" 37 "github.com/stretchr/testify/require" 38 "google.golang.org/protobuf/types/known/fieldmaskpb" 39 "google.golang.org/protobuf/types/known/wrapperspb" 40 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 41 "k8s.io/apimachinery/pkg/runtime" 42 "k8s.io/apimachinery/pkg/util/wait" 43 "k8s.io/apimachinery/pkg/watch" 44 k8stesting "k8s.io/client-go/testing" 45 "k8s.io/client-go/tools/cache" 46 "k8s.io/utils/clock" 47 testclocks "k8s.io/utils/clock/testing" 48 ) 49 50 // patchGameServer is a helper function for the AddReactor "patch" that creates and applies a patch 51 // to a gameserver. Returns a patched copy and does not modify the original game server. 52 func patchGameServer(t *testing.T, action k8stesting.Action, gs *agonesv1.GameServer) *agonesv1.GameServer { 53 pa := action.(k8stesting.PatchAction) 54 patchJSON := pa.GetPatch() 55 patch, err := jsonpatch.DecodePatch(patchJSON) 56 assert.NoError(t, err) 57 gsCopy := gs.DeepCopy() 58 gsJSON, err := json.Marshal(gsCopy) 59 assert.NoError(t, err) 60 patchedGs, err := patch.Apply(gsJSON) 61 assert.NoError(t, err) 62 err = json.Unmarshal(patchedGs, &gsCopy) 63 assert.NoError(t, err) 64 65 return gsCopy 66 } 67 68 func TestSidecarRun(t *testing.T) { 69 t.Parallel() 70 71 now := time.Now().UTC() 72 nowTs, err := now.MarshalText() 73 require.NoError(t, err) 74 75 type expected struct { 76 state agonesv1.GameServerState 77 labels map[string]string 78 annotations map[string]string 79 recordings []string 80 } 81 82 fixtures := map[string]struct { 83 f func(*SDKServer, context.Context) 84 clock clock.WithTickerAndDelayedExecution 85 expected expected 86 }{ 87 "ready": { 88 f: func(sc *SDKServer, ctx context.Context) { 89 sc.Ready(ctx, &sdk.Empty{}) // nolint: errcheck 90 }, 91 expected: expected{ 92 state: agonesv1.GameServerStateRequestReady, 93 recordings: []string{"Normal " + string(agonesv1.GameServerStateRequestReady)}, 94 }, 95 }, 96 "shutdown": { 97 f: func(sc *SDKServer, ctx context.Context) { 98 sc.Shutdown(ctx, &sdk.Empty{}) // nolint: errcheck 99 }, 100 expected: expected{ 101 state: agonesv1.GameServerStateShutdown, 102 recordings: []string{"Normal " + string(agonesv1.GameServerStateShutdown)}, 103 }, 104 }, 105 "unhealthy": { 106 f: func(sc *SDKServer, _ context.Context) { 107 time.Sleep(1 * time.Second) 108 sc.checkHealthUpdateState() // normally invoked from health check loop 109 time.Sleep(2 * time.Second) // exceed 1s timeout 110 sc.checkHealthUpdateState() // normally invoked from health check loop 111 }, 112 expected: expected{ 113 state: agonesv1.GameServerStateUnhealthy, 114 recordings: []string{"Warning " + string(agonesv1.GameServerStateUnhealthy)}, 115 }, 116 }, 117 "label": { 118 f: func(sc *SDKServer, ctx context.Context) { 119 _, err := sc.SetLabel(ctx, &sdk.KeyValue{Key: "foo", Value: "value-foo"}) 120 assert.Nil(t, err) 121 _, err = sc.SetLabel(ctx, &sdk.KeyValue{Key: "bar", Value: "value-bar"}) 122 assert.Nil(t, err) 123 }, 124 expected: expected{ 125 labels: map[string]string{ 126 metadataPrefix + "foo": "value-foo", 127 metadataPrefix + "bar": "value-bar"}, 128 }, 129 }, 130 "annotation": { 131 f: func(sc *SDKServer, ctx context.Context) { 132 _, err := sc.SetAnnotation(ctx, &sdk.KeyValue{Key: "test-1", Value: "annotation-1"}) 133 assert.Nil(t, err) 134 _, err = sc.SetAnnotation(ctx, &sdk.KeyValue{Key: "test-2", Value: "annotation-2"}) 135 assert.Nil(t, err) 136 }, 137 expected: expected{ 138 annotations: map[string]string{ 139 metadataPrefix + "test-1": "annotation-1", 140 metadataPrefix + "test-2": "annotation-2"}, 141 }, 142 }, 143 "allocated": { 144 f: func(sc *SDKServer, ctx context.Context) { 145 _, err := sc.Allocate(ctx, &sdk.Empty{}) 146 assert.NoError(t, err) 147 }, 148 clock: testclocks.NewFakeClock(now), 149 expected: expected{ 150 state: agonesv1.GameServerStateAllocated, 151 recordings: []string{string(agonesv1.GameServerStateAllocated)}, 152 annotations: map[string]string{ 153 gameserverallocations.LastAllocatedAnnotationKey: string(nowTs), 154 }, 155 }, 156 }, 157 "reserved": { 158 f: func(sc *SDKServer, ctx context.Context) { 159 _, err := sc.Reserve(ctx, &sdk.Duration{Seconds: 10}) 160 assert.NoError(t, err) 161 }, 162 expected: expected{ 163 state: agonesv1.GameServerStateReserved, 164 recordings: []string{string(agonesv1.GameServerStateReserved)}, 165 }, 166 }, 167 } 168 169 for k, v := range fixtures { 170 t.Run(k, func(t *testing.T) { 171 m := agtesting.NewMocks() 172 done := make(chan bool) 173 174 gs := agonesv1.GameServer{ 175 ObjectMeta: metav1.ObjectMeta{ 176 Name: "test", Namespace: "default", ResourceVersion: "0", 177 }, 178 Spec: agonesv1.GameServerSpec{ 179 Health: agonesv1.Health{Disabled: false, FailureThreshold: 1, PeriodSeconds: 1, InitialDelaySeconds: 0}, 180 }, 181 Status: agonesv1.GameServerStatus{ 182 State: agonesv1.GameServerStateStarting, 183 }, 184 } 185 gs.ApplyDefaults() 186 187 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 188 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 189 }) 190 191 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 192 defer close(done) 193 194 gsCopy := patchGameServer(t, action, &gs) 195 196 if v.expected.state != "" { 197 assert.Equal(t, v.expected.state, gsCopy.Status.State) 198 } 199 for label, value := range v.expected.labels { 200 assert.Equal(t, value, gsCopy.ObjectMeta.Labels[label]) 201 } 202 for ann, value := range v.expected.annotations { 203 assert.Equal(t, value, gsCopy.ObjectMeta.Annotations[ann]) 204 } 205 return true, gsCopy, nil 206 }) 207 208 sc, err := NewSDKServer("test", "default", m.KubeClient, m.AgonesClient, logrus.DebugLevel, 8080, 500*time.Millisecond) 209 stop := make(chan struct{}) 210 defer close(stop) 211 ctx, cancel := context.WithCancel(context.Background()) 212 defer cancel() 213 214 assert.NoError(t, sc.WaitForConnection(ctx)) 215 sc.informerFactory.Start(stop) 216 assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced)) 217 218 assert.Nil(t, err) 219 sc.recorder = m.FakeRecorder 220 if v.clock != nil { 221 sc.clock = v.clock 222 } 223 224 wg := sync.WaitGroup{} 225 wg.Add(1) 226 227 go func() { 228 err := sc.Run(ctx) 229 assert.Nil(t, err) 230 wg.Done() 231 }() 232 v.f(sc, ctx) 233 234 select { 235 case <-done: 236 case <-time.After(10 * time.Second): 237 assert.Fail(t, "Timeout on Run") 238 } 239 240 logrus.Info("attempting to find event recording") 241 for _, str := range v.expected.recordings { 242 agtesting.AssertEventContains(t, m.FakeRecorder.Events, str) 243 } 244 245 cancel() 246 wg.Wait() 247 }) 248 } 249 } 250 251 func TestSDKServerSyncGameServer(t *testing.T) { 252 t.Parallel() 253 254 type expected struct { 255 state agonesv1.GameServerState 256 labels map[string]string 257 annotations map[string]string 258 } 259 260 type scData struct { 261 gsState agonesv1.GameServerState 262 gsLabels map[string]string 263 gsAnnotations map[string]string 264 } 265 266 fixtures := map[string]struct { 267 expected expected 268 key string 269 scData scData 270 }{ 271 "ready": { 272 key: string(updateState), 273 scData: scData{ 274 gsState: agonesv1.GameServerStateReady, 275 }, 276 expected: expected{ 277 state: agonesv1.GameServerStateReady, 278 }, 279 }, 280 "label": { 281 key: string(updateLabel), 282 scData: scData{ 283 gsLabels: map[string]string{"foo": "bar"}, 284 }, 285 expected: expected{ 286 labels: map[string]string{metadataPrefix + "foo": "bar"}, 287 }, 288 }, 289 "annotation": { 290 key: string(updateAnnotation), 291 scData: scData{ 292 gsAnnotations: map[string]string{"test": "annotation"}, 293 }, 294 expected: expected{ 295 annotations: map[string]string{metadataPrefix + "test": "annotation"}, 296 }, 297 }, 298 } 299 300 for k, v := range fixtures { 301 t.Run(k, func(t *testing.T) { 302 m := agtesting.NewMocks() 303 sc, err := defaultSidecar(m) 304 assert.Nil(t, err) 305 306 sc.gsState = v.scData.gsState 307 sc.gsLabels = v.scData.gsLabels 308 sc.gsAnnotations = v.scData.gsAnnotations 309 310 updated := false 311 gs := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{ 312 UID: "1234", 313 Name: sc.gameServerName, Namespace: sc.namespace, ResourceVersion: "0", 314 Labels: map[string]string{}, Annotations: map[string]string{}}, 315 } 316 317 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 318 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 319 }) 320 321 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 322 gsCopy := patchGameServer(t, action, &gs) 323 324 if v.expected.state != "" { 325 assert.Equal(t, v.expected.state, gsCopy.Status.State) 326 } 327 for label, value := range v.expected.labels { 328 assert.Equal(t, value, gsCopy.ObjectMeta.Labels[label]) 329 } 330 for ann, value := range v.expected.annotations { 331 assert.Equal(t, value, gsCopy.ObjectMeta.Annotations[ann]) 332 } 333 updated = true 334 return false, gsCopy, nil 335 }) 336 337 ctx, cancel := context.WithCancel(context.Background()) 338 defer cancel() 339 sc.informerFactory.Start(ctx.Done()) 340 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 341 sc.gsWaitForSync.Done() 342 343 err = sc.syncGameServer(ctx, v.key) 344 assert.Nil(t, err) 345 assert.True(t, updated, "should have updated") 346 }) 347 } 348 } 349 350 func TestSidecarUpdateState(t *testing.T) { 351 t.Parallel() 352 353 fixtures := map[string]struct { 354 f func(gs *agonesv1.GameServer) 355 }{ 356 "unhealthy": { 357 f: func(gs *agonesv1.GameServer) { 358 gs.Status.State = agonesv1.GameServerStateUnhealthy 359 }, 360 }, 361 "shutdown": { 362 f: func(gs *agonesv1.GameServer) { 363 gs.Status.State = agonesv1.GameServerStateShutdown 364 }, 365 }, 366 "deleted": { 367 f: func(gs *agonesv1.GameServer) { 368 now := metav1.Now() 369 gs.ObjectMeta.DeletionTimestamp = &now 370 }, 371 }, 372 } 373 374 for k, v := range fixtures { 375 t.Run(k, func(t *testing.T) { 376 m := agtesting.NewMocks() 377 sc, err := defaultSidecar(m) 378 require.NoError(t, err) 379 sc.gsState = agonesv1.GameServerStateReady 380 381 updated := false 382 383 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 384 gs := agonesv1.GameServer{ 385 ObjectMeta: metav1.ObjectMeta{Name: sc.gameServerName, Namespace: sc.namespace, ResourceVersion: "0"}, 386 Status: agonesv1.GameServerStatus{}, 387 } 388 389 // apply mutation 390 v.f(&gs) 391 392 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{gs}}, nil 393 }) 394 m.AgonesClient.AddReactor("patch", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 395 updated = true 396 return true, nil, nil 397 }) 398 399 ctx, cancel := context.WithCancel(context.Background()) 400 defer cancel() 401 sc.informerFactory.Start(ctx.Done()) 402 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 403 sc.gsWaitForSync.Done() 404 405 err = sc.updateState(ctx) 406 assert.Nil(t, err) 407 assert.False(t, updated) 408 }) 409 } 410 } 411 412 func TestSidecarHealthLastUpdated(t *testing.T) { 413 t.Parallel() 414 now := time.Now().UTC() 415 m := agtesting.NewMocks() 416 417 sc, err := defaultSidecar(m) 418 require.NoError(t, err) 419 420 sc.health = agonesv1.Health{Disabled: false} 421 fc := testclocks.NewFakeClock(now) 422 sc.clock = fc 423 424 stream := newEmptyMockStream() 425 426 wg := sync.WaitGroup{} 427 wg.Add(1) 428 go func() { 429 err := sc.Health(stream) 430 assert.Nil(t, err) 431 wg.Done() 432 }() 433 434 // Test once with a single message 435 fc.Step(3 * time.Second) 436 stream.msgs <- &sdk.Empty{} 437 438 err = waitForMessage(sc) 439 assert.Nil(t, err) 440 sc.healthMutex.RLock() 441 assert.Equal(t, sc.clock.Now().UTC().String(), sc.healthLastUpdated.String()) 442 sc.healthMutex.RUnlock() 443 444 // Test again, since the value has been set, that it is re-set 445 fc.Step(3 * time.Second) 446 stream.msgs <- &sdk.Empty{} 447 err = waitForMessage(sc) 448 assert.Nil(t, err) 449 sc.healthMutex.RLock() 450 assert.Equal(t, sc.clock.Now().UTC().String(), sc.healthLastUpdated.String()) 451 sc.healthMutex.RUnlock() 452 453 // make sure closing doesn't change the time 454 fc.Step(3 * time.Second) 455 close(stream.msgs) 456 assert.NotEqual(t, sc.clock.Now().UTC().String(), sc.healthLastUpdated.String()) 457 458 wg.Wait() 459 } 460 461 func TestSidecarUnhealthyMessage(t *testing.T) { 462 t.Parallel() 463 464 m := agtesting.NewMocks() 465 sc, err := NewSDKServer("test", "default", m.KubeClient, m.AgonesClient, logrus.DebugLevel, 8080, 500*time.Millisecond) 466 require.NoError(t, err) 467 468 gs := agonesv1.GameServer{ 469 ObjectMeta: metav1.ObjectMeta{ 470 Name: "test", Namespace: "default", ResourceVersion: "0", 471 }, 472 Spec: agonesv1.GameServerSpec{}, 473 Status: agonesv1.GameServerStatus{ 474 State: agonesv1.GameServerStateStarting, 475 }, 476 } 477 gs.ApplyDefaults() 478 479 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 480 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 481 }) 482 483 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 484 gsCopy := patchGameServer(t, action, &gs) 485 486 return true, gsCopy, nil 487 }) 488 489 ctx, cancel := context.WithCancel(context.Background()) 490 defer cancel() 491 stop := make(chan struct{}) 492 defer close(stop) 493 494 assert.NoError(t, sc.WaitForConnection(ctx)) 495 sc.informerFactory.Start(stop) 496 assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced)) 497 498 sc.recorder = m.FakeRecorder 499 500 go func() { 501 err := sc.Run(ctx) 502 assert.Nil(t, err) 503 }() 504 505 // manually push through an unhealthy state change 506 sc.enqueueState(agonesv1.GameServerStateUnhealthy) 507 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Health check failure") 508 509 // try to push back to Ready, enqueueState should block it. 510 sc.enqueueState(agonesv1.GameServerStateRequestReady) 511 sc.gsUpdateMutex.Lock() 512 assert.Equal(t, agonesv1.GameServerStateUnhealthy, sc.gsState) 513 sc.gsUpdateMutex.Unlock() 514 } 515 516 func TestSidecarHealthy(t *testing.T) { 517 t.Parallel() 518 519 m := agtesting.NewMocks() 520 sc, err := defaultSidecar(m) 521 require.NoError(t, err) 522 523 // manually set the values 524 sc.health = agonesv1.Health{FailureThreshold: 1} 525 sc.healthTimeout = 5 * time.Second 526 sc.touchHealthLastUpdated() 527 528 now := time.Now().UTC() 529 fc := testclocks.NewFakeClock(now) 530 sc.clock = fc 531 532 stream := newEmptyMockStream() 533 534 wg := sync.WaitGroup{} 535 wg.Add(1) 536 go func() { 537 err := sc.Health(stream) 538 assert.Nil(t, err) 539 wg.Done() 540 }() 541 542 fixtures := map[string]struct { 543 timeAdd time.Duration 544 disabled bool 545 expectedHealthy bool 546 }{ 547 "disabled, under timeout": {disabled: true, timeAdd: time.Second, expectedHealthy: true}, 548 "disabled, over timeout": {disabled: true, timeAdd: 15 * time.Second, expectedHealthy: true}, 549 "enabled, under timeout": {disabled: false, timeAdd: time.Second, expectedHealthy: true}, 550 "enabled, over timeout": {disabled: false, timeAdd: 15 * time.Second, expectedHealthy: false}, 551 } 552 553 for k, v := range fixtures { 554 t.Run(k, func(t *testing.T) { 555 logrus.WithField("test", k).Infof("Test Running") 556 sc.health.Disabled = v.disabled 557 fc.SetTime(time.Now().UTC()) 558 stream.msgs <- &sdk.Empty{} 559 err = waitForMessage(sc) 560 assert.Nil(t, err) 561 562 fc.Step(v.timeAdd) 563 sc.checkHealth() 564 assert.Equal(t, v.expectedHealthy, sc.healthy()) 565 }) 566 } 567 568 t.Run("initial delay", func(t *testing.T) { 569 sc.health.Disabled = false 570 fc.SetTime(time.Now().UTC()) 571 sc.touchHealthLastUpdated() 572 573 // initial delay is handled by kubelet, runHealth() isn't 574 // called until container starts. 575 fc.Step(10 * time.Second) 576 sc.touchHealthLastUpdated() 577 sc.checkHealth() 578 assert.True(t, sc.healthy()) 579 580 fc.Step(10 * time.Second) 581 sc.checkHealth() 582 assert.False(t, sc.healthy()) 583 }) 584 585 t.Run("health failure threshold", func(t *testing.T) { 586 sc.health.Disabled = false 587 sc.health.FailureThreshold = 3 588 fc.SetTime(time.Now().UTC()) 589 sc.touchHealthLastUpdated() 590 591 sc.checkHealth() 592 assert.True(t, sc.healthy()) 593 assert.Equal(t, int32(0), sc.healthFailureCount) 594 595 fc.Step(10 * time.Second) 596 sc.checkHealth() 597 assert.True(t, sc.healthy()) 598 sc.checkHealth() 599 assert.True(t, sc.healthy()) 600 sc.checkHealth() 601 assert.False(t, sc.healthy()) 602 603 stream.msgs <- &sdk.Empty{} 604 err = waitForMessage(sc) 605 assert.Nil(t, err) 606 fc.Step(10 * time.Second) 607 assert.True(t, sc.healthy()) 608 }) 609 610 close(stream.msgs) 611 wg.Wait() 612 } 613 614 func TestSidecarHTTPHealthCheck(t *testing.T) { 615 m := agtesting.NewMocks() 616 sc, err := NewSDKServer("test", "default", m.KubeClient, m.AgonesClient, logrus.DebugLevel, 8080, 500*time.Millisecond) 617 require.NoError(t, err) 618 619 now := time.Now().Add(time.Hour).UTC() 620 fc := testclocks.NewFakeClock(now) 621 // now we control time - so slow machines won't fail anymore 622 sc.clock = fc 623 624 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 625 gs := agonesv1.GameServer{ 626 ObjectMeta: metav1.ObjectMeta{Name: sc.gameServerName, Namespace: sc.namespace}, 627 Spec: agonesv1.GameServerSpec{ 628 Health: agonesv1.Health{Disabled: false, FailureThreshold: 1, PeriodSeconds: 1, InitialDelaySeconds: 0}, 629 }, 630 } 631 632 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{gs}}, nil 633 }) 634 635 ctx, cancel := context.WithCancel(context.Background()) 636 defer cancel() 637 wg := sync.WaitGroup{} 638 wg.Add(1) 639 640 step := 2 * time.Second 641 642 go func() { 643 err := sc.Run(ctx) 644 assert.Nil(t, err) 645 // gate 646 assert.Equal(t, 1*time.Second, sc.healthTimeout) 647 wg.Done() 648 }() 649 650 testHTTPHealth(t, "http://localhost:8080/healthz", "ok", http.StatusOK) 651 testHTTPHealth(t, "http://localhost:8080/gshealthz", "ok", http.StatusOK) 652 653 assert.Equal(t, now, sc.healthLastUpdated) 654 655 fc.Step(step) 656 time.Sleep(step) 657 sc.checkHealthUpdateState() 658 assert.False(t, sc.healthy()) 659 cancel() 660 wg.Wait() // wait for go routine test results. 661 } 662 663 func TestSDKServerGetGameServer(t *testing.T) { 664 t.Parallel() 665 666 fixture := &agonesv1.GameServer{ 667 ObjectMeta: metav1.ObjectMeta{ 668 Name: "test", 669 Namespace: "default", 670 }, 671 Status: agonesv1.GameServerStatus{ 672 State: agonesv1.GameServerStateReady, 673 }, 674 } 675 676 m := agtesting.NewMocks() 677 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 678 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*fixture}}, nil 679 }) 680 681 stop := make(chan struct{}) 682 defer close(stop) 683 684 sc, err := defaultSidecar(m) 685 require.NoError(t, err) 686 687 sc.informerFactory.Start(stop) 688 assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced)) 689 sc.gsWaitForSync.Done() 690 691 result, err := sc.GetGameServer(context.Background(), &sdk.Empty{}) 692 require.NoError(t, err) 693 assert.Equal(t, fixture.ObjectMeta.Name, result.ObjectMeta.Name) 694 assert.Equal(t, fixture.ObjectMeta.Namespace, result.ObjectMeta.Namespace) 695 assert.Equal(t, string(fixture.Status.State), result.Status.State) 696 } 697 698 func TestSDKServerWatchGameServer(t *testing.T) { 699 t.Parallel() 700 701 fixture := &agonesv1.GameServer{ 702 ObjectMeta: metav1.ObjectMeta{ 703 Name: "test", 704 Namespace: "default", 705 }, 706 Status: agonesv1.GameServerStatus{ 707 State: agonesv1.GameServerStateReady, 708 }, 709 } 710 711 m := agtesting.NewMocks() 712 fakeWatch := watch.NewFake() 713 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 714 715 sc, err := defaultSidecar(m) 716 require.NoError(t, err) 717 assert.Empty(t, sc.connectedStreams) 718 719 ctx, cancel := context.WithCancel(context.Background()) 720 defer cancel() 721 sc.ctx = ctx 722 sc.informerFactory.Start(ctx.Done()) 723 724 fakeWatch.Add(fixture.DeepCopy()) 725 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 726 sc.gsWaitForSync.Done() 727 728 // wait for the GameServer to be populated, as we can't rely on WaitForCacheSync 729 require.Eventually(t, func() bool { 730 _, err := sc.gameServer() 731 return err == nil 732 }, time.Minute, time.Second, "Could not find the GameServer") 733 734 stream := newGameServerMockStream() 735 asyncWatchGameServer(t, sc, stream) 736 737 require.Nil(t, waitConnectedStreamCount(sc, 1)) 738 require.Equal(t, stream, sc.connectedStreams[0]) 739 740 // modify for 2nd event in watch stream 741 fixture.Status.State = agonesv1.GameServerStateAllocated 742 fakeWatch.Modify(fixture.DeepCopy()) 743 744 totalSendCalls := 0 745 running := true 746 for running { 747 select { 748 case gs := <-stream.msgs: 749 assert.Equal(t, fixture.ObjectMeta.Name, gs.ObjectMeta.Name) 750 totalSendCalls++ 751 switch totalSendCalls { 752 case 1: 753 assert.Equal(t, string(agonesv1.GameServerStateReady), gs.Status.State) 754 case 2: 755 assert.Equal(t, string(agonesv1.GameServerStateAllocated), gs.Status.State) 756 } 757 // we shouldn't get more than 2, but let's put an upper bound on this 758 // just in case we suddenly get way more than we expect. 759 if totalSendCalls > 10 { 760 assert.FailNow(t, "We should have only received two events. Got over 10 instead.") 761 } 762 case <-time.After(5 * time.Second): 763 // we can't `break` out of the loop, hence we need `running`. 764 running = false 765 } 766 } 767 768 // There are two stream.Send() calls should happen: one in sendGameServerUpdate, 769 // another one in WatchGameServer. 770 assert.Equal(t, 2, totalSendCalls) 771 } 772 773 func TestSDKServerSendGameServerUpdate(t *testing.T) { 774 t.Parallel() 775 fixture := &agonesv1.GameServer{ 776 ObjectMeta: metav1.ObjectMeta{ 777 Name: "test", 778 Namespace: "default", 779 }, 780 Status: agonesv1.GameServerStatus{ 781 State: agonesv1.GameServerStateReady, 782 }, 783 } 784 785 m := agtesting.NewMocks() 786 fakeWatch := watch.NewFake() 787 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 788 sc, err := defaultSidecar(m) 789 require.NoError(t, err) 790 assert.Empty(t, sc.connectedStreams) 791 792 ctx, cancel := context.WithCancel(context.Background()) 793 defer cancel() 794 sc.ctx = ctx 795 sc.informerFactory.Start(ctx.Done()) 796 797 fakeWatch.Add(fixture.DeepCopy()) 798 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 799 sc.gsWaitForSync.Done() 800 801 // wait for the GameServer to be populated, as we can't rely on WaitForCacheSync 802 require.Eventually(t, func() bool { 803 _, err := sc.gameServer() 804 return err == nil 805 }, time.Minute, time.Second, "Could not find the GameServer") 806 807 stream := newGameServerMockStream() 808 asyncWatchGameServer(t, sc, stream) 809 assert.Nil(t, waitConnectedStreamCount(sc, 1)) 810 811 sc.sendGameServerUpdate(fixture) 812 813 var sdkGS *sdk.GameServer 814 select { 815 case sdkGS = <-stream.msgs: 816 case <-time.After(3 * time.Second): 817 assert.Fail(t, "Event stream should not have timed out") 818 } 819 820 assert.Equal(t, fixture.ObjectMeta.Name, sdkGS.ObjectMeta.Name) 821 } 822 823 func TestSDKServer_SendGameServerUpdateRemovesDisconnectedStream(t *testing.T) { 824 t.Parallel() 825 826 fixture := &agonesv1.GameServer{ 827 ObjectMeta: metav1.ObjectMeta{ 828 Name: "test", 829 Namespace: "default", 830 }, 831 Status: agonesv1.GameServerStatus{ 832 State: agonesv1.GameServerStateReady, 833 }, 834 } 835 836 m := agtesting.NewMocks() 837 fakeWatch := watch.NewFake() 838 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 839 sc, err := defaultSidecar(m) 840 require.NoError(t, err) 841 assert.Empty(t, sc.connectedStreams) 842 843 ctx, cancel := context.WithCancel(context.Background()) 844 t.Cleanup(cancel) 845 sc.ctx = ctx 846 sc.informerFactory.Start(ctx.Done()) 847 848 fakeWatch.Add(fixture.DeepCopy()) 849 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 850 sc.gsWaitForSync.Done() 851 852 // Wait for the GameServer to be populated, as we can't rely on WaitForCacheSync. 853 require.Eventually(t, func() bool { 854 _, err := sc.gameServer() 855 return err == nil 856 }, time.Minute, time.Second, "Could not find the GameServer") 857 858 // Create and initialize two streams. 859 streamOne := newGameServerMockStream() 860 streamOneCtx, streamOneCancel := context.WithCancel(context.Background()) 861 t.Cleanup(streamOneCancel) 862 streamOne.ctx = streamOneCtx 863 asyncWatchGameServer(t, sc, streamOne) 864 865 streamTwo := newGameServerMockStream() 866 streamTwoCtx, streamTwoCancel := context.WithCancel(context.Background()) 867 t.Cleanup(streamTwoCancel) 868 streamTwo.ctx = streamTwoCtx 869 asyncWatchGameServer(t, sc, streamTwo) 870 871 // Verify that two streams are connected. 872 assert.Nil(t, waitConnectedStreamCount(sc, 2)) 873 streamOneCancel() 874 streamTwoCancel() 875 876 // Trigger stream removal by sending a game server update. 877 sc.sendGameServerUpdate(fixture) 878 // Verify that zero streams are connected. 879 assert.Nil(t, waitConnectedStreamCount(sc, 0)) 880 } 881 882 func TestSDKServerUpdateEventHandler(t *testing.T) { 883 t.Parallel() 884 fixture := &agonesv1.GameServer{ 885 ObjectMeta: metav1.ObjectMeta{ 886 Name: "test", 887 Namespace: "default", 888 }, 889 Status: agonesv1.GameServerStatus{ 890 State: agonesv1.GameServerStateReady, 891 }, 892 } 893 894 m := agtesting.NewMocks() 895 fakeWatch := watch.NewFake() 896 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 897 sc, err := defaultSidecar(m) 898 require.NoError(t, err) 899 assert.Empty(t, sc.connectedStreams) 900 901 ctx, cancel := context.WithCancel(context.Background()) 902 defer cancel() 903 sc.ctx = ctx 904 sc.informerFactory.Start(ctx.Done()) 905 906 fakeWatch.Add(fixture.DeepCopy()) 907 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 908 sc.gsWaitForSync.Done() 909 910 // wait for the GameServer to be populated, as we can't rely on WaitForCacheSync 911 require.Eventually(t, func() bool { 912 _, err := sc.gameServer() 913 return err == nil 914 }, time.Minute, time.Second, "Could not find the GameServer") 915 stream := newGameServerMockStream() 916 asyncWatchGameServer(t, sc, stream) 917 assert.Nil(t, waitConnectedStreamCount(sc, 1)) 918 919 // need to add it before it can be modified 920 fakeWatch.Add(fixture.DeepCopy()) 921 fakeWatch.Modify(fixture.DeepCopy()) 922 923 var sdkGS *sdk.GameServer 924 select { 925 case sdkGS = <-stream.msgs: 926 case <-time.After(3 * time.Second): 927 assert.Fail(t, "Event stream should not have timed out") 928 } 929 930 assert.NotNil(t, sdkGS) 931 assert.Equal(t, fixture.ObjectMeta.Name, sdkGS.ObjectMeta.Name) 932 } 933 934 func TestSDKServerReserveTimeoutOnRun(t *testing.T) { 935 t.Parallel() 936 m := agtesting.NewMocks() 937 938 updated := make(chan agonesv1.GameServerStatus, 1) 939 940 gs := agonesv1.GameServer{ 941 ObjectMeta: metav1.ObjectMeta{ 942 Name: "test", Namespace: "default", ResourceVersion: "0", 943 }, 944 Status: agonesv1.GameServerStatus{ 945 State: agonesv1.GameServerStateReserved, 946 }, 947 } 948 gs.ApplyDefaults() 949 950 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 951 n := metav1.NewTime(metav1.Now().Add(time.Second)) 952 gsCopy := gs.DeepCopy() 953 gsCopy.Status.ReservedUntil = &n 954 955 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gsCopy}}, nil 956 }) 957 958 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 959 gsCopy := patchGameServer(t, action, &gs) 960 961 updated <- gsCopy.Status 962 963 return true, gsCopy, nil 964 }) 965 966 sc, err := defaultSidecar(m) 967 require.NoError(t, err) 968 969 ctx, cancel := context.WithCancel(context.Background()) 970 assert.NoError(t, sc.WaitForConnection(ctx)) 971 sc.informerFactory.Start(ctx.Done()) 972 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 973 974 wg := sync.WaitGroup{} 975 wg.Add(1) 976 977 go func() { 978 err = sc.Run(ctx) 979 assert.Nil(t, err) 980 wg.Done() 981 }() 982 983 select { 984 case status := <-updated: 985 assert.Equal(t, agonesv1.GameServerStateRequestReady, status.State) 986 assert.Nil(t, status.ReservedUntil) 987 case <-time.After(5 * time.Second): 988 assert.Fail(t, "should have been an update") 989 } 990 991 cancel() 992 wg.Wait() 993 } 994 995 func TestSDKServerReserveTimeout(t *testing.T) { 996 t.Parallel() 997 m := agtesting.NewMocks() 998 999 state := make(chan agonesv1.GameServerStatus, 100) 1000 defer close(state) 1001 1002 gs := agonesv1.GameServer{ 1003 ObjectMeta: metav1.ObjectMeta{ 1004 Name: "test", Namespace: "default", ResourceVersion: "0", 1005 }, 1006 Spec: agonesv1.GameServerSpec{Health: agonesv1.Health{Disabled: true}}, 1007 } 1008 gs.ApplyDefaults() 1009 1010 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1011 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1012 }) 1013 1014 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1015 gsCopy := patchGameServer(t, action, &gs) 1016 1017 state <- gsCopy.Status 1018 return true, gsCopy, nil 1019 }) 1020 1021 sc, err := defaultSidecar(m) 1022 1023 assert.NoError(t, err) 1024 ctx, cancel := context.WithCancel(context.Background()) 1025 assert.NoError(t, sc.WaitForConnection(ctx)) 1026 sc.informerFactory.Start(ctx.Done()) 1027 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1028 1029 wg := sync.WaitGroup{} 1030 wg.Add(1) 1031 1032 go func() { 1033 err = sc.Run(ctx) 1034 assert.Nil(t, err) 1035 wg.Done() 1036 }() 1037 1038 assertStateChange := func(expected agonesv1.GameServerState, additional func(status agonesv1.GameServerStatus)) { 1039 select { 1040 case current := <-state: 1041 assert.Equal(t, expected, current.State) 1042 additional(current) 1043 case <-time.After(5 * time.Second): 1044 assert.Fail(t, "should have gone to Reserved by now") 1045 } 1046 } 1047 assertReservedUntilDuration := func(d time.Duration) func(status agonesv1.GameServerStatus) { 1048 return func(status agonesv1.GameServerStatus) { 1049 assert.WithinDuration(t, time.Now().Add(d), status.ReservedUntil.Time, 1500*time.Millisecond) 1050 } 1051 } 1052 assertReservedUntilNil := func(status agonesv1.GameServerStatus) { 1053 assert.Nil(t, status.ReservedUntil) 1054 } 1055 1056 _, err = sc.Reserve(context.Background(), &sdk.Duration{Seconds: 3}) 1057 assert.NoError(t, err) 1058 assertStateChange(agonesv1.GameServerStateReserved, assertReservedUntilDuration(3*time.Second)) 1059 1060 // Wait for the game server to go back to being Ready. 1061 assertStateChange(agonesv1.GameServerStateRequestReady, func(status agonesv1.GameServerStatus) { 1062 assert.Nil(t, status.ReservedUntil) 1063 }) 1064 1065 // Test that a 0 second input into Reserved, never will go back to Ready 1066 _, err = sc.Reserve(context.Background(), &sdk.Duration{Seconds: 0}) 1067 assert.NoError(t, err) 1068 assertStateChange(agonesv1.GameServerStateReserved, assertReservedUntilNil) 1069 assert.False(t, sc.reserveTimer.Stop()) 1070 1071 // Test that a negative input into Reserved, is the same as a 0 input 1072 _, err = sc.Reserve(context.Background(), &sdk.Duration{Seconds: -100}) 1073 assert.NoError(t, err) 1074 assertStateChange(agonesv1.GameServerStateReserved, assertReservedUntilNil) 1075 assert.False(t, sc.reserveTimer.Stop()) 1076 1077 // Test that the timer to move Reserved->Ready is reset when requesting another state. 1078 1079 // Test the return to a Ready state. 1080 _, err = sc.Reserve(context.Background(), &sdk.Duration{Seconds: 3}) 1081 assert.NoError(t, err) 1082 assertStateChange(agonesv1.GameServerStateReserved, assertReservedUntilDuration(3*time.Second)) 1083 1084 _, err = sc.Ready(context.Background(), &sdk.Empty{}) 1085 assert.NoError(t, err) 1086 assertStateChange(agonesv1.GameServerStateRequestReady, assertReservedUntilNil) 1087 assert.False(t, sc.reserveTimer.Stop()) 1088 1089 // Test Allocated resets the timer on Reserved->Ready 1090 _, err = sc.Reserve(context.Background(), &sdk.Duration{Seconds: 3}) 1091 assert.NoError(t, err) 1092 assertStateChange(agonesv1.GameServerStateReserved, assertReservedUntilDuration(3*time.Second)) 1093 1094 _, err = sc.Allocate(context.Background(), &sdk.Empty{}) 1095 assert.NoError(t, err) 1096 assertStateChange(agonesv1.GameServerStateAllocated, assertReservedUntilNil) 1097 assert.False(t, sc.reserveTimer.Stop()) 1098 1099 // Test Shutdown resets the timer on Reserved->Ready 1100 _, err = sc.Reserve(context.Background(), &sdk.Duration{Seconds: 3}) 1101 assert.NoError(t, err) 1102 assertStateChange(agonesv1.GameServerStateReserved, assertReservedUntilDuration(3*time.Second)) 1103 1104 _, err = sc.Shutdown(context.Background(), &sdk.Empty{}) 1105 assert.NoError(t, err) 1106 assertStateChange(agonesv1.GameServerStateShutdown, assertReservedUntilNil) 1107 assert.False(t, sc.reserveTimer.Stop()) 1108 1109 cancel() 1110 wg.Wait() 1111 } 1112 1113 func TestSDKServerUpdateCounter(t *testing.T) { 1114 t.Parallel() 1115 agruntime.FeatureTestMutex.Lock() 1116 defer agruntime.FeatureTestMutex.Unlock() 1117 1118 err := agruntime.ParseFeatures(string(agruntime.FeatureCountsAndLists) + "=true") 1119 require.NoError(t, err, "Can not parse FeatureCountsAndLists feature") 1120 1121 counters := map[string]agonesv1.CounterStatus{ 1122 "widgets": {Count: int64(10), Capacity: int64(100)}, 1123 "foo": {Count: int64(10), Capacity: int64(100)}, 1124 "bar": {Count: int64(10), Capacity: int64(100)}, 1125 "baz": {Count: int64(10), Capacity: int64(100)}, 1126 "bazel": {Count: int64(10), Capacity: int64(100)}, 1127 "fish": {Count: int64(10), Capacity: int64(100)}, 1128 "onefish": {Count: int64(10), Capacity: int64(100)}, 1129 "twofish": {Count: int64(10), Capacity: int64(100)}, 1130 "redfish": {Count: int64(10), Capacity: int64(100)}, 1131 "bluefish": {Count: int64(10), Capacity: int64(100)}, 1132 "fivefish": {Count: int64(10), Capacity: int64(100)}, 1133 "starfish": {Count: int64(10), Capacity: int64(100)}, 1134 } 1135 1136 fixtures := map[string]struct { 1137 counterName string 1138 requests []*beta.UpdateCounterRequest 1139 want agonesv1.CounterStatus 1140 updateErrs []bool 1141 updated bool 1142 }{ 1143 "increment": { 1144 counterName: "widgets", 1145 requests: []*beta.UpdateCounterRequest{{ 1146 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1147 Name: "widgets", 1148 CountDiff: 9, 1149 }}}, 1150 want: agonesv1.CounterStatus{Count: int64(19), Capacity: int64(100)}, 1151 updateErrs: []bool{false}, 1152 updated: true, 1153 }, 1154 "increment illegal": { 1155 counterName: "foo", 1156 requests: []*beta.UpdateCounterRequest{{ 1157 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1158 Name: "foo", 1159 CountDiff: 100, 1160 }}}, 1161 want: agonesv1.CounterStatus{Count: int64(10), Capacity: int64(100)}, 1162 updateErrs: []bool{true}, 1163 updated: false, 1164 }, 1165 "decrement": { 1166 counterName: "bar", 1167 requests: []*beta.UpdateCounterRequest{{ 1168 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1169 Name: "bar", 1170 CountDiff: -1, 1171 }}}, 1172 want: agonesv1.CounterStatus{Count: int64(9), Capacity: int64(100)}, 1173 updateErrs: []bool{false}, 1174 updated: true, 1175 }, 1176 "decrement illegal": { 1177 counterName: "baz", 1178 requests: []*beta.UpdateCounterRequest{{ 1179 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1180 Name: "baz", 1181 CountDiff: -11, 1182 }}}, 1183 want: agonesv1.CounterStatus{Count: int64(10), Capacity: int64(100)}, 1184 updateErrs: []bool{true}, 1185 updated: false, 1186 }, 1187 "set capacity": { 1188 counterName: "bazel", 1189 requests: []*beta.UpdateCounterRequest{{ 1190 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1191 Name: "bazel", 1192 Capacity: wrapperspb.Int64(0), 1193 }}}, 1194 want: agonesv1.CounterStatus{Count: int64(0), Capacity: int64(0)}, 1195 updateErrs: []bool{false}, 1196 updated: true, 1197 }, 1198 "set capacity illegal": { 1199 counterName: "fish", 1200 requests: []*beta.UpdateCounterRequest{{ 1201 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1202 Name: "fish", 1203 Capacity: wrapperspb.Int64(-1), 1204 }}}, 1205 want: agonesv1.CounterStatus{Count: int64(10), Capacity: int64(100)}, 1206 updateErrs: []bool{true}, 1207 updated: false, 1208 }, 1209 "set count": { 1210 counterName: "onefish", 1211 requests: []*beta.UpdateCounterRequest{{ 1212 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1213 Name: "onefish", 1214 Count: wrapperspb.Int64(42), 1215 }}}, 1216 want: agonesv1.CounterStatus{Count: int64(42), Capacity: int64(100)}, 1217 updateErrs: []bool{false}, 1218 updated: true, 1219 }, 1220 "set count illegal": { 1221 counterName: "twofish", 1222 requests: []*beta.UpdateCounterRequest{{ 1223 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1224 Name: "twofish", 1225 Count: wrapperspb.Int64(101), 1226 }}}, 1227 want: agonesv1.CounterStatus{Count: int64(10), Capacity: int64(100)}, 1228 updateErrs: []bool{true}, 1229 updated: false, 1230 }, 1231 "increment past set capacity illegal": { 1232 counterName: "redfish", 1233 requests: []*beta.UpdateCounterRequest{{ 1234 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1235 Name: "redfish", 1236 Capacity: wrapperspb.Int64(0), 1237 }}, 1238 {CounterUpdateRequest: &beta.CounterUpdateRequest{ 1239 Name: "redfish", 1240 CountDiff: 1, 1241 }}}, 1242 want: agonesv1.CounterStatus{Count: int64(0), Capacity: int64(0)}, 1243 updateErrs: []bool{false, true}, 1244 updated: true, 1245 }, 1246 "decrement past set capacity illegal": { 1247 counterName: "bluefish", 1248 requests: []*beta.UpdateCounterRequest{{ 1249 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1250 Name: "bluefish", 1251 Capacity: wrapperspb.Int64(0), 1252 }}, 1253 {CounterUpdateRequest: &beta.CounterUpdateRequest{ 1254 Name: "bluefish", 1255 CountDiff: -1, 1256 }}}, 1257 want: agonesv1.CounterStatus{Count: int64(0), Capacity: int64(0)}, 1258 updateErrs: []bool{false, true}, 1259 updated: true, 1260 }, 1261 "setcapacity, setcount, and diffcount": { 1262 counterName: "fivefish", 1263 requests: []*beta.UpdateCounterRequest{{ 1264 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1265 Name: "fivefish", 1266 Capacity: wrapperspb.Int64(25), 1267 Count: wrapperspb.Int64(0), 1268 CountDiff: 25, 1269 }}}, 1270 want: agonesv1.CounterStatus{Count: int64(25), Capacity: int64(25)}, 1271 updateErrs: []bool{false}, 1272 updated: true, 1273 }, 1274 "projected state returned": { 1275 counterName: "starfish", 1276 requests: []*beta.UpdateCounterRequest{ 1277 {CounterUpdateRequest: &beta.CounterUpdateRequest{ 1278 Name: "starfish", 1279 CountDiff: 7, 1280 }}, 1281 }, 1282 want: agonesv1.CounterStatus{Count: int64(17), Capacity: int64(100)}, 1283 updateErrs: []bool{false}, 1284 updated: true, 1285 }, 1286 } 1287 1288 for test, testCase := range fixtures { 1289 t.Run(test, func(t *testing.T) { 1290 m := agtesting.NewMocks() 1291 1292 gs := agonesv1.GameServer{ 1293 ObjectMeta: metav1.ObjectMeta{ 1294 Name: "test", Namespace: "default", ResourceVersion: "0", Generation: 1, 1295 }, 1296 Spec: agonesv1.GameServerSpec{ 1297 SdkServer: agonesv1.SdkServer{ 1298 LogLevel: "Debug", 1299 }, 1300 }, 1301 Status: agonesv1.GameServerStatus{ 1302 Counters: counters, 1303 }, 1304 } 1305 gs.ApplyDefaults() 1306 1307 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1308 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1309 }) 1310 1311 updated := make(chan map[string]agonesv1.CounterStatus, 10) 1312 1313 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1314 gsCopy := patchGameServer(t, action, &gs) 1315 1316 updated <- gsCopy.Status.Counters 1317 return true, gsCopy, nil 1318 }) 1319 1320 ctx, cancel := context.WithCancel(context.Background()) 1321 sc, err := defaultSidecar(m) 1322 require.NoError(t, err) 1323 assert.NoError(t, sc.WaitForConnection(ctx)) 1324 sc.informerFactory.Start(ctx.Done()) 1325 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1326 1327 wg := sync.WaitGroup{} 1328 wg.Add(1) 1329 1330 go func() { 1331 err = sc.Run(ctx) 1332 assert.NoError(t, err) 1333 wg.Done() 1334 }() 1335 1336 // check initial value comes through 1337 require.Eventually(t, func() bool { 1338 counter, err := sc.GetCounter(context.Background(), &beta.GetCounterRequest{Name: testCase.counterName}) 1339 return counter.Count == 10 && counter.Capacity == 100 && err == nil 1340 }, 10*time.Second, time.Second) 1341 1342 // Update the Counter 1343 for i, req := range testCase.requests { 1344 resp, err := sc.UpdateCounter(context.Background(), req) 1345 if testCase.updateErrs[i] { 1346 assert.Error(t, err) 1347 } else { 1348 assert.NoError(t, err) 1349 assert.Equal(t, testCase.want.Count, resp.Count) 1350 assert.Equal(t, testCase.want.Capacity, resp.Capacity) 1351 1352 } 1353 } 1354 1355 got, err := sc.GetCounter(context.Background(), &beta.GetCounterRequest{Name: testCase.counterName}) 1356 assert.NoError(t, err) 1357 assert.Equal(t, testCase.want.Count, got.Count) 1358 assert.Equal(t, testCase.want.Capacity, got.Capacity) 1359 1360 // on an update, confirm that the update hits the K8s api 1361 if testCase.updated { 1362 select { 1363 case value := <-updated: 1364 assert.NotNil(t, value[testCase.counterName]) 1365 assert.Equal(t, 1366 agonesv1.CounterStatus{Count: testCase.want.Count, Capacity: testCase.want.Capacity}, 1367 value[testCase.counterName]) 1368 case <-time.After(10 * time.Second): 1369 assert.Fail(t, "Counter should have been patched") 1370 } 1371 } 1372 1373 cancel() 1374 wg.Wait() 1375 }) 1376 } 1377 } 1378 1379 func TestSDKServerAddListValue(t *testing.T) { 1380 t.Parallel() 1381 agruntime.FeatureTestMutex.Lock() 1382 defer agruntime.FeatureTestMutex.Unlock() 1383 1384 err := agruntime.ParseFeatures(string(agruntime.FeatureCountsAndLists) + "=true") 1385 require.NoError(t, err, "Can not parse FeatureCountsAndLists feature") 1386 1387 lists := map[string]agonesv1.ListStatus{ 1388 "foo": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(10)}, 1389 "bar": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(10)}, 1390 "baz": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(10)}, 1391 } 1392 1393 fixtures := map[string]struct { 1394 listName string 1395 requests []*beta.AddListValueRequest 1396 want agonesv1.ListStatus 1397 updateErrs []bool 1398 updated bool 1399 expectedUpdatesQueueLen int 1400 }{ 1401 "Add value": { 1402 listName: "foo", 1403 requests: []*beta.AddListValueRequest{{Name: "foo", Value: "five"}}, 1404 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four", "five"}, Capacity: int64(10)}, 1405 updateErrs: []bool{false}, 1406 updated: true, 1407 expectedUpdatesQueueLen: 0, 1408 }, 1409 "Add multiple values including duplicates": { 1410 listName: "bar", 1411 requests: []*beta.AddListValueRequest{ 1412 {Name: "bar", Value: "five"}, 1413 {Name: "bar", Value: "one"}, 1414 {Name: "bar", Value: "five"}, 1415 {Name: "bar", Value: "zero"}, 1416 }, 1417 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four", "five", "zero"}, Capacity: int64(10)}, 1418 updateErrs: []bool{false, true, true, false}, 1419 updated: true, 1420 expectedUpdatesQueueLen: 0, 1421 }, 1422 "Add multiple values past capacity": { 1423 listName: "baz", 1424 requests: []*beta.AddListValueRequest{ 1425 {Name: "baz", Value: "five"}, 1426 {Name: "baz", Value: "six"}, 1427 {Name: "baz", Value: "seven"}, 1428 {Name: "baz", Value: "eight"}, 1429 {Name: "baz", Value: "nine"}, 1430 {Name: "baz", Value: "ten"}, 1431 {Name: "baz", Value: "eleven"}, 1432 }, 1433 want: agonesv1.ListStatus{ 1434 Values: []string{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"}, 1435 Capacity: int64(10), 1436 }, 1437 updateErrs: []bool{false, false, false, false, false, false, true}, 1438 updated: true, 1439 expectedUpdatesQueueLen: 0, 1440 }, 1441 } 1442 1443 // nolint:dupl // Linter errors on lines are duplicate of TestSDKServerUpdateList, TestSDKServerRemoveListValue 1444 for test, testCase := range fixtures { 1445 t.Run(test, func(t *testing.T) { 1446 m := agtesting.NewMocks() 1447 1448 gs := agonesv1.GameServer{ 1449 ObjectMeta: metav1.ObjectMeta{ 1450 Name: "test", Namespace: "default", ResourceVersion: "0", Generation: 1, 1451 }, 1452 Spec: agonesv1.GameServerSpec{ 1453 SdkServer: agonesv1.SdkServer{ 1454 LogLevel: "Debug", 1455 }, 1456 }, 1457 Status: agonesv1.GameServerStatus{ 1458 Lists: lists, 1459 }, 1460 } 1461 gs.ApplyDefaults() 1462 1463 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1464 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1465 }) 1466 1467 updated := make(chan map[string]agonesv1.ListStatus, 10) 1468 1469 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1470 gsCopy := patchGameServer(t, action, &gs) 1471 1472 updated <- gsCopy.Status.Lists 1473 return true, gsCopy, nil 1474 }) 1475 1476 ctx, cancel := context.WithCancel(context.Background()) 1477 sc, err := defaultSidecar(m) 1478 require.NoError(t, err) 1479 assert.NoError(t, sc.WaitForConnection(ctx)) 1480 sc.informerFactory.Start(ctx.Done()) 1481 require.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1482 sc.gsWaitForSync.Done() 1483 1484 // check initial value comes through 1485 require.Eventually(t, func() bool { 1486 list, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1487 return cmp.Equal(list.Values, []string{"one", "two", "three", "four"}) && list.Capacity == 10 && err == nil 1488 }, 10*time.Second, time.Second) 1489 1490 // Update the List 1491 for i, req := range testCase.requests { 1492 _, err = sc.AddListValue(context.Background(), req) 1493 if testCase.updateErrs[i] { 1494 assert.Error(t, err) 1495 } else { 1496 assert.NoError(t, err) 1497 } 1498 } 1499 1500 got, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1501 assert.NoError(t, err) 1502 assert.Equal(t, testCase.want.Values, got.Values) 1503 assert.Equal(t, testCase.want.Capacity, got.Capacity) 1504 1505 // start workerqueue processing at this point, so there is no chance of processing the above updates 1506 // earlier. 1507 sc.gsWaitForSync.Add(1) 1508 go func() { 1509 err = sc.Run(ctx) 1510 assert.NoError(t, err) 1511 }() 1512 1513 // on an update, confirm that the update hits the K8s api 1514 if testCase.updated { 1515 select { 1516 case value := <-updated: 1517 assert.NotNil(t, value[testCase.listName]) 1518 assert.Equal(t, 1519 agonesv1.ListStatus{Values: testCase.want.Values, Capacity: testCase.want.Capacity}, 1520 value[testCase.listName]) 1521 case <-time.After(10 * time.Second): 1522 assert.Fail(t, "List should have been patched") 1523 } 1524 } 1525 1526 // on an update, confirms that the update queue list contains the right amount of items 1527 glu := sc.gsListUpdatesLen() 1528 assert.Equal(t, testCase.expectedUpdatesQueueLen, glu) 1529 1530 cancel() 1531 }) 1532 } 1533 } 1534 1535 func TestSDKServerRemoveListValue(t *testing.T) { 1536 t.Parallel() 1537 agruntime.FeatureTestMutex.Lock() 1538 defer agruntime.FeatureTestMutex.Unlock() 1539 1540 err := agruntime.ParseFeatures(string(agruntime.FeatureCountsAndLists) + "=true") 1541 require.NoError(t, err, "Can not parse FeatureCountsAndLists feature") 1542 1543 lists := map[string]agonesv1.ListStatus{ 1544 "foo": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1545 "bar": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1546 "baz": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1547 } 1548 1549 fixtures := map[string]struct { 1550 listName string 1551 requests []*beta.RemoveListValueRequest 1552 want agonesv1.ListStatus 1553 updateErrs []bool 1554 updated bool 1555 expectedUpdatesQueueLen int 1556 }{ 1557 "Remove value": { 1558 listName: "foo", 1559 requests: []*beta.RemoveListValueRequest{{Name: "foo", Value: "two"}}, 1560 want: agonesv1.ListStatus{Values: []string{"one", "three", "four"}, Capacity: int64(100)}, 1561 updateErrs: []bool{false}, 1562 updated: true, 1563 expectedUpdatesQueueLen: 0, 1564 }, 1565 "Remove multiple values including duplicates": { 1566 listName: "bar", 1567 requests: []*beta.RemoveListValueRequest{ 1568 {Name: "bar", Value: "two"}, 1569 {Name: "bar", Value: "three"}, 1570 {Name: "bar", Value: "two"}, 1571 }, 1572 want: agonesv1.ListStatus{Values: []string{"one", "four"}, Capacity: int64(100)}, 1573 updateErrs: []bool{false, false, true}, 1574 updated: true, 1575 expectedUpdatesQueueLen: 0, 1576 }, 1577 "Remove all values": { 1578 listName: "baz", 1579 requests: []*beta.RemoveListValueRequest{ 1580 {Name: "baz", Value: "three"}, 1581 {Name: "baz", Value: "two"}, 1582 {Name: "baz", Value: "four"}, 1583 {Name: "baz", Value: "one"}, 1584 }, 1585 want: agonesv1.ListStatus{Values: []string{}, Capacity: int64(100)}, 1586 updateErrs: []bool{false, false, false, false}, 1587 updated: true, 1588 expectedUpdatesQueueLen: 0, 1589 }, 1590 } 1591 1592 // nolint:dupl // Linter errors on lines are duplicate of TestSDKServerUpdateList, TestSDKServerAddListValue 1593 for test, testCase := range fixtures { 1594 t.Run(test, func(t *testing.T) { 1595 m := agtesting.NewMocks() 1596 1597 gs := agonesv1.GameServer{ 1598 ObjectMeta: metav1.ObjectMeta{ 1599 Name: "test", Namespace: "default", ResourceVersion: "0", Generation: 1, 1600 }, 1601 Spec: agonesv1.GameServerSpec{ 1602 SdkServer: agonesv1.SdkServer{ 1603 LogLevel: "Debug", 1604 }, 1605 }, 1606 Status: agonesv1.GameServerStatus{ 1607 Lists: lists, 1608 }, 1609 } 1610 gs.ApplyDefaults() 1611 1612 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1613 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1614 }) 1615 1616 updated := make(chan map[string]agonesv1.ListStatus, 10) 1617 1618 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1619 gsCopy := patchGameServer(t, action, &gs) 1620 1621 updated <- gsCopy.Status.Lists 1622 return true, gsCopy, nil 1623 }) 1624 1625 ctx, cancel := context.WithCancel(context.Background()) 1626 sc, err := defaultSidecar(m) 1627 require.NoError(t, err) 1628 assert.NoError(t, sc.WaitForConnection(ctx)) 1629 sc.informerFactory.Start(ctx.Done()) 1630 require.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1631 sc.gsWaitForSync.Done() 1632 1633 // check initial value comes through 1634 require.Eventually(t, func() bool { 1635 list, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1636 return cmp.Equal(list.Values, []string{"one", "two", "three", "four"}) && list.Capacity == 100 && err == nil 1637 }, 10*time.Second, time.Second) 1638 1639 // Update the List 1640 for i, req := range testCase.requests { 1641 _, err = sc.RemoveListValue(context.Background(), req) 1642 if testCase.updateErrs[i] { 1643 assert.Error(t, err) 1644 } else { 1645 assert.NoError(t, err) 1646 } 1647 } 1648 1649 got, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1650 assert.NoError(t, err) 1651 assert.Equal(t, testCase.want.Values, got.Values) 1652 assert.Equal(t, testCase.want.Capacity, got.Capacity) 1653 1654 // start workerqueue processing at this point, so there is no chance of processing the above updates 1655 // earlier. 1656 sc.gsWaitForSync.Add(1) 1657 go func() { 1658 err = sc.Run(ctx) 1659 assert.NoError(t, err) 1660 }() 1661 1662 // on an update, confirm that the update hits the K8s api 1663 if testCase.updated { 1664 select { 1665 case value := <-updated: 1666 assert.NotNil(t, value[testCase.listName]) 1667 assert.Equal(t, 1668 agonesv1.ListStatus{Values: testCase.want.Values, Capacity: testCase.want.Capacity}, 1669 value[testCase.listName]) 1670 case <-time.After(10 * time.Second): 1671 assert.Fail(t, "List should have been patched") 1672 } 1673 } 1674 1675 // on an update, confirms that the update queue list contains the right amount of items 1676 glu := sc.gsListUpdatesLen() 1677 assert.Equal(t, testCase.expectedUpdatesQueueLen, glu) 1678 1679 cancel() 1680 }) 1681 } 1682 } 1683 1684 func TestSDKServerUpdateList(t *testing.T) { 1685 t.Parallel() 1686 agruntime.FeatureTestMutex.Lock() 1687 defer agruntime.FeatureTestMutex.Unlock() 1688 1689 err := agruntime.ParseFeatures(string(agruntime.FeatureCountsAndLists) + "=true") 1690 require.NoError(t, err, "Can not parse FeatureCountsAndLists feature") 1691 1692 lists := map[string]agonesv1.ListStatus{ 1693 "foo": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1694 "bar": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1695 "baz": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1696 "qux": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1697 "quux": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1698 } 1699 1700 fixtures := map[string]struct { 1701 listName string 1702 request *beta.UpdateListRequest 1703 want agonesv1.ListStatus 1704 updateErr bool 1705 updated bool 1706 expectedUpdatesQueueLen int 1707 }{ 1708 "set capacity to max": { 1709 listName: "foo", 1710 request: &beta.UpdateListRequest{ 1711 List: &beta.List{ 1712 Name: "foo", 1713 Capacity: int64(1000), 1714 }, 1715 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, 1716 }, 1717 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four"}, Capacity: int64(1000)}, 1718 updateErr: false, 1719 updated: true, 1720 expectedUpdatesQueueLen: 0, 1721 }, 1722 "set capacity to min values are truncated": { 1723 listName: "bar", 1724 request: &beta.UpdateListRequest{ 1725 List: &beta.List{ 1726 Name: "bar", 1727 Capacity: int64(0), 1728 }, 1729 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, 1730 }, 1731 want: agonesv1.ListStatus{Values: []string{}, Capacity: int64(0)}, 1732 updateErr: false, 1733 updated: true, 1734 expectedUpdatesQueueLen: 0, 1735 }, 1736 "set capacity past max": { 1737 listName: "baz", 1738 request: &beta.UpdateListRequest{ 1739 List: &beta.List{ 1740 Name: "baz", 1741 Capacity: int64(1001), 1742 }, 1743 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, 1744 }, 1745 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1746 updateErr: true, 1747 updated: false, 1748 expectedUpdatesQueueLen: 0, 1749 }, 1750 // New test cases to test updating values 1751 "update values below capacity": { 1752 listName: "qux", 1753 request: &beta.UpdateListRequest{ 1754 List: &beta.List{ 1755 Name: "qux", 1756 Capacity: int64(100), 1757 Values: []string{"one", "two", "three", "four", "five", "six"}, 1758 }, 1759 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity", "values"}}, 1760 }, 1761 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four", "five", "six"}, Capacity: int64(100)}, 1762 updateErr: false, 1763 updated: true, 1764 expectedUpdatesQueueLen: 0, 1765 }, 1766 "update values above capacity": { 1767 listName: "quux", 1768 request: &beta.UpdateListRequest{ 1769 List: &beta.List{ 1770 Name: "quux", 1771 Capacity: int64(4), 1772 Values: []string{"one", "two", "three", "four", "five", "six"}, 1773 }, 1774 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity", "values"}}, 1775 }, 1776 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four"}, Capacity: int64(4)}, 1777 updateErr: false, 1778 updated: true, 1779 expectedUpdatesQueueLen: 0, 1780 }, 1781 } 1782 1783 // nolint:dupl // Linter errors on lines are duplicate of TestSDKServerAddListValue, TestSDKServerRemoveListValue 1784 for test, testCase := range fixtures { 1785 t.Run(test, func(t *testing.T) { 1786 m := agtesting.NewMocks() 1787 1788 gs := agonesv1.GameServer{ 1789 ObjectMeta: metav1.ObjectMeta{ 1790 Name: "test", Namespace: "default", ResourceVersion: "0", Generation: 1, 1791 }, 1792 Spec: agonesv1.GameServerSpec{ 1793 SdkServer: agonesv1.SdkServer{ 1794 LogLevel: "Debug", 1795 }, 1796 }, 1797 Status: agonesv1.GameServerStatus{ 1798 Lists: lists, 1799 }, 1800 } 1801 gs.ApplyDefaults() 1802 1803 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1804 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1805 }) 1806 1807 updated := make(chan map[string]agonesv1.ListStatus, 10) 1808 1809 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1810 gsCopy := patchGameServer(t, action, &gs) 1811 1812 updated <- gsCopy.Status.Lists 1813 return true, gsCopy, nil 1814 }) 1815 1816 ctx, cancel := context.WithCancel(context.Background()) 1817 sc, err := defaultSidecar(m) 1818 require.NoError(t, err) 1819 assert.NoError(t, sc.WaitForConnection(ctx)) 1820 sc.informerFactory.Start(ctx.Done()) 1821 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1822 1823 wg := sync.WaitGroup{} 1824 wg.Add(1) 1825 1826 go func() { 1827 err = sc.Run(ctx) 1828 assert.NoError(t, err) 1829 wg.Done() 1830 }() 1831 1832 // check initial value comes through 1833 require.Eventually(t, func() bool { 1834 list, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1835 return cmp.Equal(list.Values, []string{"one", "two", "three", "four"}) && list.Capacity == 100 && err == nil 1836 }, 10*time.Second, time.Second) 1837 1838 // Update the List 1839 _, err = sc.UpdateList(context.Background(), testCase.request) 1840 if testCase.updateErr { 1841 assert.Error(t, err) 1842 } else { 1843 assert.NoError(t, err) 1844 } 1845 1846 got, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1847 assert.NoError(t, err) 1848 assert.Equal(t, testCase.want.Values, got.Values) 1849 assert.Equal(t, testCase.want.Capacity, got.Capacity) 1850 1851 // on an update, confirm that the update hits the K8s api 1852 if testCase.updated { 1853 select { 1854 case value := <-updated: 1855 assert.NotNil(t, value[testCase.listName]) 1856 assert.Equal(t, 1857 agonesv1.ListStatus{Values: testCase.want.Values, Capacity: testCase.want.Capacity}, 1858 value[testCase.listName]) 1859 case <-time.After(10 * time.Second): 1860 assert.Fail(t, "List should have been patched") 1861 } 1862 } 1863 1864 // on an update, confirm that the update queue list contains the right amount of items 1865 glu := sc.gsListUpdatesLen() 1866 assert.Equal(t, testCase.expectedUpdatesQueueLen, glu) 1867 1868 cancel() 1869 wg.Wait() 1870 }) 1871 } 1872 } 1873 1874 func TestDeleteValues(t *testing.T) { 1875 t.Parallel() 1876 1877 list := []string{"pDtUOSwMys", "MIaQYdeONT", "ZTwRNgZfxk", "ybtlfzfJau", "JwoYseCCyU", "JQJXhknLeG", 1878 "KDmxroeFvi", "fguLESWvmr", "xRUFzgrtuE", "UwElufBLtA", "jAySktznPe", "JZZRLkAtpQ", "BzHLffHxLd", 1879 "KWOyTiXsGP", "CtHFOMotCK", "SBOFIJBoBu", "gjYoIQLbAk", "krWVhxssxR", "ZTqRMKAqSx", "oDalBXZckY", 1880 "ZxATCXhBHk", "MTwgrrHePq", "KNGxlixHYt", "taZswVczZU", "beoXmuxAHE", "VbiLLJrRVs", "GrIEuiUlkB", 1881 "IPJhGxiKWY", "gYXZtGeFyd", "GYvKpRRsfj", "jRldDqcuEd", "ffPeeHOtMW", "AoEMlXWXVI", "HIjLrcvIqx", 1882 "GztXdbnxqg", "zSyNSIyQbp", "lntxdkIjVt", "jOgkkkaytV", "uHMvVtWKoc", "hetOAzBePn", "KqqkCbGLjS", 1883 "OQHRRtqIlq", "KFyHqLSACF", "nMZTcGlgAz", "iriNEjRLmh", "PRdGOtnyIo", "JDNDFYCIGi", "acalItODHz", 1884 "HJjxJnZWEu", "dmFWypNcDY", "fokGntWpON", "tQLmmXfDNW", "ZvyARYuebj", "ipHGcRmfWt", "MpTXveRDRg", 1885 "xPMoVLWeyj", "tXWeapJxkh", "KCMSWWiPMq", "fwsVKiWLuv", "AkKUUqwaOB", "DDlrgoWHGq", "DHScNuprJo", 1886 "PRMEGliSBU", "kqwktsjCNb", "vDuQZIhUHp", "YoazMkShki", "IwmXsZvlcp", "CJdrVMsjiD", "xNLnNvLRMN", 1887 "nKxDYSOkKx", "MWnrxVVOgK", "YnTHFAunKs", "DzUpkUxpuV", "kNVqCzjRxS", "IzqYWHDloX", "LvlVEniBqp", 1888 "CmdFcgTgzM", "qmORqLRaKv", "MxMnLiGOsY", "vAiAorAIdu", "pfhhTRFcpp", "ByqwQcKJYQ", "mKaeTCghbC", 1889 "eJssFVxVSI", "PGFMEopXax", "pYKCWZzGMf", "wIeRbiOdkf", "EKlxOXvqdF", "qOOorODUsn", "rcVUwlHOME", 1890 "etoDkduCkv", "iqUxYYUfpz", "ALyMkpYnbY", "TwfhVKGaIE", "zWsXruOeOn", "gNEmlDWmnj", "gEvodaSjIJ", 1891 "kOjWgLKjKE", "ATxBnODCKg", "liMbkiUTAs"} 1892 1893 toDeleteMap := map[string]bool{"pDtUOSwMys": true, "beoXmuxAHE": true, "IPJhGxiKWY": true, 1894 "gYXZtGeFyd": true, "PRMEGliSBU": true, "kqwktsjCNb": true, "mKaeTCghbC": true, 1895 "PGFMEopXax": true, "qOOorODUsn": true, "rcVUwlHOME": true} 1896 1897 newList := deleteValues(list, toDeleteMap) 1898 assert.Equal(t, len(list)-len(toDeleteMap), len(newList)) 1899 } 1900 1901 func TestSDKServerPlayerCapacity(t *testing.T) { 1902 t.Parallel() 1903 agruntime.FeatureTestMutex.Lock() 1904 defer agruntime.FeatureTestMutex.Unlock() 1905 1906 err := agruntime.ParseFeatures(string(agruntime.FeaturePlayerTracking) + "=true") 1907 require.NoError(t, err, "Can not parse FeaturePlayerTracking feature") 1908 1909 m := agtesting.NewMocks() 1910 ctx, cancel := context.WithCancel(context.Background()) 1911 defer cancel() 1912 1913 sc, err := defaultSidecar(m) 1914 require.NoError(t, err) 1915 1916 gs := agonesv1.GameServer{ 1917 ObjectMeta: metav1.ObjectMeta{ 1918 Name: "test", Namespace: "default", ResourceVersion: "0", 1919 }, 1920 Spec: agonesv1.GameServerSpec{ 1921 SdkServer: agonesv1.SdkServer{ 1922 LogLevel: "Debug", 1923 }, 1924 Players: &agonesv1.PlayersSpec{ 1925 InitialCapacity: 10, 1926 }, 1927 }, 1928 } 1929 gs.ApplyDefaults() 1930 1931 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1932 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1933 }) 1934 1935 updated := make(chan int64, 10) 1936 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1937 1938 gsCopy := patchGameServer(t, action, &gs) 1939 1940 updated <- gsCopy.Status.Players.Capacity 1941 return true, gsCopy, nil 1942 }) 1943 1944 assert.NoError(t, sc.WaitForConnection(ctx)) 1945 sc.informerFactory.Start(ctx.Done()) 1946 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1947 1948 go func() { 1949 err = sc.Run(ctx) 1950 assert.NoError(t, err) 1951 }() 1952 1953 // check initial value comes through 1954 1955 // async, so check after a period 1956 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 1957 count, err := sc.GetPlayerCapacity(context.Background(), &alpha.Empty{}) 1958 return count.Count == 10, err 1959 }) 1960 assert.NoError(t, err) 1961 1962 // on update from the SDK, the value is available from GetPlayerCapacity 1963 _, err = sc.SetPlayerCapacity(context.Background(), &alpha.Count{Count: 20}) 1964 assert.NoError(t, err) 1965 1966 count, err := sc.GetPlayerCapacity(context.Background(), &alpha.Empty{}) 1967 require.NoError(t, err) 1968 assert.Equal(t, int64(20), count.Count) 1969 1970 // on an update, confirm that the update hits the K8s api 1971 select { 1972 case value := <-updated: 1973 assert.Equal(t, int64(20), value) 1974 case <-time.After(time.Minute): 1975 assert.Fail(t, "Should have been patched") 1976 } 1977 1978 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "PlayerCapacity Set to 20") 1979 } 1980 1981 func TestSDKServerPlayerConnectAndDisconnectWithoutPlayerTracking(t *testing.T) { 1982 t.Parallel() 1983 agruntime.FeatureTestMutex.Lock() 1984 defer agruntime.FeatureTestMutex.Unlock() 1985 1986 err := agruntime.ParseFeatures(string(agruntime.FeaturePlayerTracking) + "=false") 1987 require.NoError(t, err, "Can not parse FeaturePlayerTracking feature") 1988 1989 fixture := &agonesv1.GameServer{ 1990 ObjectMeta: metav1.ObjectMeta{ 1991 Name: "test", 1992 Namespace: "default", 1993 }, 1994 Status: agonesv1.GameServerStatus{ 1995 State: agonesv1.GameServerStateReady, 1996 }, 1997 } 1998 1999 m := agtesting.NewMocks() 2000 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2001 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*fixture}}, nil 2002 }) 2003 2004 ctx, cancel := context.WithCancel(context.Background()) 2005 defer cancel() 2006 2007 sc, err := defaultSidecar(m) 2008 require.NoError(t, err) 2009 2010 assert.NoError(t, sc.WaitForConnection(ctx)) 2011 sc.informerFactory.Start(ctx.Done()) 2012 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 2013 2014 go func() { 2015 err = sc.Run(ctx) 2016 assert.NoError(t, err) 2017 }() 2018 2019 // check initial value comes through 2020 // async, so check after a period 2021 e := &alpha.Empty{} 2022 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 2023 count, err := sc.GetPlayerCapacity(context.Background(), e) 2024 2025 assert.Nil(t, count) 2026 return false, err 2027 }) 2028 assert.Error(t, err) 2029 2030 count, err := sc.GetPlayerCount(context.Background(), e) 2031 require.Error(t, err) 2032 assert.Nil(t, count) 2033 2034 list, err := sc.GetConnectedPlayers(context.Background(), e) 2035 require.Error(t, err) 2036 assert.Nil(t, list) 2037 2038 id := &alpha.PlayerID{PlayerID: "test-player"} 2039 2040 ok, err := sc.PlayerConnect(context.Background(), id) 2041 require.Error(t, err) 2042 assert.False(t, ok.Bool) 2043 2044 ok, err = sc.IsPlayerConnected(context.Background(), id) 2045 require.Error(t, err) 2046 assert.False(t, ok.Bool) 2047 2048 ok, err = sc.PlayerDisconnect(context.Background(), id) 2049 require.Error(t, err) 2050 assert.False(t, ok.Bool) 2051 } 2052 2053 func TestSDKServerPlayerConnectAndDisconnect(t *testing.T) { 2054 t.Parallel() 2055 agruntime.FeatureTestMutex.Lock() 2056 defer agruntime.FeatureTestMutex.Unlock() 2057 2058 err := agruntime.ParseFeatures(string(agruntime.FeaturePlayerTracking) + "=true") 2059 require.NoError(t, err, "Can not parse FeaturePlayerTracking feature") 2060 2061 m := agtesting.NewMocks() 2062 ctx, cancel := context.WithCancel(context.Background()) 2063 defer cancel() 2064 2065 sc, err := defaultSidecar(m) 2066 require.NoError(t, err) 2067 2068 capacity := int64(3) 2069 gs := agonesv1.GameServer{ 2070 ObjectMeta: metav1.ObjectMeta{ 2071 Name: "test", Namespace: "default", ResourceVersion: "0", 2072 }, 2073 Spec: agonesv1.GameServerSpec{ 2074 SdkServer: agonesv1.SdkServer{ 2075 LogLevel: "Debug", 2076 }, 2077 // this is here to give us a reference, so we know when sc.Run() has completed. 2078 Players: &agonesv1.PlayersSpec{ 2079 InitialCapacity: capacity, 2080 }, 2081 }, 2082 } 2083 gs.ApplyDefaults() 2084 2085 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2086 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 2087 }) 2088 2089 updated := make(chan *agonesv1.PlayerStatus, 10) 2090 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 2091 gsCopy := patchGameServer(t, action, &gs) 2092 updated <- gsCopy.Status.Players 2093 return true, gsCopy, nil 2094 }) 2095 2096 assert.NoError(t, sc.WaitForConnection(ctx)) 2097 sc.informerFactory.Start(ctx.Done()) 2098 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 2099 2100 go func() { 2101 err = sc.Run(ctx) 2102 assert.NoError(t, err) 2103 }() 2104 2105 // check initial value comes through 2106 // async, so check after a period 2107 e := &alpha.Empty{} 2108 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 2109 count, err := sc.GetPlayerCapacity(context.Background(), e) 2110 return count.Count == capacity, err 2111 }) 2112 assert.NoError(t, err) 2113 2114 count, err := sc.GetPlayerCount(context.Background(), e) 2115 require.NoError(t, err) 2116 assert.Equal(t, int64(0), count.Count) 2117 2118 list, err := sc.GetConnectedPlayers(context.Background(), e) 2119 require.NoError(t, err) 2120 assert.Empty(t, list.List) 2121 2122 ok, err := sc.IsPlayerConnected(context.Background(), &alpha.PlayerID{PlayerID: "1"}) 2123 require.NoError(t, err) 2124 assert.False(t, ok.Bool, "no player connected yet") 2125 2126 // sdk value should always be correct, even if we send more than one update per second. 2127 for i := int64(0); i < capacity; i++ { 2128 token := strconv.FormatInt(i, 10) 2129 id := &alpha.PlayerID{PlayerID: token} 2130 ok, err := sc.PlayerConnect(context.Background(), id) 2131 require.NoError(t, err) 2132 assert.True(t, ok.Bool, "Player "+token+" should not yet be connected") 2133 2134 ok, err = sc.IsPlayerConnected(context.Background(), id) 2135 require.NoError(t, err) 2136 assert.True(t, ok.Bool, "Player "+token+" should be connected") 2137 } 2138 count, err = sc.GetPlayerCount(context.Background(), e) 2139 require.NoError(t, err) 2140 assert.Equal(t, capacity, count.Count) 2141 2142 list, err = sc.GetConnectedPlayers(context.Background(), e) 2143 require.NoError(t, err) 2144 assert.Equal(t, []string{"0", "1", "2"}, list.List) 2145 2146 // on an update, confirm that the update hits the K8s api, only once 2147 select { 2148 case value := <-updated: 2149 assert.Equal(t, capacity, value.Count) 2150 assert.Equal(t, []string{"0", "1", "2"}, value.IDs) 2151 case <-time.After(5 * time.Second): 2152 assert.Fail(t, "Should have been updated") 2153 } 2154 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "PlayerCount Set to 3") 2155 2156 // confirm there was only one update 2157 select { 2158 case <-updated: 2159 assert.Fail(t, "There should be only one update for the player connections") 2160 case <-time.After(2 * time.Second): 2161 } 2162 2163 // should return an error if we try and add another, since we're at capacity 2164 nopePlayer := &alpha.PlayerID{PlayerID: "nope"} 2165 _, err = sc.PlayerConnect(context.Background(), nopePlayer) 2166 assert.EqualError(t, err, "players are already at capacity") 2167 2168 // sdk value should always be correct, even if we send more than one update per second. 2169 // let's leave one player behind 2170 for i := int64(0); i < capacity-1; i++ { 2171 token := strconv.FormatInt(i, 10) 2172 id := &alpha.PlayerID{PlayerID: token} 2173 ok, err := sc.PlayerDisconnect(context.Background(), id) 2174 require.NoError(t, err) 2175 assert.Truef(t, ok.Bool, "Player %s should be disconnected", token) 2176 2177 ok, err = sc.IsPlayerConnected(context.Background(), id) 2178 require.NoError(t, err) 2179 assert.Falsef(t, ok.Bool, "Player %s should be connected", token) 2180 } 2181 count, err = sc.GetPlayerCount(context.Background(), e) 2182 require.NoError(t, err) 2183 assert.Equal(t, int64(1), count.Count) 2184 2185 list, err = sc.GetConnectedPlayers(context.Background(), e) 2186 require.NoError(t, err) 2187 assert.Equal(t, []string{"2"}, list.List) 2188 2189 // on an update, confirm that the update hits the K8s api, only once 2190 select { 2191 case value := <-updated: 2192 assert.Equal(t, int64(1), value.Count) 2193 assert.Equal(t, []string{"2"}, value.IDs) 2194 case <-time.After(5 * time.Second): 2195 assert.Fail(t, "Should have been updated") 2196 } 2197 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "PlayerCount Set to 1") 2198 2199 // confirm there was only one update 2200 select { 2201 case <-updated: 2202 assert.Fail(t, "There should be only one update for the player disconnections") 2203 case <-time.After(2 * time.Second): 2204 } 2205 2206 // last player is still there 2207 ok, err = sc.IsPlayerConnected(context.Background(), &alpha.PlayerID{PlayerID: "2"}) 2208 require.NoError(t, err) 2209 assert.True(t, ok.Bool, "Player 2 should be connected") 2210 2211 // finally, check idempotency of connect and disconnect 2212 id := &alpha.PlayerID{PlayerID: "2"} // only one left behind 2213 ok, err = sc.PlayerConnect(context.Background(), id) 2214 require.NoError(t, err) 2215 assert.False(t, ok.Bool, "Player 2 should already be connected") 2216 count, err = sc.GetPlayerCount(context.Background(), e) 2217 require.NoError(t, err) 2218 assert.Equal(t, int64(1), count.Count) 2219 2220 // no longer there. 2221 id.PlayerID = "0" 2222 ok, err = sc.PlayerDisconnect(context.Background(), id) 2223 require.NoError(t, err) 2224 assert.False(t, ok.Bool, "Player 2 should already be disconnected") 2225 count, err = sc.GetPlayerCount(context.Background(), e) 2226 require.NoError(t, err) 2227 assert.Equal(t, int64(1), count.Count) 2228 2229 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 2230 2231 list, err = sc.GetConnectedPlayers(context.Background(), e) 2232 require.NoError(t, err) 2233 assert.Equal(t, []string{"2"}, list.List) 2234 } 2235 2236 func TestSDKServerGracefulTerminationInterrupt(t *testing.T) { 2237 t.Parallel() 2238 agruntime.FeatureTestMutex.Lock() 2239 defer agruntime.FeatureTestMutex.Unlock() 2240 2241 m := agtesting.NewMocks() 2242 fakeWatch := watch.NewFake() 2243 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 2244 2245 gs := agonesv1.GameServer{ 2246 ObjectMeta: metav1.ObjectMeta{ 2247 Name: "test", Namespace: "default", 2248 }, 2249 Spec: agonesv1.GameServerSpec{Health: agonesv1.Health{Disabled: true}}, 2250 } 2251 gs.ApplyDefaults() 2252 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2253 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 2254 }) 2255 sc, err := defaultSidecar(m) 2256 assert.Nil(t, err) 2257 2258 ctx, cancel := context.WithCancel(context.Background()) 2259 sdkCtx := sc.NewSDKServerContext(ctx) 2260 assert.NoError(t, sc.WaitForConnection(sdkCtx)) 2261 sc.informerFactory.Start(sdkCtx.Done()) 2262 assert.True(t, cache.WaitForCacheSync(sdkCtx.Done(), sc.gameServerSynced)) 2263 2264 wg := sync.WaitGroup{} 2265 wg.Add(1) 2266 2267 go func() { 2268 err := sc.Run(sdkCtx) 2269 assert.Nil(t, err) 2270 wg.Done() 2271 }() 2272 2273 assertContextCancelled := func(expected error, timeout time.Duration, ctx context.Context) { 2274 select { 2275 case <-ctx.Done(): 2276 require.Equal(t, expected, ctx.Err()) 2277 case <-time.After(timeout): 2278 require.Fail(t, "should have gone to Reserved by now") 2279 } 2280 } 2281 2282 _, err = sc.Ready(sdkCtx, &sdk.Empty{}) 2283 require.NoError(t, err) 2284 assert.Equal(t, agonesv1.GameServerStateRequestReady, sc.gsState) 2285 // Mock interruption signal 2286 cancel() 2287 // Assert ctx is cancelled and sdkCtx is not cancelled 2288 assertContextCancelled(context.Canceled, 1*time.Second, ctx) 2289 assert.Nil(t, sdkCtx.Err()) 2290 // Assert gs is still requestReady 2291 assert.Equal(t, agonesv1.GameServerStateRequestReady, sc.gsState) 2292 // gs Shutdown 2293 gs.Status.State = agonesv1.GameServerStateShutdown 2294 fakeWatch.Modify(gs.DeepCopy()) 2295 2296 // Assert sdkCtx is cancelled after shutdown 2297 assertContextCancelled(context.Canceled, 1*time.Second, sdkCtx) 2298 wg.Wait() 2299 } 2300 2301 func TestSDKServerGracefulTerminationShutdown(t *testing.T) { 2302 t.Parallel() 2303 agruntime.FeatureTestMutex.Lock() 2304 defer agruntime.FeatureTestMutex.Unlock() 2305 2306 m := agtesting.NewMocks() 2307 fakeWatch := watch.NewFake() 2308 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 2309 2310 gs := agonesv1.GameServer{ 2311 ObjectMeta: metav1.ObjectMeta{ 2312 Name: "test", Namespace: "default", 2313 }, 2314 Spec: agonesv1.GameServerSpec{Health: agonesv1.Health{Disabled: true}}, 2315 } 2316 gs.ApplyDefaults() 2317 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2318 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 2319 }) 2320 2321 sc, err := defaultSidecar(m) 2322 assert.Nil(t, err) 2323 2324 ctx, cancel := context.WithCancel(context.Background()) 2325 sdkCtx := sc.NewSDKServerContext(ctx) 2326 assert.NoError(t, sc.WaitForConnection(sdkCtx)) 2327 sc.informerFactory.Start(sdkCtx.Done()) 2328 assert.True(t, cache.WaitForCacheSync(sdkCtx.Done(), sc.gameServerSynced)) 2329 2330 wg := sync.WaitGroup{} 2331 wg.Add(1) 2332 2333 go func() { 2334 err = sc.Run(sdkCtx) 2335 assert.Nil(t, err) 2336 wg.Done() 2337 }() 2338 2339 assertContextCancelled := func(expected error, timeout time.Duration, ctx context.Context) { 2340 select { 2341 case <-ctx.Done(): 2342 require.Equal(t, expected, ctx.Err()) 2343 case <-time.After(timeout): 2344 require.Fail(t, "should have gone to Reserved by now") 2345 } 2346 } 2347 2348 _, err = sc.Ready(sdkCtx, &sdk.Empty{}) 2349 require.NoError(t, err) 2350 assert.Equal(t, agonesv1.GameServerStateRequestReady, sc.gsState) 2351 // gs Shutdown 2352 gs.Status.State = agonesv1.GameServerStateShutdown 2353 fakeWatch.Modify(gs.DeepCopy()) 2354 2355 // assert none of the context have been cancelled 2356 assert.Nil(t, sdkCtx.Err()) 2357 assert.Nil(t, ctx.Err()) 2358 // Mock interruption signal 2359 cancel() 2360 // Assert ctx is cancelled and sdkCtx is not cancelled 2361 assertContextCancelled(context.Canceled, 2*time.Second, ctx) 2362 assertContextCancelled(context.Canceled, 2*time.Second, sdkCtx) 2363 wg.Wait() 2364 } 2365 2366 func TestSDKServerGracefulTerminationGameServerStateChannel(t *testing.T) { 2367 t.Parallel() 2368 agruntime.FeatureTestMutex.Lock() 2369 defer agruntime.FeatureTestMutex.Unlock() 2370 2371 m := agtesting.NewMocks() 2372 fakeWatch := watch.NewFake() 2373 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 2374 2375 gs := agonesv1.GameServer{ 2376 ObjectMeta: metav1.ObjectMeta{ 2377 Name: "test", Namespace: "default", 2378 }, 2379 Spec: agonesv1.GameServerSpec{Health: agonesv1.Health{Disabled: true}}, 2380 } 2381 gs.ApplyDefaults() 2382 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2383 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 2384 }) 2385 2386 sc, err := defaultSidecar(m) 2387 assert.Nil(t, err) 2388 2389 ctx, cancel := context.WithCancel(context.Background()) 2390 defer cancel() 2391 sdkCtx := sc.NewSDKServerContext(ctx) 2392 sc.informerFactory.Start(sdkCtx.Done()) 2393 assert.True(t, cache.WaitForCacheSync(sdkCtx.Done(), sc.gameServerSynced)) 2394 2395 gs.Status.State = agonesv1.GameServerStateShutdown 2396 fakeWatch.Modify(gs.DeepCopy()) 2397 2398 select { 2399 case current := <-sc.gsStateChannel: 2400 require.Equal(t, agonesv1.GameServerStateShutdown, current) 2401 case <-time.After(5 * time.Second): 2402 require.Fail(t, "should have gone to Shutdown by now") 2403 } 2404 } 2405 2406 func defaultSidecar(m agtesting.Mocks) (*SDKServer, error) { 2407 server, err := NewSDKServer("test", "default", m.KubeClient, m.AgonesClient, logrus.DebugLevel, 8080, 500*time.Millisecond) 2408 if err != nil { 2409 return server, err 2410 } 2411 2412 server.recorder = m.FakeRecorder 2413 return server, err 2414 } 2415 2416 func waitForMessage(sc *SDKServer) error { 2417 return wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Second, true, func(_ context.Context) (done bool, err error) { 2418 sc.healthMutex.RLock() 2419 defer sc.healthMutex.RUnlock() 2420 return sc.clock.Now().UTC() == sc.healthLastUpdated, nil 2421 }) 2422 } 2423 2424 func waitConnectedStreamCount(sc *SDKServer, count int) error { //nolint:unparam // Keep flexibility. 2425 return wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 2426 sc.streamMutex.RLock() 2427 defer sc.streamMutex.RUnlock() 2428 return len(sc.connectedStreams) == count, nil 2429 }) 2430 } 2431 2432 func asyncWatchGameServer(t *testing.T, sc *SDKServer, stream sdk.SDK_WatchGameServerServer) { 2433 // Note that WatchGameServer() uses getGameServer() and would block 2434 // if gsWaitForSync is not Done(). 2435 go func() { 2436 err := sc.WatchGameServer(&sdk.Empty{}, stream) 2437 require.NoError(t, err) 2438 }() 2439 }