diff --git a/build/Makefile b/build/Makefile index 1cedfa14be..75e9b722ad 100644 --- a/build/Makefile +++ b/build/Makefile @@ -57,7 +57,7 @@ KIND_PROFILE ?= agones KIND_CONTAINER_NAME=kind-$(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.8 +GS_TEST_IMAGE ?= gcr.io/agones-images/udp-server:0.9 # Directory that this Makefile is in. mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) diff --git a/examples/fleet.yaml b/examples/fleet.yaml index eea0dc334a..9a2c508c8d 100644 --- a/examples/fleet.yaml +++ b/examples/fleet.yaml @@ -67,4 +67,4 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 \ No newline at end of file + image: gcr.io/agones-images/udp-server:0.9 \ No newline at end of file diff --git a/examples/simple-udp/Makefile b/examples/simple-udp/Makefile index 7a1030e08e..411710c370 100644 --- a/examples/simple-udp/Makefile +++ b/examples/simple-udp/Makefile @@ -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.8 +server_tag = $(REPOSITORY)/udp-server:0.9 root_path = $(realpath $(project_path)/../..) # _____ _ diff --git a/examples/simple-udp/fleet-distributed.yaml b/examples/simple-udp/fleet-distributed.yaml index 4a397e5baa..ff53f58689 100644 --- a/examples/simple-udp/fleet-distributed.yaml +++ b/examples/simple-udp/fleet-distributed.yaml @@ -32,7 +32,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 resources: requests: memory: "32Mi" diff --git a/examples/simple-udp/fleet.yaml b/examples/simple-udp/fleet.yaml index c0448327bb..e2d91c8de4 100644 --- a/examples/simple-udp/fleet.yaml +++ b/examples/simple-udp/fleet.yaml @@ -27,7 +27,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 resources: requests: memory: "64Mi" diff --git a/examples/simple-udp/gameserver.yaml b/examples/simple-udp/gameserver.yaml index 121551ff68..306f0ec902 100644 --- a/examples/simple-udp/gameserver.yaml +++ b/examples/simple-udp/gameserver.yaml @@ -25,7 +25,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 resources: requests: memory: "32Mi" diff --git a/examples/simple-udp/gameserverset.yaml b/examples/simple-udp/gameserverset.yaml index e62b2da8cb..6d534e3c18 100644 --- a/examples/simple-udp/gameserverset.yaml +++ b/examples/simple-udp/gameserverset.yaml @@ -31,4 +31,4 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 \ No newline at end of file + image: gcr.io/agones-images/udp-server:0.9 \ No newline at end of file diff --git a/examples/simple-udp/main.go b/examples/simple-udp/main.go index bedd977d8e..d2c6b87665 100644 --- a/examples/simple-udp/main.go +++ b/examples/simple-udp/main.go @@ -85,6 +85,8 @@ func readWriteLoop(conn net.PacketConn, stop chan struct{}, s *sdk.SDK) { switch parts[0] { // shuts down the gameserver case "EXIT": + // respond here, as we os.Exit() before we get to below + respond(conn, sender, "ACK: "+txt+"\n") exit(s) // turns off the health pings diff --git a/install/helm/agones/templates/NOTES.txt b/install/helm/agones/templates/NOTES.txt index 9d31364ee5..7a534bcdb3 100644 --- a/install/helm/agones/templates/NOTES.txt +++ b/install/helm/agones/templates/NOTES.txt @@ -19,7 +19,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 Finally don't forget to explore our documentation and usage guides on how to develop and host dedicated game servers on top of Agones. : diff --git a/pkg/gameservers/controller.go b/pkg/gameservers/controller.go index 9322d786d3..f439fc607c 100644 --- a/pkg/gameservers/controller.go +++ b/pkg/gameservers/controller.go @@ -729,8 +729,7 @@ func (c *Controller) syncGameServerRequestReadyState(gs *v1alpha1.GameServer) (* // syncGameServerShutdownState deletes the GameServer (and therefore the backing Pod) if it is in shutdown state func (c *Controller) syncGameServerShutdownState(gs *v1alpha1.GameServer) error { - if !gs.ObjectMeta.DeletionTimestamp.IsZero() || - (gs.Status.State != v1alpha1.GameServerStateShutdown && gs.Status.State != v1alpha1.GameServerStateUnhealthy) { + if !(gs.Status.State == v1alpha1.GameServerStateShutdown && gs.ObjectMeta.DeletionTimestamp.IsZero()) { return nil } diff --git a/pkg/gameservers/health.go b/pkg/gameservers/health.go index 2c028b5b84..9bfb25eaa0 100644 --- a/pkg/gameservers/health.go +++ b/pkg/gameservers/health.go @@ -164,8 +164,7 @@ func (hc *HealthController) syncGameServer(key string) error { } // at this point we don't care, we're already Unhealthy / deleting - if !gs.ObjectMeta.DeletionTimestamp.IsZero() || gs.Status.State == v1alpha1.GameServerStateShutdown || - gs.Status.State == v1alpha1.GameServerStateUnhealthy { + if gs.IsBeingDeleted() || gs.Status.State == v1alpha1.GameServerStateUnhealthy { return nil } diff --git a/pkg/gameserversets/controller.go b/pkg/gameserversets/controller.go index c3a879edda..c98ab87d21 100644 --- a/pkg/gameserversets/controller.go +++ b/pkg/gameserversets/controller.go @@ -467,7 +467,7 @@ func computeReconciliationAction(strategy apis.SchedulingStrategy, list []*v1alp toDelete = append(toDelete, potentialDeletions[0:deleteCount]...) } - if deleteCount > maxDeletions { + if len(toDelete) > maxDeletions { toDelete = toDelete[0:maxDeletions] partialReconciliation = true } diff --git a/pkg/gameserversets/controller_test.go b/pkg/gameserversets/controller_test.go index d4ace95659..1a96dc4151 100644 --- a/pkg/gameserversets/controller_test.go +++ b/pkg/gameserversets/controller_test.go @@ -157,6 +157,44 @@ func TestComputeReconciliationAction(t *testing.T) { wantNumServersToAdd: 0, wantIsPartial: true, }, + { + desc: "DeletingUnhealthyGameServers", + list: []*v1alpha1.GameServer{ + gsWithState(v1alpha1.GameServerStateReady), + gsWithState(v1alpha1.GameServerStateUnhealthy), + gsWithState(v1alpha1.GameServerStateUnhealthy), + }, + targetReplicaCount: 3, + wantNumServersToAdd: 2, + wantNumServersToDelete: 2, + }, + { + desc: "DeletingErrorGameServers", + list: []*v1alpha1.GameServer{ + gsWithState(v1alpha1.GameServerStateReady), + gsWithState(v1alpha1.GameServerStateError), + gsWithState(v1alpha1.GameServerStateError), + }, + targetReplicaCount: 3, + wantNumServersToAdd: 2, + wantNumServersToDelete: 2, + }, + { + desc: "DeletingPartialGameServers", + list: []*v1alpha1.GameServer{ + gsWithState(v1alpha1.GameServerStateReady), + gsWithState(v1alpha1.GameServerStateUnhealthy), + gsWithState(v1alpha1.GameServerStateError), + gsWithState(v1alpha1.GameServerStateUnhealthy), + gsWithState(v1alpha1.GameServerStateError), + gsWithState(v1alpha1.GameServerStateUnhealthy), + gsWithState(v1alpha1.GameServerStateError), + }, + targetReplicaCount: 3, + wantNumServersToAdd: 2, + wantNumServersToDelete: 3, + wantIsPartial: true, + }, } for _, tc := range cases { diff --git a/pkg/sdkserver/sdkserver.go b/pkg/sdkserver/sdkserver.go index 4a68549b00..d3d72d161f 100644 --- a/pkg/sdkserver/sdkserver.go +++ b/pkg/sdkserver/sdkserver.go @@ -252,6 +252,12 @@ func (s *SDKServer) updateState() error { return err } + // if we are currently in shutdown/being deleted, there is no escaping + if gs.IsBeingDeleted() { + s.logger.Info("GameServerState being shutdown. Skipping update.") + return nil + } + // if the state is currently unhealthy, you can't go back to Ready if gs.Status.State == stablev1alpha1.GameServerStateUnhealthy { s.logger.Info("GameServerState already unhealthy. Skipping update.") @@ -484,7 +490,7 @@ func (s *SDKServer) sendGameServerUpdate(gs *stablev1alpha1.GameServer) { func (s *SDKServer) runHealth() { s.checkHealth() if !s.healthy() { - s.logger.WithField("gameServerName", s.gameServerName).Info("being marked as not healthy") + s.logger.WithField("gameServerName", s.gameServerName).Info("has failed health check") s.enqueueState(stablev1alpha1.GameServerStateUnhealthy) } } diff --git a/pkg/sdkserver/sdkserver_test.go b/pkg/sdkserver/sdkserver_test.go index d47d91dd8f..70ddd99924 100644 --- a/pkg/sdkserver/sdkserver_test.go +++ b/pkg/sdkserver/sdkserver_test.go @@ -295,37 +295,62 @@ func TestSDKServerSyncGameServer(t *testing.T) { func TestSidecarUpdateState(t *testing.T) { t.Parallel() - t.Run("ignore state change when unhealthy", func(t *testing.T) { - m := agtesting.NewMocks() - sc, err := defaultSidecar(m) - assert.Nil(t, err) - sc.gsState = v1alpha1.GameServerStateReady + fixtures := map[string]struct { + f func(gs *v1alpha1.GameServer) + }{ + "unhealthy": { + f: func(gs *v1alpha1.GameServer) { + gs.Status.State = v1alpha1.GameServerStateUnhealthy + }, + }, + "shutdown": { + f: func(gs *v1alpha1.GameServer) { + gs.Status.State = v1alpha1.GameServerStateShutdown + }, + }, + "deleted": { + f: func(gs *v1alpha1.GameServer) { + now := metav1.Now() + gs.ObjectMeta.DeletionTimestamp = &now + }, + }, + } - updated := false + for k, v := range fixtures { + t.Run(k, func(t *testing.T) { + m := agtesting.NewMocks() + sc, err := defaultSidecar(m) + assert.Nil(t, err) + sc.gsState = v1alpha1.GameServerStateReady - m.AgonesClient.AddReactor("list", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { - gs := v1alpha1.GameServer{ - ObjectMeta: metav1.ObjectMeta{Name: sc.gameServerName, Namespace: sc.namespace}, - Status: v1alpha1.GameServerStatus{ - State: v1alpha1.GameServerStateUnhealthy, - }, - } - return true, &v1alpha1.GameServerList{Items: []v1alpha1.GameServer{gs}}, nil - }) - m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { - updated = true - return true, nil, nil - }) + updated := false - stop := make(chan struct{}) - defer close(stop) - sc.informerFactory.Start(stop) - assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced)) + m.AgonesClient.AddReactor("list", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { + gs := v1alpha1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: sc.gameServerName, Namespace: sc.namespace}, + Status: v1alpha1.GameServerStatus{}, + } - err = sc.updateState() - assert.Nil(t, err) - assert.False(t, updated) - }) + // apply mutation + v.f(&gs) + + return true, &v1alpha1.GameServerList{Items: []v1alpha1.GameServer{gs}}, nil + }) + m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) { + updated = true + return true, nil, nil + }) + + stop := make(chan struct{}) + defer close(stop) + sc.informerFactory.Start(stop) + assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced)) + + err = sc.updateState() + assert.Nil(t, err) + assert.False(t, updated) + }) + } } func TestSidecarHealthLastUpdated(t *testing.T) { diff --git a/site/content/en/docs/Advanced/limiting-resources.md b/site/content/en/docs/Advanced/limiting-resources.md index 05fb36ba2d..3c04be0cc0 100644 --- a/site/content/en/docs/Advanced/limiting-resources.md +++ b/site/content/en/docs/Advanced/limiting-resources.md @@ -38,7 +38,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 resources: limit: cpu: "250m" #this is our limit here diff --git a/site/content/en/docs/Advanced/scheduling-and-autoscaling.md b/site/content/en/docs/Advanced/scheduling-and-autoscaling.md index c210ba0e79..7f1790dda1 100644 --- a/site/content/en/docs/Advanced/scheduling-and-autoscaling.md +++ b/site/content/en/docs/Advanced/scheduling-and-autoscaling.md @@ -80,7 +80,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 ``` This is the *default* Fleet scheduling strategy. It is designed for dynamic Kubernetes environments, wherein you wish @@ -135,7 +135,7 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 ``` This Fleet scheduling strategy is designed for static Kubernetes environments, such as when you are running Kubernetes diff --git a/site/content/en/docs/Advanced/service-accounts.md b/site/content/en/docs/Advanced/service-accounts.md index 6b6b6c2220..bb9bca7b7b 100644 --- a/site/content/en/docs/Advanced/service-accounts.md +++ b/site/content/en/docs/Advanced/service-accounts.md @@ -39,7 +39,7 @@ spec: serviceAccountName: my-special-service-account # a custom service account containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.8 + image: gcr.io/agones-images/udp-server:0.9 ``` If a service account is configured, the mounted key is not overwritten, as it assumed that you want to have full control diff --git a/site/content/en/docs/Getting Started/create-fleet.md b/site/content/en/docs/Getting Started/create-fleet.md index 79f31194f7..473bcd79cf 100644 --- a/site/content/en/docs/Getting Started/create-fleet.md +++ b/site/content/en/docs/Getting Started/create-fleet.md @@ -111,7 +111,7 @@ Spec: Creation Timestamp: Spec: Containers: - Image: gcr.io/agones-images/udp-server:0.8 + Image: gcr.io/agones-images/udp-server:0.9 Name: simple-udp Resources: Status: @@ -308,7 +308,7 @@ status: creationTimestamp: null spec: containers: - - image: gcr.io/agones-images/udp-server:0.8 + - image: gcr.io/agones-images/udp-server:0.9 name: simple-udp resources: {} status: diff --git a/site/content/en/docs/Getting Started/create-gameserver.md b/site/content/en/docs/Getting Started/create-gameserver.md index c031c6c4d6..f3381f11f4 100644 --- a/site/content/en/docs/Getting Started/create-gameserver.md +++ b/site/content/en/docs/Getting Started/create-gameserver.md @@ -107,7 +107,7 @@ Spec: Creation Timestamp: Spec: Containers: - Image: gcr.io/agones-images/udp-server:0.8 + Image: gcr.io/agones-images/udp-server:0.9 Name: simple-udp Resources: Limits: diff --git a/site/content/en/docs/Guides/access-api.md b/site/content/en/docs/Guides/access-api.md index c1c84a610e..1cf8476e82 100644 --- a/site/content/en/docs/Guides/access-api.md +++ b/site/content/en/docs/Guides/access-api.md @@ -90,7 +90,7 @@ func main() { Spec: v1alpha1.GameServerSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ - Containers: []corev1.Container{{Name: "udp-server", Image: "gcr.io/agones-images/udp-server:0.8"}}, + Containers: []corev1.Container{{Name: "udp-server", Image: "gcr.io/agones-images/udp-server:0.9"}}, }, }, }, @@ -178,7 +178,7 @@ $ curl http://localhost:8001/apis/stable.agones.dev/v1alpha1/namespaces/default/ "kind": "GameServer", "metadata": { "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"stable.agones.dev/v1alpha1\",\"kind\":\"GameServer\",\"metadata\":{\"annotations\":{},\"name\":\"simple-udp\",\"namespace\":\"default\"},\"spec\":{\"containerPort\":7654,\"hostPort\":7777,\"portPolicy\":\"static\",\"template\":{\"spec\":{\"containers\":[{\"image\":\"gcr.io/agones-images/udp-server:0.8\",\"name\":\"simple-udp\"}]}}}}\n" + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"stable.agones.dev/v1alpha1\",\"kind\":\"GameServer\",\"metadata\":{\"annotations\":{},\"name\":\"simple-udp\",\"namespace\":\"default\"},\"spec\":{\"containerPort\":7654,\"hostPort\":7777,\"portPolicy\":\"static\",\"template\":{\"spec\":{\"containers\":[{\"image\":\"gcr.io/agones-images/udp-server:0.9\",\"name\":\"simple-udp\"}]}}}}\n" }, "clusterName": "", "creationTimestamp": "2018-03-02T21:41:05Z", @@ -210,7 +210,7 @@ $ curl http://localhost:8001/apis/stable.agones.dev/v1alpha1/namespaces/default/ "spec": { "containers": [ { - "image": "gcr.io/agones-images/udp-server:0.8", + "image": "gcr.io/agones-images/udp-server:0.9", "name": "simple-udp", "resources": {} } @@ -317,7 +317,7 @@ $ curl -d '{"apiVersion":"stable.agones.dev/v1alpha1","kind":"FleetAllocation"," "spec": { "containers": [ { - "image": "gcr.io/agones-images/udp-server:0.8", + "image": "gcr.io/agones-images/udp-server:0.9", "name": "simple-udp", "resources": {} } diff --git a/test/e2e/fleet_test.go b/test/e2e/fleet_test.go index 135243457d..d31716edf9 100644 --- a/test/e2e/fleet_test.go +++ b/test/e2e/fleet_test.go @@ -23,6 +23,7 @@ import ( "agones.dev/agones/pkg/apis" allocationv1alpha1 "agones.dev/agones/pkg/apis/allocation/v1alpha1" "agones.dev/agones/pkg/apis/stable/v1alpha1" + stablev1alpha1 "agones.dev/agones/pkg/client/clientset/versioned/typed/stable/v1alpha1" e2e "agones.dev/agones/test/e2e/framework" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -802,50 +803,95 @@ func TestUpdateFleetScheduling(t *testing.T) { }) } -// TestFleetRecreateGameServerOnPodDeletion ensure that if a pod from a GameServer -// is deleted, the GameServer is deleted and replaced. -func TestFleetRecreateGameServerOnPodDeletion(t *testing.T) { +// TestFleetRecreateGameServers tests various gameserver shutdown scenarios to ensure +// that recreation happens as expected +func TestFleetRecreateGameServers(t *testing.T) { t.Parallel() - alpha1 := framework.AgonesClient.StableV1alpha1() - flt := defaultFleet() - flt.Spec.Replicas = 1 + tests := map[string]struct { + f func(t *testing.T, list *v1alpha1.GameServerList) + }{ + "pod deletion": {f: func(t *testing.T, list *v1alpha1.GameServerList) { + podClient := framework.KubeClient.CoreV1().Pods(defaultNs) - flt, err := alpha1.Fleets(defaultNs).Create(flt) - podClient := framework.KubeClient.CoreV1().Pods(defaultNs) + for _, gs := range list.Items { + pod, err := podClient.Get(gs.ObjectMeta.Name, metav1.GetOptions{}) + assert.NoError(t, err) - if assert.Nil(t, err) { - defer alpha1.Fleets(defaultNs).Delete(flt.ObjectMeta.Name, nil) // nolint:errcheck + assert.True(t, metav1.IsControlledBy(pod, &gs)) + + err = podClient.Delete(pod.ObjectMeta.Name, nil) + assert.NoError(t, err) + } + }}, + "gameserver shutdown": {f: func(t *testing.T, list *v1alpha1.GameServerList) { + for _, gs := range list.Items { + var reply string + reply, err := e2e.SendGameServerUDP(&gs, "EXIT") + if err != nil { + t.Fatalf("Could not message GameServer: %v", err) + } + + assert.Equal(t, "ACK: EXIT\n", reply) + } + }}, + "gameserver unhealthy": {f: func(t *testing.T, list *v1alpha1.GameServerList) { + for _, gs := range list.Items { + var reply string + reply, err := e2e.SendGameServerUDP(&gs, "UNHEALTHY") + if err != nil { + t.Fatalf("Could not message GameServer: %v", err) + } + + assert.Equal(t, "ACK: UNHEALTHY\n", reply) + } + }}, } - framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + for k, v := range tests { + t.Run(k, func(t *testing.T) { + alpha1 := framework.AgonesClient.StableV1alpha1() + flt := defaultFleet() + // add more game servers, to hunt for race conditions + flt.Spec.Replicas = 10 - selector := labels.SelectorFromSet(labels.Set{v1alpha1.FleetNameLabel: flt.ObjectMeta.Name}) - list, err := alpha1.GameServers(defaultNs).List(metav1.ListOptions{LabelSelector: selector.String()}) - assert.NoError(t, err) + flt, err := alpha1.Fleets(defaultNs).Create(flt) + if assert.Nil(t, err) { + defer alpha1.Fleets(defaultNs).Delete(flt.ObjectMeta.Name, nil) // nolint:errcheck + } - assert.Len(t, list.Items, 1) - gs := list.Items[0] - pod, err := podClient.Get(gs.ObjectMeta.Name, metav1.GetOptions{}) - assert.NoError(t, err) + framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) - assert.True(t, metav1.IsControlledBy(pod, &gs)) + list, err := listGameServers(flt, alpha1) + assert.NoError(t, err) + assert.Len(t, list.Items, int(flt.Spec.Replicas)) - err = podClient.Delete(pod.ObjectMeta.Name, nil) - assert.NoError(t, err) + // apply deletion function + logrus.Info("applying deletion function") + v.f(t, list) - err = wait.Poll(time.Second, time.Minute, func() (done bool, err error) { - _, err = alpha1.GameServers(defaultNs).Get(gs.ObjectMeta.Name, metav1.GetOptions{}) + for i, gs := range list.Items { + err = wait.Poll(time.Second, time.Minute, func() (done bool, err error) { + _, err = alpha1.GameServers(defaultNs).Get(gs.ObjectMeta.Name, metav1.GetOptions{}) - if err != nil && k8serrors.IsNotFound(err) { - return true, nil - } + if err != nil && k8serrors.IsNotFound(err) { + logrus.Infof("gameserver %d/%d not found", i+1, flt.Spec.Replicas) + return true, nil + } - return false, err - }) - assert.NoError(t, err) + return false, err + }) + assert.NoError(t, err) + } - framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas)) + }) + } +} + +func listGameServers(flt *v1alpha1.Fleet, getter stablev1alpha1.GameServersGetter) (*v1alpha1.GameServerList, error) { + selector := labels.SelectorFromSet(labels.Set{v1alpha1.FleetNameLabel: flt.ObjectMeta.Name}) + return getter.GameServers(defaultNs).List(metav1.ListOptions{LabelSelector: selector.String()}) } // Counts the number of gameservers with the specified scheduling strategy in a fleet diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index 3046a5c05f..0930748321 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -94,6 +94,8 @@ func (f *Framework) CreateGameServerAndWaitUntilReady(ns string, gs *v1alpha1.Ga return nil, fmt.Errorf("Ready GameServer instance has no port: %v", readyGs.Status) } + logrus.WithField("name", newGs.ObjectMeta.Name).Info("GameServer Ready") + return readyGs, nil } diff --git a/test/e2e/gameserver_test.go b/test/e2e/gameserver_test.go index e061652523..0ce47bbb72 100644 --- a/test/e2e/gameserver_test.go +++ b/test/e2e/gameserver_test.go @@ -175,14 +175,10 @@ func TestUnhealthyGameServersWithoutFreePorts(t *testing.T) { } newGs, err := gameServers.Create(gs.DeepCopy()) - assert.Nil(t, err) - - _, err = framework.WaitForGameServerState(newGs, v1alpha1.GameServerStateUnhealthy, 10*time.Second) - assert.NotNil(t, err) + assert.NoError(t, err) - _, err = gameServers.Get(newGs.Name, metav1.GetOptions{}) - assert.NotNil(t, err) - assert.True(t, k8serrors.IsNotFound(err)) + _, err = framework.WaitForGameServerState(newGs, v1alpha1.GameServerStateUnhealthy, time.Minute) + assert.NoError(t, err) } func TestGameServerUnhealthyAfterDeletingPod(t *testing.T) { @@ -208,27 +204,7 @@ func TestGameServerUnhealthyAfterDeletingPod(t *testing.T) { err = podClient.Delete(pod.ObjectMeta.Name, nil) assert.NoError(t, err) - // TODO [markmandel@google.com]: Should GameServers that are Unhealthy and not in a fleet be deleted? - err = wait.PollImmediate(2*time.Second, time.Minute, func() (bool, error) { - gs, err := framework.AgonesClient.StableV1alpha1().GameServers(readyGs.Namespace).Get(readyGs.ObjectMeta.Name, metav1.GetOptions{}) - - // just in case - if k8serrors.IsNotFound(err) { - return true, nil - } - - if err != nil { - logrus.WithError(err).Warn("error retrieving gameserver") - return false, nil - } - - if gs.Status.State == v1alpha1.GameServerStateUnhealthy { - return true, nil - } - - return false, nil - }) - + _, err = framework.WaitForGameServerState(readyGs, v1alpha1.GameServerStateUnhealthy, time.Minute) assert.NoError(t, err) } @@ -314,6 +290,35 @@ func TestGameServerSelfAllocate(t *testing.T) { assert.NoError(t, err) } +func TestGameServerShutdown(t *testing.T) { + t.Parallel() + gs := defaultGameServer() + readyGs, err := framework.CreateGameServerAndWaitUntilReady(defaultNs, gs) + if err != nil { + t.Fatalf("Could not get a GameServer ready: %v", err) + } + assert.Equal(t, readyGs.Status.State, v1alpha1.GameServerStateReady) + + reply, err := e2eframework.SendGameServerUDP(readyGs, "EXIT") + if err != nil { + t.Fatalf("Could not message GameServer: %v", err) + } + + assert.Equal(t, "ACK: EXIT\n", reply) + + err = wait.PollImmediate(time.Second, time.Minute, func() (bool, error) { + gs, err = framework.AgonesClient.StableV1alpha1().GameServers(defaultNs).Get(readyGs.ObjectMeta.Name, metav1.GetOptions{}) + + if k8serrors.IsNotFound(err) { + return true, nil + } + + return false, err + }) + + assert.NoError(t, err) +} + func defaultGameServer() *v1alpha1.GameServer { gs := &v1alpha1.GameServer{ObjectMeta: metav1.ObjectMeta{GenerateName: "udp-server", Namespace: defaultNs}, Spec: v1alpha1.GameServerSpec{ diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index 39c88a057b..76a7cea91a 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -34,8 +34,8 @@ 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/udp-server:0.8", - "gameserver image to use for those tests, gcr.io/agones-images/udp-server:0.8") + gsimage := flag.String("gameserver-image", "gcr.io/agones-images/udp-server:0.9", + "gameserver image to use for those tests, gcr.io/agones-images/udp-server:0.9") pullSecret := flag.String("pullsecret", "", "optional secret to be used for pulling the gameserver and/or Agones SDK sidecar images") stressTestLevel := flag.Int("stress", 0, "enable stress test at given level 0-100")