Skip to content

Commit

Permalink
Adds gameserverallocation e2e tests for Lists (#3516)
Browse files Browse the repository at this point in the history
  • Loading branch information
igooch authored Nov 21, 2023
1 parent 9b69ce6 commit f5bdb4d
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 16 deletions.
15 changes: 11 additions & 4 deletions pkg/apis/agones/v1/gameserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,7 @@ func (gs *GameServer) UpdateListCapacity(name string, capacity int64) error {
}
if list, ok := gs.Status.Lists[name]; ok {
list.Capacity = capacity
list.Values = truncateList(list.Capacity, list.Values)
gs.Status.Lists[name] = list
return nil
}
Expand All @@ -940,16 +941,22 @@ func (gs *GameServer) AppendListValues(name string, values []string) error {
mergedList := MergeRemoveDuplicates(list.Values, values)
// Any duplicate values are silently dropped.
list.Values = mergedList
// Truncate values if more than capacity
if len(list.Values) > int(list.Capacity) {
list.Values = append([]string{}, list.Values[:list.Capacity]...)
}
list.Values = truncateList(list.Capacity, list.Values)
gs.Status.Lists[name] = list
return nil
}
return errors.Errorf("unable to AppendListValues: Name %s, Values %s. List not found in GameServer %s", name, values, gs.ObjectMeta.GetName())
}

// truncateList truncates the list to the given capacity
func truncateList(capacity int64, list []string) []string {
if list == nil || len(list) <= int(capacity) {
return list
}
list = append([]string{}, list[:capacity]...)
return list
}

