agones.dev/agones@v1.53.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 } 1134 1135 fixtures := map[string]struct { 1136 counterName string 1137 requests []*beta.UpdateCounterRequest 1138 want agonesv1.CounterStatus 1139 updateErrs []bool 1140 updated bool 1141 }{ 1142 "increment": { 1143 counterName: "widgets", 1144 requests: []*beta.UpdateCounterRequest{{ 1145 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1146 Name: "widgets", 1147 CountDiff: 9, 1148 }}}, 1149 want: agonesv1.CounterStatus{Count: int64(19), Capacity: int64(100)}, 1150 updateErrs: []bool{false}, 1151 updated: true, 1152 }, 1153 "increment illegal": { 1154 counterName: "foo", 1155 requests: []*beta.UpdateCounterRequest{{ 1156 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1157 Name: "foo", 1158 CountDiff: 100, 1159 }}}, 1160 want: agonesv1.CounterStatus{Count: int64(10), Capacity: int64(100)}, 1161 updateErrs: []bool{true}, 1162 updated: false, 1163 }, 1164 "decrement": { 1165 counterName: "bar", 1166 requests: []*beta.UpdateCounterRequest{{ 1167 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1168 Name: "bar", 1169 CountDiff: -1, 1170 }}}, 1171 want: agonesv1.CounterStatus{Count: int64(9), Capacity: int64(100)}, 1172 updateErrs: []bool{false}, 1173 updated: true, 1174 }, 1175 "decrement illegal": { 1176 counterName: "baz", 1177 requests: []*beta.UpdateCounterRequest{{ 1178 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1179 Name: "baz", 1180 CountDiff: -11, 1181 }}}, 1182 want: agonesv1.CounterStatus{Count: int64(10), Capacity: int64(100)}, 1183 updateErrs: []bool{true}, 1184 updated: false, 1185 }, 1186 "set capacity": { 1187 counterName: "bazel", 1188 requests: []*beta.UpdateCounterRequest{{ 1189 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1190 Name: "bazel", 1191 Capacity: wrapperspb.Int64(0), 1192 }}}, 1193 want: agonesv1.CounterStatus{Count: int64(0), Capacity: int64(0)}, 1194 updateErrs: []bool{false}, 1195 updated: true, 1196 }, 1197 "set capacity illegal": { 1198 counterName: "fish", 1199 requests: []*beta.UpdateCounterRequest{{ 1200 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1201 Name: "fish", 1202 Capacity: wrapperspb.Int64(-1), 1203 }}}, 1204 want: agonesv1.CounterStatus{Count: int64(10), Capacity: int64(100)}, 1205 updateErrs: []bool{true}, 1206 updated: false, 1207 }, 1208 "set count": { 1209 counterName: "onefish", 1210 requests: []*beta.UpdateCounterRequest{{ 1211 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1212 Name: "onefish", 1213 Count: wrapperspb.Int64(42), 1214 }}}, 1215 want: agonesv1.CounterStatus{Count: int64(42), Capacity: int64(100)}, 1216 updateErrs: []bool{false}, 1217 updated: true, 1218 }, 1219 "set count illegal": { 1220 counterName: "twofish", 1221 requests: []*beta.UpdateCounterRequest{{ 1222 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1223 Name: "twofish", 1224 Count: wrapperspb.Int64(101), 1225 }}}, 1226 want: agonesv1.CounterStatus{Count: int64(10), Capacity: int64(100)}, 1227 updateErrs: []bool{true}, 1228 updated: false, 1229 }, 1230 "increment past set capacity illegal": { 1231 counterName: "redfish", 1232 requests: []*beta.UpdateCounterRequest{{ 1233 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1234 Name: "redfish", 1235 Capacity: wrapperspb.Int64(0), 1236 }}, 1237 {CounterUpdateRequest: &beta.CounterUpdateRequest{ 1238 Name: "redfish", 1239 CountDiff: 1, 1240 }}}, 1241 want: agonesv1.CounterStatus{Count: int64(0), Capacity: int64(0)}, 1242 updateErrs: []bool{false, true}, 1243 updated: true, 1244 }, 1245 "decrement past set capacity illegal": { 1246 counterName: "bluefish", 1247 requests: []*beta.UpdateCounterRequest{{ 1248 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1249 Name: "bluefish", 1250 Capacity: wrapperspb.Int64(0), 1251 }}, 1252 {CounterUpdateRequest: &beta.CounterUpdateRequest{ 1253 Name: "bluefish", 1254 CountDiff: -1, 1255 }}}, 1256 want: agonesv1.CounterStatus{Count: int64(0), Capacity: int64(0)}, 1257 updateErrs: []bool{false, true}, 1258 updated: true, 1259 }, 1260 "setcapacity, setcount, and diffcount": { 1261 counterName: "fivefish", 1262 requests: []*beta.UpdateCounterRequest{{ 1263 CounterUpdateRequest: &beta.CounterUpdateRequest{ 1264 Name: "fivefish", 1265 Capacity: wrapperspb.Int64(25), 1266 Count: wrapperspb.Int64(0), 1267 CountDiff: 25, 1268 }}}, 1269 want: agonesv1.CounterStatus{Count: int64(25), Capacity: int64(25)}, 1270 updateErrs: []bool{false}, 1271 updated: true, 1272 }, 1273 } 1274 1275 for test, testCase := range fixtures { 1276 t.Run(test, func(t *testing.T) { 1277 m := agtesting.NewMocks() 1278 1279 gs := agonesv1.GameServer{ 1280 ObjectMeta: metav1.ObjectMeta{ 1281 Name: "test", Namespace: "default", ResourceVersion: "0", Generation: 1, 1282 }, 1283 Spec: agonesv1.GameServerSpec{ 1284 SdkServer: agonesv1.SdkServer{ 1285 LogLevel: "Debug", 1286 }, 1287 }, 1288 Status: agonesv1.GameServerStatus{ 1289 Counters: counters, 1290 }, 1291 } 1292 gs.ApplyDefaults() 1293 1294 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1295 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1296 }) 1297 1298 updated := make(chan map[string]agonesv1.CounterStatus, 10) 1299 1300 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1301 gsCopy := patchGameServer(t, action, &gs) 1302 1303 updated <- gsCopy.Status.Counters 1304 return true, gsCopy, nil 1305 }) 1306 1307 ctx, cancel := context.WithCancel(context.Background()) 1308 sc, err := defaultSidecar(m) 1309 require.NoError(t, err) 1310 assert.NoError(t, sc.WaitForConnection(ctx)) 1311 sc.informerFactory.Start(ctx.Done()) 1312 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1313 1314 wg := sync.WaitGroup{} 1315 wg.Add(1) 1316 1317 go func() { 1318 err = sc.Run(ctx) 1319 assert.NoError(t, err) 1320 wg.Done() 1321 }() 1322 1323 // check initial value comes through 1324 require.Eventually(t, func() bool { 1325 counter, err := sc.GetCounter(context.Background(), &beta.GetCounterRequest{Name: testCase.counterName}) 1326 return counter.Count == 10 && counter.Capacity == 100 && err == nil 1327 }, 10*time.Second, time.Second) 1328 1329 // Update the Counter 1330 for i, req := range testCase.requests { 1331 _, err = sc.UpdateCounter(context.Background(), req) 1332 if testCase.updateErrs[i] { 1333 assert.Error(t, err) 1334 } else { 1335 assert.NoError(t, err) 1336 } 1337 } 1338 1339 got, err := sc.GetCounter(context.Background(), &beta.GetCounterRequest{Name: testCase.counterName}) 1340 assert.NoError(t, err) 1341 assert.Equal(t, testCase.want.Count, got.Count) 1342 assert.Equal(t, testCase.want.Capacity, got.Capacity) 1343 1344 // on an update, confirm that the update hits the K8s api 1345 if testCase.updated { 1346 select { 1347 case value := <-updated: 1348 assert.NotNil(t, value[testCase.counterName]) 1349 assert.Equal(t, 1350 agonesv1.CounterStatus{Count: testCase.want.Count, Capacity: testCase.want.Capacity}, 1351 value[testCase.counterName]) 1352 case <-time.After(10 * time.Second): 1353 assert.Fail(t, "Counter should have been patched") 1354 } 1355 } 1356 1357 cancel() 1358 wg.Wait() 1359 }) 1360 } 1361 } 1362 1363 func TestSDKServerAddListValue(t *testing.T) { 1364 t.Parallel() 1365 agruntime.FeatureTestMutex.Lock() 1366 defer agruntime.FeatureTestMutex.Unlock() 1367 1368 err := agruntime.ParseFeatures(string(agruntime.FeatureCountsAndLists) + "=true") 1369 require.NoError(t, err, "Can not parse FeatureCountsAndLists feature") 1370 1371 lists := map[string]agonesv1.ListStatus{ 1372 "foo": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(10)}, 1373 "bar": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(10)}, 1374 "baz": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(10)}, 1375 } 1376 1377 fixtures := map[string]struct { 1378 listName string 1379 requests []*beta.AddListValueRequest 1380 want agonesv1.ListStatus 1381 updateErrs []bool 1382 updated bool 1383 expectedUpdatesQueueLen int 1384 }{ 1385 "Add value": { 1386 listName: "foo", 1387 requests: []*beta.AddListValueRequest{{Name: "foo", Value: "five"}}, 1388 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four", "five"}, Capacity: int64(10)}, 1389 updateErrs: []bool{false}, 1390 updated: true, 1391 expectedUpdatesQueueLen: 0, 1392 }, 1393 "Add multiple values including duplicates": { 1394 listName: "bar", 1395 requests: []*beta.AddListValueRequest{ 1396 {Name: "bar", Value: "five"}, 1397 {Name: "bar", Value: "one"}, 1398 {Name: "bar", Value: "five"}, 1399 {Name: "bar", Value: "zero"}, 1400 }, 1401 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four", "five", "zero"}, Capacity: int64(10)}, 1402 updateErrs: []bool{false, true, true, false}, 1403 updated: true, 1404 expectedUpdatesQueueLen: 0, 1405 }, 1406 "Add multiple values past capacity": { 1407 listName: "baz", 1408 requests: []*beta.AddListValueRequest{ 1409 {Name: "baz", Value: "five"}, 1410 {Name: "baz", Value: "six"}, 1411 {Name: "baz", Value: "seven"}, 1412 {Name: "baz", Value: "eight"}, 1413 {Name: "baz", Value: "nine"}, 1414 {Name: "baz", Value: "ten"}, 1415 {Name: "baz", Value: "eleven"}, 1416 }, 1417 want: agonesv1.ListStatus{ 1418 Values: []string{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"}, 1419 Capacity: int64(10), 1420 }, 1421 updateErrs: []bool{false, false, false, false, false, false, true}, 1422 updated: true, 1423 expectedUpdatesQueueLen: 0, 1424 }, 1425 } 1426 1427 // nolint:dupl // Linter errors on lines are duplicate of TestSDKServerUpdateList, TestSDKServerRemoveListValue 1428 for test, testCase := range fixtures { 1429 t.Run(test, func(t *testing.T) { 1430 m := agtesting.NewMocks() 1431 1432 gs := agonesv1.GameServer{ 1433 ObjectMeta: metav1.ObjectMeta{ 1434 Name: "test", Namespace: "default", ResourceVersion: "0", Generation: 1, 1435 }, 1436 Spec: agonesv1.GameServerSpec{ 1437 SdkServer: agonesv1.SdkServer{ 1438 LogLevel: "Debug", 1439 }, 1440 }, 1441 Status: agonesv1.GameServerStatus{ 1442 Lists: lists, 1443 }, 1444 } 1445 gs.ApplyDefaults() 1446 1447 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1448 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1449 }) 1450 1451 updated := make(chan map[string]agonesv1.ListStatus, 10) 1452 1453 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1454 gsCopy := patchGameServer(t, action, &gs) 1455 1456 updated <- gsCopy.Status.Lists 1457 return true, gsCopy, nil 1458 }) 1459 1460 ctx, cancel := context.WithCancel(context.Background()) 1461 sc, err := defaultSidecar(m) 1462 require.NoError(t, err) 1463 assert.NoError(t, sc.WaitForConnection(ctx)) 1464 sc.informerFactory.Start(ctx.Done()) 1465 require.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1466 sc.gsWaitForSync.Done() 1467 1468 // check initial value comes through 1469 require.Eventually(t, func() bool { 1470 list, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1471 return cmp.Equal(list.Values, []string{"one", "two", "three", "four"}) && list.Capacity == 10 && err == nil 1472 }, 10*time.Second, time.Second) 1473 1474 // Update the List 1475 for i, req := range testCase.requests { 1476 _, err = sc.AddListValue(context.Background(), req) 1477 if testCase.updateErrs[i] { 1478 assert.Error(t, err) 1479 } else { 1480 assert.NoError(t, err) 1481 } 1482 } 1483 1484 got, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1485 assert.NoError(t, err) 1486 assert.Equal(t, testCase.want.Values, got.Values) 1487 assert.Equal(t, testCase.want.Capacity, got.Capacity) 1488 1489 // start workerqueue processing at this point, so there is no chance of processing the above updates 1490 // earlier. 1491 sc.gsWaitForSync.Add(1) 1492 go func() { 1493 err = sc.Run(ctx) 1494 assert.NoError(t, err) 1495 }() 1496 1497 // on an update, confirm that the update hits the K8s api 1498 if testCase.updated { 1499 select { 1500 case value := <-updated: 1501 assert.NotNil(t, value[testCase.listName]) 1502 assert.Equal(t, 1503 agonesv1.ListStatus{Values: testCase.want.Values, Capacity: testCase.want.Capacity}, 1504 value[testCase.listName]) 1505 case <-time.After(10 * time.Second): 1506 assert.Fail(t, "List should have been patched") 1507 } 1508 } 1509 1510 // on an update, confirms that the update queue list contains the right amount of items 1511 glu := sc.gsListUpdatesLen() 1512 assert.Equal(t, testCase.expectedUpdatesQueueLen, glu) 1513 1514 cancel() 1515 }) 1516 } 1517 } 1518 1519 func TestSDKServerRemoveListValue(t *testing.T) { 1520 t.Parallel() 1521 agruntime.FeatureTestMutex.Lock() 1522 defer agruntime.FeatureTestMutex.Unlock() 1523 1524 err := agruntime.ParseFeatures(string(agruntime.FeatureCountsAndLists) + "=true") 1525 require.NoError(t, err, "Can not parse FeatureCountsAndLists feature") 1526 1527 lists := map[string]agonesv1.ListStatus{ 1528 "foo": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1529 "bar": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1530 "baz": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1531 } 1532 1533 fixtures := map[string]struct { 1534 listName string 1535 requests []*beta.RemoveListValueRequest 1536 want agonesv1.ListStatus 1537 updateErrs []bool 1538 updated bool 1539 expectedUpdatesQueueLen int 1540 }{ 1541 "Remove value": { 1542 listName: "foo", 1543 requests: []*beta.RemoveListValueRequest{{Name: "foo", Value: "two"}}, 1544 want: agonesv1.ListStatus{Values: []string{"one", "three", "four"}, Capacity: int64(100)}, 1545 updateErrs: []bool{false}, 1546 updated: true, 1547 expectedUpdatesQueueLen: 0, 1548 }, 1549 "Remove multiple values including duplicates": { 1550 listName: "bar", 1551 requests: []*beta.RemoveListValueRequest{ 1552 {Name: "bar", Value: "two"}, 1553 {Name: "bar", Value: "three"}, 1554 {Name: "bar", Value: "two"}, 1555 }, 1556 want: agonesv1.ListStatus{Values: []string{"one", "four"}, Capacity: int64(100)}, 1557 updateErrs: []bool{false, false, true}, 1558 updated: true, 1559 expectedUpdatesQueueLen: 0, 1560 }, 1561 "Remove all values": { 1562 listName: "baz", 1563 requests: []*beta.RemoveListValueRequest{ 1564 {Name: "baz", Value: "three"}, 1565 {Name: "baz", Value: "two"}, 1566 {Name: "baz", Value: "four"}, 1567 {Name: "baz", Value: "one"}, 1568 }, 1569 want: agonesv1.ListStatus{Values: []string{}, Capacity: int64(100)}, 1570 updateErrs: []bool{false, false, false, false}, 1571 updated: true, 1572 expectedUpdatesQueueLen: 0, 1573 }, 1574 } 1575 1576 // nolint:dupl // Linter errors on lines are duplicate of TestSDKServerUpdateList, TestSDKServerAddListValue 1577 for test, testCase := range fixtures { 1578 t.Run(test, func(t *testing.T) { 1579 m := agtesting.NewMocks() 1580 1581 gs := agonesv1.GameServer{ 1582 ObjectMeta: metav1.ObjectMeta{ 1583 Name: "test", Namespace: "default", ResourceVersion: "0", Generation: 1, 1584 }, 1585 Spec: agonesv1.GameServerSpec{ 1586 SdkServer: agonesv1.SdkServer{ 1587 LogLevel: "Debug", 1588 }, 1589 }, 1590 Status: agonesv1.GameServerStatus{ 1591 Lists: lists, 1592 }, 1593 } 1594 gs.ApplyDefaults() 1595 1596 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1597 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1598 }) 1599 1600 updated := make(chan map[string]agonesv1.ListStatus, 10) 1601 1602 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1603 gsCopy := patchGameServer(t, action, &gs) 1604 1605 updated <- gsCopy.Status.Lists 1606 return true, gsCopy, nil 1607 }) 1608 1609 ctx, cancel := context.WithCancel(context.Background()) 1610 sc, err := defaultSidecar(m) 1611 require.NoError(t, err) 1612 assert.NoError(t, sc.WaitForConnection(ctx)) 1613 sc.informerFactory.Start(ctx.Done()) 1614 require.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1615 sc.gsWaitForSync.Done() 1616 1617 // check initial value comes through 1618 require.Eventually(t, func() bool { 1619 list, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1620 return cmp.Equal(list.Values, []string{"one", "two", "three", "four"}) && list.Capacity == 100 && err == nil 1621 }, 10*time.Second, time.Second) 1622 1623 // Update the List 1624 for i, req := range testCase.requests { 1625 _, err = sc.RemoveListValue(context.Background(), req) 1626 if testCase.updateErrs[i] { 1627 assert.Error(t, err) 1628 } else { 1629 assert.NoError(t, err) 1630 } 1631 } 1632 1633 got, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1634 assert.NoError(t, err) 1635 assert.Equal(t, testCase.want.Values, got.Values) 1636 assert.Equal(t, testCase.want.Capacity, got.Capacity) 1637 1638 // start workerqueue processing at this point, so there is no chance of processing the above updates 1639 // earlier. 1640 sc.gsWaitForSync.Add(1) 1641 go func() { 1642 err = sc.Run(ctx) 1643 assert.NoError(t, err) 1644 }() 1645 1646 // on an update, confirm that the update hits the K8s api 1647 if testCase.updated { 1648 select { 1649 case value := <-updated: 1650 assert.NotNil(t, value[testCase.listName]) 1651 assert.Equal(t, 1652 agonesv1.ListStatus{Values: testCase.want.Values, Capacity: testCase.want.Capacity}, 1653 value[testCase.listName]) 1654 case <-time.After(10 * time.Second): 1655 assert.Fail(t, "List should have been patched") 1656 } 1657 } 1658 1659 // on an update, confirms that the update queue list contains the right amount of items 1660 glu := sc.gsListUpdatesLen() 1661 assert.Equal(t, testCase.expectedUpdatesQueueLen, glu) 1662 1663 cancel() 1664 }) 1665 } 1666 } 1667 1668 func TestSDKServerUpdateList(t *testing.T) { 1669 t.Parallel() 1670 agruntime.FeatureTestMutex.Lock() 1671 defer agruntime.FeatureTestMutex.Unlock() 1672 1673 err := agruntime.ParseFeatures(string(agruntime.FeatureCountsAndLists) + "=true") 1674 require.NoError(t, err, "Can not parse FeatureCountsAndLists feature") 1675 1676 lists := map[string]agonesv1.ListStatus{ 1677 "foo": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1678 "bar": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1679 "baz": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1680 "qux": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1681 "quux": {Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1682 } 1683 1684 fixtures := map[string]struct { 1685 listName string 1686 request *beta.UpdateListRequest 1687 want agonesv1.ListStatus 1688 updateErr bool 1689 updated bool 1690 expectedUpdatesQueueLen int 1691 }{ 1692 "set capacity to max": { 1693 listName: "foo", 1694 request: &beta.UpdateListRequest{ 1695 List: &beta.List{ 1696 Name: "foo", 1697 Capacity: int64(1000), 1698 }, 1699 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, 1700 }, 1701 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four"}, Capacity: int64(1000)}, 1702 updateErr: false, 1703 updated: true, 1704 expectedUpdatesQueueLen: 0, 1705 }, 1706 "set capacity to min values are truncated": { 1707 listName: "bar", 1708 request: &beta.UpdateListRequest{ 1709 List: &beta.List{ 1710 Name: "bar", 1711 Capacity: int64(0), 1712 }, 1713 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, 1714 }, 1715 want: agonesv1.ListStatus{Values: []string{}, Capacity: int64(0)}, 1716 updateErr: false, 1717 updated: true, 1718 expectedUpdatesQueueLen: 0, 1719 }, 1720 "set capacity past max": { 1721 listName: "baz", 1722 request: &beta.UpdateListRequest{ 1723 List: &beta.List{ 1724 Name: "baz", 1725 Capacity: int64(1001), 1726 }, 1727 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, 1728 }, 1729 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four"}, Capacity: int64(100)}, 1730 updateErr: true, 1731 updated: false, 1732 expectedUpdatesQueueLen: 0, 1733 }, 1734 // New test cases to test updating values 1735 "update values below capacity": { 1736 listName: "qux", 1737 request: &beta.UpdateListRequest{ 1738 List: &beta.List{ 1739 Name: "qux", 1740 Capacity: int64(100), 1741 Values: []string{"one", "two", "three", "four", "five", "six"}, 1742 }, 1743 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity", "values"}}, 1744 }, 1745 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four", "five", "six"}, Capacity: int64(100)}, 1746 updateErr: false, 1747 updated: true, 1748 expectedUpdatesQueueLen: 0, 1749 }, 1750 "update values above capacity": { 1751 listName: "quux", 1752 request: &beta.UpdateListRequest{ 1753 List: &beta.List{ 1754 Name: "quux", 1755 Capacity: int64(4), 1756 Values: []string{"one", "two", "three", "four", "five", "six"}, 1757 }, 1758 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity", "values"}}, 1759 }, 1760 want: agonesv1.ListStatus{Values: []string{"one", "two", "three", "four"}, Capacity: int64(4)}, 1761 updateErr: false, 1762 updated: true, 1763 expectedUpdatesQueueLen: 0, 1764 }, 1765 } 1766 1767 // nolint:dupl // Linter errors on lines are duplicate of TestSDKServerAddListValue, TestSDKServerRemoveListValue 1768 for test, testCase := range fixtures { 1769 t.Run(test, func(t *testing.T) { 1770 m := agtesting.NewMocks() 1771 1772 gs := agonesv1.GameServer{ 1773 ObjectMeta: metav1.ObjectMeta{ 1774 Name: "test", Namespace: "default", ResourceVersion: "0", Generation: 1, 1775 }, 1776 Spec: agonesv1.GameServerSpec{ 1777 SdkServer: agonesv1.SdkServer{ 1778 LogLevel: "Debug", 1779 }, 1780 }, 1781 Status: agonesv1.GameServerStatus{ 1782 Lists: lists, 1783 }, 1784 } 1785 gs.ApplyDefaults() 1786 1787 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1788 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1789 }) 1790 1791 updated := make(chan map[string]agonesv1.ListStatus, 10) 1792 1793 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1794 gsCopy := patchGameServer(t, action, &gs) 1795 1796 updated <- gsCopy.Status.Lists 1797 return true, gsCopy, nil 1798 }) 1799 1800 ctx, cancel := context.WithCancel(context.Background()) 1801 sc, err := defaultSidecar(m) 1802 require.NoError(t, err) 1803 assert.NoError(t, sc.WaitForConnection(ctx)) 1804 sc.informerFactory.Start(ctx.Done()) 1805 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1806 1807 wg := sync.WaitGroup{} 1808 wg.Add(1) 1809 1810 go func() { 1811 err = sc.Run(ctx) 1812 assert.NoError(t, err) 1813 wg.Done() 1814 }() 1815 1816 // check initial value comes through 1817 require.Eventually(t, func() bool { 1818 list, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1819 return cmp.Equal(list.Values, []string{"one", "two", "three", "four"}) && list.Capacity == 100 && err == nil 1820 }, 10*time.Second, time.Second) 1821 1822 // Update the List 1823 _, err = sc.UpdateList(context.Background(), testCase.request) 1824 if testCase.updateErr { 1825 assert.Error(t, err) 1826 } else { 1827 assert.NoError(t, err) 1828 } 1829 1830 got, err := sc.GetList(context.Background(), &beta.GetListRequest{Name: testCase.listName}) 1831 assert.NoError(t, err) 1832 assert.Equal(t, testCase.want.Values, got.Values) 1833 assert.Equal(t, testCase.want.Capacity, got.Capacity) 1834 1835 // on an update, confirm that the update hits the K8s api 1836 if testCase.updated { 1837 select { 1838 case value := <-updated: 1839 assert.NotNil(t, value[testCase.listName]) 1840 assert.Equal(t, 1841 agonesv1.ListStatus{Values: testCase.want.Values, Capacity: testCase.want.Capacity}, 1842 value[testCase.listName]) 1843 case <-time.After(10 * time.Second): 1844 assert.Fail(t, "List should have been patched") 1845 } 1846 } 1847 1848 // on an update, confirm that the update queue list contains the right amount of items 1849 glu := sc.gsListUpdatesLen() 1850 assert.Equal(t, testCase.expectedUpdatesQueueLen, glu) 1851 1852 cancel() 1853 wg.Wait() 1854 }) 1855 } 1856 } 1857 1858 func TestDeleteValues(t *testing.T) { 1859 t.Parallel() 1860 1861 list := []string{"pDtUOSwMys", "MIaQYdeONT", "ZTwRNgZfxk", "ybtlfzfJau", "JwoYseCCyU", "JQJXhknLeG", 1862 "KDmxroeFvi", "fguLESWvmr", "xRUFzgrtuE", "UwElufBLtA", "jAySktznPe", "JZZRLkAtpQ", "BzHLffHxLd", 1863 "KWOyTiXsGP", "CtHFOMotCK", "SBOFIJBoBu", "gjYoIQLbAk", "krWVhxssxR", "ZTqRMKAqSx", "oDalBXZckY", 1864 "ZxATCXhBHk", "MTwgrrHePq", "KNGxlixHYt", "taZswVczZU", "beoXmuxAHE", "VbiLLJrRVs", "GrIEuiUlkB", 1865 "IPJhGxiKWY", "gYXZtGeFyd", "GYvKpRRsfj", "jRldDqcuEd", "ffPeeHOtMW", "AoEMlXWXVI", "HIjLrcvIqx", 1866 "GztXdbnxqg", "zSyNSIyQbp", "lntxdkIjVt", "jOgkkkaytV", "uHMvVtWKoc", "hetOAzBePn", "KqqkCbGLjS", 1867 "OQHRRtqIlq", "KFyHqLSACF", "nMZTcGlgAz", "iriNEjRLmh", "PRdGOtnyIo", "JDNDFYCIGi", "acalItODHz", 1868 "HJjxJnZWEu", "dmFWypNcDY", "fokGntWpON", "tQLmmXfDNW", "ZvyARYuebj", "ipHGcRmfWt", "MpTXveRDRg", 1869 "xPMoVLWeyj", "tXWeapJxkh", "KCMSWWiPMq", "fwsVKiWLuv", "AkKUUqwaOB", "DDlrgoWHGq", "DHScNuprJo", 1870 "PRMEGliSBU", "kqwktsjCNb", "vDuQZIhUHp", "YoazMkShki", "IwmXsZvlcp", "CJdrVMsjiD", "xNLnNvLRMN", 1871 "nKxDYSOkKx", "MWnrxVVOgK", "YnTHFAunKs", "DzUpkUxpuV", "kNVqCzjRxS", "IzqYWHDloX", "LvlVEniBqp", 1872 "CmdFcgTgzM", "qmORqLRaKv", "MxMnLiGOsY", "vAiAorAIdu", "pfhhTRFcpp", "ByqwQcKJYQ", "mKaeTCghbC", 1873 "eJssFVxVSI", "PGFMEopXax", "pYKCWZzGMf", "wIeRbiOdkf", "EKlxOXvqdF", "qOOorODUsn", "rcVUwlHOME", 1874 "etoDkduCkv", "iqUxYYUfpz", "ALyMkpYnbY", "TwfhVKGaIE", "zWsXruOeOn", "gNEmlDWmnj", "gEvodaSjIJ", 1875 "kOjWgLKjKE", "ATxBnODCKg", "liMbkiUTAs"} 1876 1877 toDeleteMap := map[string]bool{"pDtUOSwMys": true, "beoXmuxAHE": true, "IPJhGxiKWY": true, 1878 "gYXZtGeFyd": true, "PRMEGliSBU": true, "kqwktsjCNb": true, "mKaeTCghbC": true, 1879 "PGFMEopXax": true, "qOOorODUsn": true, "rcVUwlHOME": true} 1880 1881 newList := deleteValues(list, toDeleteMap) 1882 assert.Equal(t, len(list)-len(toDeleteMap), len(newList)) 1883 } 1884 1885 func TestSDKServerPlayerCapacity(t *testing.T) { 1886 t.Parallel() 1887 agruntime.FeatureTestMutex.Lock() 1888 defer agruntime.FeatureTestMutex.Unlock() 1889 1890 err := agruntime.ParseFeatures(string(agruntime.FeaturePlayerTracking) + "=true") 1891 require.NoError(t, err, "Can not parse FeaturePlayerTracking feature") 1892 1893 m := agtesting.NewMocks() 1894 ctx, cancel := context.WithCancel(context.Background()) 1895 defer cancel() 1896 1897 sc, err := defaultSidecar(m) 1898 require.NoError(t, err) 1899 1900 gs := agonesv1.GameServer{ 1901 ObjectMeta: metav1.ObjectMeta{ 1902 Name: "test", Namespace: "default", ResourceVersion: "0", 1903 }, 1904 Spec: agonesv1.GameServerSpec{ 1905 SdkServer: agonesv1.SdkServer{ 1906 LogLevel: "Debug", 1907 }, 1908 Players: &agonesv1.PlayersSpec{ 1909 InitialCapacity: 10, 1910 }, 1911 }, 1912 } 1913 gs.ApplyDefaults() 1914 1915 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1916 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 1917 }) 1918 1919 updated := make(chan int64, 10) 1920 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 1921 1922 gsCopy := patchGameServer(t, action, &gs) 1923 1924 updated <- gsCopy.Status.Players.Capacity 1925 return true, gsCopy, nil 1926 }) 1927 1928 assert.NoError(t, sc.WaitForConnection(ctx)) 1929 sc.informerFactory.Start(ctx.Done()) 1930 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1931 1932 go func() { 1933 err = sc.Run(ctx) 1934 assert.NoError(t, err) 1935 }() 1936 1937 // check initial value comes through 1938 1939 // async, so check after a period 1940 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 1941 count, err := sc.GetPlayerCapacity(context.Background(), &alpha.Empty{}) 1942 return count.Count == 10, err 1943 }) 1944 assert.NoError(t, err) 1945 1946 // on update from the SDK, the value is available from GetPlayerCapacity 1947 _, err = sc.SetPlayerCapacity(context.Background(), &alpha.Count{Count: 20}) 1948 assert.NoError(t, err) 1949 1950 count, err := sc.GetPlayerCapacity(context.Background(), &alpha.Empty{}) 1951 require.NoError(t, err) 1952 assert.Equal(t, int64(20), count.Count) 1953 1954 // on an update, confirm that the update hits the K8s api 1955 select { 1956 case value := <-updated: 1957 assert.Equal(t, int64(20), value) 1958 case <-time.After(time.Minute): 1959 assert.Fail(t, "Should have been patched") 1960 } 1961 1962 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "PlayerCapacity Set to 20") 1963 } 1964 1965 func TestSDKServerPlayerConnectAndDisconnectWithoutPlayerTracking(t *testing.T) { 1966 t.Parallel() 1967 agruntime.FeatureTestMutex.Lock() 1968 defer agruntime.FeatureTestMutex.Unlock() 1969 1970 err := agruntime.ParseFeatures(string(agruntime.FeaturePlayerTracking) + "=false") 1971 require.NoError(t, err, "Can not parse FeaturePlayerTracking feature") 1972 1973 fixture := &agonesv1.GameServer{ 1974 ObjectMeta: metav1.ObjectMeta{ 1975 Name: "test", 1976 Namespace: "default", 1977 }, 1978 Status: agonesv1.GameServerStatus{ 1979 State: agonesv1.GameServerStateReady, 1980 }, 1981 } 1982 1983 m := agtesting.NewMocks() 1984 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 1985 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*fixture}}, nil 1986 }) 1987 1988 ctx, cancel := context.WithCancel(context.Background()) 1989 defer cancel() 1990 1991 sc, err := defaultSidecar(m) 1992 require.NoError(t, err) 1993 1994 assert.NoError(t, sc.WaitForConnection(ctx)) 1995 sc.informerFactory.Start(ctx.Done()) 1996 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 1997 1998 go func() { 1999 err = sc.Run(ctx) 2000 assert.NoError(t, err) 2001 }() 2002 2003 // check initial value comes through 2004 // async, so check after a period 2005 e := &alpha.Empty{} 2006 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 2007 count, err := sc.GetPlayerCapacity(context.Background(), e) 2008 2009 assert.Nil(t, count) 2010 return false, err 2011 }) 2012 assert.Error(t, err) 2013 2014 count, err := sc.GetPlayerCount(context.Background(), e) 2015 require.Error(t, err) 2016 assert.Nil(t, count) 2017 2018 list, err := sc.GetConnectedPlayers(context.Background(), e) 2019 require.Error(t, err) 2020 assert.Nil(t, list) 2021 2022 id := &alpha.PlayerID{PlayerID: "test-player"} 2023 2024 ok, err := sc.PlayerConnect(context.Background(), id) 2025 require.Error(t, err) 2026 assert.False(t, ok.Bool) 2027 2028 ok, err = sc.IsPlayerConnected(context.Background(), id) 2029 require.Error(t, err) 2030 assert.False(t, ok.Bool) 2031 2032 ok, err = sc.PlayerDisconnect(context.Background(), id) 2033 require.Error(t, err) 2034 assert.False(t, ok.Bool) 2035 } 2036 2037 func TestSDKServerPlayerConnectAndDisconnect(t *testing.T) { 2038 t.Parallel() 2039 agruntime.FeatureTestMutex.Lock() 2040 defer agruntime.FeatureTestMutex.Unlock() 2041 2042 err := agruntime.ParseFeatures(string(agruntime.FeaturePlayerTracking) + "=true") 2043 require.NoError(t, err, "Can not parse FeaturePlayerTracking feature") 2044 2045 m := agtesting.NewMocks() 2046 ctx, cancel := context.WithCancel(context.Background()) 2047 defer cancel() 2048 2049 sc, err := defaultSidecar(m) 2050 require.NoError(t, err) 2051 2052 capacity := int64(3) 2053 gs := agonesv1.GameServer{ 2054 ObjectMeta: metav1.ObjectMeta{ 2055 Name: "test", Namespace: "default", ResourceVersion: "0", 2056 }, 2057 Spec: agonesv1.GameServerSpec{ 2058 SdkServer: agonesv1.SdkServer{ 2059 LogLevel: "Debug", 2060 }, 2061 // this is here to give us a reference, so we know when sc.Run() has completed. 2062 Players: &agonesv1.PlayersSpec{ 2063 InitialCapacity: capacity, 2064 }, 2065 }, 2066 } 2067 gs.ApplyDefaults() 2068 2069 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2070 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 2071 }) 2072 2073 updated := make(chan *agonesv1.PlayerStatus, 10) 2074 m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { 2075 gsCopy := patchGameServer(t, action, &gs) 2076 updated <- gsCopy.Status.Players 2077 return true, gsCopy, nil 2078 }) 2079 2080 assert.NoError(t, sc.WaitForConnection(ctx)) 2081 sc.informerFactory.Start(ctx.Done()) 2082 assert.True(t, cache.WaitForCacheSync(ctx.Done(), sc.gameServerSynced)) 2083 2084 go func() { 2085 err = sc.Run(ctx) 2086 assert.NoError(t, err) 2087 }() 2088 2089 // check initial value comes through 2090 // async, so check after a period 2091 e := &alpha.Empty{} 2092 err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 2093 count, err := sc.GetPlayerCapacity(context.Background(), e) 2094 return count.Count == capacity, err 2095 }) 2096 assert.NoError(t, err) 2097 2098 count, err := sc.GetPlayerCount(context.Background(), e) 2099 require.NoError(t, err) 2100 assert.Equal(t, int64(0), count.Count) 2101 2102 list, err := sc.GetConnectedPlayers(context.Background(), e) 2103 require.NoError(t, err) 2104 assert.Empty(t, list.List) 2105 2106 ok, err := sc.IsPlayerConnected(context.Background(), &alpha.PlayerID{PlayerID: "1"}) 2107 require.NoError(t, err) 2108 assert.False(t, ok.Bool, "no player connected yet") 2109 2110 // sdk value should always be correct, even if we send more than one update per second. 2111 for i := int64(0); i < capacity; i++ { 2112 token := strconv.FormatInt(i, 10) 2113 id := &alpha.PlayerID{PlayerID: token} 2114 ok, err := sc.PlayerConnect(context.Background(), id) 2115 require.NoError(t, err) 2116 assert.True(t, ok.Bool, "Player "+token+" should not yet be connected") 2117 2118 ok, err = sc.IsPlayerConnected(context.Background(), id) 2119 require.NoError(t, err) 2120 assert.True(t, ok.Bool, "Player "+token+" should be connected") 2121 } 2122 count, err = sc.GetPlayerCount(context.Background(), e) 2123 require.NoError(t, err) 2124 assert.Equal(t, capacity, count.Count) 2125 2126 list, err = sc.GetConnectedPlayers(context.Background(), e) 2127 require.NoError(t, err) 2128 assert.Equal(t, []string{"0", "1", "2"}, list.List) 2129 2130 // on an update, confirm that the update hits the K8s api, only once 2131 select { 2132 case value := <-updated: 2133 assert.Equal(t, capacity, value.Count) 2134 assert.Equal(t, []string{"0", "1", "2"}, value.IDs) 2135 case <-time.After(5 * time.Second): 2136 assert.Fail(t, "Should have been updated") 2137 } 2138 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "PlayerCount Set to 3") 2139 2140 // confirm there was only one update 2141 select { 2142 case <-updated: 2143 assert.Fail(t, "There should be only one update for the player connections") 2144 case <-time.After(2 * time.Second): 2145 } 2146 2147 // should return an error if we try and add another, since we're at capacity 2148 nopePlayer := &alpha.PlayerID{PlayerID: "nope"} 2149 _, err = sc.PlayerConnect(context.Background(), nopePlayer) 2150 assert.EqualError(t, err, "players are already at capacity") 2151 2152 // sdk value should always be correct, even if we send more than one update per second. 2153 // let's leave one player behind 2154 for i := int64(0); i < capacity-1; i++ { 2155 token := strconv.FormatInt(i, 10) 2156 id := &alpha.PlayerID{PlayerID: token} 2157 ok, err := sc.PlayerDisconnect(context.Background(), id) 2158 require.NoError(t, err) 2159 assert.Truef(t, ok.Bool, "Player %s should be disconnected", token) 2160 2161 ok, err = sc.IsPlayerConnected(context.Background(), id) 2162 require.NoError(t, err) 2163 assert.Falsef(t, ok.Bool, "Player %s should be connected", token) 2164 } 2165 count, err = sc.GetPlayerCount(context.Background(), e) 2166 require.NoError(t, err) 2167 assert.Equal(t, int64(1), count.Count) 2168 2169 list, err = sc.GetConnectedPlayers(context.Background(), e) 2170 require.NoError(t, err) 2171 assert.Equal(t, []string{"2"}, list.List) 2172 2173 // on an update, confirm that the update hits the K8s api, only once 2174 select { 2175 case value := <-updated: 2176 assert.Equal(t, int64(1), value.Count) 2177 assert.Equal(t, []string{"2"}, value.IDs) 2178 case <-time.After(5 * time.Second): 2179 assert.Fail(t, "Should have been updated") 2180 } 2181 agtesting.AssertEventContains(t, m.FakeRecorder.Events, "PlayerCount Set to 1") 2182 2183 // confirm there was only one update 2184 select { 2185 case <-updated: 2186 assert.Fail(t, "There should be only one update for the player disconnections") 2187 case <-time.After(2 * time.Second): 2188 } 2189 2190 // last player is still there 2191 ok, err = sc.IsPlayerConnected(context.Background(), &alpha.PlayerID{PlayerID: "2"}) 2192 require.NoError(t, err) 2193 assert.True(t, ok.Bool, "Player 2 should be connected") 2194 2195 // finally, check idempotency of connect and disconnect 2196 id := &alpha.PlayerID{PlayerID: "2"} // only one left behind 2197 ok, err = sc.PlayerConnect(context.Background(), id) 2198 require.NoError(t, err) 2199 assert.False(t, ok.Bool, "Player 2 should already be connected") 2200 count, err = sc.GetPlayerCount(context.Background(), e) 2201 require.NoError(t, err) 2202 assert.Equal(t, int64(1), count.Count) 2203 2204 // no longer there. 2205 id.PlayerID = "0" 2206 ok, err = sc.PlayerDisconnect(context.Background(), id) 2207 require.NoError(t, err) 2208 assert.False(t, ok.Bool, "Player 2 should already be disconnected") 2209 count, err = sc.GetPlayerCount(context.Background(), e) 2210 require.NoError(t, err) 2211 assert.Equal(t, int64(1), count.Count) 2212 2213 agtesting.AssertNoEvent(t, m.FakeRecorder.Events) 2214 2215 list, err = sc.GetConnectedPlayers(context.Background(), e) 2216 require.NoError(t, err) 2217 assert.Equal(t, []string{"2"}, list.List) 2218 } 2219 2220 func TestSDKServerGracefulTerminationInterrupt(t *testing.T) { 2221 t.Parallel() 2222 agruntime.FeatureTestMutex.Lock() 2223 defer agruntime.FeatureTestMutex.Unlock() 2224 2225 m := agtesting.NewMocks() 2226 fakeWatch := watch.NewFake() 2227 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 2228 2229 gs := agonesv1.GameServer{ 2230 ObjectMeta: metav1.ObjectMeta{ 2231 Name: "test", Namespace: "default", 2232 }, 2233 Spec: agonesv1.GameServerSpec{Health: agonesv1.Health{Disabled: true}}, 2234 } 2235 gs.ApplyDefaults() 2236 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2237 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 2238 }) 2239 sc, err := defaultSidecar(m) 2240 assert.Nil(t, err) 2241 2242 ctx, cancel := context.WithCancel(context.Background()) 2243 sdkCtx := sc.NewSDKServerContext(ctx) 2244 assert.NoError(t, sc.WaitForConnection(sdkCtx)) 2245 sc.informerFactory.Start(sdkCtx.Done()) 2246 assert.True(t, cache.WaitForCacheSync(sdkCtx.Done(), sc.gameServerSynced)) 2247 2248 wg := sync.WaitGroup{} 2249 wg.Add(1) 2250 2251 go func() { 2252 err := sc.Run(sdkCtx) 2253 assert.Nil(t, err) 2254 wg.Done() 2255 }() 2256 2257 assertContextCancelled := func(expected error, timeout time.Duration, ctx context.Context) { 2258 select { 2259 case <-ctx.Done(): 2260 require.Equal(t, expected, ctx.Err()) 2261 case <-time.After(timeout): 2262 require.Fail(t, "should have gone to Reserved by now") 2263 } 2264 } 2265 2266 _, err = sc.Ready(sdkCtx, &sdk.Empty{}) 2267 require.NoError(t, err) 2268 assert.Equal(t, agonesv1.GameServerStateRequestReady, sc.gsState) 2269 // Mock interruption signal 2270 cancel() 2271 // Assert ctx is cancelled and sdkCtx is not cancelled 2272 assertContextCancelled(context.Canceled, 1*time.Second, ctx) 2273 assert.Nil(t, sdkCtx.Err()) 2274 // Assert gs is still requestReady 2275 assert.Equal(t, agonesv1.GameServerStateRequestReady, sc.gsState) 2276 // gs Shutdown 2277 gs.Status.State = agonesv1.GameServerStateShutdown 2278 fakeWatch.Modify(gs.DeepCopy()) 2279 2280 // Assert sdkCtx is cancelled after shutdown 2281 assertContextCancelled(context.Canceled, 1*time.Second, sdkCtx) 2282 wg.Wait() 2283 } 2284 2285 func TestSDKServerGracefulTerminationShutdown(t *testing.T) { 2286 t.Parallel() 2287 agruntime.FeatureTestMutex.Lock() 2288 defer agruntime.FeatureTestMutex.Unlock() 2289 2290 m := agtesting.NewMocks() 2291 fakeWatch := watch.NewFake() 2292 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 2293 2294 gs := agonesv1.GameServer{ 2295 ObjectMeta: metav1.ObjectMeta{ 2296 Name: "test", Namespace: "default", 2297 }, 2298 Spec: agonesv1.GameServerSpec{Health: agonesv1.Health{Disabled: true}}, 2299 } 2300 gs.ApplyDefaults() 2301 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2302 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 2303 }) 2304 2305 sc, err := defaultSidecar(m) 2306 assert.Nil(t, err) 2307 2308 ctx, cancel := context.WithCancel(context.Background()) 2309 sdkCtx := sc.NewSDKServerContext(ctx) 2310 assert.NoError(t, sc.WaitForConnection(sdkCtx)) 2311 sc.informerFactory.Start(sdkCtx.Done()) 2312 assert.True(t, cache.WaitForCacheSync(sdkCtx.Done(), sc.gameServerSynced)) 2313 2314 wg := sync.WaitGroup{} 2315 wg.Add(1) 2316 2317 go func() { 2318 err = sc.Run(sdkCtx) 2319 assert.Nil(t, err) 2320 wg.Done() 2321 }() 2322 2323 assertContextCancelled := func(expected error, timeout time.Duration, ctx context.Context) { 2324 select { 2325 case <-ctx.Done(): 2326 require.Equal(t, expected, ctx.Err()) 2327 case <-time.After(timeout): 2328 require.Fail(t, "should have gone to Reserved by now") 2329 } 2330 } 2331 2332 _, err = sc.Ready(sdkCtx, &sdk.Empty{}) 2333 require.NoError(t, err) 2334 assert.Equal(t, agonesv1.GameServerStateRequestReady, sc.gsState) 2335 // gs Shutdown 2336 gs.Status.State = agonesv1.GameServerStateShutdown 2337 fakeWatch.Modify(gs.DeepCopy()) 2338 2339 // assert none of the context have been cancelled 2340 assert.Nil(t, sdkCtx.Err()) 2341 assert.Nil(t, ctx.Err()) 2342 // Mock interruption signal 2343 cancel() 2344 // Assert ctx is cancelled and sdkCtx is not cancelled 2345 assertContextCancelled(context.Canceled, 2*time.Second, ctx) 2346 assertContextCancelled(context.Canceled, 2*time.Second, sdkCtx) 2347 wg.Wait() 2348 } 2349 2350 func TestSDKServerGracefulTerminationGameServerStateChannel(t *testing.T) { 2351 t.Parallel() 2352 agruntime.FeatureTestMutex.Lock() 2353 defer agruntime.FeatureTestMutex.Unlock() 2354 2355 m := agtesting.NewMocks() 2356 fakeWatch := watch.NewFake() 2357 m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) 2358 2359 gs := agonesv1.GameServer{ 2360 ObjectMeta: metav1.ObjectMeta{ 2361 Name: "test", Namespace: "default", 2362 }, 2363 Spec: agonesv1.GameServerSpec{Health: agonesv1.Health{Disabled: true}}, 2364 } 2365 gs.ApplyDefaults() 2366 m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) { 2367 return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs.DeepCopy()}}, nil 2368 }) 2369 2370 sc, err := defaultSidecar(m) 2371 assert.Nil(t, err) 2372 2373 ctx, cancel := context.WithCancel(context.Background()) 2374 defer cancel() 2375 sdkCtx := sc.NewSDKServerContext(ctx) 2376 sc.informerFactory.Start(sdkCtx.Done()) 2377 assert.True(t, cache.WaitForCacheSync(sdkCtx.Done(), sc.gameServerSynced)) 2378 2379 gs.Status.State = agonesv1.GameServerStateShutdown 2380 fakeWatch.Modify(gs.DeepCopy()) 2381 2382 select { 2383 case current := <-sc.gsStateChannel: 2384 require.Equal(t, agonesv1.GameServerStateShutdown, current) 2385 case <-time.After(5 * time.Second): 2386 require.Fail(t, "should have gone to Shutdown by now") 2387 } 2388 } 2389 2390 func defaultSidecar(m agtesting.Mocks) (*SDKServer, error) { 2391 server, err := NewSDKServer("test", "default", m.KubeClient, m.AgonesClient, logrus.DebugLevel, 8080, 500*time.Millisecond) 2392 if err != nil { 2393 return server, err 2394 } 2395 2396 server.recorder = m.FakeRecorder 2397 return server, err 2398 } 2399 2400 func waitForMessage(sc *SDKServer) error { 2401 return wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Second, true, func(_ context.Context) (done bool, err error) { 2402 sc.healthMutex.RLock() 2403 defer sc.healthMutex.RUnlock() 2404 return sc.clock.Now().UTC() == sc.healthLastUpdated, nil 2405 }) 2406 } 2407 2408 func waitConnectedStreamCount(sc *SDKServer, count int) error { //nolint:unparam // Keep flexibility. 2409 return wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) { 2410 sc.streamMutex.RLock() 2411 defer sc.streamMutex.RUnlock() 2412 return len(sc.connectedStreams) == count, nil 2413 }) 2414 } 2415 2416 func asyncWatchGameServer(t *testing.T, sc *SDKServer, stream sdk.SDK_WatchGameServerServer) { 2417 // Note that WatchGameServer() uses getGameServer() and would block 2418 // if gsWaitForSync is not Done(). 2419 go func() { 2420 err := sc.WatchGameServer(&sdk.Empty{}, stream) 2421 require.NoError(t, err) 2422 }() 2423 }