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

Gameserver status subresource #947

Closed
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
3 changes: 3 additions & 0 deletions install/helm/agones/templates/crds/gameserver.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,8 @@ spec:
validation:
openAPIV3Schema:
{{- include "gameserver.validation" . | indent 6 }}
subresources:
# status enables the status subresource.
status: {}

{{- end }}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ rules:
resources: ["fleets"]
verbs: ["get", "list", "update", "watch"]
- apiGroups: ["agones.dev"]
resources: ["fleets/status", "gameserversets/status"]
resources: ["gameservers/status", "fleets/status", "gameserversets/status"]
verbs: ["update"]
- apiGroups: ["multicluster.agones.dev"]
resources: ["gameserverallocationpolicies"]
Expand Down
3 changes: 3 additions & 0 deletions install/helm/agones/templates/serviceaccounts/sdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ rules:
- apiGroups: ["agones.dev"]
resources: ["gameservers"]
verbs: ["list", "update", "watch"]
- apiGroups: ["agones.dev"]
resources: ["gameservers/status"]
verbs: ["update"]
---
{{- range .Values.gameservers.namespaces }}
apiVersion: rbac.authorization.k8s.io/v1
Expand Down
8 changes: 7 additions & 1 deletion install/yaml/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ rules:
resources: ["fleets"]
verbs: ["get", "list", "update", "watch"]
- apiGroups: ["agones.dev"]
resources: ["fleets/status", "gameserversets/status"]
resources: ["gameservers/status", "fleets/status", "gameserversets/status"]
verbs: ["update"]
- apiGroups: ["multicluster.agones.dev"]
resources: ["gameserverallocationpolicies"]
Expand Down Expand Up @@ -161,6 +161,9 @@ rules:
- apiGroups: ["agones.dev"]
resources: ["gameservers"]
verbs: ["list", "update", "watch"]
- apiGroups: ["agones.dev"]
resources: ["gameservers/status"]
verbs: ["update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
Expand Down Expand Up @@ -621,6 +624,9 @@ spec:
type: integer
minimum: 1
maximum: 2147483648
subresources:
# status enables the status subresource.
status: {}

---
# Source: agones/templates/crds/gameserverallocationpolicy.yaml
Expand Down
23 changes: 11 additions & 12 deletions pkg/apis/agones/v1/gameserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,17 @@ func (gs *GameServer) ApplyDefaults() {
gs.ObjectMeta.Finalizers = append(gs.ObjectMeta.Finalizers, agones.GroupName)

gs.Spec.ApplyDefaults()
gs.applyStateDefaults()
}

// ApplyStatusDefaults applies the default values to the Status subresource.
func (gs *GameServer) ApplyStatusDefaults() {
if gs.Status.State == "" {
gs.Status.State = GameServerStateCreating
// ApplyStatusDefaults() should be called after applyPortDefaults()
if gs.HasPortPolicy(Dynamic) || gs.HasPortPolicy(Passthrough) {
gs.Status.State = GameServerStatePortAllocation
}
}
}

// ApplyDefaults applies default values to the GameServerSpec if they are not already populated
Expand Down Expand Up @@ -232,17 +242,6 @@ func (gss *GameServerSpec) applyHealthDefaults() {
}
}

// applyStateDefaults applies state defaults
func (gs *GameServer) applyStateDefaults() {
if gs.Status.State == "" {
gs.Status.State = GameServerStateCreating
// applyStateDefaults() should be called after applyPortDefaults()
if gs.HasPortPolicy(Dynamic) || gs.HasPortPolicy(Passthrough) {
gs.Status.State = GameServerStatePortAllocation
}
}
}

