Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E2E Tests for GameServer Player Tracking #1541

Merged
merged 2 commits into from
May 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-images

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 @@ -176,6 +176,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))
aLekSer marked this conversation as resolved.
Show resolved Hide resolved
continue

case "PLAYER_COUNT":
respond(conn, sender, getPlayerCount(s))
aLekSer marked this conversation as resolved.
Show resolved Hide resolved
continue
}

respond(conn, sender, "ACK: "+txt+"\n")
Expand Down Expand Up @@ -284,6 +330,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 @@ -147,15 +147,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)
}