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

adding tests && refactoring gameserver api #158

Merged
merged 3 commits into from
Feb 9, 2022
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export IMAGE_NAME_NODE_AGENT=thundernetes-nodeagent
export IMAGE_NAME_INIT_CONTAINER=thundernetes-initcontainer
export IMAGE_NAME_NETCORE_SAMPLE=thundernetes-netcore
export IMAGE_NAME_OPENARENA_SAMPLE=thundernetes-openarena
export IMAGE_NAME_GAMESERVER_API=thundernetes-gameserverapi

export IMAGE_TAG?=$(shell git rev-list HEAD --max-count=1 --abbrev-commit)

Expand Down
239 changes: 239 additions & 0 deletions cmd/e2e/gameserverapi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"time"

"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
mpsv1alpha1 "github.com/playfab/thundernetes/pkg/operator/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
port = 5001
host = "localhost"
contentType = "application/json"
timeout = time.Second * 10
duration = time.Second * 10
interval = time.Millisecond * 250
)

var imgName string

var _ = Describe("GameServerAPI tests", func() {
imgName = os.Getenv("IMG")
Expect(imgName).NotTo(BeEmpty())

client := http.Client{
Timeout: 5 * time.Second,
}

testNamespace := "gameserverapi"
url := fmt.Sprintf("http://%s:%d/api/v1", host, port)

buildName := "test-build-gameserverapi"
buildNameNoImage := "test-build-gameserverapi-no-image"

It("should return an error when creating a GameServerBuild that does not have a name", func() {
build := createGameServerBuild(buildNameNoImage, testNamespace)
build.Name = ""
b, err := json.Marshal(build)
Expect(err).ToNot(HaveOccurred())
req, err := client.Post(url+"/gameserverbuilds", contentType, bytes.NewReader(b))
Expect(err).ToNot(HaveOccurred())
Expect(req.StatusCode).To(Equal(http.StatusInternalServerError))
})

It("should return an error when deleting a non-existent GameServerBuild", func() {
req, err := http.NewRequest("DELETE", url+"/gameserverbuilds/nonexistentbuild", nil)
Expect(err).ToNot(HaveOccurred())
res, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
Expect(res.StatusCode).To(Equal(http.StatusNotFound))
})

It("should return an error when getting an non-existing GameServerBuild", func() {
r, err := client.Get(fmt.Sprintf("%s/gameserverbuilds/%s/%s", url, testNamespace, "nonexistentbuild"))
Expect(err).ToNot(HaveOccurred())
Expect(r.StatusCode).To(Equal(http.StatusNotFound))
})

It("should return an error when getting an non-existing GameServer", func() {
r, err := client.Get(fmt.Sprintf("%s/gameservers/%s/%s", url, testNamespace, "nonexistentgameserver"))
Expect(err).ToNot(HaveOccurred())
Expect(r.StatusCode).To(Equal(http.StatusNotFound))
})

It("should create a new GameServerBuild, list it and then delete it", func() {
build := createGameServerBuild(buildName, testNamespace)
b, err := json.Marshal(build)
Expect(err).ToNot(HaveOccurred())
req, err := client.Post(url+"/gameserverbuilds", contentType, bytes.NewReader(b))
Expect(err).ToNot(HaveOccurred())
Expect(req.StatusCode).To(Equal(http.StatusCreated))

// list all the GameServerBuilds and see if the one we created is there
r, err := client.Get(url + "/gameserverbuilds")
Expect(err).ToNot(HaveOccurred())
Expect(r.StatusCode).To(Equal(http.StatusOK))
defer r.Body.Close()
var l mpsv1alpha1.GameServerBuildList
body, err := ioutil.ReadAll(r.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body, &l)
Expect(err).ToNot(HaveOccurred())
var found bool
for _, b := range l.Items {
if b.Name == buildName {
found = true
break
}
}
Expect(found).To(BeTrue())

// get the specific GameServerBuild
r, err = client.Get(fmt.Sprintf("%s/gameserverbuilds/%s/%s", url, testNamespace, buildName))
Expect(err).ToNot(HaveOccurred())
Expect(r.StatusCode).To(Equal(http.StatusOK))
var bu mpsv1alpha1.GameServerBuild
body, err = ioutil.ReadAll(r.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body, &bu)
Expect(err).ToNot(HaveOccurred())
Expect(bu.Name).To(Equal(buildName))

// list GameServers for GameServerBuild
r, err = client.Get(fmt.Sprintf("%s/gameserverbuilds/%s/%s/gameservers", url, testNamespace, buildName))
Expect(err).ToNot(HaveOccurred())
var gsList mpsv1alpha1.GameServerList
body, err = ioutil.ReadAll(r.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body, &gsList)
Expect(err).ToNot(HaveOccurred())
Expect(len(gsList.Items)).To(Equal(2))

gsName := gsList.Items[0].Name
// get a GameServer
r, err = client.Get(fmt.Sprintf("%s/gameservers/%s/%s", url, testNamespace, gsName))
Expect(err).ToNot(HaveOccurred())
var gs mpsv1alpha1.GameServer
body, err = ioutil.ReadAll(r.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body, &gs)
Expect(err).ToNot(HaveOccurred())
Expect(gs.Name).To(Equal(gsName))

// delete this GameServer
req1, err := http.NewRequest("DELETE", fmt.Sprintf("%s/gameservers/%s/%s", url, testNamespace, gsName), nil)
Expect(err).ToNot(HaveOccurred())
res, err := client.Do(req1)
Expect(err).ToNot(HaveOccurred())
Expect(res.StatusCode).To(Equal(http.StatusOK))

// make sure this GameServer is not returned any more
// a finalizer runs so it will not disappear at once
Eventually(func() int {
r, err = client.Get(fmt.Sprintf("%s/gameservers/%s/%s", url, testNamespace, gsName))
Expect(err).ToNot(HaveOccurred())
return r.StatusCode
}, timeout, interval).Should(Equal(http.StatusNotFound))

// make sure controller creates an extra GameServer
Eventually(func() int {
r, err = client.Get(fmt.Sprintf("%s/gameserverbuilds/%s/%s/gameservers", url, testNamespace, buildName))
Expect(err).ToNot(HaveOccurred())
var gsList mpsv1alpha1.GameServerList
body, err = ioutil.ReadAll(r.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body, &gsList)
Expect(err).ToNot(HaveOccurred())
return len(gsList.Items)
}, time.Second*5, time.Millisecond*500).Should(Equal(2))

// TODO: allocate so GameServerDetails can be created
// TODO: list GameServerDetails for GameServerBuild
// TODO: get a GameServerDetail

// update the GameServerBuild to 3 standingBy and 6 max
patchBody := map[string]interface{}{
"standingBy": 3,
"max": 6,
}
pb, err := json.Marshal(patchBody)
Expect(err).ToNot(HaveOccurred())
req2, err := http.NewRequest("PATCH", fmt.Sprintf("%s/gameserverbuilds/%s/%s", url, testNamespace, buildName), bytes.NewReader(pb))
Expect(err).ToNot(HaveOccurred())
res, err = client.Do(req2)
Expect(err).ToNot(HaveOccurred())
Expect(res.StatusCode).To(Equal(http.StatusOK))

// get the specific GameServerBuild again and make sure the values were updated
r, err = client.Get(fmt.Sprintf("%s/gameserverbuilds/%s/%s", url, testNamespace, buildName))
Expect(err).ToNot(HaveOccurred())
Expect(r.StatusCode).To(Equal(http.StatusOK))
body, err = ioutil.ReadAll(r.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body, &bu)
Expect(err).ToNot(HaveOccurred())
Expect(bu.Spec.StandingBy).To(Equal(3))
Expect(bu.Spec.Max).To(Equal(6))

// delete the GameServerBuild
req3, err := http.NewRequest("DELETE", fmt.Sprintf("%s/gameserverbuilds/%s/%s", url, testNamespace, buildName), nil)
Expect(err).ToNot(HaveOccurred())
res, err = client.Do(req3)
Expect(err).ToNot(HaveOccurred())
Expect(res.StatusCode).To(Equal(http.StatusOK))

// make sure the GameServerBuild is gone
r, err = client.Get(fmt.Sprintf("%s/gameserverbuilds/%s/%s", url, testNamespace, buildName))
Expect(err).ToNot(HaveOccurred())
Expect(r.StatusCode).To(Equal(http.StatusNotFound))
})
})

