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

New feature: SDK cached gameserver #1642

Merged
merged 10 commits into from
Jun 29, 2020
2 changes: 1 addition & 1 deletion build/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ KIND_CONTAINER_NAME=$(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.21

ALPHA_FEATURE_GATES ?= "PlayerTracking=true"
ALPHA_FEATURE_GATES ?= "PlayerTracking=true&SDKWatchSendOnExecute=true"

# Directory that this Makefile is in.
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
Expand Down
2 changes: 1 addition & 1 deletion cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ steps:
#

- name: 'e2e-runner'
args: ['PlayerTracking=true&ContainerPortAllocation=false', 'e2e-test-cluster']
args: ['PlayerTracking=true&ContainerPortAllocation=false&SDKWatchSendOnExecute=true', 'e2e-test-cluster']
id: e2e-feature-gates
waitFor:
- push-images
Expand Down
14 changes: 13 additions & 1 deletion pkg/sdkserver/sdkserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,6 @@ func (s *SDKServer) GetGameServer(context.Context, *sdk.Empty) (*sdk.GameServer,
if err != nil {
return nil, err
}

return convert(gs), nil
}

Expand All @@ -499,6 +498,19 @@ func (s *SDKServer) GetGameServer(context.Context, *sdk.Empty) (*sdk.GameServer,
func (s *SDKServer) WatchGameServer(_ *sdk.Empty, stream sdk.SDK_WatchGameServerServer) error {
s.logger.Debug("Received WatchGameServer request, adding stream to connectedStreams")
s.streamMutex.Lock()

if runtime.FeatureEnabled(runtime.FeatureSDKWatchSendOnExecute) {
gs, err := s.GetGameServer(context.Background(), &sdk.Empty{})
if err != nil {
return err
}

err = stream.Send(gs)
if err != nil {
return err
}
}

s.connectedStreams = append(s.connectedStreams, stream)
s.streamMutex.Unlock()
// don't exit until we shutdown, because that will close the stream
Expand Down
63 changes: 63 additions & 0 deletions pkg/sdkserver/sdkserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,69 @@ func TestSDKServerWatchGameServer(t *testing.T) {
assert.Equal(t, stream, sc.connectedStreams[1])
}

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

agruntime.FeatureTestMutex.Lock()
defer agruntime.FeatureTestMutex.Unlock()

fixture := &agonesv1.GameServer{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Status: agonesv1.GameServerStatus{
State: agonesv1.GameServerStateReady,
},
}

m := agtesting.NewMocks()
m.AgonesClient.AddReactor("list", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*fixture}}, nil
})

err := agruntime.ParseFeatures(string(agruntime.FeatureSDKWatchSendOnExecute) + "=true")
if !assert.NoError(t, err) {
t.Fatal("Can not parse FeatureSDKWatchSendOnExecute")
}

sc, err := defaultSidecar(m)
if !assert.NoError(t, err) {
t.Fatal("Can not create sidecar")
}
assert.Empty(t, sc.connectedStreams)

stop := make(chan struct{})
defer close(stop)
sc.informerFactory.Start(stop)
assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced))
sc.gsWaitForSync.Done()

stream := newGameServerMockStream()
asyncWatchGameServer(t, sc, stream)

assert.Nil(t, waitConnectedStreamCount(sc, 1))
assert.Equal(t, stream, sc.connectedStreams[0])

totalSendCalls := 0
for i := 0; i < 2; i++ {
select {
case _, ok := <-stream.msgs:
if ok {
totalSendCalls++
} else {
assert.Fail(t, "Channel is closed!")
}
default:
t.Log("No gameserver in the stream, moving on.")
}
}

// if SDKWatchSendOnExecute feature is turned on, there are two stream.Send() calls should happen:
// one in sendGameServerUpdate, another one in WatchGameServer.
assert.Equal(t, 2, totalSendCalls)
}

func TestSDKServerSendGameServerUpdate(t *testing.T) {
t.Parallel()
m := agtesting.NewMocks()
Expand Down
4 changes: 4 additions & 0 deletions pkg/util/runtime/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const (

// FeatureContainerPortAllocation is a feature flag to enable/disable allocating ports to several containers in a pod
FeatureContainerPortAllocation Feature = "ContainerPortAllocation"

// FeatureSDKWatchSendOnExecute is a feature flag to enable/disable immediate game server return after SDK.WatchGameServer is called
FeatureSDKWatchSendOnExecute Feature = "SDKWatchSendOnExecute"
akremsa marked this conversation as resolved.
Show resolved Hide resolved
)

var (
Expand All @@ -46,6 +49,7 @@ var (
FeatureExample: true,
FeaturePlayerTracking: false,
FeatureContainerPortAllocation: true,
FeatureSDKWatchSendOnExecute: false,
}

// featureGates is the storage of what features are enabled
Expand Down
4 changes: 4 additions & 0 deletions site/content/en/docs/Guides/Client SDKs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ the `message GameServer`.
For language specific documentation, have a look at the respective source (linked above),
and the {{< ghlink href="examples" >}}examples{{< /ghlink >}}.

{{% feature publishVersion="1.7.0" %}}
You can use `SDKWatchSendOnExecute` feature passed as a [feature gate] ({{< ref "/docs/Guides/feature-stages.md#feature-gates" >}}) if you want a `GameServer` to be returned right after `SDK.WatchGameServer` is called.
{{% /feature %}}

### Metadata Management

#### SetLabel(key, value)
Expand Down
1 change: 1 addition & 0 deletions site/content/en/docs/Guides/feature-stages.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ The current set of `alpha` and `beta` feature gates are:
| Example Gate (not in use) | `Example` | Disabled | None | 0.13.0 |
| [Port Allocations to Multiple Containers]({{< ref "/docs/Reference/gameserver.md" >}}) | `ContainerPortAllocation` | Enabled | `Beta` | 1.7.0 |
| [Player Tracking]({{< ref "/docs/Guides/player-tracking.md" >}}) | `PlayerTracking` | Disabled | `Alpha` | 1.6.0 |
| [SDK Send GameServer on Watch execution]({{< ref "/docs/Guides/Client SDKs/_index.md#watchgameserver-function-gameserver" >}}) | `SDKWatchSendOnExecute` | Disabled | `Alpha` | 1.7.0 |
<sup>*</sup>Multicluster Allocation was started before this process was in place, and therefore does not have a
feature gate and cannot be disabled.
{{% /feature %}}
Expand Down