diff --git a/install/helm/agones/templates/crds/gameserver.yaml b/install/helm/agones/templates/crds/gameserver.yaml index 4e8eb3fc26..92cee52ce9 100644 --- a/install/helm/agones/templates/crds/gameserver.yaml +++ b/install/helm/agones/templates/crds/gameserver.yaml @@ -53,5 +53,8 @@ spec: validation: openAPIV3Schema: {{- include "gameserver.validation" . | indent 6 }} + subresources: + # status enables the status subresource. + status: {} {{- end }} diff --git a/install/helm/agones/templates/serviceaccounts/controller.yaml b/install/helm/agones/templates/serviceaccounts/controller.yaml index 845dc4dec2..aa6f6e66ea 100644 --- a/install/helm/agones/templates/serviceaccounts/controller.yaml +++ b/install/helm/agones/templates/serviceaccounts/controller.yaml @@ -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"] diff --git a/install/helm/agones/templates/serviceaccounts/sdk.yaml b/install/helm/agones/templates/serviceaccounts/sdk.yaml index 34e56665ac..32e41c8191 100644 --- a/install/helm/agones/templates/serviceaccounts/sdk.yaml +++ b/install/helm/agones/templates/serviceaccounts/sdk.yaml @@ -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 diff --git a/install/yaml/install.yaml b/install/yaml/install.yaml index 800c1ee79c..3c85b7a8a8 100644 --- a/install/yaml/install.yaml +++ b/install/yaml/install.yaml @@ -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"] @@ -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 @@ -621,6 +624,9 @@ spec: type: integer minimum: 1 maximum: 2147483648 + subresources: + # status enables the status subresource. + status: {} --- # Source: agones/templates/crds/gameserverallocationpolicy.yaml diff --git a/pkg/apis/agones/v1/gameserver.go b/pkg/apis/agones/v1/gameserver.go index e38f035841..d3145af514 100644 --- a/pkg/apis/agones/v1/gameserver.go +++ b/pkg/apis/agones/v1/gameserver.go @@ -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 @@ -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 { diff --git a/pkg/apis/agones/v1/gameserver_test.go b/pkg/apis/agones/v1/gameserver_test.go index 01577efaf9..eb5e5736e8 100644 --- a/pkg/apis/agones/v1/gameserver_test.go +++ b/pkg/apis/agones/v1/gameserver_test.go @@ -62,7 +62,6 @@ func TestGameServerApplyDefaults(t *testing.T) { type expected struct { protocol corev1.Protocol - state GameServerState policy PortPolicy health Health scheduling apis.SchedulingStrategy @@ -84,7 +83,6 @@ func TestGameServerApplyDefaults(t *testing.T) { container: "testing", expected: expected{ protocol: "UDP", - state: GameServerStatePortAllocation, policy: Dynamic, scheduling: apis.Packed, health: Health{ @@ -107,7 +105,6 @@ func TestGameServerApplyDefaults(t *testing.T) { container: "testing", expected: expected{ protocol: "UDP", - state: GameServerStatePortAllocation, policy: Passthrough, scheduling: apis.Packed, health: Health{ @@ -143,7 +140,6 @@ func TestGameServerApplyDefaults(t *testing.T) { container: "testing2", expected: expected{ protocol: "TCP", - state: "TestState", policy: Static, scheduling: apis.Packed, health: Health{ @@ -164,7 +160,6 @@ func TestGameServerApplyDefaults(t *testing.T) { container: "testing", expected: expected{ protocol: "UDP", - state: GameServerStateCreating, policy: Static, scheduling: apis.Packed, health: Health{ @@ -186,7 +181,6 @@ func TestGameServerApplyDefaults(t *testing.T) { container: "testing", expected: expected{ protocol: "UDP", - state: GameServerStatePortAllocation, policy: Dynamic, scheduling: apis.Packed, health: Health{ @@ -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}, @@ -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{ diff --git a/pkg/gameserverallocations/controller.go b/pkg/gameserverallocations/controller.go index df43ce7ecb..1f31a2ee14 100644 --- a/pkg/gameserverallocations/controller.go +++ b/pkg/gameserverallocations/controller.go @@ -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" @@ -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) @@ -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 diff --git a/pkg/gameserverallocations/controller_test.go b/pkg/gameserverallocations/controller_test.go index fe480836e1..1ea7c3dcb5 100644 --- a/pkg/gameserverallocations/controller_test.go +++ b/pkg/gameserverallocations/controller_test.go @@ -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" @@ -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)) @@ -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() diff --git a/pkg/gameservers/controller.go b/pkg/gameservers/controller.go index 75d6b7bc3b..393aa2f649 100644 --- a/pkg/gameservers/controller.go +++ b/pkg/gameservers/controller.go @@ -203,7 +203,6 @@ func fastRateLimiter() workqueue.RateLimiter { // creationMutationHandler is the handler for the mutating webhook that sets the // the default values on the GameServer // Should only be called on gameserver create operations. -// nolint:dupl func (c *Controller) creationMutationHandler(review admv1beta1.AdmissionReview) (admv1beta1.AdmissionReview, error) { obj := review.Request.Object gs := &agonesv1.GameServer{} @@ -360,6 +359,9 @@ func (c *Controller) syncGameServer(key string) error { if gs, err = c.syncGameServerDeletionTimestamp(gs); err != nil { return err } + if gs, err = c.syncDefaultGameServerStatus(gs); err != nil { + return err + } if gs, err = c.syncGameServerPortAllocationState(gs); err != nil { return err } @@ -427,26 +429,54 @@ func (c *Controller) syncGameServerDeletionTimestamp(gs *agonesv1.GameServer) (* return gs, errors.Wrapf(err, "error removing finalizer for GameServer %s", gsCopy.ObjectMeta.Name) } +// syncDefaultGameServerStatus sets the default status state for the GameServer, if it is blank +// This is required, as we cannot set a default value for a status subresource through a mutating webhook +// TODO: move this to Validation Schema defaulting, when it is available (https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#defaulting) +func (c *Controller) syncDefaultGameServerStatus(gs *agonesv1.GameServer) (*agonesv1.GameServer, error) { + if !(gs.Status.State == "" && gs.ObjectMeta.DeletionTimestamp.IsZero()) { + return gs, nil + } + gsCopy := gs.DeepCopy() + + gsCopy.ApplyStatusDefaults() + gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).UpdateStatus(gsCopy) + if err != nil { + return gs, errors.Wrapf(err, "error updating GameServer, %s to default status values", gs.ObjectMeta.Name) + } + c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "Default status set") + + return gs, nil +} + // syncGameServerPortAllocationState gives a port to a dynamically allocating GameServer +// TOXO: update the test for this. func (c *Controller) syncGameServerPortAllocationState(gs *agonesv1.GameServer) (*agonesv1.GameServer, error) { if !(gs.Status.State == agonesv1.GameServerStatePortAllocation && gs.ObjectMeta.DeletionTimestamp.IsZero()) { return gs, nil } gsCopy := c.portAllocator.Allocate(gs.DeepCopy()) - - gsCopy.Status.State = agonesv1.GameServerStateCreating - c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "Port allocated") - c.loggerForGameServer(gsCopy).Info("Syncing Port Allocation GameServerState") gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(gsCopy) if err != nil { // if the GameServer doesn't get updated with the port data, then put the port // back in the pool, as it will get retried on the next pass c.portAllocator.DeAllocate(gsCopy) - return gs, errors.Wrapf(err, "error updating GameServer %s to default values", gs.Name) + return gs, errors.Wrapf(err, "error updating GameServer %s port details", gs.ObjectMeta.Name) } + gsCopy = gs.DeepCopy() + gsCopy.Status.State = agonesv1.GameServerStateCreating + gs, err = c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).UpdateStatus(gsCopy) + if err != nil { + // if the GameServer doesn't get updated with the status data, then put the port + // back in the pool, as it will get retried on the next pass + c.portAllocator.DeAllocate(gsCopy) + return gs, errors.Wrapf(err, "error updating GameServer %s to %s", gs.ObjectMeta.Name, agonesv1.GameServerStateCreating) + } + + c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "Port allocated") + return gs, nil } @@ -477,7 +507,7 @@ func (c *Controller) syncGameServerCreatingState(gs *agonesv1.GameServer) (*agon gsCopy := gs.DeepCopy() gsCopy.Status.State = agonesv1.GameServerStateStarting - gs, err = c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(gsCopy) + gs, err = c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).UpdateStatus(gsCopy) if err != nil { return gs, errors.Wrapf(err, "error updating GameServer %s to Starting state", gs.Name) } @@ -505,12 +535,11 @@ func (c *Controller) syncDevelopmentGameServer(gs *agonesv1.GameServer) (*agones for _, p := range gs.Spec.Ports { ports = append(ports, p.Status()) } - // TODO: Use UpdateStatus() when it's available. gsCopy.Status.State = agonesv1.GameServerStateReady gsCopy.Status.Ports = ports gsCopy.Status.Address = devIPAddress gsCopy.Status.NodeName = devIPAddress - gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(gsCopy) + gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).UpdateStatus(gsCopy) if err != nil { return gs, errors.Wrapf(err, "error updating GameServer %s to %v status", gs.Name, gs.Status) } @@ -658,7 +687,7 @@ func (c *Controller) syncGameServerStartingState(gs *agonesv1.GameServer) (*agon } gsCopy.Status.State = agonesv1.GameServerStateScheduled - gs, err = c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(gsCopy) + gs, err = c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).UpdateStatus(gsCopy) if err != nil { return gs, errors.Wrapf(err, "error updating GameServer %s to Scheduled state", gs.Name) } @@ -722,7 +751,7 @@ func (c *Controller) syncGameServerRequestReadyState(gs *agonesv1.GameServer) (* } gsCopy.Status.State = agonesv1.GameServerStateReady - gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(gsCopy) + gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).UpdateStatus(gsCopy) if err != nil { return gs, errors.Wrapf(err, "error setting Ready, Port and address on GameServer %s Status", gs.ObjectMeta.Name) } @@ -756,7 +785,7 @@ func (c *Controller) moveToErrorState(gs *agonesv1.GameServer, msg string) (*ago copy := gs.DeepCopy() copy.Status.State = agonesv1.GameServerStateError - gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(copy) + gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).UpdateStatus(copy) if err != nil { return gs, errors.Wrapf(err, "error moving GameServer %s to Error State", gs.ObjectMeta.Name) } diff --git a/pkg/gameservers/controller_test.go b/pkg/gameservers/controller_test.go index 0ea2e9d723..bd018d9a90 100644 --- a/pkg/gameservers/controller_test.go +++ b/pkg/gameservers/controller_test.go @@ -102,17 +102,23 @@ func TestControllerSyncGameServer(t *testing.T) { expectedState := agonesv1.GameServerState("notastate") switch updateCount { case 1: - expectedState = agonesv1.GameServerStateCreating + expectedState = agonesv1.GameServerStatePortAllocation case 2: - expectedState = agonesv1.GameServerStateStarting + expectedState = agonesv1.GameServerStatePortAllocation case 3: + expectedState = agonesv1.GameServerStateCreating + case 4: + expectedState = agonesv1.GameServerStateStarting + case 5: expectedState = agonesv1.GameServerStateScheduled } - assert.Equal(t, expectedState, gs.Status.State) + assert.Equal(t, expectedState, gs.Status.State, "update count: ", updateCount) if expectedState == agonesv1.GameServerStateScheduled { assert.Equal(t, ipFixture, gs.Status.Address) - assert.NotEmpty(t, gs.Status.Ports[0].Port) + if assert.NotEmpty(t, gs.Status.Ports) { + assert.NotEmpty(t, gs.Status.Ports[0].Port) + } } return true, gs, nil @@ -126,7 +132,7 @@ func TestControllerSyncGameServer(t *testing.T) { err = c.syncGameServer("default/test") assert.Nil(t, err) - assert.Equal(t, 3, updateCount, "update reactor should fire thrice") + assert.Equal(t, 5, updateCount, "update reactor should fire thrice") assert.True(t, podCreated, "pod should be created") }) @@ -180,6 +186,7 @@ func TestControllerSyncGameServerWithDevIP(t *testing.T) { fixture := templateDevGs.DeepCopy() fixture.ApplyDefaults() + fixture.ApplyStatusDefaults() mocks.KubeClient.AddReactor("list", "nodes", func(action k8stesting.Action) (bool, runtime.Object, error) { return false, nil, k8serrors.NewMethodNotSupported(schema.GroupResource{}, "list nodes should not be called") diff --git a/pkg/gameservers/health.go b/pkg/gameservers/health.go index 45af430e4e..a0a8e96511 100644 --- a/pkg/gameservers/health.go +++ b/pkg/gameservers/health.go @@ -187,7 +187,7 @@ func (hc *HealthController) syncGameServer(key string) error { gsCopy := gs.DeepCopy() gsCopy.Status.State = agonesv1.GameServerStateUnhealthy - if _, err := hc.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(gsCopy); err != nil { + if _, err := hc.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).UpdateStatus(gsCopy); err != nil { return errors.Wrapf(err, "error updating GameServer %s to unhealthy", gs.ObjectMeta.Name) } diff --git a/pkg/gameserversets/controller.go b/pkg/gameserversets/controller.go index 5037452170..8cdba2c93b 100644 --- a/pkg/gameserversets/controller.go +++ b/pkg/gameserversets/controller.go @@ -500,7 +500,7 @@ func (c *Controller) deleteGameServers(gsSet *agonesv1.GameServerSet, toDelete [ // We should not delete the gameservers directly buy set their state to shutdown and let the gameserver controller to delete gsCopy := gs.DeepCopy() gsCopy.Status.State = agonesv1.GameServerStateShutdown - _, err := c.gameServerGetter.GameServers(gs.Namespace).Update(gsCopy) + _, err := c.gameServerGetter.GameServers(gs.Namespace).UpdateStatus(gsCopy) if err != nil { return errors.Wrapf(err, "error updating gameserver %s from status %s to Shutdown status.", gs.ObjectMeta.Name, gs.Status.State) } diff --git a/pkg/sdkserver/sdkserver.go b/pkg/sdkserver/sdkserver.go index e4d528a535..87f0675df5 100644 --- a/pkg/sdkserver/sdkserver.go +++ b/pkg/sdkserver/sdkserver.go @@ -272,18 +272,19 @@ func (s *SDKServer) updateState() error { } s.gsUpdateMutex.RLock() - gs.Status.State = s.gsState + gsCopy := gs.DeepCopy() + gsCopy.Status.State = s.gsState // If we are setting the Reserved status, check for the duration, and set that too. - if gs.Status.State == agonesv1.GameServerStateReserved && s.gsReserveDuration != nil { + if gsCopy.Status.State == agonesv1.GameServerStateReserved && s.gsReserveDuration != nil { n := metav1.NewTime(time.Now().Add(*s.gsReserveDuration)) - gs.Status.ReservedUntil = &n + gsCopy.Status.ReservedUntil = &n } else { - gs.Status.ReservedUntil = nil + gsCopy.Status.ReservedUntil = nil } s.gsUpdateMutex.RUnlock() - _, err = gameServers.Update(gs) + gs, err = gameServers.UpdateStatus(gsCopy) if err != nil { return errors.Wrapf(err, "could not update GameServer %s/%s to state %s", s.namespace, s.gameServerName, gs.Status.State) } diff --git a/test/e2e/fleet_test.go b/test/e2e/fleet_test.go index 8b06f115a1..f55bb6c874 100644 --- a/test/e2e/fleet_test.go +++ b/test/e2e/fleet_test.go @@ -449,7 +449,7 @@ func TestReservedGameServerInFleet(t *testing.T) { // mark one as reserved gsCopy := gsList[0].DeepCopy() gsCopy.Status.State = agonesv1.GameServerStateReserved - _, err = client.GameServers(defaultNs).Update(gsCopy) + _, err = client.GameServers(defaultNs).UpdateStatus(gsCopy) assert.NoError(t, err) // make sure counts are correct