Skip to content

Commit

Permalink
E2E Tests for GameServer Player Tracking (googleforgames#1541)
Browse files Browse the repository at this point in the history
* E2E Tests for GameServer Player Tracking

Updated the udp-simple example to accept various player tracking
commands, and also implemented e2e tests to test it working on a
singular GameServer instance.

Work on googleforgames#1507
  • Loading branch information
markmandel authored and ilkercelikyilmaz committed Oct 23, 2020
1 parent 07a7951 commit 389b0fe
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 4 deletions.
2 changes: 1 addition & 1 deletion build/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ KIND_PROFILE ?= agones
KIND_CONTAINER_NAME=$(KIND_PROFILE)-control-plane

# Game Server image to use while doing end-to-end tests
GS_TEST_IMAGE ?= gcr.io/agones-images/udp-server:0.19
GS_TEST_IMAGE ?= gcr.io/agones-images/udp-server:0.20

ALL_FEATURE_GATES ?= "PlayerTracking=true&ContainerPortAllocation=true"

Expand Down
2 changes: 1 addition & 1 deletion examples/simple-udp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ REPOSITORY = gcr.io/agones-scale-test-1

mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
project_path := $(dir $(mkfile_path))
server_tag = $(REPOSITORY)/udp-server:0.19
server_tag = $(REPOSITORY)/udp-server:0.20
root_path = $(realpath $(project_path)/../..)

# _____ _
Expand Down
113 changes: 113 additions & 0 deletions examples/simple-udp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,52 @@ func readWriteLoop(conn net.PacketConn, stop chan struct{}, s *sdk.SDK) {
respond(conn, sender, "ERROR: Invalid ANNOTATION command, must use zero or 2 arguments\n")
continue
}
case "PLAYER_CAPACITY":
switch len(parts) {
case 1:
respond(conn, sender, getPlayerCapacity(s))
continue
case 2:
if cap, err := strconv.Atoi(parts[1]); err != nil {
respond(conn, sender, err.Error()+"\n")
continue
} else {
setPlayerCapacity(s, int64(cap))
}
default:
respond(conn, sender, "ERROR: Invalid PLAYER_CAPACITY, should have 0 or 1 arguments\n")
continue
}

case "PLAYER_CONNECT":
if len(parts) < 2 {
respond(conn, sender, "ERROR: Invalid PLAYER_CONNECT, should have 1 arguments\n")
continue
}
playerConnect(s, parts[1])

case "PLAYER_DISCONNECT":
if len(parts) < 2 {
respond(conn, sender, "ERROR: Invalid PLAYER_CONNECT, should have 1 arguments\n")
continue
}
playerDisconnect(s, parts[1])

case "PLAYER_CONNECTED":
if len(parts) < 2 {
respond(conn, sender, "ERROR: Invalid PLAYER_CONNECTED, should have 1 arguments\n")
continue
}
respond(conn, sender, playerIsConnected(s, parts[1]))
continue

case "GET_PLAYERS":
respond(conn, sender, getConnectedPlayers(s))
continue

case "PLAYER_COUNT":
respond(conn, sender, getPlayerCount(s))
continue
}

respond(conn, sender, "ACK: "+txt+"\n")
Expand Down Expand Up @@ -312,6 +358,73 @@ func setLabel(s *sdk.SDK, key, value string) {
}
}

// setPlayerCapacity sets the player capacity to the given value
func setPlayerCapacity(s *sdk.SDK, capacity int64) {
log.Printf("Setting Player Capacity to %d", capacity)
if err := s.Alpha().SetPlayerCapacity(capacity); err != nil {
log.Fatalf("could not set capacity: %v", err)
}
}

// getPlayerCapacity returns the current player capacity as a string
func getPlayerCapacity(s *sdk.SDK) string {
log.Print("Getting Player Capacity")
capacity, err := s.Alpha().GetPlayerCapacity()
if err != nil {
log.Fatalf("could not get capacity: %v", err)
}
return strconv.FormatInt(capacity, 10) + "\n"
}

// playerConnect connects a given player
func playerConnect(s *sdk.SDK, id string) {
log.Printf("Connecting Player: %s", id)
if _, err := s.Alpha().PlayerConnect(id); err != nil {
log.Fatalf("could not connect player: %v", err)
}
}

// playerDisconnect disconnects a given player
func playerDisconnect(s *sdk.SDK, id string) {
log.Printf("Disconnecting Player: %s", id)
if _, err := s.Alpha().PlayerDisconnect(id); err != nil {
log.Fatalf("could not disconnect player: %v", err)
}
}

