agones.dev/agones@v1.53.0/pkg/gameserverallocations/find_test.go (about)

     1  // Copyright 2019 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  	"fmt"
    19  	"testing"
    20  
    21  	"agones.dev/agones/pkg/apis"
    22  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    23  	allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
    24  	agtesting "agones.dev/agones/pkg/testing"
    25  	"agones.dev/agones/pkg/util/runtime"
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	k8sruntime "k8s.io/apimachinery/pkg/runtime"
    30  	k8stesting "k8s.io/client-go/testing"
    31  )
    32  
    33  func TestFindGameServerForAllocationPacked(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	oneLabel := map[string]string{"role": "gameserver"}
    37  	twoLabels := map[string]string{"role": "gameserver", "preferred": "true"}
    38  
    39  	gsa := &allocationv1.GameServerAllocation{
    40  		ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
    41  		Spec: allocationv1.GameServerAllocationSpec{
    42  			Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{
    43  				MatchLabels: oneLabel,
    44  			}}},
    45  			Scheduling: apis.Packed,
    46  		},
    47  	}
    48  
    49  	n := metav1.Now()
    50  	twoLabelsGsa := gsa.DeepCopy()
    51  	twoLabelsGsa.Spec.Selectors = append(
    52  		[]allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{
    53  			MatchLabels: map[string]string{"preferred": "true"},
    54  		}}}, twoLabelsGsa.Spec.Selectors...)
    55  
    56  	fixtures := map[string]struct {
    57  		list     []agonesv1.GameServer
    58  		test     func(*testing.T, []*agonesv1.GameServer)
    59  		features string
    60  		gsa      *allocationv1.GameServerAllocation
    61  	}{
    62  		"empty selector": {
    63  			list: []agonesv1.GameServer{{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}}},
    64  			test: func(t *testing.T, list []*agonesv1.GameServer) {
    65  				require.Len(t, list, 1)
    66  
    67  				emptyGSA := &allocationv1.GameServerAllocation{
    68  					ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
    69  					Spec: allocationv1.GameServerAllocationSpec{
    70  						Scheduling: apis.Packed,
    71  					},
    72  				}
    73  				emptyGSA.ApplyDefaults()
    74  				emptyGSA.Converter()
    75  				allErrs := emptyGSA.Validate()
    76  				require.Len(t, allErrs, 0)
    77  				require.Len(t, emptyGSA.Spec.Selectors, 1)
    78  
    79  				gs, index, err := findGameServerForAllocation(emptyGSA, list)
    80  				assert.NotNil(t, gs)
    81  				assert.Equal(t, 0, index)
    82  				assert.NoError(t, err)
    83  			},
    84  		},
    85  		"one label with player state (StateAllocationFilter)": {
    86  			// nolint: dupl
    87  			list: []agonesv1.GameServer{
    88  				{ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: defaultNs, Labels: oneLabel, DeletionTimestamp: &n}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
    89  				{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
    90  				{ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
    91  				{ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}},
    92  				{ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}},
    93  				{ObjectMeta: metav1.ObjectMeta{Name: "gs5", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateError}},
    94  				{ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: "does-not-apply", Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
    95  			},
    96  			test: func(t *testing.T, list []*agonesv1.GameServer) {
    97  				require.Len(t, list, 5)
    98  				require.Equal(t, agonesv1.GameServerStateReady, *gsa.Spec.Selectors[0].GameServerState)
    99  
   100  				gs, index, err := findGameServerForAllocation(gsa, list)
   101  				assert.NoError(t, err)
   102  				require.NotNil(t, gs)
   103  				assert.Equal(t, "node1", gs.Status.NodeName)
   104  				assert.Equal(t, "gs1", gs.ObjectMeta.Name)
   105  				assert.Equal(t, gs, list[index])
   106  				assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   107  
   108  				// remove the allocated game server
   109  				list = append(list[:index], list[index+1:]...)
   110  				assert.Len(t, list, 4)
   111  
   112  				gs, index, err = findGameServerForAllocation(gsa, list)
   113  				assert.NoError(t, err)
   114  				require.NotNil(t, gs)
   115  
   116  				assert.Equal(t, "node2", gs.Status.NodeName)
   117  				assert.Equal(t, "gs2", gs.ObjectMeta.Name)
   118  				assert.Equal(t, gs, list[index])
   119  				assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   120  
   121  				// now try an allocated state
   122  				gsa := gsa.DeepCopy()
   123  				allocated := agonesv1.GameServerStateAllocated
   124  				gsa.Spec.Selectors[0].GameServerState = &allocated
   125  
   126  				gs, index, err = findGameServerForAllocation(gsa, list)
   127  				assert.NoError(t, err)
   128  				require.NotNil(t, gs)
   129  				assert.Equal(t, "node1", gs.Status.NodeName)
   130  				// either is valid
   131  				assert.Contains(t, []string{"gs3", "gs4"}, gs.ObjectMeta.Name)
   132  				assert.Equal(t, gs, list[index])
   133  				assert.Equal(t, allocated, gs.Status.State)
   134  
   135  				// finally, we have nothing left
   136  				list = nil
   137  				gs, _, err = findGameServerForAllocation(gsa, list)
   138  				assert.Error(t, err)
   139  				assert.Equal(t, ErrNoGameServer, err)
   140  				assert.Nil(t, gs)
   141  			},
   142  			features: fmt.Sprintf("%s=true", runtime.FeaturePlayerAllocationFilter),
   143  		},
   144  		"one label with player counts and state (PlayerAllocationFilter)": {
   145  			list: []agonesv1.GameServer{
   146  				{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
   147  				{ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   148  				{ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, Labels: oneLabel},
   149  					Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated,
   150  						Players: &agonesv1.PlayerStatus{Count: 10, Capacity: 15}}},
   151  				{ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, Labels: oneLabel},
   152  					Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated, Players: &agonesv1.PlayerStatus{
   153  						Count:    3,
   154  						Capacity: 15,
   155  					}}},
   156  			},
   157  			test: func(t *testing.T, list []*agonesv1.GameServer) {
   158  				gsa := gsa.DeepCopy()
   159  				allocated := agonesv1.GameServerStateAllocated
   160  				gsa.Spec.Selectors[0].GameServerState = &allocated
   161  				gsa.Spec.Selectors[0].Players = &allocationv1.PlayerSelector{
   162  					MinAvailable: 1,
   163  					MaxAvailable: 10,
   164  				}
   165  				require.Len(t, list, 4)
   166  
   167  				gs, index, err := findGameServerForAllocation(gsa, list)
   168  				assert.NoError(t, err)
   169  				require.NotNil(t, gs)
   170  				assert.Equal(t, "node1", gs.Status.NodeName)
   171  				assert.Equal(t, "gs3", gs.ObjectMeta.Name)
   172  				assert.Equal(t, gs, list[index])
   173  				assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   174  			},
   175  			features: fmt.Sprintf("%s=true", runtime.FeaturePlayerAllocationFilter),
   176  		},
   177  		"preferred": {
   178  			list: []agonesv1.GameServer{
   179  				{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: twoLabels}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
   180  				{ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   181  				{ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
   182  				{ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, Labels: twoLabels}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   183  				{ObjectMeta: metav1.ObjectMeta{Name: "gs5", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
   184  				{ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: defaultNs, Labels: oneLabel}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
   185  			},
   186  			test: func(t *testing.T, list []*agonesv1.GameServer) {
   187  				assert.Len(t, list, 6)
   188  
   189  				gs, index, err := findGameServerForAllocation(twoLabelsGsa, list)
   190  				assert.NoError(t, err)
   191  				assert.Equal(t, "node1", gs.Status.NodeName)
   192  				assert.Equal(t, "gs1", gs.ObjectMeta.Name)
   193  				assert.Equal(t, gs, list[index])
   194  				assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   195  
   196  				list = append(list[:index], list[index+1:]...)
   197  				gs, index, err = findGameServerForAllocation(twoLabelsGsa, list)
   198  				assert.NoError(t, err)
   199  				assert.Equal(t, "node2", gs.Status.NodeName)
   200  				assert.Equal(t, "gs4", gs.ObjectMeta.Name)
   201  				assert.Equal(t, gs, list[index])
   202  				assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   203  
   204  				list = append(list[:index], list[index+1:]...)
   205  				gs, index, err = findGameServerForAllocation(twoLabelsGsa, list)
   206  				assert.NoError(t, err)
   207  				assert.Equal(t, "node1", gs.Status.NodeName)
   208  				assert.Contains(t, []string{"gs3", "gs5", "gs6"}, gs.ObjectMeta.Name)
   209  				assert.Equal(t, gs, list[index])
   210  				assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   211  			},
   212  			features: fmt.Sprintf("%s=false", runtime.FeaturePlayerAllocationFilter),
   213  		},
   214  		"allocation trap": {
   215  			list: []agonesv1.GameServer{
   216  				{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}},
   217  				{ObjectMeta: metav1.ObjectMeta{Name: "gs2", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}},
   218  				{ObjectMeta: metav1.ObjectMeta{Name: "gs3", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}},
   219  				{ObjectMeta: metav1.ObjectMeta{Name: "gs4", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateAllocated}},
   220  				{ObjectMeta: metav1.ObjectMeta{Name: "gs5", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   221  				{ObjectMeta: metav1.ObjectMeta{Name: "gs6", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   222  				{ObjectMeta: metav1.ObjectMeta{Name: "gs7", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   223  				{ObjectMeta: metav1.ObjectMeta{Name: "gs8", Labels: oneLabel, Namespace: defaultNs}, Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   224  			},
   225  			test: func(t *testing.T, list []*agonesv1.GameServer) {
   226  				assert.Len(t, list, 8)
   227  
   228  				gs, index, err := findGameServerForAllocation(gsa, list)
   229  				assert.Nil(t, err)
   230  				assert.Equal(t, "node2", gs.Status.NodeName)
   231  				assert.Equal(t, gs, list[index])
   232  				assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   233  			},
   234  			features: fmt.Sprintf("%s=false", runtime.FeaturePlayerAllocationFilter),
   235  		},
   236  	}
   237  
   238  	for k, v := range fixtures {
   239  		t.Run(k, func(t *testing.T) {
   240  			runtime.FeatureTestMutex.Lock()
   241  			defer runtime.FeatureTestMutex.Unlock()
   242  			// we always set the feature flag in all these tests, so always process it.
   243  			require.NoError(t, runtime.ParseFeatures(v.features))
   244  
   245  			gsa.ApplyDefaults()
   246  			allErrs := gsa.Validate()
   247  			require.Len(t, allErrs, 0)
   248  
   249  			twoLabelsGsa.ApplyDefaults()
   250  			allErrs = twoLabelsGsa.Validate()
   251  			require.Len(t, allErrs, 0)
   252  
   253  			controller, m := newFakeController()
   254  			c := controller.allocator.allocationCache
   255  
   256  			m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   257  				return true, &agonesv1.GameServerList{Items: v.list}, nil
   258  			})
   259  
   260  			ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced)
   261  			defer cancel()
   262  
   263  			// This call initializes the cache
   264  			err := c.syncCache()
   265  			assert.Nil(t, err)
   266  
   267  			err = c.counter.Run(ctx, 0)
   268  			assert.Nil(t, err)
   269  
   270  			list := c.ListSortedGameServers(v.gsa)
   271  			v.test(t, list)
   272  		})
   273  	}
   274  }
   275  
   276  func TestFindGameServerForAllocationDistributed(t *testing.T) {
   277  	t.Parallel()
   278  
   279  	// TODO: remove when `CountsAndLists` feature flag is moved to stable.
   280  	// NOTE: CountsAndLists has different behavior for Distributed, and the game server list is not random.
   281  	runtime.FeatureTestMutex.Lock()
   282  	defer runtime.FeatureTestMutex.Unlock()
   283  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=false"))
   284  
   285  	controller, m := newFakeController()
   286  	c := controller.allocator.allocationCache
   287  	labels := map[string]string{"role": "gameserver"}
   288  
   289  	gsa := &allocationv1.GameServerAllocation{
   290  		ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs},
   291  		Spec: allocationv1.GameServerAllocationSpec{
   292  			Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{
   293  				MatchLabels: labels,
   294  			}}},
   295  			Scheduling: apis.Distributed,
   296  		},
   297  	}
   298  	gsa.ApplyDefaults()
   299  	allErrs := gsa.Validate()
   300  	require.Len(t, allErrs, 0)
   301  
   302  	gsList := []agonesv1.GameServer{
   303  		{ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, Labels: labels},
   304  			Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
   305  		{ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, Labels: labels},
   306  			Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
   307  		{ObjectMeta: metav1.ObjectMeta{Name: "gs3", Namespace: defaultNs, Labels: labels},
   308  			Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateReady}},
   309  		{ObjectMeta: metav1.ObjectMeta{Name: "gs4", Namespace: defaultNs, Labels: labels},
   310  			Status: agonesv1.GameServerStatus{NodeName: "node1", State: agonesv1.GameServerStateError}},
   311  		{ObjectMeta: metav1.ObjectMeta{Name: "gs5", Namespace: defaultNs, Labels: labels},
   312  			Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   313  		{ObjectMeta: metav1.ObjectMeta{Name: "gs6", Namespace: defaultNs, Labels: labels},
   314  			Status: agonesv1.GameServerStatus{NodeName: "node2", State: agonesv1.GameServerStateReady}},
   315  		{ObjectMeta: metav1.ObjectMeta{Name: "gs7", Namespace: defaultNs, Labels: labels},
   316  			Status: agonesv1.GameServerStatus{NodeName: "node3", State: agonesv1.GameServerStateReady}},
   317  	}
   318  
   319  	m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   320  		return true, &agonesv1.GameServerList{Items: gsList}, nil
   321  	})
   322  
   323  	ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced)
   324  	defer cancel()
   325  
   326  	// This call initializes the cache
   327  	err := c.syncCache()
   328  	assert.Nil(t, err)
   329  
   330  	err = c.counter.Run(ctx, 0)
   331  	assert.Nil(t, err)
   332  
   333  	list := c.ListSortedGameServers(gsa)
   334  	assert.Len(t, list, 6)
   335  
   336  	gs, index, err := findGameServerForAllocation(gsa, list)
   337  	assert.NoError(t, err)
   338  	assert.Equal(t, gs, list[index])
   339  	assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   340  
   341  	past := gs
   342  	// we should get a different result in 10 tries, so we can see we get some randomness.
   343  	for i := 0; i < 10; i++ {
   344  		gs, index, err = findGameServerForAllocation(gsa, list)
   345  		assert.NoError(t, err)
   346  		assert.Equal(t, gs, list[index])
   347  		assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   348  
   349  		if gs.ObjectMeta.Name != past.ObjectMeta.Name {
   350  			return
   351  		}
   352  	}
   353  
   354  	assert.FailNow(t, "We should get a different gameserver by now")
   355  
   356  }