// applyPortDefaults applies default values for all ports
func (gss *GameServerSpec) applyPortDefaults() {
for i, p := range gss.Ports {
Expand Down
42 changes: 34 additions & 8 deletions pkg/apis/agones/v1/gameserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ func TestGameServerApplyDefaults(t *testing.T) {

type expected struct {
protocol corev1.Protocol
state GameServerState
policy PortPolicy
health Health
scheduling apis.SchedulingStrategy
Expand All @@ -84,7 +83,6 @@ func TestGameServerApplyDefaults(t *testing.T) {
container: "testing",
expected: expected{
protocol: "UDP",
state: GameServerStatePortAllocation,
policy: Dynamic,
scheduling: apis.Packed,
health: Health{
Expand All @@ -107,7 +105,6 @@ func TestGameServerApplyDefaults(t *testing.T) {
container: "testing",
expected: expected{
protocol: "UDP",
state: GameServerStatePortAllocation,
policy: Passthrough,
scheduling: apis.Packed,
health: Health{
Expand Down Expand Up @@ -143,7 +140,6 @@ func TestGameServerApplyDefaults(t *testing.T) {
container: "testing2",
expected: expected{
protocol: "TCP",
state: "TestState",
policy: Static,
scheduling: apis.Packed,
health: Health{
Expand All @@ -164,7 +160,6 @@ func TestGameServerApplyDefaults(t *testing.T) {
container: "testing",
expected: expected{
protocol: "UDP",
state: GameServerStateCreating,
policy: Static,
scheduling: apis.Packed,
health: Health{
Expand All @@ -186,7 +181,6 @@ func TestGameServerApplyDefaults(t *testing.T) {
container: "testing",
expected: expected{
protocol: "UDP",
state: GameServerStatePortAllocation,
policy: Dynamic,
scheduling: apis.Packed,
health: Health{
Expand All @@ -212,7 +206,6 @@ func TestGameServerApplyDefaults(t *testing.T) {
container: "testing",
expected: expected{
protocol: corev1.ProtocolTCP,
state: GameServerStateCreating,
policy: Static,
scheduling: apis.Packed,
health: Health{Disabled: true},
Expand All @@ -230,13 +223,46 @@ func TestGameServerApplyDefaults(t *testing.T) {
assert.Contains(t, test.gameServer.ObjectMeta.Finalizers, agones.GroupName)
assert.Equal(t, test.container, spec.Container)
assert.Equal(t, test.expected.protocol, spec.Ports[0].Protocol)
assert.Equal(t, test.expected.state, test.gameServer.Status.State)
assert.Equal(t, test.expected.health, test.gameServer.Spec.Health)
assert.Equal(t, test.expected.scheduling, test.gameServer.Spec.Scheduling)
})
}
}

func TestGameServerApplyStatusDefaults(t *testing.T) {
t.Parallel()

fixtures := map[string]struct {
gameServer *GameServer
expected GameServerState
}{
"not dynamic or passthrough": {gameServer: &GameServer{}, expected: GameServerStateCreating},
"dynamic": {
gameServer: &GameServer{
Spec: GameServerSpec{
Ports: []GameServerPort{{PortPolicy: Dynamic, ContainerPort: 7777}},
},
},
expected: GameServerStatePortAllocation,
},
"passthrough": {
gameServer: &GameServer{
Spec: GameServerSpec{
Ports: []GameServerPort{{PortPolicy: Passthrough, ContainerPort: 7777}},
},
},
expected: GameServerStatePortAllocation,
},
}

for k, v := range fixtures {
t.Run(k, func(t *testing.T) {
v.gameServer.ApplyStatusDefaults()
assert.Equal(t, v.expected, v.gameServer.Status.State)
})
}
}

func TestGameServerValidate(t *testing.T) {
gs := GameServer{
Spec: GameServerSpec{
Expand Down
43 changes: 37 additions & 6 deletions pkg/gameserverallocations/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"strconv"
"time"

"k8s.io/apimachinery/pkg/types"

"agones.dev/agones/pkg/apis/agones"
agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
Expand Down Expand Up @@ -636,6 +638,7 @@ func (c *Controller) runLocalAllocations(updateWorkerCount int) {
// Each worker will concurrently attempt to move the GameServer to an Allocated
// state and then respond to the initial request's response channel with the
// details of that update
// TOXO: update the test for this
func (c *Controller) allocationUpdateWorkers(workerCount int) chan<- response {
updateQueue := make(chan response)

Expand All @@ -645,20 +648,48 @@ func (c *Controller) allocationUpdateWorkers(workerCount int) chan<- response {
select {
case res := <-updateQueue:
gsCopy := res.gs.DeepCopy()
c.patchMetadata(gsCopy, res.request.gsa.Spec.MetaPatch)
gsCopy.Status.State = agonesv1.GameServerStateAllocated

gs, err := c.gameServerGetter.GameServers(res.gs.ObjectMeta.Namespace).Update(gsCopy)
// start with changing the state, as that locks it for just our use
gs, err := c.gameServerGetter.GameServers(res.gs.ObjectMeta.Namespace).UpdateStatus(gsCopy)
if err != nil {
key, _ := cache.MetaNamespaceKeyFunc(gs)
// since we could not allocate, we should put it back
c.readyGameServers.Store(key, gs)
res.err = errors.Wrap(err, "error updating allocated gameserver")
} else {
res.gs = gs
c.recorder.Event(res.gs, corev1.EventTypeNormal, string(res.gs.Status.State), "Allocated")
res.err = errors.Wrap(err, "error updating marking gameserver as allocated")
res.request.response <- res
continue
}

// update the metadata at this stage
gsCopy = gs.DeepCopy()
c.patchMetadata(gsCopy, res.request.gsa.Spec.MetaPatch)

// TOXO: build patch by hand.
b, err := gs.Patch(gsCopy)
c.loggerForGameServerAllocation(res.request.gsa).WithField("patch", string(b)).Info("allocation patch")
if err != nil {
res.err = errors.Wrap(err, "this should not happen, ever")
res.request.response <- res
continue
}

// TOXO: I'm a little worried about this. We might need a cleanup system for when failures happen, and bad things occur.
// ignore if there is nothing to apply
if string(b) != "[]" {
gs, err = c.gameServerGetter.GameServers(res.gs.ObjectMeta.Namespace).Patch(gs.ObjectMeta.Name, types.JSONPatchType, b)
if err != nil {
key, _ := cache.MetaNamespaceKeyFunc(gs)
// since we could not allocate, we should put it back
c.readyGameServers.Store(key, gs)
res.err = errors.Wrap(err, "error updating gameserver metadata")
res.request.response <- res
continue
}
}

res.gs = gs
c.recorder.Event(res.gs, corev1.EventTypeNormal, string(res.gs.Status.State), "Allocated")
res.request.response <- res
case <-c.stop:
return
Expand Down
24 changes: 24 additions & 0 deletions pkg/gameserverallocations/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
agtesting "agones.dev/agones/pkg/testing"
"agones.dev/agones/pkg/util/apiserver"
"github.com/heptiolabs/healthcheck"
"github.com/mattbaird/jsonpatch"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -164,6 +165,8 @@ func TestControllerAllocate(t *testing.T) {
return true, &agonesv1.GameServerList{Items: gsList}, nil
})

var allocated *agonesv1.GameServer

updated := false
gsWatch := watch.NewFake()
m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
Expand All @@ -175,8 +178,29 @@ func TestControllerAllocate(t *testing.T) {
assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
gsWatch.Modify(gs)

allocated = gs

return true, gs, nil
})
m.AgonesClient.AddReactor("patch", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
pa := action.(k8stesting.PatchAction)

logrus.WithField("patch", string(pa.GetPatch())).Info("*** I GOT PATCHED ***")
// Fake the patch action, if it says what we expect it to say.
patch := []jsonpatch.JsonPatchOperation{}
err := json.Unmarshal(pa.GetPatch(), &patch)
assert.NoError(t, err)

assert.Len(t, patch, 2)
assert.Contains(t, string(pa.GetPatch()), "deathmatch")
assert.Contains(t, string(pa.GetPatch()), "searide")
assert.Equal(t, allocated.ObjectMeta.Name, pa.GetName())

allocated.ObjectMeta.Labels["mode"] = "deathmatch"
allocated.ObjectMeta.Annotations = map[string]string{"map": "searide"}

return true, allocated, nil
})

stop, cancel := agtesting.StartInformers(m)
defer cancel()
Expand Down
Loading