agones.dev/agones@v1.53.0/pkg/sdkserver/sdkserver_test.go (about)

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