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  }