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

e2e flow for High Density GameServers #2516

Merged
merged 2 commits into from
Mar 17, 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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ systems, since it works around the common Kubernetes and/or Agones container lif

Utilising the new allocation `gameServerState` filter as well as the existing ability to edit the
`GameServer` labels at both [allocation time]({{% ref "/docs/Reference/gameserverallocation.md" %}}), and from
within the game server process, [via the SDK]({{% ref "/docs/Guides/Client SDKs/_index.md#setlabelkey-value" %}}),
within the game server process, [via the SDK][sdk],
means Agones is able to atomically remove a `GameServer` from the list of potentially allocatable
`GameServers` at allocation time, and then return it back into the pool of allocatable `GameServers` if and when the
game server process deems that is has room to host another game session.
Expand Down Expand Up @@ -72,11 +72,20 @@ spec:
{{< alert title="Info" color="info">}}
It's important to note that the labels that the `GameServer` process use to add itself back into the pool of
allocatable instances, must start with the prefix `agones.dev/sdk-`, since only labels that have this prefix are
available to be [updated from the SDK]({{% ref "/docs/Guides/Client SDKs/_index.md#setlabelkey-value" %}}).
available to be [updated from the SDK][sdk].
{{< /alert >}}

## Consistency

Agones, and Kubernetes itself are built as eventually consistent, self-healing systems. To that end, it is worth
noting that there may be minor delays between each of the operations in the above flow. For example, depending on the
cluster load, it may take up to a second for an [SDK driven label change][sdk] on a `GameServer` record to be
visible to the Agones allocation system. We recommend building your integrations with Agones with this in mind.

## Next Steps

* View the details about [using the SDK]({{% ref "/docs/Guides/Client SDKs/_index.md#setlabelkey-value" %}}) to set
* View the details about [using the SDK][sdk] to set
labels on the `GameServer`.
* Check all the options available on [`GameServerAllocation`]({{% ref "/docs/Reference/gameserverallocation.md" %}}).

[sdk]: {{% ref "/docs/Guides/Client SDKs/_index.md#setlabelkey-value" %}}
73 changes: 73 additions & 0 deletions test/e2e/gameserverallocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,79 @@ func TestCreateFleetAndGameServerStateFilterAllocation(t *testing.T) {
require.NotEqual(t, gs1.ObjectMeta.Annotations["agones.dev/last-allocated"], gs2.ObjectMeta.Annotations["agones.dev/last-allocated"])
}

func TestHighDensityGameServerFlow(t *testing.T) {
if !runtime.FeatureEnabled(runtime.FeatureStateAllocationFilter) {
t.SkipNow()
}
t.Parallel()
log := e2e.TestLogger(t)
ctx := context.Background()

fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace)
fleet := defaultFleet(framework.Namespace)
lockLabel := "agones.dev/sdk-available"
// to start they are all available
fleet.Spec.Template.ObjectMeta.Labels = map[string]string{lockLabel: "true"}

flt, err := fleets.Create(ctx, fleet, metav1.CreateOptions{})
require.NoError(t, err)
defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck

framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))

fleetSelector := metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}
allocatedSelector := fleetSelector.DeepCopy()

allocated := agonesv1.GameServerStateAllocated
allocatedSelector.MatchLabels[lockLabel] = "true"
gsa := &allocationv1.GameServerAllocation{
Spec: allocationv1.GameServerAllocationSpec{
MetaPatch: allocationv1.MetaPatch{Labels: map[string]string{lockLabel: "false"}},
Selectors: []allocationv1.GameServerSelector{
{LabelSelector: *allocatedSelector, GameServerState: &allocated},
{LabelSelector: fleetSelector},
},
}}

// standard allocation
result, err := framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa, metav1.CreateOptions{})
require.NoError(t, err)
require.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(result.Status.State))

gs, err := framework.AgonesClient.AgonesV1().GameServers(fleet.ObjectMeta.Namespace).Get(ctx, result.Status.GameServerName, metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, allocated, gs.Status.State)

// set the label to being available again
_, err = framework.SendGameServerUDP(t, gs, "LABEL available true")
require.NoError(t, err)

// wait for the label to be applied!
require.Eventuallyf(t, func() bool {
gs, err := framework.AgonesClient.AgonesV1().GameServers(fleet.ObjectMeta.Namespace).Get(ctx, result.Status.GameServerName, metav1.GetOptions{})
require.NoError(t, err)
log.WithField("labels", gs.ObjectMeta.Labels).Info("checking labels")
return gs.ObjectMeta.Labels[lockLabel] == "true"
}, time.Minute, time.Second, "GameServer did not unlock")

// Run the same allocation again, we should get back the preferred item.
expected := result.Status.GameServerName

// we will run this as an Eventually, as caches are eventually consistent
require.Eventuallyf(t, func() bool {
result, err = framework.AgonesClient.AllocationV1().GameServerAllocations(fleet.ObjectMeta.Namespace).Create(ctx, gsa, metav1.CreateOptions{})
require.NoError(t, err)
roberthbailey marked this conversation as resolved.
Show resolved Hide resolved
require.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(result.Status.State))

if expected != result.Status.GameServerName {
roberthbailey marked this conversation as resolved.
Show resolved Hide resolved
log.WithField("expected", expected).WithField("gsa", result).Info("Re-allocation attempt failed. Retrying.")
return false
}

return true
}, time.Minute, time.Second, "Could not re-allocation")
}

func TestCreateFleetAndGameServerPlayerCapacityAllocation(t *testing.T) {
if !(runtime.FeatureEnabled(runtime.FeatureStateAllocationFilter) && runtime.FeatureEnabled(runtime.FeaturePlayerAllocationFilter)) {
t.SkipNow()
Expand Down