// MergeRemoveDuplicates merges two lists and removes any duplicate values.
// Maintains ordering, so new values from list2 are appended to the end of list1.
// Returns a new list with unique values only.
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/allocation/v1/gameserverallocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ func TestGameServerListActions(t *testing.T) {
want *agonesv1.GameServer
wantErr bool
}{
"update list capacity": {
"update list capacity truncates list": {
la: ListAction{
Capacity: int64Pointer(0),
},
Expand All @@ -962,7 +962,7 @@ func TestGameServerListActions(t *testing.T) {
want: &agonesv1.GameServer{Status: agonesv1.GameServerStatus{
Lists: map[string]agonesv1.ListStatus{
"pages": {
Values: []string{"page1", "page2"},
Values: []string{},
Capacity: 0,
}}}},
wantErr: false,
Expand Down
247 changes: 237 additions & 10 deletions test/e2e/gameserverallocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ func TestCreateFleetAndGameServerPlayerCapacityAllocation(t *testing.T) {
require.NotEqual(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"], gs2.ObjectMeta.Annotations["agones.dev/last-allocated"])
}

func TestCounterGameServerAllocation(t *testing.T) {
func TestCounterAndListGameServerAllocation(t *testing.T) {
if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
t.SkipNow()
}
Expand All @@ -276,6 +276,12 @@ func TestCounterGameServerAllocation(t *testing.T) {
Capacity: 10,
},
}
flt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
"players": {
Values: []string{"player0"},
Capacity: 10,
},
}

flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
require.NoError(t, err)
Expand All @@ -295,7 +301,7 @@ func TestCounterGameServerAllocation(t *testing.T) {
wantAllocated allocationv1.GameServerAllocationState // For a valid GSA: "allocated" if you expect the GSA to succed in allocating a GameServer, "unallocated" if not
wantState agonesv1.GameServerState
}{
"Allocate to same GameServer MinAvailable (available capacity)": {
"Counter Allocate to same GameServer MinAvailable (available capacity)": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
Expand All @@ -316,7 +322,28 @@ func TestCounterGameServerAllocation(t *testing.T) {
wantAllocated: allocated,
wantState: stateAllocated,
},
"Allocate to same GameServer MaxAvailable": {
"List Allocate to same GameServer MinAvailable (available capacity)": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
{
LabelSelector: fleetSelector,
GameServerState: &stateAllocated,
Lists: map[string]allocationv1.ListSelector{
"players": {
MinAvailable: 9,
}}}, {
LabelSelector: fleetSelector,
GameServerState: &ready,
Lists: map[string]allocationv1.ListSelector{
"players": {
MinAvailable: 9,
}}}}}},
wantGsaErr: false,
wantAllocated: allocated,
wantState: stateAllocated,
},
"Counter Allocate to same GameServer MaxAvailable": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
Expand All @@ -337,6 +364,27 @@ func TestCounterGameServerAllocation(t *testing.T) {
wantAllocated: allocated,
wantState: stateAllocated,
},
"List Allocate to same GameServer MaxAvailable": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
{
LabelSelector: fleetSelector,
GameServerState: &stateAllocated,
Lists: map[string]allocationv1.ListSelector{
"players": {
MaxAvailable: 9,
}}}, {
LabelSelector: fleetSelector,
GameServerState: &ready,
Lists: map[string]allocationv1.ListSelector{
"players": {
MaxAvailable: 9,
}}}}}},
wantGsaErr: false,
wantAllocated: allocated,
wantState: stateAllocated,
},
"Allocate to same GameServer MinCount (count value)": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Expand Down Expand Up @@ -379,8 +427,29 @@ func TestCounterGameServerAllocation(t *testing.T) {
wantAllocated: allocated,
wantState: stateAllocated,
},
"List Allocate to same GameServer Contains Value": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
{
LabelSelector: fleetSelector,
GameServerState: &stateAllocated,
Lists: map[string]allocationv1.ListSelector{
"players": {
ContainsValue: "player0",
}}}, {
LabelSelector: fleetSelector,
GameServerState: &ready,
Lists: map[string]allocationv1.ListSelector{
"players": {
ContainsValue: "player0",
}}}}}},
wantGsaErr: false,
wantAllocated: allocated,
wantState: stateAllocated,
},
// 0 for MaxCount or MaxAvailable means unlimited maximum. Default for all fields: 0
"Allocate to same GameServer no values": {
"Allocate to same GameServer no Counter values": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
Expand All @@ -397,20 +466,50 @@ func TestCounterGameServerAllocation(t *testing.T) {
wantAllocated: allocated,
wantState: stateAllocated,
},
"Allocate to same GameServer no List values": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
{
LabelSelector: fleetSelector,
GameServerState: &stateAllocated,
Lists: map[string]allocationv1.ListSelector{
"players": {}}}, {
LabelSelector: fleetSelector,
GameServerState: &ready,
Lists: map[string]allocationv1.ListSelector{
"players": {}}}}}},
wantGsaErr: false,
wantAllocated: allocated,
wantState: stateAllocated,
},
"Counter does not exist": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{{
LabelSelector: fleetSelector,
GameServerState: &ready,
Counters: map[string]allocationv1.CounterSelector{
"lames": {
"players": {
MinAvailable: 1,
}}}}}},
wantGsaErr: false,
wantAllocated: unallocated,
},
"MaxAvailable < MinAvailable": {
"List does not exist": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{{
LabelSelector: fleetSelector,
GameServerState: &ready,
Lists: map[string]allocationv1.ListSelector{
"games": {
MinAvailable: 1,
}}}}}},
wantGsaErr: false,
wantAllocated: unallocated,
},
"Counter MaxAvailable < MinAvailable": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{{
Expand All @@ -423,7 +522,20 @@ func TestCounterGameServerAllocation(t *testing.T) {
}}}}}},
wantGsaErr: true,
},
"Maxcount < MinCount": {
"List MaxAvailable < MinAvailable": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{{
LabelSelector: fleetSelector,
GameServerState: &ready,
Lists: map[string]allocationv1.ListSelector{
"players": {
MaxAvailable: 8,
MinAvailable: 9,
}}}}}},
wantGsaErr: true,
},
"Counter Maxcount < MinCount": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{{
Expand All @@ -436,6 +548,19 @@ func TestCounterGameServerAllocation(t *testing.T) {
}}}}}},
wantGsaErr: true,
},
"List Value does not exist": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{{
LabelSelector: fleetSelector,
GameServerState: &ready,
Lists: map[string]allocationv1.ListSelector{
"players": {
ContainsValue: "player1",
}}}}}},
wantGsaErr: false,
wantAllocated: unallocated,
},
"Negative values for MinCount, MaxCount, MaxAvailable, MinAvailable": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Expand Down Expand Up @@ -489,7 +614,7 @@ func TestCounterGameServerAllocation(t *testing.T) {
require.NotEqual(t, gs1.ObjectMeta.ResourceVersion, gs2.ObjectMeta.ResourceVersion)
require.NotEqual(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"], gs2.ObjectMeta.Annotations["agones.dev/last-allocated"])

// Reset any GameServers in state Allocated -> Ready. Note: This does not reset any changes to Counters.
// Reset any GameServers in state Allocated -> Ready. Note: This does not reset any changes to Counters or Lists.
list, err := framework.ListGameServersFromFleet(flt)
require.NoError(t, err)
for _, gs := range list {
Expand Down Expand Up @@ -703,9 +828,9 @@ func TestCounterGameServerAllocationActions(t *testing.T) {
}

// Reset any GameServers in state Allocated -> Ready, and reset any changes to Counters.
list, err := framework.ListGameServersFromFleet(flt)
gsList, err := framework.ListGameServersFromFleet(flt)
require.NoError(t, err)
for _, gs := range list {
for _, gs := range gsList {
if gs.Status.State == ready && cmp.Equal(gs.Status.Counters, counters) {
continue
}
Expand All @@ -720,6 +845,108 @@ func TestCounterGameServerAllocationActions(t *testing.T) {
}
}

func TestListGameServerAllocationActions(t *testing.T) {
if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
t.SkipNow()
}
t.Parallel()
ctx := context.Background()
client := framework.AgonesClient.AgonesV1()

lists := map[string]agonesv1.ListStatus{}
lists["players"] = agonesv1.ListStatus{
Values: []string{"player0", "player1", "player2"},
Capacity: 8,
}

flt := defaultFleet(framework.Namespace)
flt.Spec.Template.Spec.Lists = lists

flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
require.NoError(t, err)
defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))

fleetSelector := metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}
allocated := agonesv1.GameServerStateAllocated
ready := agonesv1.GameServerStateReady
one := int64(1)

testCases := map[string]struct {
gsa allocationv1.GameServerAllocation
wantGsaErr bool
wantCapacity *int64
wantValues []string
}{
"add List values": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
{LabelSelector: fleetSelector},
},
Lists: map[string]allocationv1.ListAction{
"players": {
AddValues: []string{"player3", "player4", "player5"},
}}}},
wantGsaErr: false,
wantValues: []string{"player0", "player1", "player2", "player3", "player4", "player5"},
},
"change List capacity truncates": {
gsa: allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
Selectors: []allocationv1.GameServerSelector{
{LabelSelector: fleetSelector},
},
Lists: map[string]allocationv1.ListAction{
"players": {
Capacity: &one,
}}}},
wantGsaErr: false,
wantValues: []string{"player0"},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {

gsa, err := framework.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(ctx, testCase.gsa.DeepCopy(), metav1.CreateOptions{})
if testCase.wantGsaErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, string(allocated), string(gsa.Status.State))

gs1, err := framework.AgonesClient.AgonesV1().GameServers(flt.ObjectMeta.Namespace).Get(ctx, gsa.Status.GameServerName, metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, allocated, gs1.Status.State)
assert.NotNil(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"])

list, ok := gs1.Status.Lists["players"]
assert.True(t, ok)
if testCase.wantCapacity != nil {
assert.Equal(t, *testCase.wantCapacity, list.Capacity)
}
assert.Equal(t, testCase.wantValues, list.Values)

// Reset any GameServers in state Allocated -> Ready, and reset any changes to Lists.
gsList, err := framework.ListGameServersFromFleet(flt)
require.NoError(t, err)
for _, gs := range gsList {
if gs.Status.State == ready && cmp.Equal(gs.Status.Lists, lists) {
continue
}
gsCopy := gs.DeepCopy()
gsCopy.Status.State = ready
gsCopy.Status.Lists = lists
reqReadyGs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
require.NoError(t, err)
require.Equal(t, ready, reqReadyGs.Status.State)
}
})
}
}

func TestMultiClusterAllocationOnLocalCluster(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit f5bdb4d

Please sign in to comment.