func createGameServerBuild(name, namespace string) mpsv1alpha1.GameServerBuild {
return mpsv1alpha1.GameServerBuild{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: mpsv1alpha1.GameServerBuildSpec{
BuildID: uuid.New().String(),
TitleID: "1E03",
PortsToExpose: []mpsv1alpha1.PortToExpose{{ContainerName: "netcore-sample", PortName: "gameport"}},
BuildMetadata: []mpsv1alpha1.BuildMetadataItem{
{Key: "metadatakey1", Value: "metadatavalue1"},
{Key: "metadatakey2", Value: "metadatavalue2"},
{Key: "metadatakey3", Value: "metadatavalue3"},
},
StandingBy: 2,
Max: 4,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Image: imgName,
Name: "netcore-sample",
ImagePullPolicy: corev1.PullIfNotPresent,
Ports: []corev1.ContainerPort{
{
Name: "gameport",
ContainerPort: 80,
},
},
},
},
},
},
},
}
}
4 changes: 2 additions & 2 deletions cmd/e2e/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var (
)

const (
loopTimesConst int = 10
loopTimesConst int = 15
delayInSecondsForLoopTest int = 1
LabelBuildID = "BuildID"
invalidStatusCode string = "invalid status code"
Expand Down Expand Up @@ -351,7 +351,7 @@ func main() {
validateBuildState(ctx, buildState{buildID: testBuildSleepBeforeReadyForPlayersID, buildName: testBuildSleepBeforeReadyForPlayersName, standingByCount: 3, activeCount: 1, podRunningCount: 4})
validateThatAllocatedServersHaveReadyForPlayersUnblocked(ctx, testBuildSleepBeforeReadyForPlayersID)

fmt.Printf("Allocating on build %s with a new sessionID", testBuild1Name)
fmt.Printf("Allocating on build %s with a new sessionID\n", testBuild1Name)
sessionID1_2 := uuid.New().String()
if err := allocate(test1BuildID, sessionID1_2, cert); err != nil {
handleError(err)
Expand Down
15 changes: 15 additions & 0 deletions cmd/e2e/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestEndToEnd(t *testing.T) {
defer GinkgoRecover()
RegisterFailHandler(Fail)

RunSpecs(t, "End To End Suite")
}
10 changes: 10 additions & 0 deletions cmd/e2e/tools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build tools
// +build tools

package main

import (
_ "github.com/onsi/ginkgo/v2/ginkgo"
)

// https://onsi.github.io/ginkgo/#recommended-continuous-integration-configuration
59 changes: 59 additions & 0 deletions cmd/gameserverapi/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: thundernetes-gameserverapi
namespace: thundernetes-system
labels:
app: thundernetes-gameserverapi
spec:
selector:
matchLabels:
app: thundernetes-gameserverapi
replicas: 1
template:
metadata:
labels:
app: thundernetes-gameserverapi
spec:
containers:
- image: thundernetes-gameserverapi:${IMAGE_TAG}
name: gameserverapi
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 500Mi
limits:
cpu: 100m
memory: 500Mi
ports:
- containerPort: 5001
hostPort: 5001
livenessProbe:
httpGet:
path: /healthz
port: 5001
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 5001
initialDelaySeconds: 3
periodSeconds: 10
serviceAccountName: thundernetes-controller-manager
terminationGracePeriodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: thundernetes-gameserverapi
namespace: thundernetes-system
spec:
selector:
app: thundernetes-gameserverapi
ports:
- protocol: TCP
port: 5001
targetPort: 5001
type: LoadBalancer
Loading