// playerIsConnected returns a bool as a string if a player is connected
func playerIsConnected(s *sdk.SDK, id string) string {
log.Printf("Checking if player %s is connected", id)

connected, err := s.Alpha().IsPlayerConnected(id)
if err != nil {
log.Fatalf("could not retrieve if player is connected: %v", err)
}

return strconv.FormatBool(connected) + "\n"
}

// getConnectedPlayers returns a comma delimeted list of connected players
func getConnectedPlayers(s *sdk.SDK) string {
log.Print("Retrieving connected player list")
list, err := s.Alpha().GetConnectedPlayers()
if err != nil {
log.Fatalf("could not retrieve connected players: %s", err)
}

return strings.Join(list, ",") + "\n"
}

// getPlayerCount returns the count of connected players as a string
func getPlayerCount(s *sdk.SDK) string {
log.Print("Retrieving connected player count")
count, err := s.Alpha().GetPlayerCount()
if err != nil {
log.Fatalf("could not retrieve player count: %s", err)
}
return strconv.FormatInt(count, 10) + "\n"
}

// doHealth sends the regular Health Pings
func doHealth(sdk *sdk.SDK, stop <-chan struct{}) {
tick := time.Tick(2 * time.Second)
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,15 @@ func NewFromFlags() (*Framework, error) {
}

viper.SetDefault(kubeconfigFlag, filepath.Join(usr.HomeDir, "/.kube/config"))
viper.SetDefault(gsimageFlag, "gcr.io/agones-images/udp-server:0.19")
viper.SetDefault(gsimageFlag, "gcr.io/agones-images/udp-server:0.20")
viper.SetDefault(pullSecretFlag, "")
viper.SetDefault(stressTestLevelFlag, 0)
viper.SetDefault(perfOutputDirFlag, "")
viper.SetDefault(versionFlag, "")
viper.SetDefault(runtime.FeatureGateFlag, "")

kubeconfig := pflag.String(kubeconfigFlag, viper.GetString(kubeconfigFlag), "kube config path, e.g. $HOME/.kube/config")
gsimage := pflag.String(gsimageFlag, viper.GetString(gsimageFlag), "gameserver image to use for those tests, gcr.io/agones-images/udp-server:0.19")
gsimage := pflag.String(gsimageFlag, viper.GetString(gsimageFlag), "gameserver image to use for those tests, gcr.io/agones-images/udp-server:0.20")
pullSecret := pflag.String(pullSecretFlag, viper.GetString(pullSecretFlag), "optional secret to be used for pulling the gameserver and/or Agones SDK sidecar images")
stressTestLevel := pflag.Int(stressTestLevelFlag, viper.GetInt(stressTestLevelFlag), "enable stress test at given level 0-100")
perfOutputDir := pflag.String(perfOutputDirFlag, viper.GetString(perfOutputDirFlag), "write performance statistics to the specified directory")
Expand Down
158 changes: 158 additions & 0 deletions test/e2e/gameserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,3 +736,161 @@ func TestGameServerResourceValidation(t *testing.T) {
assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type)
assert.Equal(t, "container", statusErr.Status().Details.Causes[0].Field)
}

func TestGameServerSetPlayerCapacity(t *testing.T) {
if !runtime.FeatureEnabled(runtime.FeaturePlayerTracking) {
t.SkipNow()
}
t.Parallel()

t.Run("no initial capacity set", func(t *testing.T) {
gs := framework.DefaultGameServer(defaultNs)
gs, err := framework.CreateGameServerAndWaitUntilReady(defaultNs, gs)
if err != nil {
t.Fatalf("Could not get a GameServer ready: %v", err)
}
assert.Equal(t, gs.Status.State, agonesv1.GameServerStateReady)
assert.Equal(t, int64(0), gs.Status.Players.Capacity)

reply, err := e2eframework.SendGameServerUDP(gs, "PLAYER_CAPACITY")
assert.NoError(t, err)
assert.Equal(t, "0\n", reply)

reply, err = e2eframework.SendGameServerUDP(gs, "PLAYER_CAPACITY 20")
if err != nil {
t.Fatalf("Could not message GameServer: %v", err)
}
assert.Equal(t, "ACK: PLAYER_CAPACITY 20\n", reply)

reply, err = e2eframework.SendGameServerUDP(gs, "PLAYER_CAPACITY")
assert.NoError(t, err)
assert.Equal(t, "20\n", reply)

err = wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
gs, err := framework.AgonesClient.AgonesV1().GameServers(defaultNs).Get(gs.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return gs.Status.Players.Capacity == 20, nil
})
assert.NoError(t, err)
})

