agones.dev/agones@v1.54.0/pkg/gameserverallocations/allocator_test.go (about)

     1  // Copyright 2021 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 gameserverallocations
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"agones.dev/agones/pkg/apis"
    25  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    26  	allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
    27  	multiclusterv1 "agones.dev/agones/pkg/apis/multicluster/v1"
    28  	"agones.dev/agones/pkg/gameservers"
    29  	agtesting "agones.dev/agones/pkg/testing"
    30  	"agones.dev/agones/pkg/util/runtime"
    31  	"agones.dev/agones/test/e2e/framework"
    32  	"github.com/heptiolabs/healthcheck"
    33  	"github.com/sirupsen/logrus"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  	corev1 "k8s.io/api/core/v1"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	k8sruntime "k8s.io/apimachinery/pkg/runtime"
    39  	"k8s.io/apimachinery/pkg/util/wait"
    40  	"k8s.io/apimachinery/pkg/watch"
    41  	k8stesting "k8s.io/client-go/testing"
    42  	"k8s.io/client-go/tools/cache"
    43  )
    44  
    45  func TestAllocatorAllocate(t *testing.T) {
    46  	t.Parallel()
    47  
    48  	// TODO: remove when `CountsAndLists` feature flag is moved to stable.
    49  	runtime.FeatureTestMutex.Lock()
    50  	defer runtime.FeatureTestMutex.Unlock()
    51  
    52  	f, gsList := defaultFixtures(4)
    53  	a, m := newFakeAllocator()
    54  	n := metav1.Now()
    55  	labels := map[string]string{"mode": "deathmatch"}
    56  	annotations := map[string]string{"map": "searide"}
    57  	fam := allocationv1.MetaPatch{Labels: labels, Annotations: annotations}
    58  
    59  	gsList[3].ObjectMeta.DeletionTimestamp = &n
    60  
    61  	m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
    62  		return true, &agonesv1.GameServerList{Items: gsList}, nil
    63  	})
    64  
    65  	updated := false
    66  	gsWatch := watch.NewFake()
    67  	m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
    68  	m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
    69  		ua := action.(k8stesting.UpdateAction)
    70  		gs := ua.GetObject().(*agonesv1.GameServer)
    71  
    72  		updated = true
    73  		assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
    74  		gsWatch.Modify(gs)
    75  
    76  		return true, gs, nil
    77  	})
    78  
    79  	ctx, cancel := agtesting.StartInformers(m, a.allocationCache.gameServerSynced)
    80  	defer cancel()
    81  
    82  	require.NoError(t, a.Run(ctx))
    83  	// wait for it to be up and running
    84  	err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) {
    85  		return a.allocationCache.workerqueue.RunCount() == 1, nil
    86  	})
    87  	require.NoError(t, err)
    88  
    89  	gsa := allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{Name: "gsa-1", Namespace: defaultNs},
    90  		Spec: allocationv1.GameServerAllocationSpec{
    91  			Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}}},
    92  			MetaPatch: fam,
    93  		}}
    94  	gsa.ApplyDefaults()
    95  	errs := gsa.Validate()
    96  	require.Len(t, errs, 0)
    97  
    98  	gs, err := a.allocate(ctx, &gsa)
    99  	require.NoError(t, err)
   100  	assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   101  	assert.True(t, updated)
   102  	for key, value := range fam.Labels {
   103  		v, ok := gs.ObjectMeta.Labels[key]
   104  		assert.True(t, ok)
   105  		assert.Equal(t, v, value)
   106  	}
   107  	for key, value := range fam.Annotations {
   108  		v, ok := gs.ObjectMeta.Annotations[key]
   109  		assert.True(t, ok)
   110  		assert.Equal(t, v, value)
   111  	}
   112  
   113  	updated = false
   114  	gs, err = a.allocate(ctx, &gsa)
   115  	require.NoError(t, err)
   116  	assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   117  	assert.True(t, updated)
   118  
   119  	updated = false
   120  	gs, err = a.allocate(ctx, &gsa)
   121  	require.NoError(t, err)
   122  	assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   123  	assert.True(t, updated)
   124  
   125  	updated = false
   126  	_, err = a.allocate(ctx, &gsa)
   127  	require.Error(t, err)
   128  	assert.Equal(t, ErrNoGameServer, err)
   129  	assert.False(t, updated)
   130  }
   131  
   132  func TestAllocatorAllocatePriority(t *testing.T) {
   133  	t.Parallel()
   134  
   135  	// TODO: remove when `CountsAndLists` feature flag is moved to stable.
   136  	runtime.FeatureTestMutex.Lock()
   137  	defer runtime.FeatureTestMutex.Unlock()
   138  
   139  	run := func(t *testing.T, name string, test func(t *testing.T, a *Allocator, gas *allocationv1.GameServerAllocation)) {
   140  		f, gsList := defaultFixtures(4)
   141  		a, m := newFakeAllocator()
   142  
   143  		gsList[0].Status.NodeName = n1
   144  		gsList[1].Status.NodeName = n2
   145  		gsList[2].Status.NodeName = n1
   146  		gsList[3].Status.NodeName = n1
   147  
   148  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   149  			return true, &agonesv1.GameServerList{Items: gsList}, nil
   150  		})
   151  
   152  		gsWatch := watch.NewFake()
   153  		m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
   154  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   155  			ua := action.(k8stesting.UpdateAction)
   156  			gs := ua.GetObject().(*agonesv1.GameServer)
   157  			gsWatch.Modify(gs)
   158  
   159  			return true, gs, nil
   160  		})
   161  
   162  		ctx, cancel := agtesting.StartInformers(m, a.allocationCache.gameServerSynced)
   163  		defer cancel()
   164  
   165  		require.NoError(t, a.Run(ctx))
   166  		// wait for it to be up and running
   167  		err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) {
   168  			return a.allocationCache.workerqueue.RunCount() == 1, nil
   169  		})
   170  		require.NoError(t, err)
   171  
   172  		gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{Name: "fa-1", Namespace: defaultNs},
   173  			Spec: allocationv1.GameServerAllocationSpec{
   174  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}}},
   175  			}}
   176  		gsa.ApplyDefaults()
   177  		errs := gsa.Validate()
   178  		require.Len(t, errs, 0)
   179  
   180  		t.Run(name, func(t *testing.T) {
   181  			test(t, a, gsa.DeepCopy())
   182  		})
   183  	}
   184  
   185  	run(t, "packed", func(t *testing.T, a *Allocator, gas *allocationv1.GameServerAllocation) {
   186  		ctx := context.Background()
   187  		// priority should be node1, then node2
   188  		gs1, err := a.allocate(ctx, gas)
   189  		assert.NoError(t, err)
   190  		assert.Equal(t, n1, gs1.Status.NodeName)
   191  
   192  		gs2, err := a.allocate(ctx, gas)
   193  		assert.NoError(t, err)
   194  		assert.Equal(t, n1, gs2.Status.NodeName)
   195  		assert.NotEqual(t, gs1.ObjectMeta.Name, gs2.ObjectMeta.Name)
   196  
   197  		gs3, err := a.allocate(ctx, gas)
   198  		assert.NoError(t, err)
   199  		assert.Equal(t, n1, gs3.Status.NodeName)
   200  		assert.NotContains(t, []string{gs1.ObjectMeta.Name, gs2.ObjectMeta.Name}, gs3.ObjectMeta.Name)
   201  
   202  		gs4, err := a.allocate(ctx, gas)
   203  		assert.NoError(t, err)
   204  		assert.Equal(t, n2, gs4.Status.NodeName)
   205  		assert.NotContains(t, []string{gs1.ObjectMeta.Name, gs2.ObjectMeta.Name, gs3.ObjectMeta.Name}, gs4.ObjectMeta.Name)
   206  
   207  		// should have none left
   208  		_, err = a.allocate(ctx, gas)
   209  		assert.Equal(t, err, ErrNoGameServer)
   210  	})
   211  
   212  	run(t, "distributed", func(t *testing.T, a *Allocator, gas *allocationv1.GameServerAllocation) {
   213  		// make a copy, to avoid the race check
   214  		gas = gas.DeepCopy()
   215  		gas.Spec.Scheduling = apis.Distributed
   216  
   217  		// distributed is randomised, so no set pattern
   218  		ctx := context.Background()
   219  
   220  		gs1, err := a.allocate(ctx, gas)
   221  		assert.NoError(t, err)
   222  
   223  		gs2, err := a.allocate(ctx, gas)
   224  		assert.NoError(t, err)
   225  		assert.NotEqual(t, gs1.ObjectMeta.Name, gs2.ObjectMeta.Name)
   226  
   227  		gs3, err := a.allocate(ctx, gas)
   228  		assert.NoError(t, err)
   229  		assert.NotContains(t, []string{gs1.ObjectMeta.Name, gs2.ObjectMeta.Name}, gs3.ObjectMeta.Name)
   230  
   231  		gs4, err := a.allocate(ctx, gas)
   232  		assert.NoError(t, err)
   233  		assert.NotContains(t, []string{gs1.ObjectMeta.Name, gs2.ObjectMeta.Name, gs3.ObjectMeta.Name}, gs4.ObjectMeta.Name)
   234  
   235  		// should have none left
   236  		_, err = a.allocate(ctx, gas)
   237  		assert.Equal(t, err, ErrNoGameServer)
   238  	})
   239  }
   240  
   241  func TestAllocatorApplyAllocationToGameServer(t *testing.T) {
   242  	t.Parallel()
   243  	m := agtesting.NewMocks()
   244  	ctx := context.Background()
   245  
   246  	m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   247  		ua := action.(k8stesting.UpdateAction)
   248  		gs := ua.GetObject().(*agonesv1.GameServer)
   249  		return true, gs, nil
   250  	})
   251  
   252  	allocator := NewAllocator(m.AgonesInformerFactory.Multicluster().V1().GameServerAllocationPolicies(),
   253  		m.KubeInformerFactory.Core().V1().Secrets(),
   254  		m.AgonesClient.AgonesV1(), m.KubeClient,
   255  		NewAllocationCache(m.AgonesInformerFactory.Agones().V1().GameServers(), gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory), healthcheck.NewHandler()),
   256  		time.Second, 5*time.Second, 500*time.Millisecond,
   257  	)
   258  
   259  	gs, err := allocator.applyAllocationToGameServer(ctx, allocationv1.MetaPatch{}, &agonesv1.GameServer{}, &allocationv1.GameServerAllocation{})
   260  	assert.NoError(t, err)
   261  	assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   262  	assert.NotNil(t, gs.ObjectMeta.Annotations["agones.dev/last-allocated"])
   263  	var ts time.Time
   264  	assert.NoError(t, ts.UnmarshalText([]byte(gs.ObjectMeta.Annotations[LastAllocatedAnnotationKey])))
   265  
   266  	gs, err = allocator.applyAllocationToGameServer(ctx, allocationv1.MetaPatch{Labels: map[string]string{"foo": "bar"}}, &agonesv1.GameServer{}, &allocationv1.GameServerAllocation{})
   267  	assert.NoError(t, err)
   268  	assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   269  	assert.Equal(t, "bar", gs.ObjectMeta.Labels["foo"])
   270  	assert.NotNil(t, gs.ObjectMeta.Annotations["agones.dev/last-allocated"])
   271  
   272  	gs, err = allocator.applyAllocationToGameServer(ctx,
   273  		allocationv1.MetaPatch{Labels: map[string]string{"foo": "bar"}, Annotations: map[string]string{"bar": "foo"}},
   274  		&agonesv1.GameServer{}, &allocationv1.GameServerAllocation{})
   275  	assert.NoError(t, err)
   276  	assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   277  	assert.Equal(t, "bar", gs.ObjectMeta.Labels["foo"])
   278  	assert.Equal(t, "foo", gs.ObjectMeta.Annotations["bar"])
   279  	assert.NotNil(t, gs.ObjectMeta.Annotations[LastAllocatedAnnotationKey])
   280  }
   281  
   282  func TestAllocatorApplyAllocationToGameServerCountsListsActions(t *testing.T) {
   283  	t.Parallel()
   284  
   285  	m := agtesting.NewMocks()
   286  	ctx := context.Background()
   287  	mp := allocationv1.MetaPatch{}
   288  
   289  	m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   290  		ua := action.(k8stesting.UpdateAction)
   291  		gs := ua.GetObject().(*agonesv1.GameServer)
   292  		return true, gs, nil
   293  	})
   294  
   295  	allocator := NewAllocator(m.AgonesInformerFactory.Multicluster().V1().GameServerAllocationPolicies(),
   296  		m.KubeInformerFactory.Core().V1().Secrets(),
   297  		m.AgonesClient.AgonesV1(), m.KubeClient,
   298  		NewAllocationCache(m.AgonesInformerFactory.Agones().V1().GameServers(), gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory), healthcheck.NewHandler()),
   299  		time.Second, 5*time.Second, 500*time.Millisecond,
   300  	)
   301  
   302  	ONE := int64(1)
   303  	FORTY := int64(40)
   304  	THOUSAND := int64(1000)
   305  	INCREMENT := "Increment"
   306  	READY := agonesv1.GameServerStateReady
   307  
   308  	gs1 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, UID: "1"},
   309  		Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady,
   310  			Lists: map[string]agonesv1.ListStatus{
   311  				"players": {
   312  					Values:   []string{"alice", "bob", "cat"},
   313  					Capacity: 100,
   314  				},
   315  			},
   316  			Counters: map[string]agonesv1.CounterStatus{
   317  				"rooms": {
   318  					Count:    101,
   319  					Capacity: 1000,
   320  				}}}}
   321  	gs2 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, UID: "2"},
   322  		Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady,
   323  			Lists: map[string]agonesv1.ListStatus{
   324  				"players": {
   325  					Values:   []string{},
   326  					Capacity: 100,
   327  				},
   328  			},
   329  			Counters: map[string]agonesv1.CounterStatus{
   330  				"rooms": {
   331  					Count:    101,
   332  					Capacity: 1000,
   333  				}}}}
   334  
   335  	testScenarios := map[string]struct {
   336  		features     string
   337  		gs           *agonesv1.GameServer
   338  		gsa          *allocationv1.GameServerAllocation
   339  		wantCounters map[string]agonesv1.CounterStatus
   340  		wantLists    map[string]agonesv1.ListStatus
   341  	}{
   342  		"CounterActions increment and ListActions add, delete, and update capacity": {
   343  			features: fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists),
   344  			gs:       &gs1,
   345  			gsa: &allocationv1.GameServerAllocation{
   346  				ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   347  				Spec: allocationv1.GameServerAllocationSpec{
   348  					Selectors: []allocationv1.GameServerSelector{{
   349  						GameServerState: &READY,
   350  					}},
   351  					Scheduling: apis.Packed,
   352  					Counters: map[string]allocationv1.CounterAction{
   353  						"rooms": {
   354  							Action: &INCREMENT,
   355  							Amount: &ONE,
   356  						}},
   357  					Lists: map[string]allocationv1.ListAction{
   358  						"players": {
   359  							AddValues:    []string{"x7un", "8inz"},
   360  							Capacity:     &FORTY,
   361  							DeleteValues: []string{"bob"},
   362  						}}}},
   363  			wantCounters: map[string]agonesv1.CounterStatus{
   364  				"rooms": {
   365  					Count:    102,
   366  					Capacity: 1000,
   367  				}},
   368  			wantLists: map[string]agonesv1.ListStatus{
   369  				"players": {
   370  					Values:   []string{"alice", "cat", "x7un", "8inz"},
   371  					Capacity: 40,
   372  				}},
   373  		},
   374  		"CounterActions and ListActions truncate counter Count and update list capacity": {
   375  			features: fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists),
   376  			gs:       &gs2,
   377  			gsa: &allocationv1.GameServerAllocation{
   378  				ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   379  				Spec: allocationv1.GameServerAllocationSpec{
   380  					Selectors: []allocationv1.GameServerSelector{{
   381  						GameServerState: &READY,
   382  					}},
   383  					Scheduling: apis.Packed,
   384  					Counters: map[string]allocationv1.CounterAction{
   385  						"rooms": {
   386  							Action: &INCREMENT,
   387  							Amount: &THOUSAND,
   388  						}},
   389  					Lists: map[string]allocationv1.ListAction{
   390  						"players": {
   391  							AddValues: []string{"x7un", "8inz"},
   392  							Capacity:  &ONE,
   393  						}}}},
   394  			wantCounters: map[string]agonesv1.CounterStatus{
   395  				"rooms": {
   396  					Count:    1000,
   397  					Capacity: 1000,
   398  				}},
   399  			wantLists: map[string]agonesv1.ListStatus{
   400  				"players": {
   401  					Values:   []string{"x7un"},
   402  					Capacity: 1,
   403  				}},
   404  		},
   405  	}
   406  
   407  	for test, testScenario := range testScenarios {
   408  		t.Run(test, func(t *testing.T) {
   409  			runtime.FeatureTestMutex.Lock()
   410  			defer runtime.FeatureTestMutex.Unlock()
   411  			// we always set the feature flag in all these tests, so always process it.
   412  			require.NoError(t, runtime.ParseFeatures(testScenario.features))
   413  
   414  			foundGs, err := allocator.applyAllocationToGameServer(ctx, mp, testScenario.gs, testScenario.gsa)
   415  			assert.NoError(t, err)
   416  			for counter, counterStatus := range testScenario.wantCounters {
   417  				if gsCounter, ok := foundGs.Status.Counters[counter]; ok {
   418  					assert.Equal(t, counterStatus, gsCounter)
   419  				}
   420  			}
   421  			for list, listStatus := range testScenario.wantLists {
   422  				if gsList, ok := foundGs.Status.Lists[list]; ok {
   423  					assert.Equal(t, listStatus, gsList)
   424  				}
   425  			}
   426  		})
   427  	}
   428  }
   429  
   430  func TestAllocationApplyAllocationError(t *testing.T) {
   431  	t.Parallel()
   432  	m := agtesting.NewMocks()
   433  	ctx := context.Background()
   434  
   435  	m.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   436  		return true, nil, errors.New("failed to update")
   437  	})
   438  
   439  	allocator := NewAllocator(m.AgonesInformerFactory.Multicluster().V1().GameServerAllocationPolicies(),
   440  		m.KubeInformerFactory.Core().V1().Secrets(),
   441  		m.AgonesClient.AgonesV1(), m.KubeClient,
   442  		NewAllocationCache(m.AgonesInformerFactory.Agones().V1().GameServers(), gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory), healthcheck.NewHandler()),
   443  		time.Second, 5*time.Second, 500*time.Millisecond,
   444  	)
   445  
   446  	gsa, err := allocator.applyAllocationToGameServer(ctx, allocationv1.MetaPatch{}, &agonesv1.GameServer{}, &allocationv1.GameServerAllocation{})
   447  	logrus.WithError(err).WithField("gsa", gsa).WithField("test", t.Name()).Info("Allocation should fail")
   448  	assert.Error(t, err)
   449  }
   450  
   451  func TestAllocatorAllocateOnGameServerUpdateError(t *testing.T) {
   452  	t.Parallel()
   453  
   454  	// TODO: remove when `CountsAndLists` feature flag is moved to stable.
   455  	runtime.FeatureTestMutex.Lock()
   456  	defer runtime.FeatureTestMutex.Unlock()
   457  	require.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=false&%s=false", runtime.FeaturePlayerAllocationFilter, runtime.FeatureCountsAndLists)))
   458  
   459  	a, m := newFakeAllocator()
   460  	log := framework.TestLogger(t)
   461  
   462  	// make sure there is more than can be retried, so there is always at least some.
   463  	gsLen := allocationRetry.Steps * 2
   464  	_, gsList := defaultFixtures(gsLen)
   465  	m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   466  		return true, &agonesv1.GameServerList{Items: gsList}, nil
   467  	})
   468  	m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   469  		ua := action.(k8stesting.UpdateAction)
   470  		gs := ua.GetObject().(*agonesv1.GameServer)
   471  
   472  		return true, gs, errors.New("failed to update")
   473  	})
   474  
   475  	ctx, cancel := agtesting.StartInformers(m, a.allocationCache.gameServerSynced)
   476  	defer cancel()
   477  
   478  	require.NoError(t, a.Run(ctx))
   479  	// wait for all the gameservers to be in the cache
   480  	require.Eventuallyf(t, func() bool {
   481  		return a.allocationCache.cache.Len() == gsLen
   482  	}, 10*time.Second, time.Second, fmt.Sprintf("should be %d items in the cache", gsLen))
   483  
   484  	gsa := allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{Name: "gsa-1", Namespace: defaultNs},
   485  		Spec: allocationv1.GameServerAllocationSpec{},
   486  	}
   487  
   488  	gsa.ApplyDefaults()
   489  	// without converter, we don't end up with at least one selector
   490  	gsa.Converter()
   491  	errs := gsa.Validate()
   492  	require.Len(t, errs, 0)
   493  	require.Len(t, gsa.Spec.Selectors, 1)
   494  
   495  	// try the private method
   496  	_, err := a.allocate(ctx, gsa.DeepCopy())
   497  	log.WithError(err).Info("allocate (private): failed allocation")
   498  	require.NotEqual(t, ErrNoGameServer, err)
   499  	require.EqualError(t, err, ErrGameServerUpdateConflict.Error())
   500  
   501  	// make sure we aren't in the same batch!
   502  	time.Sleep(2 * a.batchWaitTime)
   503  
   504  	// wait for all the gameservers to be in the cache
   505  	require.Eventuallyf(t, func() bool {
   506  		return a.allocationCache.cache.Len() == gsLen
   507  	}, 10*time.Second, time.Second, fmt.Sprintf("should be %d items in the cache", gsLen))
   508  
   509  	// try the public method
   510  	result, err := a.Allocate(ctx, gsa.DeepCopy())
   511  	log.WithField("result", result).WithError(err).Info("Allocate (public): failed allocation")
   512  	require.Nil(t, result)
   513  	require.NotEqual(t, ErrNoGameServer, err)
   514  	require.EqualError(t, err, ErrGameServerUpdateConflict.Error())
   515  }
   516  
   517  func TestAllocatorRunLocalAllocations(t *testing.T) {
   518  	t.Parallel()
   519  
   520  	// TODO: remove when `CountsAndLists` feature flag is moved to stable.
   521  	runtime.FeatureTestMutex.Lock()
   522  	defer runtime.FeatureTestMutex.Unlock()
   523  
   524  	t.Run("no problems", func(t *testing.T) {
   525  		f, gsList := defaultFixtures(5)
   526  		gsList[0].Status.NodeName = "special"
   527  
   528  		a, m := newFakeAllocator()
   529  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   530  			return true, &agonesv1.GameServerList{Items: gsList}, nil
   531  		})
   532  		updateCount := 0
   533  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   534  			updateCount++
   535  
   536  			uo := action.(k8stesting.UpdateAction)
   537  			gs := uo.GetObject().(*agonesv1.GameServer)
   538  
   539  			return true, gs, nil
   540  		})
   541  
   542  		ctx, cancel := agtesting.StartInformers(m, a.allocationCache.gameServerSynced)
   543  		defer cancel()
   544  
   545  		// This call initializes the cache
   546  		err := a.allocationCache.syncCache()
   547  		assert.Nil(t, err)
   548  
   549  		err = a.allocationCache.counter.Run(ctx, 0)
   550  		assert.Nil(t, err)
   551  
   552  		gsa := &allocationv1.GameServerAllocation{
   553  			ObjectMeta: metav1.ObjectMeta{
   554  				Namespace: defaultNs,
   555  			},
   556  			Spec: allocationv1.GameServerAllocationSpec{
   557  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}}},
   558  			}}
   559  		gsa.ApplyDefaults()
   560  		errs := gsa.Validate()
   561  		require.Len(t, errs, 0)
   562  
   563  		// line up 3 in a batch
   564  		j1 := request{gsa: gsa.DeepCopy(), response: make(chan response)}
   565  		a.pendingRequests <- j1
   566  		j2 := request{gsa: gsa.DeepCopy(), response: make(chan response)}
   567  		a.pendingRequests <- j2
   568  		j3 := request{gsa: gsa.DeepCopy(), response: make(chan response)}
   569  		a.pendingRequests <- j3
   570  
   571  		go a.ListenAndAllocate(ctx, 3)
   572  
   573  		res1 := <-j1.response
   574  		assert.NoError(t, res1.err)
   575  		assert.NotNil(t, res1.gs)
   576  
   577  		// since we gave gsList[0] a different nodename, it should always come first
   578  		assert.Contains(t, []string{"gs2", "gs3", "gs4", "gs5"}, res1.gs.ObjectMeta.Name)
   579  		assert.Equal(t, agonesv1.GameServerStateAllocated, res1.gs.Status.State)
   580  
   581  		res2 := <-j2.response
   582  		assert.NoError(t, res2.err)
   583  		assert.NotNil(t, res2.gs)
   584  		assert.NotEqual(t, res1.gs.ObjectMeta.Name, res2.gs.ObjectMeta.Name)
   585  		assert.Equal(t, agonesv1.GameServerStateAllocated, res2.gs.Status.State)
   586  
   587  		res3 := <-j3.response
   588  		assert.NoError(t, res3.err)
   589  		assert.NotNil(t, res3.gs)
   590  		assert.Equal(t, agonesv1.GameServerStateAllocated, res3.gs.Status.State)
   591  		assert.NotEqual(t, res1.gs.ObjectMeta.Name, res3.gs.ObjectMeta.Name)
   592  		assert.NotEqual(t, res2.gs.ObjectMeta.Name, res3.gs.ObjectMeta.Name)
   593  
   594  		assert.Equal(t, 3, updateCount)
   595  	})
   596  
   597  	t.Run("no gameservers", func(t *testing.T) {
   598  		a, m := newFakeAllocator()
   599  		ctx, cancel := agtesting.StartInformers(m, a.allocationCache.gameServerSynced)
   600  		defer cancel()
   601  
   602  		// This call initializes the cache
   603  		err := a.allocationCache.syncCache()
   604  		assert.Nil(t, err)
   605  
   606  		err = a.allocationCache.counter.Run(ctx, 0)
   607  		assert.Nil(t, err)
   608  
   609  		gsa := &allocationv1.GameServerAllocation{
   610  			ObjectMeta: metav1.ObjectMeta{
   611  				Namespace: defaultNs,
   612  			},
   613  			Spec: allocationv1.GameServerAllocationSpec{
   614  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: "thereisnofleet"}}}},
   615  			}}
   616  		gsa.ApplyDefaults()
   617  		errs := gsa.Validate()
   618  		require.Len(t, errs, 0)
   619  
   620  		j1 := request{gsa: gsa.DeepCopy(), response: make(chan response)}
   621  		a.pendingRequests <- j1
   622  
   623  		go a.ListenAndAllocate(ctx, 3)
   624  
   625  		res1 := <-j1.response
   626  		assert.Nil(t, res1.gs)
   627  		assert.Error(t, res1.err)
   628  		assert.Equal(t, ErrNoGameServer, res1.err)
   629  	})
   630  }
   631  
   632  func TestAllocatorRunLocalAllocationsCountsAndLists(t *testing.T) {
   633  	t.Parallel()
   634  
   635  	runtime.FeatureTestMutex.Lock()
   636  	defer runtime.FeatureTestMutex.Unlock()
   637  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true"))
   638  
   639  	a, m := newFakeAllocator()
   640  
   641  	gs1 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, UID: "1"},
   642  		Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady,
   643  			Counters: map[string]agonesv1.CounterStatus{
   644  				"foo": { // Available Capacity == 1000
   645  					Count:    0,
   646  					Capacity: 1000,
   647  				}}}}
   648  	gs2 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, UID: "2"},
   649  		Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady,
   650  			Counters: map[string]agonesv1.CounterStatus{
   651  				"foo": { // Available Capacity == 900
   652  					Count:    100,
   653  					Capacity: 1000,
   654  				}}}}
   655  	gs3 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, UID: "3"},
   656  		Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady,
   657  			Counters: map[string]agonesv1.CounterStatus{
   658  				"foo": { // Available Capacity == 1
   659  					Count:    999,
   660  					Capacity: 1000,
   661  				}}}}
   662  	gs4 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, UID: "4"},
   663  		Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady,
   664  			Lists: map[string]agonesv1.ListStatus{
   665  				"foo": { // Available Capacity == 10
   666  					Values:   []string{},
   667  					Capacity: 10,
   668  				}}}}
   669  	gs5 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs5", Namespace: defaultNs, UID: "5"},
   670  		Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady,
   671  			Lists: map[string]agonesv1.ListStatus{
   672  				"foo": { // Available Capacity == 9
   673  					Values:   []string{"1"},
   674  					Capacity: 10,
   675  				}}}}
   676  	gs6 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: defaultNs, UID: "6"},
   677  		Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady,
   678  			Lists: map[string]agonesv1.ListStatus{
   679  				"foo": { // Available Capacity == 1
   680  					Values:   []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"},
   681  					Capacity: 10,
   682  				}}}}
   683  
   684  	gsList := []agonesv1.GameServer{gs1, gs2, gs3, gs4, gs5, gs6}
   685  
   686  	m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   687  		return true, &agonesv1.GameServerList{Items: gsList}, nil
   688  	})
   689  
   690  	m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   691  		uo := action.(k8stesting.UpdateAction)
   692  		gs := uo.GetObject().(*agonesv1.GameServer)
   693  		return true, gs, nil
   694  	})
   695  
   696  	ctx, cancel := agtesting.StartInformers(m, a.allocationCache.gameServerSynced)
   697  	defer cancel()
   698  
   699  	// This call initializes the cache
   700  	err := a.allocationCache.syncCache()
   701  	assert.Nil(t, err)
   702  
   703  	err = a.allocationCache.counter.Run(ctx, 0)
   704  	assert.Nil(t, err)
   705  
   706  	READY := agonesv1.GameServerStateReady
   707  
   708  	gsaAscending := &allocationv1.GameServerAllocation{
   709  		ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   710  		Spec: allocationv1.GameServerAllocationSpec{
   711  			Scheduling: apis.Packed,
   712  			Selectors: []allocationv1.GameServerSelector{{
   713  				GameServerState: &READY,
   714  			}},
   715  			Priorities: []agonesv1.Priority{
   716  				{Type: agonesv1.GameServerPriorityCounter,
   717  					Key:   "foo",
   718  					Order: agonesv1.GameServerPriorityAscending},
   719  			},
   720  		}}
   721  	gsaDescending := &allocationv1.GameServerAllocation{
   722  		ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   723  		Spec: allocationv1.GameServerAllocationSpec{
   724  			Scheduling: apis.Packed,
   725  			Selectors: []allocationv1.GameServerSelector{{
   726  				GameServerState: &READY,
   727  			}},
   728  			Priorities: []agonesv1.Priority{
   729  				{Type: agonesv1.GameServerPriorityCounter,
   730  					Key:   "foo",
   731  					Order: agonesv1.GameServerPriorityDescending},
   732  			},
   733  		}}
   734  	gsaDistributed := &allocationv1.GameServerAllocation{
   735  		ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   736  		Spec: allocationv1.GameServerAllocationSpec{
   737  			Scheduling: apis.Distributed,
   738  			Selectors: []allocationv1.GameServerSelector{{
   739  				GameServerState: &READY,
   740  			}},
   741  			Priorities: []agonesv1.Priority{
   742  				{Type: agonesv1.GameServerPriorityCounter,
   743  					Key:   "foo",
   744  					Order: agonesv1.GameServerPriorityDescending},
   745  			},
   746  		}}
   747  	gsaListAscending := &allocationv1.GameServerAllocation{
   748  		ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   749  		Spec: allocationv1.GameServerAllocationSpec{
   750  			Scheduling: apis.Packed,
   751  			Selectors: []allocationv1.GameServerSelector{{
   752  				GameServerState: &READY,
   753  			}},
   754  			Priorities: []agonesv1.Priority{
   755  				{Type: agonesv1.GameServerPriorityList,
   756  					Key:   "foo",
   757  					Order: agonesv1.GameServerPriorityAscending},
   758  			},
   759  		}}
   760  	gsaListDescending := &allocationv1.GameServerAllocation{
   761  		ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   762  		Spec: allocationv1.GameServerAllocationSpec{
   763  			Scheduling: apis.Packed,
   764  			Selectors: []allocationv1.GameServerSelector{{
   765  				GameServerState: &READY,
   766  			}},
   767  			Priorities: []agonesv1.Priority{
   768  				{Type: agonesv1.GameServerPriorityList,
   769  					Key:   "foo",
   770  					Order: agonesv1.GameServerPriorityDescending},
   771  			},
   772  		}}
   773  	gsaListDistributed := &allocationv1.GameServerAllocation{
   774  		ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   775  		Spec: allocationv1.GameServerAllocationSpec{
   776  			Scheduling: apis.Distributed,
   777  			Selectors: []allocationv1.GameServerSelector{{
   778  				GameServerState: &READY,
   779  			}},
   780  			Priorities: []agonesv1.Priority{
   781  				{Type: agonesv1.GameServerPriorityList,
   782  					Key:   "foo",
   783  					Order: agonesv1.GameServerPriorityDescending},
   784  			},
   785  		}}
   786  
   787  	j1 := request{gsa: gsaDescending.DeepCopy(), response: make(chan response)}
   788  	a.pendingRequests <- j1
   789  	j2 := request{gsa: gsaAscending.DeepCopy(), response: make(chan response)}
   790  	a.pendingRequests <- j2
   791  	j3 := request{gsa: gsaDistributed.DeepCopy(), response: make(chan response)}
   792  	a.pendingRequests <- j3
   793  	j4 := request{gsa: gsaListDescending.DeepCopy(), response: make(chan response)}
   794  	a.pendingRequests <- j4
   795  	j5 := request{gsa: gsaListAscending.DeepCopy(), response: make(chan response)}
   796  	a.pendingRequests <- j5
   797  	j6 := request{gsa: gsaListDistributed.DeepCopy(), response: make(chan response)}
   798  	a.pendingRequests <- j6
   799  
   800  	go a.ListenAndAllocate(ctx, 5)
   801  
   802  	res1 := <-j1.response
   803  	assert.NoError(t, res1.err)
   804  	assert.NotNil(t, res1.gs)
   805  	assert.Equal(t, agonesv1.GameServerStateAllocated, res1.gs.Status.State)
   806  	assert.Equal(t, gs1.ObjectMeta.Name, res1.gs.ObjectMeta.Name)
   807  	assert.Equal(t, gs1.Status.Counters["foo"].Count, res1.gs.Status.Counters["foo"].Count)
   808  	assert.Equal(t, gs1.Status.Counters["foo"].Capacity, res1.gs.Status.Counters["foo"].Capacity)
   809  
   810  	res2 := <-j2.response
   811  	assert.NoError(t, res2.err)
   812  	assert.NotNil(t, res2.gs)
   813  	assert.Equal(t, agonesv1.GameServerStateAllocated, res2.gs.Status.State)
   814  	assert.Equal(t, gs3.ObjectMeta.Name, res2.gs.ObjectMeta.Name)
   815  	assert.Equal(t, gs3.Status.Counters["foo"].Count, res2.gs.Status.Counters["foo"].Count)
   816  	assert.Equal(t, gs3.Status.Counters["foo"].Capacity, res2.gs.Status.Counters["foo"].Capacity)
   817  
   818  	res3 := <-j3.response
   819  	assert.NoError(t, res3.err)
   820  	assert.NotNil(t, res3.gs)
   821  	assert.Equal(t, agonesv1.GameServerStateAllocated, res3.gs.Status.State)
   822  	assert.Equal(t, gs2.ObjectMeta.Name, res3.gs.ObjectMeta.Name)
   823  	assert.Equal(t, gs2.Status.Counters["foo"].Count, res3.gs.Status.Counters["foo"].Count)
   824  	assert.Equal(t, gs2.Status.Counters["foo"].Capacity, res3.gs.Status.Counters["foo"].Capacity)
   825  
   826  	res4 := <-j4.response
   827  	assert.NoError(t, res4.err)
   828  	assert.NotNil(t, res4.gs)
   829  	assert.Equal(t, agonesv1.GameServerStateAllocated, res4.gs.Status.State)
   830  	assert.Equal(t, gs4.ObjectMeta.Name, res4.gs.ObjectMeta.Name)
   831  	assert.Equal(t, gs4.Status.Lists["foo"].Values, res4.gs.Status.Lists["foo"].Values)
   832  	assert.Equal(t, gs4.Status.Lists["foo"].Capacity, res4.gs.Status.Lists["foo"].Capacity)
   833  
   834  	res5 := <-j5.response
   835  	assert.NoError(t, res5.err)
   836  	assert.NotNil(t, res5.gs)
   837  	assert.Equal(t, agonesv1.GameServerStateAllocated, res5.gs.Status.State)
   838  	assert.Equal(t, gs6.ObjectMeta.Name, res5.gs.ObjectMeta.Name)
   839  	assert.Equal(t, gs6.Status.Lists["foo"].Values, res5.gs.Status.Lists["foo"].Values)
   840  	assert.Equal(t, gs6.Status.Lists["foo"].Capacity, res5.gs.Status.Lists["foo"].Capacity)
   841  
   842  	res6 := <-j6.response
   843  	assert.NoError(t, res6.err)
   844  	assert.NotNil(t, res6.gs)
   845  	assert.Equal(t, agonesv1.GameServerStateAllocated, res6.gs.Status.State)
   846  	assert.Equal(t, gs5.ObjectMeta.Name, res6.gs.ObjectMeta.Name)
   847  	assert.Equal(t, gs5.Status.Lists["foo"].Values, res6.gs.Status.Lists["foo"].Values)
   848  	assert.Equal(t, gs5.Status.Lists["foo"].Capacity, res6.gs.Status.Lists["foo"].Capacity)
   849  
   850  }
   851  
   852  func TestControllerAllocationUpdateWorkers(t *testing.T) {
   853  	t.Run("no error", func(t *testing.T) {
   854  		a, m := newFakeAllocator()
   855  
   856  		updated := false
   857  		gs1 := &agonesv1.GameServer{
   858  			ObjectMeta: metav1.ObjectMeta{Name: "gs1"},
   859  		}
   860  		r := response{
   861  			request: request{
   862  				gsa:      &allocationv1.GameServerAllocation{},
   863  				response: make(chan response),
   864  			},
   865  			gs: gs1,
   866  		}
   867  
   868  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   869  			updated = true
   870  
   871  			uo := action.(k8stesting.UpdateAction)
   872  			gs := uo.GetObject().(*agonesv1.GameServer)
   873  
   874  			assert.Equal(t, gs1.ObjectMeta.Name, gs.ObjectMeta.Name)
   875  			assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   876  
   877  			return true, gs, nil
   878  		})
   879  
   880  		updateQueue := a.allocationUpdateWorkers(context.Background(), 1)
   881  
   882  		go func() {
   883  			updateQueue <- r
   884  		}()
   885  
   886  		r = <-r.request.response
   887  
   888  		assert.True(t, updated)
   889  		assert.NoError(t, r.err)
   890  		assert.Equal(t, gs1.ObjectMeta.Name, r.gs.ObjectMeta.Name)
   891  		assert.Equal(t, agonesv1.GameServerStateAllocated, r.gs.Status.State)
   892  
   893  		// Verify that the GameServer was added back to the cache after successful allocation
   894  		key, err := cache.MetaNamespaceKeyFunc(gs1)
   895  		require.NoError(t, err)
   896  		cached, ok := a.allocationCache.cache.Load(key)
   897  		require.True(t, ok, "GameServer should be in the cache after successful allocation")
   898  		require.Equal(t, gs1.ObjectMeta.Name, cached.ObjectMeta.Name)
   899  
   900  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Allocated")
   901  
   902  		// make sure we can do more allocations than number of workers
   903  		gs2 := &agonesv1.GameServer{
   904  			ObjectMeta: metav1.ObjectMeta{Name: "gs1"},
   905  		}
   906  		r = response{
   907  			request: request{
   908  				gsa:      &allocationv1.GameServerAllocation{},
   909  				response: make(chan response),
   910  			},
   911  			gs: gs2,
   912  		}
   913  
   914  		go func() {
   915  			updateQueue <- r
   916  		}()
   917  
   918  		r = <-r.request.response
   919  
   920  		assert.True(t, updated)
   921  		assert.NoError(t, r.err)
   922  		assert.Equal(t, gs2.ObjectMeta.Name, r.gs.ObjectMeta.Name)
   923  		assert.Equal(t, agonesv1.GameServerStateAllocated, r.gs.Status.State)
   924  
   925  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Allocated")
   926  	})
   927  
   928  	t.Run("error on update", func(t *testing.T) {
   929  		a, m := newFakeAllocator()
   930  
   931  		updated := false
   932  		gs1 := &agonesv1.GameServer{
   933  			ObjectMeta: metav1.ObjectMeta{Name: "gs1"},
   934  		}
   935  		key, err := cache.MetaNamespaceKeyFunc(gs1)
   936  		assert.NoError(t, err)
   937  
   938  		_, ok := a.allocationCache.cache.Load(key)
   939  		assert.False(t, ok)
   940  
   941  		r := response{
   942  			request: request{
   943  				gsa:      &allocationv1.GameServerAllocation{},
   944  				response: make(chan response),
   945  			},
   946  			gs: gs1,
   947  		}
   948  
   949  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   950  			updated = true
   951  
   952  			uo := action.(k8stesting.UpdateAction)
   953  			gs := uo.GetObject().(*agonesv1.GameServer)
   954  			assert.Equal(t, gs1.ObjectMeta.Name, gs.ObjectMeta.Name)
   955  			assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   956  
   957  			return true, gs, errors.New("something went wrong")
   958  		})
   959  
   960  		updateQueue := a.allocationUpdateWorkers(context.Background(), 1)
   961  
   962  		go func() {
   963  			updateQueue <- r
   964  		}()
   965  
   966  		r = <-r.request.response
   967  
   968  		assert.True(t, updated)
   969  		assert.EqualError(t, r.err, ErrGameServerUpdateConflict.Error())
   970  		assert.Equal(t, gs1, r.gs)
   971  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   972  
   973  		var cached *agonesv1.GameServer
   974  		cached, ok = a.allocationCache.cache.Load(key)
   975  		assert.True(t, ok)
   976  		assert.Equal(t, gs1.ObjectMeta.Name, cached.ObjectMeta.Name)
   977  	})
   978  }
   979  
   980  func TestAllocatorCreateRestClientError(t *testing.T) {
   981  	t.Parallel()
   982  	t.Run("Missing secret", func(t *testing.T) {
   983  		a, _ := newFakeAllocator()
   984  
   985  		connectionInfo := &multiclusterv1.ClusterConnectionInfo{
   986  			SecretName: "secret-name",
   987  		}
   988  		_, err := a.createRemoteClusterDialOption(defaultNs, connectionInfo)
   989  		assert.Error(t, err)
   990  		assert.Contains(t, err.Error(), "secret-name")
   991  	})
   992  	t.Run("Missing cert", func(t *testing.T) {
   993  		a, m := newFakeAllocator()
   994  
   995  		m.KubeClient.AddReactor("list", "secrets",
   996  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   997  				return true, &corev1.SecretList{
   998  					Items: []corev1.Secret{{
   999  						Data: map[string][]byte{
  1000  							"tls.crt": clientCert,
  1001  						},
  1002  						ObjectMeta: metav1.ObjectMeta{
  1003  							Name:      "secret-name",
  1004  							Namespace: defaultNs,
  1005  						},
  1006  					}}}, nil
  1007  			})
  1008  
  1009  		_, cancel := agtesting.StartInformers(m, a.secretSynced)
  1010  		defer cancel()
  1011  
  1012  		connectionInfo := &multiclusterv1.ClusterConnectionInfo{
  1013  			SecretName: "secret-name",
  1014  		}
  1015  		_, err := a.createRemoteClusterDialOption(defaultNs, connectionInfo)
  1016  		assert.Error(t, err)
  1017  		assert.Contains(t, err.Error(), "missing client certificate key pair in secret secret-name")
  1018  	})
  1019  	t.Run("Bad client cert", func(t *testing.T) {
  1020  		a, m := newFakeAllocator()
  1021  
  1022  		m.KubeClient.AddReactor("list", "secrets",
  1023  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
  1024  				return true, &corev1.SecretList{
  1025  					Items: []corev1.Secret{{
  1026  						Data: map[string][]byte{
  1027  							"tls.crt": []byte("XXX"),
  1028  							"tls.key": []byte("XXX"),
  1029  						},
  1030  						ObjectMeta: metav1.ObjectMeta{
  1031  							Name:      "secret-name",
  1032  							Namespace: defaultNs,
  1033  						},
  1034  					}}}, nil
  1035  			})
  1036  
  1037  		_, cancel := agtesting.StartInformers(m, a.secretSynced)
  1038  		defer cancel()
  1039  
  1040  		connectionInfo := &multiclusterv1.ClusterConnectionInfo{
  1041  			SecretName: "secret-name",
  1042  		}
  1043  		_, err := a.createRemoteClusterDialOption(defaultNs, connectionInfo)
  1044  		assert.Error(t, err)
  1045  		assert.Contains(t, err.Error(), "failed to find any PEM data in certificate input")
  1046  	})
  1047  	t.Run("Bad CA cert", func(t *testing.T) {
  1048  		a, m := newFakeAllocator()
  1049  
  1050  		m.KubeClient.AddReactor("list", "secrets",
  1051  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
  1052  				return true, getTestSecret("secret-name", clientCert), nil
  1053  			})
  1054  
  1055  		_, cancel := agtesting.StartInformers(m, a.secretSynced)
  1056  		defer cancel()
  1057  
  1058  		connectionInfo := &multiclusterv1.ClusterConnectionInfo{
  1059  			SecretName: "secret-name",
  1060  			ServerCA:   []byte("XXX"),
  1061  		}
  1062  		_, err := a.createRemoteClusterDialOption(defaultNs, connectionInfo)
  1063  		assert.Error(t, err)
  1064  		assert.Contains(t, err.Error(), "PEM format")
  1065  	})
  1066  	t.Run("Bad client CA cert", func(t *testing.T) {
  1067  		a, m := newFakeAllocator()
  1068  
  1069  		m.KubeClient.AddReactor("list", "secrets",
  1070  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
  1071  				return true, getTestSecret("secret-name", []byte("XXX")), nil
  1072  			})
  1073  
  1074  		_, cancel := agtesting.StartInformers(m, a.secretSynced)
  1075  		defer cancel()
  1076  
  1077  		connectionInfo := &multiclusterv1.ClusterConnectionInfo{
  1078  			SecretName: "secret-name",
  1079  		}
  1080  		_, err := a.createRemoteClusterDialOption(defaultNs, connectionInfo)
  1081  		assert.Nil(t, err)
  1082  	})
  1083  }
  1084  
  1085  // newFakeAllocator returns a fake allocator.
  1086  func newFakeAllocator() (*Allocator, agtesting.Mocks) {
  1087  	m := agtesting.NewMocks()
  1088  
  1089  	counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory)
  1090  	a := NewAllocator(
  1091  		m.AgonesInformerFactory.Multicluster().V1().GameServerAllocationPolicies(),
  1092  		m.KubeInformerFactory.Core().V1().Secrets(),
  1093  		m.AgonesClient.AgonesV1(),
  1094  		m.KubeClient,
  1095  		NewAllocationCache(m.AgonesInformerFactory.Agones().V1().GameServers(), counter, healthcheck.NewHandler()),
  1096  		time.Second,
  1097  		5*time.Second,
  1098  		500*time.Millisecond)
  1099  	a.recorder = m.FakeRecorder
  1100  
  1101  	return a, m
  1102  }