Skip to content

Commit

Permalink
adds the first e2e test which create a gameserver and connect to it
Browse files Browse the repository at this point in the history
  • Loading branch information
Cyril TOVENA committed Jul 23, 2018
1 parent b4b6b42 commit 987bb48
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ tmp
build/local-includes/*
!build/local-includes/README.md
/release
debug.test
12 changes: 11 additions & 1 deletion build/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ GCP_CLUSTER_ZONE ?= us-west1-c
# the profile to use when developing on minikube
MINIKUBE_PROFILE ?= agones

# Game Server image to use while doing end-to-end tests
GS_TEST_IMAGE ?= gcr.io/agones-images/cpp-simple-server:0.2

# Directory that this Makefile is in.
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
build_path := $(dir $(mkfile_path))
Expand Down Expand Up @@ -106,7 +109,14 @@ test: ensure-build-image lint test-go test-install-yaml

# Run go tests
test-go:
docker run --rm $(common_mounts) $(build_tag) go test -race $(agones_package)/...
docker run --rm $(common_mounts) $(build_tag) go test -race $(agones_package)/pkg/... \
$(agones_package)/sdks/...

# Runs end-to-end tests on the current configured cluster
test-e2e:
docker run --rm $(common_mounts) $(build_tag) go test -v $(agones_package)/test/e2e/... \
--kubeconfig /root/.kube/config \
--gameserver-image=$(GS_TEST_IMAGE)

# Run test on install yaml - make sure there is no change
# mostly this is for CI
Expand Down
16 changes: 16 additions & 0 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# E2E Testing

End-to-end (e2e) testing is automated testing for real user scenarios.

## Build and run test

Prerequisites:
- a running k8s cluster and kube config. We will need to pass kube config as arguments.
- Have kubeconfig file ready.

e2e tests are written as Go test. All go test techniques apply, e.g. picking
what to run, timeout length. Let's say I want to run all tests in "test/e2e/":

```
$ go test -v ./test/e2e/ --kubeconfig "$HOME/.kube/config" --gameserver-image=gcr.io/agones-images/cpp-simple-server:0.2
```
134 changes: 134 additions & 0 deletions test/e2e/framework/framework.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package framework is a package helping setting up end-to-end testing accross a
// Kubernetes cluster.
package framework

import (
"fmt"
"net"
"time"

"agones.dev/agones/pkg/apis/stable/v1alpha1"
"agones.dev/agones/pkg/client/clientset/versioned"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
// required to use gcloud login see: https://github.com/kubernetes/client-go/issues/242
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"k8s.io/client-go/tools/clientcmd"
)

// Framework is a testing framework
type Framework struct {
KubeClient kubernetes.Interface
AgonesClient versioned.Interface
GameServerImage string
}

// New setups a testing framework using a kubeconfig path and the game server image to use for testing.
func New(kubeconfig, gsimage string) (*Framework, error) {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, errors.Wrap(err, "build config from flags failed")
}

kubeClient, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, errors.Wrap(err, "creating new kube-client failed")
}

agonesClient, err := versioned.NewForConfig(config)
if err != nil {
return nil, errors.Wrap(err, "creating new agones-client failed")
}

return &Framework{
KubeClient: kubeClient,
AgonesClient: agonesClient,
GameServerImage: gsimage,
}, nil
}

// CreateGameServerAndWaitUntilReady Creates a GameServer and wait for its state to become ready.
func (f *Framework) CreateGameServerAndWaitUntilReady(ns string, gs *v1alpha1.GameServer) (*v1alpha1.GameServer, error) {
newGs, err := f.AgonesClient.StableV1alpha1().GameServers(ns).Create(gs)
if err != nil {
return nil, fmt.Errorf("creating %v GameServer instances failed (%v): %v", gs.Spec, gs.Name, err)
}

readyGs, err := f.WaitForGameServerState(newGs, v1alpha1.Ready, 5*time.Minute)

if err != nil {
return nil, fmt.Errorf("waiting for %v GameServer instance readiness timed out (%v): %v",
gs.Spec, gs.Name, err)
}

return readyGs, nil
}

// WaitForGameServerState Waits untils the gameserver reach a given state before the timeout expires
func (f *Framework) WaitForGameServerState(gs *v1alpha1.GameServer, state v1alpha1.State,
timeout time.Duration) (*v1alpha1.GameServer, error) {
var pollErr error
var readyGs *v1alpha1.GameServer

err := wait.Poll(2*time.Second, timeout, func() (bool, error) {
readyGs, pollErr = f.AgonesClient.StableV1alpha1().GameServers(gs.Namespace).Get(gs.Name, v1.GetOptions{})

if pollErr != nil {
return false, nil
}

if readyGs.Status.State == state {
return true, nil
}

return false, nil
})
if err != nil {
return nil, errors.Wrapf(pollErr, "waiting for GameServer to be %v %v/%v: %v",
state, gs.Namespace, gs.Name, err)
}
return readyGs, nil
}

// CleanUp Delete all agones resources in a given namespace
func (f *Framework) CleanUp(ns string) error {
return f.AgonesClient.StableV1alpha1().GameServers(ns).
DeleteCollection(&v1.DeleteOptions{}, v1.ListOptions{})
}

// PingGameServer pings a gameserver and returns its reply
func PingGameServer(msg, address string) (reply string, err error) {
conn, err := net.Dial("udp", address)
if err != nil {
return "", err
}
defer func() {
err = conn.Close()
}()
_, err = conn.Write([]byte(msg))
if err != nil {
return "", errors.Wrapf(err, "Could not write message %s", msg)
}
b := make([]byte, 1024)
n, err := conn.Read(b)
if err != nil {
return "", err
}
return string(b[:n]), nil
}
69 changes: 69 additions & 0 deletions test/e2e/gameserver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
"fmt"
"testing"

"agones.dev/agones/pkg/apis/stable/v1alpha1"
agonesFramework "agones.dev/agones/test/e2e/framework"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const defaultNs = "default"

func TestCreateConnect(t *testing.T) {
t.Parallel()
gs := &v1alpha1.GameServer{ObjectMeta: metav1.ObjectMeta{GenerateName: "udp-server", Namespace: defaultNs},
Spec: v1alpha1.GameServerSpec{
Container: "udp-server",
Ports: []v1alpha1.GameServerPort{v1alpha1.GameServerPort{
ContainerPort: 7654,
Name: "gameport",
PortPolicy: v1alpha1.Dynamic,
Protocol: corev1.ProtocolUDP,
}},
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "udp-server",
Image: "gcr.io/agones-images/udp-server:0.1"}},
},
},
},
}
readyGs, err := framework.CreateGameServerAndWaitUntilReady(defaultNs, gs)

if err != nil {
t.Fatalf("Could not get a GameServer ready: %v", err)
}
assert.Equal(t, len(readyGs.Status.Ports), 1)
assert.NotEmpty(t, readyGs.Status.Ports[0].Port)
assert.NotEmpty(t, readyGs.Status.Address)
assert.NotEmpty(t, readyGs.Status.NodeName)
assert.Equal(t, readyGs.Status.State, v1alpha1.Ready)

reply, err := agonesFramework.PingGameServer("Hello World !", fmt.Sprintf("%s:%d", readyGs.Status.Address,
readyGs.Status.Ports[0].Port))

if err != nil {
t.Fatalf("Could ping GameServer: %v", err)
}

assert.Equal(t, reply, "ACK: Hello World !\n")
}
57 changes: 57 additions & 0 deletions test/e2e/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
"flag"
"log"
"os"
"os/user"
"path/filepath"
"testing"

agonesFramework "agones.dev/agones/test/e2e/framework"
)

var framework *agonesFramework.Framework

func TestMain(m *testing.M) {
usr, _ := user.Current()
kubeconfig := flag.String("kubeconfig", filepath.Join(usr.HomeDir, "/.kube/config"),
"kube config path, e.g. $HOME/.kube/config")
gsimage := flag.String("gameserver-image", "gcr.io/agones-images/cpp-simple-server:0.2",
"gameserver image to use for those tests, gcr.io/agones-images/cpp-simple-server:0.2")

flag.Parse()

var (
err error
exitCode int
)

if framework, err = agonesFramework.New(*kubeconfig, *gsimage); err != nil {
log.Printf("failed to setup framework: %v\n", err)
os.Exit(1)
}
defer func() {
err = framework.CleanUp(defaultNs)
if err != nil {
log.Printf("failed to cleanup resources: %v\n", err)
}
os.Exit(exitCode)
}()
exitCode = m.Run()

}

0 comments on commit 987bb48

Please sign in to comment.