t.Run("initial capacity set", func(t *testing.T) {
gs := framework.DefaultGameServer(defaultNs)
gs.Spec.Players = &agonesv1.PlayersSpec{InitialCapacity: 10}
gs, err := framework.CreateGameServerAndWaitUntilReady(defaultNs, gs)
if err != nil {
t.Fatalf("Could not get a GameServer ready: %v", err)
}
assert.Equal(t, gs.Status.State, agonesv1.GameServerStateReady)
assert.Equal(t, int64(10), gs.Status.Players.Capacity)

reply, err := e2eframework.SendGameServerUDP(gs, "PLAYER_CAPACITY")
assert.NoError(t, err)
assert.Equal(t, "10\n", reply)

reply, err = e2eframework.SendGameServerUDP(gs, "PLAYER_CAPACITY 20")
if err != nil {
t.Fatalf("Could not message GameServer: %v", err)
}
assert.Equal(t, "ACK: PLAYER_CAPACITY 20\n", reply)

reply, err = e2eframework.SendGameServerUDP(gs, "PLAYER_CAPACITY")
assert.NoError(t, err)
assert.Equal(t, "20\n", reply)

err = wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
gs, err := framework.AgonesClient.AgonesV1().GameServers(defaultNs).Get(gs.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return gs.Status.Players.Capacity == 20, nil
})
assert.NoError(t, err)

time.Sleep(30 * time.Second)
})
}

func TestPlayerConnectAndDisconnect(t *testing.T) {
if !runtime.FeatureEnabled(runtime.FeaturePlayerTracking) {
t.SkipNow()
}
t.Parallel()

gs := framework.DefaultGameServer(defaultNs)
playerCount := int64(3)
gs.Spec.Players = &agonesv1.PlayersSpec{InitialCapacity: playerCount}
gs, err := framework.CreateGameServerAndWaitUntilReady(defaultNs, gs)
if err != nil {
t.Fatalf("Could not get a GameServer ready: %v", err)
}
assert.Equal(t, gs.Status.State, agonesv1.GameServerStateReady)
assert.Equal(t, playerCount, gs.Status.Players.Capacity)

// add three players in quick succession
for i := int64(1); i <= playerCount; i++ {
msg := "PLAYER_CONNECT " + fmt.Sprintf("%d", i)
logrus.WithField("msg", msg).Info("Sending Player Connect")
reply, err := e2eframework.SendGameServerUDP(gs, msg)
if err != nil {
t.Fatalf("Could not message GameServer: %v", err)
}
assert.Equal(t, fmt.Sprintf("ACK: %s\n", msg), reply)
}

// deliberately do this before polling, to test the SDK returning the correct
// results before it is committed to the GameServer resource.
reply, err := e2eframework.SendGameServerUDP(gs, "PLAYER_CONNECTED 1")
assert.NoError(t, err)
assert.Equal(t, "true\n", reply)

reply, err = e2eframework.SendGameServerUDP(gs, "GET_PLAYERS")
assert.NoError(t, err)
assert.ElementsMatch(t, []string{"1", "2", "3"}, strings.Split(strings.TrimSpace(reply), ","))

reply, err = e2eframework.SendGameServerUDP(gs, "PLAYER_COUNT")
assert.NoError(t, err)
assert.Equal(t, "3\n", reply)

err = wait.Poll(time.Second, time.Minute, func() (bool, error) {
gs, err = framework.AgonesClient.AgonesV1().GameServers(defaultNs).Get(gs.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return gs.Status.Players.Count == playerCount, nil
})
assert.NoError(t, err)
assert.ElementsMatch(t, []string{"1", "2", "3"}, gs.Status.Players.IDs)

// let's disconnect player 2
logrus.Info("Disconnect Player 2")
reply, err = e2eframework.SendGameServerUDP(gs, "PLAYER_DISCONNECT 2")
if err != nil {
t.Fatalf("Could not message GameServer: %v", err)
}
assert.Equal(t, "ACK: PLAYER_DISCONNECT 2\n", reply)

reply, err = e2eframework.SendGameServerUDP(gs, "PLAYER_CONNECTED 2")
assert.NoError(t, err)
assert.Equal(t, "false\n", reply)

reply, err = e2eframework.SendGameServerUDP(gs, "GET_PLAYERS")
assert.NoError(t, err)
assert.ElementsMatch(t, []string{"1", "3"}, strings.Split(strings.TrimSpace(reply), ","))

reply, err = e2eframework.SendGameServerUDP(gs, "PLAYER_COUNT")
assert.NoError(t, err)
assert.Equal(t, "2\n", reply)

err = wait.Poll(time.Second, time.Minute, func() (bool, error) {
gs, err = framework.AgonesClient.AgonesV1().GameServers(defaultNs).Get(gs.ObjectMeta.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return gs.Status.Players.Count == 2, nil
})
assert.NoError(t, err)
assert.ElementsMatch(t, []string{"1", "3"}, gs.Status.Players.IDs)
}

0 comments on commit 389b0fe

Please sign in to comment.