From 3c7304d94aaa6595cd3193d54b0da943277e42bc Mon Sep 17 00:00:00 2001 From: Mark Mandel Date: Tue, 17 Jul 2018 09:11:11 -0700 Subject: [PATCH] Implements SDK callback for GameServer updates This implements the gRPC system to send messages up to a unidirectional stream from the sdk-server to the SDK. This has been implemented in the Go, C++ and REST SDK. Closes #277 --- cmd/sdk-server/main.go | 2 +- docs/sdk_rest_api.md | 21 ++ examples/cpp-simple/Makefile | 2 +- examples/cpp-simple/fleet.yaml | 2 +- examples/cpp-simple/gameserver.yaml | 2 +- examples/cpp-simple/server.cc | 10 + examples/simple-udp/Makefile | 2 +- examples/simple-udp/server/fleet.yaml | 2 +- .../simple-udp/server/gameserver-legacy.yaml | 2 +- examples/simple-udp/server/gameserver.yaml | 2 +- examples/simple-udp/server/main.go | 18 ++ pkg/gameservers/helper_test.go | 103 ++++-- pkg/gameservers/localsdk.go | 66 ++-- pkg/gameservers/localsdk_test.go | 44 +-- pkg/gameservers/sdkserver.go | 46 ++- pkg/gameservers/sdkserver_test.go | 107 +++++- pkg/sdk/sdk.pb.go | 174 ++++++---- pkg/sdk/sdk.pb.gw.go | 50 +++ sdk.proto | 6 + sdk.swagger.json | 17 + sdks/README.md | 21 ++ sdks/cpp/README.md | 19 ++ sdks/cpp/sdk.cc | 12 + sdks/cpp/sdk.grpc.pb.cc | 26 ++ sdks/cpp/sdk.grpc.pb.h | 91 +++++- sdks/cpp/sdk.h | 5 + sdks/cpp/sdk.pb.cc | 10 +- sdks/go/sdk.go | 39 ++- sdks/go/sdk_test.go | 67 ++++ sdks/rust/src/grpc/sdk.rs | 305 +++++++++--------- sdks/rust/src/grpc/sdk_grpc.rs | 20 ++ 31 files changed, 1001 insertions(+), 292 deletions(-) diff --git a/cmd/sdk-server/main.go b/cmd/sdk-server/main.go index 56cb77ae78..4ffa12566e 100644 --- a/cmd/sdk-server/main.go +++ b/cmd/sdk-server/main.go @@ -86,7 +86,7 @@ func main() { defer cancel() if ctlConf.IsLocal { - sdk.RegisterSDKServer(grpcServer, &gameservers.LocalSDKServer{}) + sdk.RegisterSDKServer(grpcServer, gameservers.NewLocalSDKServer()) } else { var config *rest.Config config, err = rest.InClusterConfig() diff --git a/docs/sdk_rest_api.md b/docs/sdk_rest_api.md index 0c5ecd8357..09a2d73ca2 100644 --- a/docs/sdk_rest_api.md +++ b/docs/sdk_rest_api.md @@ -102,3 +102,24 @@ Response: } } ``` + +### Watch GameServer + +⚠️⚠️⚠️ **/watch/gameserver is currently a development feature and has not been released** ⚠️⚠️⚠️ + +Call this when you want to get updates of when the backing `GameServer` configuration is updated. + +These updates will come as newline delimited JSON, send on each update. To that end, you will +want to keep the http connection open, and read lines from the result stream and and process as they +come in. + +```bash +$ curl -H "Content-Type: application/json" -X GET http://localhost:59358/watch/gameserver +``` + +Response: +```json +{"result":{"object_meta":{"name":"local","namespace":"default","uid":"1234","resource_version":"v1","generation":"1","creation_timestamp":"1533766607","annotations":{"annotation":"true"},"labels":{"islocal":"true"}},"status":{"state":"Ready","address":"127.0.0.1","ports":[{"name":"default","port":7777}]}}} +{"result":{"object_meta":{"name":"local","namespace":"default","uid":"1234","resource_version":"v1","generation":"1","creation_timestamp":"1533766607","annotations":{"annotation":"true"},"labels":{"islocal":"true"}},"status":{"state":"Ready","address":"127.0.0.1","ports":[{"name":"default","port":7777}]}}} +{"result":{"object_meta":{"name":"local","namespace":"default","uid":"1234","resource_version":"v1","generation":"1","creation_timestamp":"1533766607","annotations":{"annotation":"true"},"labels":{"islocal":"true"}},"status":{"state":"Ready","address":"127.0.0.1","ports":[{"name":"default","port":7777}]}}} +``` \ No newline at end of file diff --git a/examples/cpp-simple/Makefile b/examples/cpp-simple/Makefile index e5e6b97fc5..b72342287b 100644 --- a/examples/cpp-simple/Makefile +++ b/examples/cpp-simple/Makefile @@ -32,7 +32,7 @@ REPOSITORY = gcr.io/agones-images # Directory that this Makefile is in. mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) project_path := $(dir $(mkfile_path)) -server_tag = $(REPOSITORY)/cpp-simple-server:0.2 +server_tag = $(REPOSITORY)/cpp-simple-server:0.3 # _____ _ # |_ _|_ _ _ __ __ _ ___| |_ ___ diff --git a/examples/cpp-simple/fleet.yaml b/examples/cpp-simple/fleet.yaml index f9d973526a..fba1a87131 100644 --- a/examples/cpp-simple/fleet.yaml +++ b/examples/cpp-simple/fleet.yaml @@ -30,5 +30,5 @@ spec: spec: containers: - name: cpp-simple - image: gcr.io/agones-images/cpp-simple-server:0.2 + image: gcr.io/agones-images/cpp-simple-server:0.3 # imagePullPolicy: Always # add for development \ No newline at end of file diff --git a/examples/cpp-simple/gameserver.yaml b/examples/cpp-simple/gameserver.yaml index 8eb5c8e4a7..eb759bc0ab 100644 --- a/examples/cpp-simple/gameserver.yaml +++ b/examples/cpp-simple/gameserver.yaml @@ -27,5 +27,5 @@ spec: spec: containers: - name: cpp-simple - image: gcr.io/agones-images/cpp-simple-server:0.2 + image: gcr.io/agones-images/cpp-simple-server:0.3 # imagePullPolicy: Always # add for development \ No newline at end of file diff --git a/examples/cpp-simple/server.cc b/examples/cpp-simple/server.cc index 1e41a1a761..38c9e8a7b4 100644 --- a/examples/cpp-simple/server.cc +++ b/examples/cpp-simple/server.cc @@ -33,6 +33,15 @@ void doHealth(agones::SDK *sdk) { } } +// watch GameServer Updates +void watchUpdates(agones::SDK *sdk) { + std::cout << "Starting to watch GameServer updates..." << std::endl; + sdk->WatchGameServer([](stable::agones::dev::sdk::GameServer gameserver){ + std::cout << "GameServer Update, name: " << gameserver.object_meta().name() << std::endl; + std::cout << "GameServer Update, state: " << gameserver.status().state() << std::endl; + }); +} + int main() { std::cout << "C++ Game Server has started!" << std::endl; @@ -48,6 +57,7 @@ int main() { std::cout << "...handshake complete." << std::endl; std::thread health (doHealth, sdk); + std::thread watch (watchUpdates, sdk); std::cout << "Marking server as ready..." << std::endl; grpc::Status status = sdk->Ready(); diff --git a/examples/simple-udp/Makefile b/examples/simple-udp/Makefile index 8f638ce41f..e5da71b956 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.2 +server_tag = $(REPOSITORY)/udp-server:0.3 package = agones.dev/agones/examples/simple-udp # _____ _ diff --git a/examples/simple-udp/server/fleet.yaml b/examples/simple-udp/server/fleet.yaml index 9931fcc06a..413f50d281 100644 --- a/examples/simple-udp/server/fleet.yaml +++ b/examples/simple-udp/server/fleet.yaml @@ -31,4 +31,4 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.2 \ No newline at end of file + image: gcr.io/agones-images/udp-server:0.3 \ No newline at end of file diff --git a/examples/simple-udp/server/gameserver-legacy.yaml b/examples/simple-udp/server/gameserver-legacy.yaml index 9e2fe11e03..8fa5fd67f7 100644 --- a/examples/simple-udp/server/gameserver-legacy.yaml +++ b/examples/simple-udp/server/gameserver-legacy.yaml @@ -25,4 +25,4 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.2 \ No newline at end of file + image: gcr.io/agones-images/udp-server:0.3 \ No newline at end of file diff --git a/examples/simple-udp/server/gameserver.yaml b/examples/simple-udp/server/gameserver.yaml index 5cb6dc0835..ed110b5ea4 100644 --- a/examples/simple-udp/server/gameserver.yaml +++ b/examples/simple-udp/server/gameserver.yaml @@ -25,4 +25,4 @@ spec: spec: containers: - name: simple-udp - image: gcr.io/agones-images/udp-server:0.2 \ No newline at end of file + image: gcr.io/agones-images/udp-server:0.3 \ No newline at end of file diff --git a/examples/simple-udp/server/main.go b/examples/simple-udp/server/main.go index 39c7724c94..fc73a6a925 100644 --- a/examples/simple-udp/server/main.go +++ b/examples/simple-udp/server/main.go @@ -91,6 +91,9 @@ func readWriteLoop(conn net.PacketConn, stop chan struct{}, s *sdk.SDK) { case "GAMESERVER": writeGameServerName(s, conn, sender) + + case "WATCH": + watchGameServerEvents(s) } // echo it back @@ -120,6 +123,21 @@ func writeGameServerName(s *sdk.SDK, conn net.PacketConn, sender net.Addr) { } } +// watchGameServerEvents creates a callback to log when +// gameserver events occur +func watchGameServerEvents(s *sdk.SDK) { + err := s.WatchGameServer(func(gs *coresdk.GameServer) { + j, err := json.Marshal(gs) + if err != nil { + log.Fatalf("error mashalling GameServer to JSON: %v", err) + } + log.Printf("GameServer Event: %s \n", string(j)) + }) + if err != nil { + log.Fatalf("Could not watch Game Server events, %v", err) + } +} + // doHealth sends the regular Health Pings func doHealth(sdk *sdk.SDK, stop <-chan struct{}) { tick := time.Tick(2 * time.Second) diff --git a/pkg/gameservers/helper_test.go b/pkg/gameservers/helper_test.go index 61ab4dbe10..8b2710bbf1 100644 --- a/pkg/gameservers/helper_test.go +++ b/pkg/gameservers/helper_test.go @@ -26,6 +26,7 @@ import ( "agones.dev/agones/pkg/sdk" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + netcontext "golang.org/x/net/context" "google.golang.org/grpc/metadata" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" @@ -42,20 +43,43 @@ func newSingleContainerSpec() v1alpha1.GameServerSpec { } } -// mockStream is the mock of the SDK_HealthServer for streaming -type mockStream struct { +func testHTTPHealth(t *testing.T, url string, expectedResponse string, expectedStatus int) { + // do a poll, because this code could run before the health check becomes live + err := wait.PollImmediate(time.Second, 20*time.Second, func() (done bool, err error) { + resp, err := http.Get(url) + if err != nil { + logrus.WithError(err).Error("Error connecting to ", url) + return false, nil + } + + assert.NotNil(t, resp) + if resp != nil { + defer resp.Body.Close() // nolint: errcheck + body, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err, "(%s) read response error should be nil: %v", url, err) + assert.Equal(t, expectedStatus, resp.StatusCode, "url: %s", url) + assert.Equal(t, []byte(expectedResponse), body, "(%s) response body should be '%s'", url, expectedResponse) + } + + return true, nil + }) + assert.Nil(t, err, "Timeout on %s health check, %v", url, err) +} + +// emptyMockStream is the mock of the SDK_HealthServer for streaming +type emptyMockStream struct { msgs chan *sdk.Empty } -func newMockStream() *mockStream { - return &mockStream{msgs: make(chan *sdk.Empty)} +func newEmptyMockStream() *emptyMockStream { + return &emptyMockStream{msgs: make(chan *sdk.Empty)} } -func (m *mockStream) SendAndClose(*sdk.Empty) error { +func (m *emptyMockStream) SendAndClose(*sdk.Empty) error { return nil } -func (m *mockStream) Recv() (*sdk.Empty, error) { +func (m *emptyMockStream) Recv() (*sdk.Empty, error) { empty, ok := <-m.msgs if ok { return empty, nil @@ -63,49 +87,66 @@ func (m *mockStream) Recv() (*sdk.Empty, error) { return empty, io.EOF } -func (m *mockStream) SetHeader(metadata.MD) error { +func (m *emptyMockStream) SetHeader(metadata.MD) error { panic("implement me") } -func (m *mockStream) SendHeader(metadata.MD) error { +func (m *emptyMockStream) SendHeader(metadata.MD) error { panic("implement me") } -func (m *mockStream) SetTrailer(metadata.MD) { +func (m *emptyMockStream) SetTrailer(metadata.MD) { panic("implement me") } -func (m *mockStream) Context() context.Context { +func (m *emptyMockStream) Context() context.Context { panic("implement me") } -func (m *mockStream) SendMsg(msg interface{}) error { +func (m *emptyMockStream) SendMsg(msg interface{}) error { panic("implement me") } -func (m *mockStream) RecvMsg(msg interface{}) error { +func (m *emptyMockStream) RecvMsg(msg interface{}) error { panic("implement me") } -func testHTTPHealth(t *testing.T, url string, expectedResponse string, expectedStatus int) { - // do a poll, because this code could run before the health check becomes live - err := wait.PollImmediate(time.Second, 20*time.Second, func() (done bool, err error) { - resp, err := http.Get(url) - if err != nil { - logrus.WithError(err).Error("Error connecting to ", url) - return false, nil - } +type gameServerMockStream struct { + msgs chan *sdk.GameServer +} - assert.NotNil(t, resp) - if resp != nil { - defer resp.Body.Close() // nolint: errcheck - body, err := ioutil.ReadAll(resp.Body) - assert.Nil(t, err, "(%s) read response error should be nil: %v", url, err) - assert.Equal(t, expectedStatus, resp.StatusCode, "url: %s", url) - assert.Equal(t, []byte(expectedResponse), body, "(%s) response body should be '%s'", url, expectedResponse) - } +// newGameServerMockStream implements SDK_WatchGameServerServer for testing +func newGameServerMockStream() *gameServerMockStream { + return &gameServerMockStream{ + msgs: make(chan *sdk.GameServer, 10), + } +} - return true, nil - }) - assert.Nil(t, err, "Timeout on %s health check, %v", url, err) +func (m *gameServerMockStream) Send(gs *sdk.GameServer) error { + m.msgs <- gs + return nil +} + +func (*gameServerMockStream) SetHeader(metadata.MD) error { + panic("implement me") +} + +func (*gameServerMockStream) SendHeader(metadata.MD) error { + panic("implement me") +} + +func (*gameServerMockStream) SetTrailer(metadata.MD) { + panic("implement me") +} + +func (*gameServerMockStream) Context() netcontext.Context { + panic("implement me") +} + +func (*gameServerMockStream) SendMsg(m interface{}) error { + panic("implement me") +} + +func (*gameServerMockStream) RecvMsg(m interface{}) error { + panic("implement me") } diff --git a/pkg/gameservers/localsdk.go b/pkg/gameservers/localsdk.go index 31f65c5584..c76f29cae2 100644 --- a/pkg/gameservers/localsdk.go +++ b/pkg/gameservers/localsdk.go @@ -16,7 +16,6 @@ package gameservers import ( "io" - "time" "agones.dev/agones/pkg/sdk" @@ -25,12 +24,40 @@ import ( "golang.org/x/net/context" ) -var _ sdk.SDKServer = &LocalSDKServer{} +var ( + _ sdk.SDKServer = &LocalSDKServer{} + + fixture = &sdk.GameServer{ + ObjectMeta: &sdk.GameServer_ObjectMeta{ + Name: "local", + Namespace: "default", + Uid: "1234", + Generation: 1, + ResourceVersion: "v1", + CreationTimestamp: time.Now().Unix(), + Labels: map[string]string{"islocal": "true"}, + Annotations: map[string]string{"annotation": "true"}, + }, + Status: &sdk.GameServer_Status{ + State: "Ready", + Address: "127.0.0.1", + Ports: []*sdk.GameServer_Status_Port{{Name: "default", Port: 7777}}, + }, + } +) // LocalSDKServer type is the SDKServer implementation for when the sidecar // is being run for local development, and doesn't connect to the // Kubernetes cluster type LocalSDKServer struct { + watchPeriod time.Duration +} + +// NewLocalSDKServer returns the default LocalSDKServer +func NewLocalSDKServer() *LocalSDKServer { + return &LocalSDKServer{ + watchPeriod: 5 * time.Second, + } } // Ready logs that the Ready request has been received @@ -63,23 +90,24 @@ func (l *LocalSDKServer) Health(stream sdk.SDK_HealthServer) error { // GetGameServer returns a dummy game server. func (l *LocalSDKServer) GetGameServer(context.Context, *sdk.Empty) (*sdk.GameServer, error) { logrus.Info("getting GameServer details") - gs := &sdk.GameServer{ - ObjectMeta: &sdk.GameServer_ObjectMeta{ - Name: "local", - Namespace: "default", - Uid: "1234", - Generation: 1, - ResourceVersion: "v1", - CreationTimestamp: time.Now().Unix(), - Labels: map[string]string{"islocal": "true"}, - Annotations: map[string]string{"annotation": "true"}, - }, - Status: &sdk.GameServer_Status{ - State: "Ready", - Address: "127.0.0.1", - Ports: []*sdk.GameServer_Status_Port{{Name: "default", Port: 7777}}, - }, + return fixture, nil +} + +// WatchGameServer will return a dummy GameServer (with no changes), 3 times, every 5 seconds +func (l *LocalSDKServer) WatchGameServer(_ *sdk.Empty, stream sdk.SDK_WatchGameServerServer) error { + logrus.Info("connected to watch GameServer...") + times := 3 + + for i := 0; i < times; i++ { + logrus.Info("Sending watched GameServer!") + err := stream.Send(fixture) + if err != nil { + logrus.WithError(err).Error("error sending gameserver") + return err + } + + time.Sleep(l.watchPeriod) } - return gs, nil + return nil } diff --git a/pkg/gameservers/localsdk_test.go b/pkg/gameservers/localsdk_test.go index 546aa04860..e355f7677f 100644 --- a/pkg/gameservers/localsdk_test.go +++ b/pkg/gameservers/localsdk_test.go @@ -17,7 +17,6 @@ package gameservers import ( "sync" "testing" - "time" "agones.dev/agones/pkg/sdk" @@ -28,7 +27,7 @@ import ( func TestLocal(t *testing.T) { ctx := context.Background() e := &sdk.Empty{} - l := LocalSDKServer{} + l := NewLocalSDKServer() _, err := l.Ready(ctx, e) assert.Nil(t, err, "Ready should not error") @@ -38,7 +37,7 @@ func TestLocal(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - stream := newMockStream() + stream := newEmptyMockStream() go func() { err = l.Health(stream) @@ -54,23 +53,26 @@ func TestLocal(t *testing.T) { gs, err := l.GetGameServer(ctx, e) assert.Nil(t, err) - expected := &sdk.GameServer{ - ObjectMeta: &sdk.GameServer_ObjectMeta{ - Name: "local", - Namespace: "default", - Uid: "1234", - Generation: 1, - ResourceVersion: "v1", - CreationTimestamp: time.Now().Unix(), - Labels: map[string]string{"islocal": "true"}, - Annotations: map[string]string{"annotation": "true"}, - }, - Status: &sdk.GameServer_Status{ - State: "Ready", - Address: "127.0.0.1", - Ports: []*sdk.GameServer_Status_Port{{Name: "default", Port: 7777}}, - }, - } + assert.Equal(t, fixture, gs) +} - assert.Equal(t, expected, gs) +func TestLocalSDKServerWatchGameServer(t *testing.T) { + t.Parallel() + + e := &sdk.Empty{} + l := NewLocalSDKServer() + l.watchPeriod = time.Second + + stream := newGameServerMockStream() + err := l.WatchGameServer(e, stream) + assert.Nil(t, err) + + for i := 0; i < 3; i++ { + select { + case msg := <-stream.msgs: + assert.Equal(t, fixture, msg) + case <-time.After(2 * l.watchPeriod): + assert.FailNow(t, "timeout on receiving messagess") + } + } } diff --git a/pkg/gameservers/sdkserver.go b/pkg/gameservers/sdkserver.go index e41215a7a0..b7446e63c7 100644 --- a/pkg/gameservers/sdkserver.go +++ b/pkg/gameservers/sdkserver.go @@ -66,6 +66,9 @@ type SDKServer struct { healthLastUpdated time.Time healthFailureCount int64 workerqueue *workerqueue.WorkerQueue + streamMutex sync.RWMutex + connectedStreams []sdk.SDK_WatchGameServerServer + stop <-chan struct{} recorder record.EventRecorder } @@ -100,11 +103,19 @@ func NewSDKServer(gameServerName, namespace string, healthTimeout: healthTimeout, healthMutex: sync.RWMutex{}, healthFailureCount: 0, + streamMutex: sync.RWMutex{}, } s.informerFactory = factory s.logger = runtime.NewLoggerWithType(s) + gameServers.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(_, newObj interface{}) { + gs := newObj.(*stablev1alpha1.GameServer) + s.sendGameServerUpdate(gs) + }, + }) + eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(s.logger.Infof) eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) @@ -171,7 +182,8 @@ func (s *SDKServer) Run(stop <-chan struct{}) { s.informerFactory.Start(stop) cache.WaitForCacheSync(stop, s.gameServerSynced) - + // need this for streaming gRPC commands + s.stop = stop s.workerqueue.Run(1, stop) } @@ -237,6 +249,7 @@ func (s *SDKServer) Health(stream sdk.SDK_HealthServer) error { // GetGameServer returns the current GameServer configuration and state from the backing GameServer CRD func (s *SDKServer) GetGameServer(context.Context, *sdk.Empty) (*sdk.GameServer, error) { + s.logger.Info("Received GetGameServer request") gs, err := s.gameServerLister.GameServers(s.namespace).Get(s.gameServerName) if err != nil { return nil, errors.Wrapf(err, "error retrieving gameserver %s/%s", s.namespace, s.gameServerName) @@ -245,6 +258,37 @@ func (s *SDKServer) GetGameServer(context.Context, *sdk.Empty) (*sdk.GameServer, return s.convert(gs), nil } +// WatchGameServer sends events through the stream when changes occur to the +// backing GameServer configuration / status +func (s *SDKServer) WatchGameServer(_ *sdk.Empty, stream sdk.SDK_WatchGameServerServer) error { + s.logger.Info("Received WatchGameServer request, adding stream to connectedStreams") + s.streamMutex.Lock() + s.connectedStreams = append(s.connectedStreams, stream) + s.streamMutex.Unlock() + // don't exit until we shutdown, because that will close the stream + <-s.stop + return nil +} + +// sendGameServerUpdate sends a watch game server event +func (s *SDKServer) sendGameServerUpdate(gs *stablev1alpha1.GameServer) { + s.logger.Info("Sending GameServer Event to connectedStreams") + + s.streamMutex.RLock() + defer s.streamMutex.RUnlock() + + for _, stream := range s.connectedStreams { + err := stream.Send(s.convert(gs)) + // We essentially ignoring any disconnected streams. + // I think this is fine, as disconnections shouldn't actually happen. + // but we should log them, just in case they do happen, and we can track it + if err != nil { + s.logger.WithError(errors.WithStack(err)). + Error("error sending game server update event") + } + } +} + // convert converts a K8s GameServer object, into a gRPC SDK GameServer object func (s *SDKServer) convert(gs *stablev1alpha1.GameServer) *sdk.GameServer { meta := gs.ObjectMeta diff --git a/pkg/gameservers/sdkserver_test.go b/pkg/gameservers/sdkserver_test.go index a8a86c3978..1e8104af00 100644 --- a/pkg/gameservers/sdkserver_test.go +++ b/pkg/gameservers/sdkserver_test.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" k8stesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" ) @@ -160,7 +161,7 @@ func TestSidecarHealthLastUpdated(t *testing.T) { fc := clock.NewFakeClock(now) sc.clock = fc - stream := newMockStream() + stream := newEmptyMockStream() wg := sync.WaitGroup{} wg.Add(1) @@ -208,7 +209,7 @@ func TestSidecarHealthy(t *testing.T) { fc := clock.NewFakeClock(now) sc.clock = fc - stream := newMockStream() + stream := newEmptyMockStream() wg := sync.WaitGroup{} wg.Add(1) @@ -420,6 +421,108 @@ func TestSDKServerGetGameServer(t *testing.T) { assert.Equal(t, string(fixture.Status.State), result.Status.State) } +func TestSDKServerWatchGameServer(t *testing.T) { + t.Parallel() + m := agtesting.NewMocks() + sc, err := defaultSidecar(m) + assert.Nil(t, err) + assert.Empty(t, sc.connectedStreams) + + stream := newGameServerMockStream() + asyncWatchGameServer(t, sc, stream) + assert.Nil(t, waitConnectedStreamCount(sc, 1)) + assert.Equal(t, stream, sc.connectedStreams[0]) + + stream = newGameServerMockStream() + asyncWatchGameServer(t, sc, stream) + assert.Nil(t, waitConnectedStreamCount(sc, 2)) + assert.Len(t, sc.connectedStreams, 2) + assert.Equal(t, stream, sc.connectedStreams[1]) +} + +func TestSDKServerSendGameServerUpdate(t *testing.T) { + t.Parallel() + m := agtesting.NewMocks() + sc, err := defaultSidecar(m) + assert.Nil(t, err) + assert.Empty(t, sc.connectedStreams) + + stop := make(chan struct{}) + defer close(stop) + sc.stop = stop + + stream := newGameServerMockStream() + asyncWatchGameServer(t, sc, stream) + assert.Nil(t, waitConnectedStreamCount(sc, 1)) + + fixture := &v1alpha1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test-server"}} + + sc.sendGameServerUpdate(fixture) + + var sdkGS *sdk.GameServer + select { + case sdkGS = <-stream.msgs: + case <-time.After(3 * time.Second): + assert.Fail(t, "Event stream should not have timed out") + } + + assert.Equal(t, fixture.ObjectMeta.Name, sdkGS.ObjectMeta.Name) +} + +func waitConnectedStreamCount(sc *SDKServer, count int) error { + return wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) { + sc.streamMutex.RLock() + defer sc.streamMutex.RUnlock() + return len(sc.connectedStreams) == count, nil + }) +} + +func asyncWatchGameServer(t *testing.T, sc *SDKServer, stream *gameServerMockStream) { + go func() { + err := sc.WatchGameServer(&sdk.Empty{}, stream) + assert.Nil(t, err) + }() +} + +func TestSDKServerUpdateEventHandler(t *testing.T) { + t.Parallel() + m := agtesting.NewMocks() + + fakeWatch := watch.NewFake() + m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil)) + + stop := make(chan struct{}) + defer close(stop) + + sc, err := defaultSidecar(m) + assert.Nil(t, err) + + sc.informerFactory.Start(stop) + assert.True(t, cache.WaitForCacheSync(stop, sc.gameServerSynced)) + + stream := newGameServerMockStream() + asyncWatchGameServer(t, sc, stream) + assert.Nil(t, waitConnectedStreamCount(sc, 1)) + + fixture := &v1alpha1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test-server", Namespace: "default"}, + Spec: v1alpha1.GameServerSpec{}, + } + + // need to add it before it can be modified + fakeWatch.Add(fixture.DeepCopy()) + fakeWatch.Modify(fixture.DeepCopy()) + + var sdkGS *sdk.GameServer + select { + case sdkGS = <-stream.msgs: + case <-time.After(3 * time.Second): + assert.Fail(t, "Event stream should not have timed out") + } + + assert.NotNil(t, sdkGS) + assert.Equal(t, fixture.ObjectMeta.Name, sdkGS.ObjectMeta.Name) +} + func defaultSidecar(mocks agtesting.Mocks) (*SDKServer, error) { return NewSDKServer("test", "default", true, 5*time.Second, 1, 0, mocks.KubeClient, mocks.AgonesClient) diff --git a/pkg/sdk/sdk.pb.go b/pkg/sdk/sdk.pb.go index 24ed4304bf..13a8d5d52d 100644 --- a/pkg/sdk/sdk.pb.go +++ b/pkg/sdk/sdk.pb.go @@ -49,7 +49,7 @@ func (m *Empty) Reset() { *m = Empty{} } func (m *Empty) String() string { return proto.CompactTextString(m) } func (*Empty) ProtoMessage() {} func (*Empty) Descriptor() ([]byte, []int) { - return fileDescriptor_sdk_39d49c489951ac60, []int{0} + return fileDescriptor_sdk_33bebf5c55a081c3, []int{0} } func (m *Empty) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Empty.Unmarshal(m, b) @@ -85,7 +85,7 @@ func (m *GameServer) Reset() { *m = GameServer{} } func (m *GameServer) String() string { return proto.CompactTextString(m) } func (*GameServer) ProtoMessage() {} func (*GameServer) Descriptor() ([]byte, []int) { - return fileDescriptor_sdk_39d49c489951ac60, []int{1} + return fileDescriptor_sdk_33bebf5c55a081c3, []int{1} } func (m *GameServer) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GameServer.Unmarshal(m, b) @@ -148,7 +148,7 @@ func (m *GameServer_ObjectMeta) Reset() { *m = GameServer_ObjectMeta{} } func (m *GameServer_ObjectMeta) String() string { return proto.CompactTextString(m) } func (*GameServer_ObjectMeta) ProtoMessage() {} func (*GameServer_ObjectMeta) Descriptor() ([]byte, []int) { - return fileDescriptor_sdk_39d49c489951ac60, []int{1, 0} + return fileDescriptor_sdk_33bebf5c55a081c3, []int{1, 0} } func (m *GameServer_ObjectMeta) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GameServer_ObjectMeta.Unmarshal(m, b) @@ -242,7 +242,7 @@ func (m *GameServer_Spec) Reset() { *m = GameServer_Spec{} } func (m *GameServer_Spec) String() string { return proto.CompactTextString(m) } func (*GameServer_Spec) ProtoMessage() {} func (*GameServer_Spec) Descriptor() ([]byte, []int) { - return fileDescriptor_sdk_39d49c489951ac60, []int{1, 1} + return fileDescriptor_sdk_33bebf5c55a081c3, []int{1, 1} } func (m *GameServer_Spec) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GameServer_Spec.Unmarshal(m, b) @@ -283,7 +283,7 @@ func (m *GameServer_Spec_Health) Reset() { *m = GameServer_Spec_Health{} func (m *GameServer_Spec_Health) String() string { return proto.CompactTextString(m) } func (*GameServer_Spec_Health) ProtoMessage() {} func (*GameServer_Spec_Health) Descriptor() ([]byte, []int) { - return fileDescriptor_sdk_39d49c489951ac60, []int{1, 1, 0} + return fileDescriptor_sdk_33bebf5c55a081c3, []int{1, 1, 0} } func (m *GameServer_Spec_Health) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GameServer_Spec_Health.Unmarshal(m, b) @@ -344,7 +344,7 @@ func (m *GameServer_Status) Reset() { *m = GameServer_Status{} } func (m *GameServer_Status) String() string { return proto.CompactTextString(m) } func (*GameServer_Status) ProtoMessage() {} func (*GameServer_Status) Descriptor() ([]byte, []int) { - return fileDescriptor_sdk_39d49c489951ac60, []int{1, 2} + return fileDescriptor_sdk_33bebf5c55a081c3, []int{1, 2} } func (m *GameServer_Status) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GameServer_Status.Unmarshal(m, b) @@ -397,7 +397,7 @@ func (m *GameServer_Status_Port) Reset() { *m = GameServer_Status_Port{} func (m *GameServer_Status_Port) String() string { return proto.CompactTextString(m) } func (*GameServer_Status_Port) ProtoMessage() {} func (*GameServer_Status_Port) Descriptor() ([]byte, []int) { - return fileDescriptor_sdk_39d49c489951ac60, []int{1, 2, 0} + return fileDescriptor_sdk_33bebf5c55a081c3, []int{1, 2, 0} } func (m *GameServer_Status_Port) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GameServer_Status_Port.Unmarshal(m, b) @@ -462,6 +462,8 @@ type SDKClient interface { Health(ctx context.Context, opts ...grpc.CallOption) (SDK_HealthClient, error) // Retrieve the current GameServer data GetGameServer(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GameServer, error) + // Send GameServer details whenever the GameServer is updated + WatchGameServer(ctx context.Context, in *Empty, opts ...grpc.CallOption) (SDK_WatchGameServerClient, error) } type sDKClient struct { @@ -533,6 +535,38 @@ func (c *sDKClient) GetGameServer(ctx context.Context, in *Empty, opts ...grpc.C return out, nil } +func (c *sDKClient) WatchGameServer(ctx context.Context, in *Empty, opts ...grpc.CallOption) (SDK_WatchGameServerClient, error) { + stream, err := grpc.NewClientStream(ctx, &_SDK_serviceDesc.Streams[1], c.cc, "/stable.agones.dev.sdk.SDK/WatchGameServer", opts...) + if err != nil { + return nil, err + } + x := &sDKWatchGameServerClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type SDK_WatchGameServerClient interface { + Recv() (*GameServer, error) + grpc.ClientStream +} + +type sDKWatchGameServerClient struct { + grpc.ClientStream +} + +func (x *sDKWatchGameServerClient) Recv() (*GameServer, error) { + m := new(GameServer) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // Server API for SDK service type SDKServer interface { @@ -544,6 +578,8 @@ type SDKServer interface { Health(SDK_HealthServer) error // Retrieve the current GameServer data GetGameServer(context.Context, *Empty) (*GameServer, error) + // Send GameServer details whenever the GameServer is updated + WatchGameServer(*Empty, SDK_WatchGameServerServer) error } func RegisterSDKServer(s *grpc.Server, srv SDKServer) { @@ -630,6 +666,27 @@ func _SDK_GetGameServer_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _SDK_WatchGameServer_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(SDKServer).WatchGameServer(m, &sDKWatchGameServerServer{stream}) +} + +type SDK_WatchGameServerServer interface { + Send(*GameServer) error + grpc.ServerStream +} + +type sDKWatchGameServerServer struct { + grpc.ServerStream +} + +func (x *sDKWatchGameServerServer) Send(m *GameServer) error { + return x.ServerStream.SendMsg(m) +} + var _SDK_serviceDesc = grpc.ServiceDesc{ ServiceName: "stable.agones.dev.sdk.SDK", HandlerType: (*SDKServer)(nil), @@ -653,56 +710,63 @@ var _SDK_serviceDesc = grpc.ServiceDesc{ Handler: _SDK_Health_Handler, ClientStreams: true, }, + { + StreamName: "WatchGameServer", + Handler: _SDK_WatchGameServer_Handler, + ServerStreams: true, + }, }, Metadata: "sdk.proto", } -func init() { proto.RegisterFile("sdk.proto", fileDescriptor_sdk_39d49c489951ac60) } - -var fileDescriptor_sdk_39d49c489951ac60 = []byte{ - // 699 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x95, 0xdd, 0x6e, 0xd3, 0x4a, - 0x10, 0xc7, 0xe5, 0xc4, 0x76, 0xe2, 0xc9, 0xe9, 0x39, 0xe9, 0xb6, 0x47, 0xb2, 0xac, 0x0a, 0x95, - 0x08, 0xa1, 0x50, 0x51, 0x07, 0x95, 0x9b, 0x12, 0x09, 0xc4, 0x47, 0x4b, 0x41, 0x50, 0x51, 0x39, - 0x55, 0x2f, 0x2a, 0xa4, 0x68, 0x13, 0x8f, 0x12, 0x13, 0xc7, 0x6b, 0xed, 0x6e, 0x82, 0x72, 0xcb, - 0x2b, 0xf0, 0x10, 0x70, 0x03, 0x2f, 0xc3, 0x2b, 0xf0, 0x10, 0x5c, 0x21, 0xb4, 0x6b, 0xbb, 0x09, - 0xa5, 0xf4, 0x43, 0xbd, 0xca, 0xec, 0xcc, 0xfc, 0x7f, 0xb3, 0xd9, 0x99, 0x5d, 0x83, 0x23, 0xc2, - 0x91, 0x9f, 0x72, 0x26, 0x19, 0xf9, 0x5f, 0x48, 0xda, 0x8b, 0xd1, 0xa7, 0x03, 0x96, 0xa0, 0xf0, - 0x43, 0x9c, 0xfa, 0x22, 0x1c, 0x79, 0x6b, 0x03, 0xc6, 0x06, 0x31, 0xb6, 0x68, 0x1a, 0xb5, 0x68, - 0x92, 0x30, 0x49, 0x65, 0xc4, 0x12, 0x91, 0x89, 0x1a, 0x15, 0xb0, 0x76, 0xc7, 0xa9, 0x9c, 0x35, - 0xbe, 0x3a, 0x00, 0x7b, 0x74, 0x8c, 0x1d, 0xe4, 0x53, 0xe4, 0x64, 0x1f, 0x6a, 0xac, 0xf7, 0x0e, - 0xfb, 0xb2, 0x3b, 0x46, 0x49, 0x5d, 0x63, 0xdd, 0x68, 0xd6, 0xb6, 0xee, 0xfa, 0x67, 0x96, 0xf0, - 0xe7, 0x3a, 0xff, 0x8d, 0x16, 0xed, 0xa3, 0xa4, 0x01, 0xb0, 0x13, 0x9b, 0xb4, 0xc1, 0x14, 0x29, - 0xf6, 0xdd, 0x92, 0xe6, 0xdc, 0xbe, 0x98, 0xd3, 0x49, 0xb1, 0x1f, 0x68, 0x0d, 0x79, 0x0c, 0xb6, - 0x90, 0x54, 0x4e, 0x84, 0x5b, 0xd6, 0xea, 0xe6, 0x25, 0xd4, 0x3a, 0x3f, 0xc8, 0x75, 0xde, 0x27, - 0x13, 0x60, 0xbe, 0x31, 0x42, 0xc0, 0x4c, 0xe8, 0x18, 0xf5, 0x9f, 0x72, 0x02, 0x6d, 0x93, 0x35, - 0x70, 0xd4, 0xaf, 0x48, 0x69, 0x1f, 0xf5, 0x2e, 0x9d, 0x60, 0xee, 0x20, 0x75, 0x28, 0x4f, 0xa2, - 0x50, 0xd7, 0x77, 0x02, 0x65, 0x92, 0x3b, 0x50, 0xe7, 0x28, 0xd8, 0x84, 0xf7, 0xb1, 0x3b, 0x45, - 0x2e, 0x22, 0x96, 0xb8, 0xa6, 0x0e, 0xff, 0x57, 0xf8, 0x8f, 0x32, 0x37, 0xb9, 0x01, 0x30, 0xc0, - 0x04, 0xb9, 0x3e, 0x77, 0xd7, 0x5a, 0x37, 0x9a, 0xe5, 0x60, 0xc1, 0x43, 0x36, 0x81, 0xf4, 0x39, - 0x6a, 0xbb, 0x2b, 0xa3, 0x31, 0x0a, 0x49, 0xc7, 0xa9, 0x6b, 0xeb, 0xbc, 0xe5, 0x22, 0x72, 0x58, - 0x04, 0x54, 0x7a, 0x88, 0x31, 0x9e, 0x4a, 0xaf, 0x64, 0xe9, 0x45, 0x64, 0x9e, 0xde, 0x85, 0xda, - 0x42, 0xd7, 0xdd, 0xea, 0x7a, 0xb9, 0x59, 0xdb, 0x7a, 0x78, 0x95, 0x46, 0xfa, 0x4f, 0xe6, 0xfa, - 0xdd, 0x44, 0xf2, 0x59, 0xb0, 0x48, 0x24, 0x07, 0x60, 0xc7, 0xb4, 0x87, 0xb1, 0x70, 0x1d, 0xcd, - 0xde, 0xbe, 0x12, 0xfb, 0xb5, 0x96, 0x66, 0xd8, 0x9c, 0xe3, 0x3d, 0x82, 0xfa, 0xe9, 0x92, 0xaa, - 0x03, 0x23, 0x9c, 0xe5, 0x2d, 0x53, 0x26, 0x59, 0x05, 0x6b, 0x4a, 0xe3, 0x49, 0xd1, 0xad, 0x6c, - 0xd1, 0x2e, 0x6d, 0x1b, 0xde, 0x03, 0xa8, 0x2d, 0x60, 0xaf, 0x24, 0xfd, 0x61, 0x80, 0xa9, 0x46, - 0x8f, 0xec, 0x82, 0x3d, 0x44, 0x1a, 0xcb, 0x61, 0x3e, 0xfa, 0x9b, 0x97, 0x1b, 0x59, 0xff, 0x85, - 0x16, 0x05, 0xb9, 0xd8, 0xfb, 0x6c, 0x80, 0x9d, 0xb9, 0x88, 0x07, 0xd5, 0x9d, 0x48, 0x28, 0x46, - 0xa8, 0x99, 0xd5, 0xe0, 0x64, 0x4d, 0x6e, 0xc1, 0xd2, 0x01, 0xf2, 0x88, 0x85, 0x1d, 0xec, 0xb3, - 0x24, 0x14, 0x7a, 0x63, 0x56, 0xf0, 0xbb, 0x93, 0x6c, 0x40, 0xfd, 0x39, 0x8d, 0xe2, 0x09, 0xc7, - 0xc3, 0x21, 0x47, 0x31, 0x64, 0x71, 0x36, 0x92, 0x56, 0xf0, 0x87, 0x9f, 0xdc, 0x83, 0x95, 0x97, - 0x49, 0x24, 0x23, 0x1a, 0xef, 0x60, 0x4c, 0x67, 0x05, 0xd7, 0xd4, 0xe9, 0x67, 0x85, 0xbc, 0x2f, - 0x06, 0xd8, 0xd9, 0xbd, 0x51, 0xe7, 0xa3, 0x6e, 0x4e, 0x71, 0x43, 0xb2, 0x05, 0x71, 0xa1, 0x42, - 0xc3, 0x90, 0xa3, 0x10, 0xf9, 0xb9, 0x15, 0x4b, 0xf2, 0x0c, 0xac, 0x94, 0x71, 0xa9, 0x2e, 0x68, - 0xf9, 0x92, 0x67, 0xa5, 0x0b, 0xf9, 0x07, 0x8c, 0xcb, 0x20, 0xd3, 0x7a, 0x3e, 0x98, 0x6a, 0x79, - 0xe6, 0xed, 0x24, 0x60, 0xaa, 0xa4, 0xfc, 0x58, 0xb4, 0xbd, 0xf5, 0xb3, 0x04, 0xe5, 0xce, 0xce, - 0x2b, 0x72, 0x04, 0x56, 0x80, 0x34, 0x9c, 0x91, 0xb5, 0xbf, 0x94, 0xd5, 0xef, 0x9b, 0x77, 0x6e, - 0xb4, 0xb1, 0xfc, 0xe1, 0xdb, 0xf7, 0x8f, 0xa5, 0x5a, 0xc3, 0x6e, 0x71, 0xc5, 0x6a, 0x1b, 0x1b, - 0xe4, 0x2d, 0x54, 0x3b, 0xc3, 0x89, 0x0c, 0xd9, 0xfb, 0xe4, 0x5a, 0xe8, 0x55, 0x8d, 0xfe, 0xb7, - 0xe1, 0xb4, 0x44, 0x8e, 0x53, 0xf4, 0xe3, 0x93, 0xb9, 0xb8, 0x0e, 0x9b, 0x68, 0xf6, 0x3f, 0x8d, - 0x4a, 0x2b, 0x9b, 0xb7, 0xb6, 0xb1, 0xd1, 0x34, 0x08, 0xc2, 0xd2, 0x1e, 0xca, 0x85, 0xc7, 0xfc, - 0xfc, 0x12, 0x37, 0x2f, 0x6c, 0x57, 0x63, 0x45, 0xd7, 0x59, 0x22, 0xb5, 0xd6, 0x40, 0xbd, 0x89, - 0xda, 0xf9, 0xd4, 0x3a, 0x2e, 0x8b, 0x70, 0xd4, 0xb3, 0xf5, 0x87, 0xe4, 0xfe, 0xaf, 0x00, 0x00, - 0x00, 0xff, 0xff, 0xc5, 0x0e, 0x54, 0x28, 0x8a, 0x06, 0x00, 0x00, +func init() { proto.RegisterFile("sdk.proto", fileDescriptor_sdk_33bebf5c55a081c3) } + +var fileDescriptor_sdk_33bebf5c55a081c3 = []byte{ + // 725 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x95, 0xcd, 0x6e, 0xdb, 0x46, + 0x10, 0xc7, 0x41, 0xf3, 0x43, 0xe2, 0xa8, 0xae, 0xe5, 0xb5, 0x0b, 0xb0, 0x84, 0x51, 0xb8, 0x42, + 0x51, 0xa8, 0x46, 0x4d, 0x19, 0xee, 0xc5, 0x15, 0xd0, 0xa2, 0x1f, 0x76, 0xdd, 0xa2, 0x35, 0x6a, + 0x50, 0x86, 0x0b, 0x18, 0x05, 0x84, 0x15, 0x39, 0x90, 0x58, 0x51, 0x5c, 0x62, 0x77, 0x25, 0x43, + 0xd7, 0xbe, 0x42, 0x1e, 0x22, 0xb9, 0x24, 0x2f, 0x93, 0x57, 0xc8, 0x3d, 0xd7, 0x1c, 0x83, 0x5d, + 0x92, 0x96, 0xe2, 0x38, 0xfe, 0x80, 0x73, 0xd2, 0xec, 0xcc, 0xfc, 0x7f, 0xb3, 0xda, 0x99, 0x5d, + 0x82, 0x2b, 0xe2, 0x71, 0x90, 0x73, 0x26, 0x19, 0xf9, 0x4c, 0x48, 0x3a, 0x48, 0x31, 0xa0, 0x43, + 0x96, 0xa1, 0x08, 0x62, 0x9c, 0x05, 0x22, 0x1e, 0xfb, 0x5b, 0x43, 0xc6, 0x86, 0x29, 0x76, 0x68, + 0x9e, 0x74, 0x68, 0x96, 0x31, 0x49, 0x65, 0xc2, 0x32, 0x51, 0x88, 0x5a, 0x35, 0xb0, 0x8f, 0x26, + 0xb9, 0x9c, 0xb7, 0x5e, 0xb8, 0x00, 0xc7, 0x74, 0x82, 0x3d, 0xe4, 0x33, 0xe4, 0xe4, 0x04, 0x1a, + 0x6c, 0xf0, 0x1f, 0x46, 0xb2, 0x3f, 0x41, 0x49, 0x3d, 0x63, 0xdb, 0x68, 0x37, 0xf6, 0xbf, 0x0d, + 0x6e, 0x2c, 0x11, 0x2c, 0x74, 0xc1, 0xdf, 0x5a, 0x74, 0x82, 0x92, 0x86, 0xc0, 0xae, 0x6c, 0xd2, + 0x05, 0x4b, 0xe4, 0x18, 0x79, 0x2b, 0x9a, 0xf3, 0xf5, 0xdd, 0x9c, 0x5e, 0x8e, 0x51, 0xa8, 0x35, + 0xe4, 0x27, 0x70, 0x84, 0xa4, 0x72, 0x2a, 0x3c, 0x53, 0xab, 0xdb, 0xf7, 0x50, 0xeb, 0xfc, 0xb0, + 0xd4, 0xf9, 0x4f, 0x2d, 0x80, 0xc5, 0xc6, 0x08, 0x01, 0x2b, 0xa3, 0x13, 0xd4, 0x7f, 0xca, 0x0d, + 0xb5, 0x4d, 0xb6, 0xc0, 0x55, 0xbf, 0x22, 0xa7, 0x11, 0xea, 0x5d, 0xba, 0xe1, 0xc2, 0x41, 0x9a, + 0x60, 0x4e, 0x93, 0x58, 0xd7, 0x77, 0x43, 0x65, 0x92, 0x6f, 0xa0, 0xc9, 0x51, 0xb0, 0x29, 0x8f, + 0xb0, 0x3f, 0x43, 0x2e, 0x12, 0x96, 0x79, 0x96, 0x0e, 0xaf, 0x55, 0xfe, 0xf3, 0xc2, 0x4d, 0xbe, + 0x00, 0x18, 0x62, 0x86, 0x5c, 0x9f, 0xbb, 0x67, 0x6f, 0x1b, 0x6d, 0x33, 0x5c, 0xf2, 0x90, 0x5d, + 0x20, 0x11, 0x47, 0x6d, 0xf7, 0x65, 0x32, 0x41, 0x21, 0xe9, 0x24, 0xf7, 0x1c, 0x9d, 0xb7, 0x5e, + 0x45, 0xce, 0xaa, 0x80, 0x4a, 0x8f, 0x31, 0xc5, 0x6b, 0xe9, 0xb5, 0x22, 0xbd, 0x8a, 0x2c, 0xd2, + 0xfb, 0xd0, 0x58, 0xea, 0xba, 0x57, 0xdf, 0x36, 0xdb, 0x8d, 0xfd, 0x1f, 0x1e, 0xd2, 0xc8, 0xe0, + 0xe7, 0x85, 0xfe, 0x28, 0x93, 0x7c, 0x1e, 0x2e, 0x13, 0xc9, 0x29, 0x38, 0x29, 0x1d, 0x60, 0x2a, + 0x3c, 0x57, 0xb3, 0x0f, 0x1e, 0xc4, 0xfe, 0x4b, 0x4b, 0x0b, 0x6c, 0xc9, 0xf1, 0x7f, 0x84, 0xe6, + 0xf5, 0x92, 0xaa, 0x03, 0x63, 0x9c, 0x97, 0x2d, 0x53, 0x26, 0xd9, 0x04, 0x7b, 0x46, 0xd3, 0x69, + 0xd5, 0xad, 0x62, 0xd1, 0x5d, 0x39, 0x30, 0xfc, 0xef, 0xa1, 0xb1, 0x84, 0x7d, 0x90, 0xf4, 0x8d, + 0x01, 0x96, 0x1a, 0x3d, 0x72, 0x04, 0xce, 0x08, 0x69, 0x2a, 0x47, 0xe5, 0xe8, 0xef, 0xde, 0x6f, + 0x64, 0x83, 0xdf, 0xb5, 0x28, 0x2c, 0xc5, 0xfe, 0x33, 0x03, 0x9c, 0xc2, 0x45, 0x7c, 0xa8, 0x1f, + 0x26, 0x42, 0x31, 0x62, 0xcd, 0xac, 0x87, 0x57, 0x6b, 0xf2, 0x15, 0xac, 0x9e, 0x22, 0x4f, 0x58, + 0xdc, 0xc3, 0x88, 0x65, 0xb1, 0xd0, 0x1b, 0xb3, 0xc3, 0x77, 0x9d, 0x64, 0x07, 0x9a, 0xbf, 0xd1, + 0x24, 0x9d, 0x72, 0x3c, 0x1b, 0x71, 0x14, 0x23, 0x96, 0x16, 0x23, 0x69, 0x87, 0xef, 0xf9, 0xc9, + 0x1e, 0x6c, 0xfc, 0x91, 0x25, 0x32, 0xa1, 0xe9, 0x21, 0xa6, 0x74, 0x5e, 0x71, 0x2d, 0x9d, 0x7e, + 0x53, 0xc8, 0x7f, 0x6e, 0x80, 0x53, 0xdc, 0x1b, 0x75, 0x3e, 0xea, 0xe6, 0x54, 0x37, 0xa4, 0x58, + 0x10, 0x0f, 0x6a, 0x34, 0x8e, 0x39, 0x0a, 0x51, 0x9e, 0x5b, 0xb5, 0x24, 0xbf, 0x82, 0x9d, 0x33, + 0x2e, 0xd5, 0x05, 0x35, 0xef, 0x79, 0x56, 0xba, 0x50, 0x70, 0xca, 0xb8, 0x0c, 0x0b, 0xad, 0x1f, + 0x80, 0xa5, 0x96, 0x37, 0xde, 0x4e, 0x02, 0x96, 0x4a, 0x2a, 0x8f, 0x45, 0xdb, 0xfb, 0xaf, 0x4d, + 0x30, 0x7b, 0x87, 0x7f, 0x92, 0x73, 0xb0, 0x43, 0xa4, 0xf1, 0x9c, 0x6c, 0x7d, 0xa0, 0xac, 0x7e, + 0xdf, 0xfc, 0x5b, 0xa3, 0xad, 0xf5, 0xff, 0x5f, 0xbe, 0x7a, 0xb2, 0xd2, 0x68, 0x39, 0x1d, 0xae, + 0x58, 0x5d, 0x63, 0x87, 0xfc, 0x0b, 0xf5, 0xde, 0x68, 0x2a, 0x63, 0x76, 0x99, 0x3d, 0x0a, 0xbd, + 0xa9, 0xd1, 0x9f, 0xb6, 0xdc, 0x8e, 0x28, 0x71, 0x8a, 0x7e, 0x71, 0x35, 0x17, 0x8f, 0x61, 0x13, + 0xcd, 0xfe, 0xa4, 0x55, 0xeb, 0x14, 0xf3, 0xd6, 0x35, 0x76, 0xda, 0x06, 0x41, 0x58, 0x3d, 0x46, + 0xb9, 0xf4, 0x98, 0xdf, 0x5e, 0xe2, 0xcb, 0x3b, 0xdb, 0xd5, 0xda, 0xd0, 0x75, 0x56, 0x49, 0xa3, + 0x33, 0x54, 0x6f, 0x62, 0x41, 0x65, 0xb0, 0xf6, 0x0f, 0x95, 0xd1, 0xe8, 0x63, 0x16, 0xfa, 0x5c, + 0x17, 0xda, 0x20, 0xeb, 0x9d, 0x4b, 0x85, 0x5e, 0x2a, 0xb7, 0x67, 0xfc, 0x62, 0x5f, 0x98, 0x22, + 0x1e, 0x0f, 0x1c, 0xfd, 0xe5, 0xfa, 0xee, 0x6d, 0x00, 0x00, 0x00, 0xff, 0xff, 0x59, 0x57, 0x6a, + 0x19, 0xfb, 0x06, 0x00, 0x00, } diff --git a/pkg/sdk/sdk.pb.gw.go b/pkg/sdk/sdk.pb.gw.go index dad7493bb1..f20d3e8c25 100644 --- a/pkg/sdk/sdk.pb.gw.go +++ b/pkg/sdk/sdk.pb.gw.go @@ -119,6 +119,23 @@ func request_SDK_GetGameServer_0(ctx context.Context, marshaler runtime.Marshale } +func request_SDK_WatchGameServer_0(ctx context.Context, marshaler runtime.Marshaler, client SDKClient, req *http.Request, pathParams map[string]string) (SDK_WatchGameServerClient, runtime.ServerMetadata, error) { + var protoReq Empty + var metadata runtime.ServerMetadata + + stream, err := client.WatchGameServer(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + // RegisterSDKHandlerFromEndpoint is same as RegisterSDKHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterSDKHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -273,6 +290,35 @@ func RegisterSDKHandlerClient(ctx context.Context, mux *runtime.ServeMux, client }) + mux.Handle("GET", pattern_SDK_WatchGameServer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SDK_WatchGameServer_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_SDK_WatchGameServer_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -284,6 +330,8 @@ var ( pattern_SDK_Health_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"health"}, "")) pattern_SDK_GetGameServer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"gameserver"}, "")) + + pattern_SDK_WatchGameServer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"watch", "gameserver"}, "")) ) var ( @@ -294,4 +342,6 @@ var ( forward_SDK_Health_0 = runtime.ForwardResponseMessage forward_SDK_GetGameServer_0 = runtime.ForwardResponseMessage + + forward_SDK_WatchGameServer_0 = runtime.ForwardResponseStream ) diff --git a/sdk.proto b/sdk.proto index 91e5069650..ee08a47723 100644 --- a/sdk.proto +++ b/sdk.proto @@ -48,6 +48,12 @@ service SDK { get: "/gameserver" }; } + // Send GameServer details whenever the GameServer is updated + rpc WatchGameServer (Empty) returns (stream GameServer) { + option (google.api.http) = { + get: "/watch/gameserver" + }; + } } message Empty { diff --git a/sdk.swagger.json b/sdk.swagger.json index e090542fed..db50c77acd 100644 --- a/sdk.swagger.json +++ b/sdk.swagger.json @@ -112,6 +112,23 @@ "SDK" ] } + }, + "/watch/gameserver": { + "get": { + "summary": "Send GameServer details whenever the GameServer is updated", + "operationId": "WatchGameServer", + "responses": { + "200": { + "description": "(streaming responses)", + "schema": { + "$ref": "#/definitions/sdkGameServer" + } + } + }, + "tags": [ + "SDK" + ] + } } }, "definitions": { diff --git a/sdks/README.md b/sdks/README.md index ded1404d96..1dd10f1497 100644 --- a/sdks/README.md +++ b/sdks/README.md @@ -61,6 +61,27 @@ specifically at the `message GameServer`. For language specific documentation, have a look at the respective source (linked above), and the [examples](../examples). +### WatchGameServer(function(gameserver){...}) + +⚠️⚠️⚠️ **`WatchGameServer` is currently a development feature and has not been released** ⚠️⚠️⚠ + +This executes the passed in callback with the current `GameServer` details whenever the underlying `GameServer` configuration is updated. +This can be useful to track `GameServer > Status > State` changes, `metadata` changes, such as labels and annotations, and more. + +In combination with this SDK, manipulating [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) and +[Labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) can also be a useful way to communicate information through to running game server processes from outside those processes. +This is especially useful when combined with `FleetAllocation` [applied metadata](../docs/fleet_spec.md#fleet-allocation-specification). + +Since the GameServer contains an entire [PodTemplate](https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/#pod-templates) +the returned object is limited to that configuration that was deemed useful. If there are +areas that you feel are missing, please [file an issue](https://github.com/GoogleCloudPlatform/agones/issues) or pull request. + +The easiest way to see what is exposed, is to check the [`sdk.proto`](https://github.com/GoogleCloudPlatform/agones/blob/master/sdk.proto), +specifically at the `message GameServer`. + +For language specific documentation, have a look at the respective source (linked above), +and the [examples](../examples). + ## Local Development When the game server is running on Agones, the SDK communicates over TCP to a small diff --git a/sdks/cpp/README.md b/sdks/cpp/README.md index f36cdfa126..369dd7d299 100644 --- a/sdks/cpp/README.md +++ b/sdks/cpp/README.md @@ -74,6 +74,25 @@ if (!status.ok()) {...} For more information, you can also read the [SDK Overview](../), check out [sdk.h](sdk.h) and also look at the [C++ example](../../examples/cpp-simple). +To get updates on the [backing `GameServer`](../README.md#gameserver) as they happen, +call `sdk->WatchGameServer([](stable::agones::dev::sdk::GameServer gameserver){...})`. + +⚠️⚠️⚠️ **`WatchGameServer` is currently a development feature and has not been released** ⚠️⚠️️⚠️ + +This will call the passed in `std::function` +synchronously (this is a blocking function, so you may want to run it in its own thread) whenever the backing `GameServer` +is updated. + +```cpp +sdk->WatchGameServer([](stable::agones::dev::sdk::GameServer gameserver){ + std::cout << "GameServer Update, name: " << gameserver.object_meta().name() << std::endl; + std::cout << "GameServer Update, state: " << gameserver.status().state() << std::endl; +}); +``` + +For more information, you can also read the [SDK Overview](../), check out [sdk.h](sdk.h) and also look at the +[C++ example](../../examples/cpp-simple). + ### Failure When running on Agones, the above functions should only fail under exceptional circumstances, so please file a bug if it occurs. diff --git a/sdks/cpp/sdk.cc b/sdks/cpp/sdk.cc index 9f34ef8f61..216c9cf6cd 100644 --- a/sdks/cpp/sdk.cc +++ b/sdks/cpp/sdk.cc @@ -59,6 +59,18 @@ namespace agones { return stub->GetGameServer(context, request, response); } + grpc::Status SDK::WatchGameServer(const std::function callback) { + grpc::ClientContext *context = new grpc::ClientContext(); + stable::agones::dev::sdk::Empty request; + stable::agones::dev::sdk::GameServer gameServer; + + std::unique_ptr> reader = stub->WatchGameServer(context, request); + while (reader->Read(&gameServer)) { + callback(gameServer); + } + return reader->Finish(); + } + grpc::Status SDK::Shutdown() { grpc::ClientContext *context = new grpc::ClientContext(); context->set_deadline(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_seconds(30, GPR_TIMESPAN))); diff --git a/sdks/cpp/sdk.grpc.pb.cc b/sdks/cpp/sdk.grpc.pb.cc index 6f6eb5763b..29970cbf9b 100644 --- a/sdks/cpp/sdk.grpc.pb.cc +++ b/sdks/cpp/sdk.grpc.pb.cc @@ -38,6 +38,7 @@ static const char* SDK_method_names[] = { "/stable.agones.dev.sdk.SDK/Shutdown", "/stable.agones.dev.sdk.SDK/Health", "/stable.agones.dev.sdk.SDK/GetGameServer", + "/stable.agones.dev.sdk.SDK/WatchGameServer", }; std::unique_ptr< SDK::Stub> SDK::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) { @@ -51,6 +52,7 @@ SDK::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel) , rpcmethod_Shutdown_(SDK_method_names[1], ::grpc::internal::RpcMethod::NORMAL_RPC, channel) , rpcmethod_Health_(SDK_method_names[2], ::grpc::internal::RpcMethod::CLIENT_STREAMING, channel) , rpcmethod_GetGameServer_(SDK_method_names[3], ::grpc::internal::RpcMethod::NORMAL_RPC, channel) + , rpcmethod_WatchGameServer_(SDK_method_names[4], ::grpc::internal::RpcMethod::SERVER_STREAMING, channel) {} ::grpc::Status SDK::Stub::Ready(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::stable::agones::dev::sdk::Empty* response) { @@ -101,6 +103,18 @@ ::grpc::ClientAsyncResponseReader< ::stable::agones::dev::sdk::GameServer>* SDK: return ::grpc::internal::ClientAsyncResponseReaderFactory< ::stable::agones::dev::sdk::GameServer>::Create(channel_.get(), cq, rpcmethod_GetGameServer_, context, request, false); } +::grpc::ClientReader< ::stable::agones::dev::sdk::GameServer>* SDK::Stub::WatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request) { + return ::grpc::internal::ClientReaderFactory< ::stable::agones::dev::sdk::GameServer>::Create(channel_.get(), rpcmethod_WatchGameServer_, context, request); +} + +::grpc::ClientAsyncReader< ::stable::agones::dev::sdk::GameServer>* SDK::Stub::AsyncWatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq, void* tag) { + return ::grpc::internal::ClientAsyncReaderFactory< ::stable::agones::dev::sdk::GameServer>::Create(channel_.get(), cq, rpcmethod_WatchGameServer_, context, request, true, tag); +} + +::grpc::ClientAsyncReader< ::stable::agones::dev::sdk::GameServer>* SDK::Stub::PrepareAsyncWatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncReaderFactory< ::stable::agones::dev::sdk::GameServer>::Create(channel_.get(), cq, rpcmethod_WatchGameServer_, context, request, false, nullptr); +} + SDK::Service::Service() { AddMethod(new ::grpc::internal::RpcServiceMethod( SDK_method_names[0], @@ -122,6 +136,11 @@ SDK::Service::Service() { ::grpc::internal::RpcMethod::NORMAL_RPC, new ::grpc::internal::RpcMethodHandler< SDK::Service, ::stable::agones::dev::sdk::Empty, ::stable::agones::dev::sdk::GameServer>( std::mem_fn(&SDK::Service::GetGameServer), this))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + SDK_method_names[4], + ::grpc::internal::RpcMethod::SERVER_STREAMING, + new ::grpc::internal::ServerStreamingHandler< SDK::Service, ::stable::agones::dev::sdk::Empty, ::stable::agones::dev::sdk::GameServer>( + std::mem_fn(&SDK::Service::WatchGameServer), this))); } SDK::Service::~Service() { @@ -155,6 +174,13 @@ ::grpc::Status SDK::Service::GetGameServer(::grpc::ServerContext* context, const return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); } +::grpc::Status SDK::Service::WatchGameServer(::grpc::ServerContext* context, const ::stable::agones::dev::sdk::Empty* request, ::grpc::ServerWriter< ::stable::agones::dev::sdk::GameServer>* writer) { + (void) context; + (void) request; + (void) writer; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + } // namespace stable } // namespace agones diff --git a/sdks/cpp/sdk.grpc.pb.h b/sdks/cpp/sdk.grpc.pb.h index ff403695ba..b128ca4b5e 100644 --- a/sdks/cpp/sdk.grpc.pb.h +++ b/sdks/cpp/sdk.grpc.pb.h @@ -86,6 +86,16 @@ class SDK final { std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::stable::agones::dev::sdk::GameServer>> PrepareAsyncGetGameServer(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) { return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::stable::agones::dev::sdk::GameServer>>(PrepareAsyncGetGameServerRaw(context, request, cq)); } + // Send GameServer details whenever the GameServer is updated + std::unique_ptr< ::grpc::ClientReaderInterface< ::stable::agones::dev::sdk::GameServer>> WatchGameServer(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request) { + return std::unique_ptr< ::grpc::ClientReaderInterface< ::stable::agones::dev::sdk::GameServer>>(WatchGameServerRaw(context, request)); + } + std::unique_ptr< ::grpc::ClientAsyncReaderInterface< ::stable::agones::dev::sdk::GameServer>> AsyncWatchGameServer(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq, void* tag) { + return std::unique_ptr< ::grpc::ClientAsyncReaderInterface< ::stable::agones::dev::sdk::GameServer>>(AsyncWatchGameServerRaw(context, request, cq, tag)); + } + std::unique_ptr< ::grpc::ClientAsyncReaderInterface< ::stable::agones::dev::sdk::GameServer>> PrepareAsyncWatchGameServer(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncReaderInterface< ::stable::agones::dev::sdk::GameServer>>(PrepareAsyncWatchGameServerRaw(context, request, cq)); + } private: virtual ::grpc::ClientAsyncResponseReaderInterface< ::stable::agones::dev::sdk::Empty>* AsyncReadyRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) = 0; virtual ::grpc::ClientAsyncResponseReaderInterface< ::stable::agones::dev::sdk::Empty>* PrepareAsyncReadyRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) = 0; @@ -96,6 +106,9 @@ class SDK final { virtual ::grpc::ClientAsyncWriterInterface< ::stable::agones::dev::sdk::Empty>* PrepareAsyncHealthRaw(::grpc::ClientContext* context, ::stable::agones::dev::sdk::Empty* response, ::grpc::CompletionQueue* cq) = 0; virtual ::grpc::ClientAsyncResponseReaderInterface< ::stable::agones::dev::sdk::GameServer>* AsyncGetGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) = 0; virtual ::grpc::ClientAsyncResponseReaderInterface< ::stable::agones::dev::sdk::GameServer>* PrepareAsyncGetGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientReaderInterface< ::stable::agones::dev::sdk::GameServer>* WatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request) = 0; + virtual ::grpc::ClientAsyncReaderInterface< ::stable::agones::dev::sdk::GameServer>* AsyncWatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq, void* tag) = 0; + virtual ::grpc::ClientAsyncReaderInterface< ::stable::agones::dev::sdk::GameServer>* PrepareAsyncWatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) = 0; }; class Stub final : public StubInterface { public: @@ -130,6 +143,15 @@ class SDK final { std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::stable::agones::dev::sdk::GameServer>> PrepareAsyncGetGameServer(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) { return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::stable::agones::dev::sdk::GameServer>>(PrepareAsyncGetGameServerRaw(context, request, cq)); } + std::unique_ptr< ::grpc::ClientReader< ::stable::agones::dev::sdk::GameServer>> WatchGameServer(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request) { + return std::unique_ptr< ::grpc::ClientReader< ::stable::agones::dev::sdk::GameServer>>(WatchGameServerRaw(context, request)); + } + std::unique_ptr< ::grpc::ClientAsyncReader< ::stable::agones::dev::sdk::GameServer>> AsyncWatchGameServer(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq, void* tag) { + return std::unique_ptr< ::grpc::ClientAsyncReader< ::stable::agones::dev::sdk::GameServer>>(AsyncWatchGameServerRaw(context, request, cq, tag)); + } + std::unique_ptr< ::grpc::ClientAsyncReader< ::stable::agones::dev::sdk::GameServer>> PrepareAsyncWatchGameServer(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncReader< ::stable::agones::dev::sdk::GameServer>>(PrepareAsyncWatchGameServerRaw(context, request, cq)); + } private: std::shared_ptr< ::grpc::ChannelInterface> channel_; @@ -142,10 +164,14 @@ class SDK final { ::grpc::ClientAsyncWriter< ::stable::agones::dev::sdk::Empty>* PrepareAsyncHealthRaw(::grpc::ClientContext* context, ::stable::agones::dev::sdk::Empty* response, ::grpc::CompletionQueue* cq) override; ::grpc::ClientAsyncResponseReader< ::stable::agones::dev::sdk::GameServer>* AsyncGetGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) override; ::grpc::ClientAsyncResponseReader< ::stable::agones::dev::sdk::GameServer>* PrepareAsyncGetGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientReader< ::stable::agones::dev::sdk::GameServer>* WatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request) override; + ::grpc::ClientAsyncReader< ::stable::agones::dev::sdk::GameServer>* AsyncWatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq, void* tag) override; + ::grpc::ClientAsyncReader< ::stable::agones::dev::sdk::GameServer>* PrepareAsyncWatchGameServerRaw(::grpc::ClientContext* context, const ::stable::agones::dev::sdk::Empty& request, ::grpc::CompletionQueue* cq) override; const ::grpc::internal::RpcMethod rpcmethod_Ready_; const ::grpc::internal::RpcMethod rpcmethod_Shutdown_; const ::grpc::internal::RpcMethod rpcmethod_Health_; const ::grpc::internal::RpcMethod rpcmethod_GetGameServer_; + const ::grpc::internal::RpcMethod rpcmethod_WatchGameServer_; }; static std::unique_ptr NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options = ::grpc::StubOptions()); @@ -161,6 +187,8 @@ class SDK final { virtual ::grpc::Status Health(::grpc::ServerContext* context, ::grpc::ServerReader< ::stable::agones::dev::sdk::Empty>* reader, ::stable::agones::dev::sdk::Empty* response); // Retrieve the current GameServer data virtual ::grpc::Status GetGameServer(::grpc::ServerContext* context, const ::stable::agones::dev::sdk::Empty* request, ::stable::agones::dev::sdk::GameServer* response); + // Send GameServer details whenever the GameServer is updated + virtual ::grpc::Status WatchGameServer(::grpc::ServerContext* context, const ::stable::agones::dev::sdk::Empty* request, ::grpc::ServerWriter< ::stable::agones::dev::sdk::GameServer>* writer); }; template class WithAsyncMethod_Ready : public BaseClass { @@ -242,7 +270,27 @@ class SDK final { ::grpc::Service::RequestAsyncUnary(3, context, request, response, new_call_cq, notification_cq, tag); } }; - typedef WithAsyncMethod_Ready > > > AsyncService; + template + class WithAsyncMethod_WatchGameServer : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service *service) {} + public: + WithAsyncMethod_WatchGameServer() { + ::grpc::Service::MarkMethodAsync(4); + } + ~WithAsyncMethod_WatchGameServer() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status WatchGameServer(::grpc::ServerContext* context, const ::stable::agones::dev::sdk::Empty* request, ::grpc::ServerWriter< ::stable::agones::dev::sdk::GameServer>* writer) final override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestWatchGameServer(::grpc::ServerContext* context, ::stable::agones::dev::sdk::Empty* request, ::grpc::ServerAsyncWriter< ::stable::agones::dev::sdk::GameServer>* writer, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncServerStreaming(4, context, request, writer, new_call_cq, notification_cq, tag); + } + }; + typedef WithAsyncMethod_Ready > > > > AsyncService; template class WithGenericMethod_Ready : public BaseClass { private: @@ -312,6 +360,23 @@ class SDK final { } }; template + class WithGenericMethod_WatchGameServer : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service *service) {} + public: + WithGenericMethod_WatchGameServer() { + ::grpc::Service::MarkMethodGeneric(4); + } + ~WithGenericMethod_WatchGameServer() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status WatchGameServer(::grpc::ServerContext* context, const ::stable::agones::dev::sdk::Empty* request, ::grpc::ServerWriter< ::stable::agones::dev::sdk::GameServer>* writer) final override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template class WithStreamedUnaryMethod_Ready : public BaseClass { private: void BaseClassMustBeDerivedFromService(const Service *service) {} @@ -372,8 +437,28 @@ class SDK final { virtual ::grpc::Status StreamedGetGameServer(::grpc::ServerContext* context, ::grpc::ServerUnaryStreamer< ::stable::agones::dev::sdk::Empty,::stable::agones::dev::sdk::GameServer>* server_unary_streamer) = 0; }; typedef WithStreamedUnaryMethod_Ready > > StreamedUnaryService; - typedef Service SplitStreamedService; - typedef WithStreamedUnaryMethod_Ready > > StreamedService; + template + class WithSplitStreamingMethod_WatchGameServer : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service *service) {} + public: + WithSplitStreamingMethod_WatchGameServer() { + ::grpc::Service::MarkMethodStreamed(4, + new ::grpc::internal::SplitServerStreamingHandler< ::stable::agones::dev::sdk::Empty, ::stable::agones::dev::sdk::GameServer>(std::bind(&WithSplitStreamingMethod_WatchGameServer::StreamedWatchGameServer, this, std::placeholders::_1, std::placeholders::_2))); + } + ~WithSplitStreamingMethod_WatchGameServer() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status WatchGameServer(::grpc::ServerContext* context, const ::stable::agones::dev::sdk::Empty* request, ::grpc::ServerWriter< ::stable::agones::dev::sdk::GameServer>* writer) final override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with split streamed + virtual ::grpc::Status StreamedWatchGameServer(::grpc::ServerContext* context, ::grpc::ServerSplitStreamer< ::stable::agones::dev::sdk::Empty,::stable::agones::dev::sdk::GameServer>* server_split_streamer) = 0; + }; + typedef WithSplitStreamingMethod_WatchGameServer SplitStreamedService; + typedef WithStreamedUnaryMethod_Ready > > > StreamedService; }; } // namespace sdk diff --git a/sdks/cpp/sdk.h b/sdks/cpp/sdk.h index e6a3a43e9e..7a5b522c40 100644 --- a/sdks/cpp/sdk.h +++ b/sdks/cpp/sdk.h @@ -42,6 +42,11 @@ namespace agones { // Marks the Game Server as ready to shutdown grpc::Status Shutdown(); + // Watch the GameServer configuration, and fire the callback + // when an update occurs. + // This is a blocking function, and as such you will likely want to run it inside a thread. + grpc::Status WatchGameServer(const std::function callback); + ~SDK(); private: diff --git a/sdks/cpp/sdk.pb.cc b/sdks/cpp/sdk.pb.cc index 44655e9088..76295107d2 100644 --- a/sdks/cpp/sdk.pb.cc +++ b/sdks/cpp/sdk.pb.cc @@ -432,7 +432,7 @@ void AddDescriptorsImpl() { "state\030\001 \001(\t\022\017\n\007address\030\002 \001(\t\022<\n\005ports\030\003 " "\003(\0132-.stable.agones.dev.sdk.GameServer.S" "tatus.Port\032\"\n\004Port\022\014\n\004name\030\001 \001(\t\022\014\n\004port" - "\030\002 \001(\0052\376\002\n\003SDK\022V\n\005Ready\022\034.stable.agones." + "\030\002 \001(\0052\357\003\n\003SDK\022V\n\005Ready\022\034.stable.agones." "dev.sdk.Empty\032\034.stable.agones.dev.sdk.Em" "pty\"\021\202\323\344\223\002\013\"\006/ready:\001*\022\\\n\010Shutdown\022\034.sta" "ble.agones.dev.sdk.Empty\032\034.stable.agones" @@ -441,11 +441,13 @@ void AddDescriptorsImpl() { "table.agones.dev.sdk.Empty\"\022\202\323\344\223\002\014\"\007/hea" "lth:\001*(\001\022e\n\rGetGameServer\022\034.stable.agone" "s.dev.sdk.Empty\032!.stable.agones.dev.sdk." - "GameServer\"\023\202\323\344\223\002\r\022\013/gameserverB\005Z\003sdkb\006" - "proto3" + "GameServer\"\023\202\323\344\223\002\r\022\013/gameserver\022o\n\017Watch" + "GameServer\022\034.stable.agones.dev.sdk.Empty" + "\032!.stable.agones.dev.sdk.GameServer\"\031\202\323\344" + "\223\002\023\022\021/watch/gameserver0\001B\005Z\003sdkb\006proto3" }; ::google::protobuf::DescriptorPool::InternalAddGeneratedFile( - descriptor, 1406); + descriptor, 1519); ::google::protobuf::MessageFactory::InternalRegisterGeneratedFile( "sdk.proto", &protobuf_RegisterTypes); ::protobuf_google_2fapi_2fannotations_2eproto::AddDescriptors(); diff --git a/sdks/go/sdk.go b/sdks/go/sdk.go index 5dd95a2f2a..76855feff0 100644 --- a/sdks/go/sdk.go +++ b/sdks/go/sdk.go @@ -23,10 +23,15 @@ import ( "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" + "io" ) const port = 59357 +// GameServerCallback is a function definition to be called +// when a GameServer CRD has been changed +type GameServerCallback func(gs *sdk.GameServer) + // SDK is an instance of the Agones SDK type SDK struct { client sdk.SDKClient @@ -39,7 +44,9 @@ type SDK struct { // Times out after 30 seconds. func NewSDK() (*SDK, error) { addr := fmt.Sprintf("localhost:%d", port) - s := &SDK{ctx: context.Background()} + s := &SDK{ + ctx: context.Background(), + } // block for at least 30 seconds ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) defer cancel() @@ -76,3 +83,33 @@ func (s *SDK) Health() error { func (s *SDK) GameServer() (*sdk.GameServer, error) { return s.client.GetGameServer(s.ctx, &sdk.Empty{}) } + +// WatchGameServer asynchronously calls the given GameServerCallback with the current GameServer +// configuration when the backing GameServer configuration is updated. +// This function can be called multiple times to add more than one GameServerCallback. +func (s *SDK) WatchGameServer(f GameServerCallback) error { + stream, err := s.client.WatchGameServer(s.ctx, &sdk.Empty{}) + if err != nil { + return errors.Wrap(err, "could not watch gameserver") + } + + go func() { + for { + var gs *sdk.GameServer + gs, err = stream.Recv() + if err != nil { + if err != io.EOF { + fmt.Printf("error watching GameServer: %s\n", err.Error()) + } else { + fmt.Println("gameserver event stream EOF received") + return + } + // This is to wait for the reconnection, and not peg the CPU at 100% + time.Sleep(time.Second) + continue + } + f(gs) + } + }() + return nil +} diff --git a/sdks/go/sdk_test.go b/sdks/go/sdk_test.go index 8858049112..b09c158c98 100644 --- a/sdks/go/sdk_test.go +++ b/sdks/go/sdk_test.go @@ -22,6 +22,7 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" + "time" ) func TestSDK(t *testing.T) { @@ -58,13 +59,47 @@ func TestSDK(t *testing.T) { assert.NotNil(t, gs) } +func TestSDKWatchGameServer(t *testing.T) { + sm := &sdkMock{ + wm: &watchMock{msgs: make(chan *sdk.GameServer, 5)}, + } + s := SDK{ + ctx: context.Background(), + client: sm, + } + + fixture := &sdk.GameServer{ObjectMeta: &sdk.GameServer_ObjectMeta{Name:"test-server"}} + + updated := make(chan struct{}, 5) + + err := s.WatchGameServer(func(gs *sdk.GameServer) { + assert.Equal(t, fixture.ObjectMeta.Name, gs.ObjectMeta.Name) + updated <- struct{}{} + }) + assert.Nil(t, err) + + sm.wm.msgs <- fixture + + select { + case <-updated: + case <-time.After(5 * time.Second): + assert.FailNow(t,"update handler should have fired") + } +} + var _ sdk.SDKClient = &sdkMock{} var _ sdk.SDK_HealthClient = &healthMock{} +var _ sdk.SDK_WatchGameServerClient = &watchMock{} type sdkMock struct { ready bool shutdown bool hm *healthMock + wm *watchMock +} + +func (m *sdkMock) WatchGameServer(ctx context.Context, in *sdk.Empty, opts ...grpc.CallOption) (sdk.SDK_WatchGameServerClient, error) { + return m.wm, nil } func (m *sdkMock) GetGameServer(ctx context.Context, in *sdk.Empty, opts ...grpc.CallOption) (*sdk.GameServer, error) { @@ -121,3 +156,35 @@ func (h *healthMock) SendMsg(m interface{}) error { func (h *healthMock) RecvMsg(m interface{}) error { panic("implement me") } + +type watchMock struct { + msgs chan *sdk.GameServer +} + +func (wm *watchMock) Recv() (*sdk.GameServer, error) { + return <- wm.msgs, nil +} + +func (*watchMock) Header() (metadata.MD, error) { + panic("implement me") +} + +func (*watchMock) Trailer() metadata.MD { + panic("implement me") +} + +func (*watchMock) CloseSend() error { + panic("implement me") +} + +func (*watchMock) Context() context.Context { + panic("implement me") +} + +func (*watchMock) SendMsg(m interface{}) error { + panic("implement me") +} + +func (*watchMock) RecvMsg(m interface{}) error { + panic("implement me") +} diff --git a/sdks/rust/src/grpc/sdk.rs b/sdks/rust/src/grpc/sdk.rs index 6c390f8791..3a9a4af01f 100644 --- a/sdks/rust/src/grpc/sdk.rs +++ b/sdks/rust/src/grpc/sdk.rs @@ -1836,7 +1836,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x18\n\x07address\x18\x02\x20\x01(\tR\x07address\x12C\n\x05ports\x18\x03\ \x20\x03(\x0b2-.stable.agones.dev.sdk.GameServer.Status.PortR\x05ports\ \x1a.\n\x04Port\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\x12\x12\n\ - \x04port\x18\x02\x20\x01(\x05R\x04port2\xfe\x02\n\x03SDK\x12V\n\x05Ready\ + \x04port\x18\x02\x20\x01(\x05R\x04port2\xef\x03\n\x03SDK\x12V\n\x05Ready\ \x12\x1c.stable.agones.dev.sdk.Empty\x1a\x1c.stable.agones.dev.sdk.Empty\ \"\x11\x82\xd3\xe4\x93\x02\x0b\"\x06/ready:\x01*\x12\\\n\x08Shutdown\x12\ \x1c.stable.agones.dev.sdk.Empty\x1a\x1c.stable.agones.dev.sdk.Empty\"\ @@ -1844,51 +1844,53 @@ static file_descriptor_proto_data: &'static [u8] = b"\ .stable.agones.dev.sdk.Empty\x1a\x1c.stable.agones.dev.sdk.Empty\"\x12\ \x82\xd3\xe4\x93\x02\x0c\"\x07/health:\x01*(\x01\x12e\n\rGetGameServer\ \x12\x1c.stable.agones.dev.sdk.Empty\x1a!.stable.agones.dev.sdk.GameServ\ - er\"\x13\x82\xd3\xe4\x93\x02\r\x12\x0b/gameserverB\x05Z\x03sdkJ\xfe\x1e\ - \n\x06\x12\x04\x0e\0c\x01\n\xd2\x04\n\x01\x0c\x12\x03\x0e\0\x122\xc7\x04\ - \x20Copyright\x202017\x20Google\x20Inc.\x20All\x20Rights\x20Reserved.\n\ - \n\x20Licensed\x20under\x20the\x20Apache\x20License,\x20Version\x202.0\ - \x20(the\x20\"License\");\n\x20you\x20may\x20not\x20use\x20this\x20file\ - \x20except\x20in\x20compliance\x20with\x20the\x20License.\n\x20You\x20ma\ - y\x20obtain\x20a\x20copy\x20of\x20the\x20License\x20at\n\n\x20\x20\x20\ - \x20\x20http://www.apache.org/licenses/LICENSE-2.0\n\n\x20Unless\x20requ\ - ired\x20by\x20applicable\x20law\x20or\x20agreed\x20to\x20in\x20writing,\ - \x20software\n\x20distributed\x20under\x20the\x20License\x20is\x20distri\ - buted\x20on\x20an\x20\"AS\x20IS\"\x20BASIS,\n\x20WITHOUT\x20WARRANTIES\ - \x20OR\x20CONDITIONS\x20OF\x20ANY\x20KIND,\x20either\x20express\x20or\ - \x20implied.\n\x20See\x20the\x20License\x20for\x20the\x20specific\x20lan\ - guage\x20governing\x20permissions\x20and\n\x20limitations\x20under\x20th\ - e\x20License.\n\n\x08\n\x01\x02\x12\x03\x10\x08\x1d\n\x08\n\x01\x08\x12\ - \x03\x11\0\x1a\n\x0b\n\x04\x08\xe7\x07\0\x12\x03\x11\0\x1a\n\x0c\n\x05\ - \x08\xe7\x07\0\x02\x12\x03\x11\x07\x11\n\r\n\x06\x08\xe7\x07\0\x02\0\x12\ - \x03\x11\x07\x11\n\x0e\n\x07\x08\xe7\x07\0\x02\0\x01\x12\x03\x11\x07\x11\ - \n\x0c\n\x05\x08\xe7\x07\0\x07\x12\x03\x11\x14\x19\n\t\n\x02\x03\0\x12\ - \x03\x13\x07%\nM\n\x02\x06\0\x12\x04\x16\02\x01\x1aA\x20SDK\x20service\ - \x20to\x20be\x20used\x20in\x20the\x20GameServer\x20SDK\x20to\x20the\x20P\ - od\x20Sidecar\n\n\n\n\x03\x06\0\x01\x12\x03\x16\x08\x0b\n1\n\x04\x06\0\ - \x02\0\x12\x04\x18\x04\x1d\x05\x1a#\x20Call\x20when\x20the\x20GameServer\ - \x20is\x20ready\n\n\x0c\n\x05\x06\0\x02\0\x01\x12\x03\x18\x08\r\n\x0c\n\ - \x05\x06\0\x02\0\x02\x12\x03\x18\x0f\x14\n\x0c\n\x05\x06\0\x02\0\x03\x12\ - \x03\x18\x1f$\n\r\n\x05\x06\0\x02\0\x04\x12\x04\x19\x08\x1c\n\n\x10\n\ - \x08\x06\0\x02\0\x04\xe7\x07\0\x12\x04\x19\x08\x1c\n\n\x10\n\t\x06\0\x02\ - \0\x04\xe7\x07\0\x02\x12\x03\x19\x0f\x20\n\x11\n\n\x06\0\x02\0\x04\xe7\ - \x07\0\x02\0\x12\x03\x19\x0f\x20\n\x12\n\x0b\x06\0\x02\0\x04\xe7\x07\0\ - \x02\0\x01\x12\x03\x19\x10\x1f\n\x11\n\t\x06\0\x02\0\x04\xe7\x07\0\x08\ - \x12\x04\x19#\x1c\t\n9\n\x04\x06\0\x02\x01\x12\x04\x1f\x04$\x05\x1a+\x20\ - Call\x20when\x20the\x20GameServer\x20is\x20shutting\x20down\n\n\x0c\n\ - \x05\x06\0\x02\x01\x01\x12\x03\x1f\x08\x10\n\x0c\n\x05\x06\0\x02\x01\x02\ - \x12\x03\x1f\x12\x17\n\x0c\n\x05\x06\0\x02\x01\x03\x12\x03\x1f\"'\n\r\n\ - \x05\x06\0\x02\x01\x04\x12\x04\x20\x08#\n\n\x10\n\x08\x06\0\x02\x01\x04\ - \xe7\x07\0\x12\x04\x20\x08#\n\n\x10\n\t\x06\0\x02\x01\x04\xe7\x07\0\x02\ - \x12\x03\x20\x0f\x20\n\x11\n\n\x06\0\x02\x01\x04\xe7\x07\0\x02\0\x12\x03\ - \x20\x0f\x20\n\x12\n\x0b\x06\0\x02\x01\x04\xe7\x07\0\x02\0\x01\x12\x03\ - \x20\x10\x1f\n\x11\n\t\x06\0\x02\x01\x04\xe7\x07\0\x08\x12\x04\x20##\t\n\ - W\n\x04\x06\0\x02\x02\x12\x04&\x04+\x05\x1aI\x20Send\x20a\x20Empty\x20ev\ - ery\x20d\x20Duration\x20to\x20declare\x20that\x20this\x20GameSever\x20is\ - \x20healthy\n\n\x0c\n\x05\x06\0\x02\x02\x01\x12\x03&\x08\x0e\n\x0c\n\x05\ - \x06\0\x02\x02\x05\x12\x03&\x10\x16\n\x0c\n\x05\x06\0\x02\x02\x02\x12\ - \x03&\x17\x1c\n\x0c\n\x05\x06\0\x02\x02\x03\x12\x03&',\n\r\n\x05\x06\0\ - \x02\x02\x04\x12\x04'\x08*\x12\n\x10\n\x08\x06\0\x02\x02\x04\xe7\x07\0\ + er\"\x13\x82\xd3\xe4\x93\x02\r\x12\x0b/gameserver\x12o\n\x0fWatchGameSer\ + ver\x12\x1c.stable.agones.dev.sdk.Empty\x1a!.stable.agones.dev.sdk.GameS\ + erver\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/watch/gameserver0\x01B\x05Z\ + \x03sdkJ\xef\x20\n\x06\x12\x04\x0e\0i\x01\n\xd2\x04\n\x01\x0c\x12\x03\ + \x0e\0\x122\xc7\x04\x20Copyright\x202017\x20Google\x20Inc.\x20All\x20Rig\ + hts\x20Reserved.\n\n\x20Licensed\x20under\x20the\x20Apache\x20License,\ + \x20Version\x202.0\x20(the\x20\"License\");\n\x20you\x20may\x20not\x20us\ + e\x20this\x20file\x20except\x20in\x20compliance\x20with\x20the\x20Licens\ + e.\n\x20You\x20may\x20obtain\x20a\x20copy\x20of\x20the\x20License\x20at\ + \n\n\x20\x20\x20\x20\x20http://www.apache.org/licenses/LICENSE-2.0\n\n\ + \x20Unless\x20required\x20by\x20applicable\x20law\x20or\x20agreed\x20to\ + \x20in\x20writing,\x20software\n\x20distributed\x20under\x20the\x20Licen\ + se\x20is\x20distributed\x20on\x20an\x20\"AS\x20IS\"\x20BASIS,\n\x20WITHO\ + UT\x20WARRANTIES\x20OR\x20CONDITIONS\x20OF\x20ANY\x20KIND,\x20either\x20\ + express\x20or\x20implied.\n\x20See\x20the\x20License\x20for\x20the\x20sp\ + ecific\x20language\x20governing\x20permissions\x20and\n\x20limitations\ + \x20under\x20the\x20License.\n\n\x08\n\x01\x02\x12\x03\x10\x08\x1d\n\x08\ + \n\x01\x08\x12\x03\x11\0\x1a\n\x0b\n\x04\x08\xe7\x07\0\x12\x03\x11\0\x1a\ + \n\x0c\n\x05\x08\xe7\x07\0\x02\x12\x03\x11\x07\x11\n\r\n\x06\x08\xe7\x07\ + \0\x02\0\x12\x03\x11\x07\x11\n\x0e\n\x07\x08\xe7\x07\0\x02\0\x01\x12\x03\ + \x11\x07\x11\n\x0c\n\x05\x08\xe7\x07\0\x07\x12\x03\x11\x14\x19\n\t\n\x02\ + \x03\0\x12\x03\x13\x07%\nM\n\x02\x06\0\x12\x04\x16\08\x01\x1aA\x20SDK\ + \x20service\x20to\x20be\x20used\x20in\x20the\x20GameServer\x20SDK\x20to\ + \x20the\x20Pod\x20Sidecar\n\n\n\n\x03\x06\0\x01\x12\x03\x16\x08\x0b\n1\n\ + \x04\x06\0\x02\0\x12\x04\x18\x04\x1d\x05\x1a#\x20Call\x20when\x20the\x20\ + GameServer\x20is\x20ready\n\n\x0c\n\x05\x06\0\x02\0\x01\x12\x03\x18\x08\ + \r\n\x0c\n\x05\x06\0\x02\0\x02\x12\x03\x18\x0f\x14\n\x0c\n\x05\x06\0\x02\ + \0\x03\x12\x03\x18\x1f$\n\r\n\x05\x06\0\x02\0\x04\x12\x04\x19\x08\x1c\n\ + \n\x10\n\x08\x06\0\x02\0\x04\xe7\x07\0\x12\x04\x19\x08\x1c\n\n\x10\n\t\ + \x06\0\x02\0\x04\xe7\x07\0\x02\x12\x03\x19\x0f\x20\n\x11\n\n\x06\0\x02\0\ + \x04\xe7\x07\0\x02\0\x12\x03\x19\x0f\x20\n\x12\n\x0b\x06\0\x02\0\x04\xe7\ + \x07\0\x02\0\x01\x12\x03\x19\x10\x1f\n\x11\n\t\x06\0\x02\0\x04\xe7\x07\0\ + \x08\x12\x04\x19#\x1c\t\n9\n\x04\x06\0\x02\x01\x12\x04\x1f\x04$\x05\x1a+\ + \x20Call\x20when\x20the\x20GameServer\x20is\x20shutting\x20down\n\n\x0c\ + \n\x05\x06\0\x02\x01\x01\x12\x03\x1f\x08\x10\n\x0c\n\x05\x06\0\x02\x01\ + \x02\x12\x03\x1f\x12\x17\n\x0c\n\x05\x06\0\x02\x01\x03\x12\x03\x1f\"'\n\ + \r\n\x05\x06\0\x02\x01\x04\x12\x04\x20\x08#\n\n\x10\n\x08\x06\0\x02\x01\ + \x04\xe7\x07\0\x12\x04\x20\x08#\n\n\x10\n\t\x06\0\x02\x01\x04\xe7\x07\0\ + \x02\x12\x03\x20\x0f\x20\n\x11\n\n\x06\0\x02\x01\x04\xe7\x07\0\x02\0\x12\ + \x03\x20\x0f\x20\n\x12\n\x0b\x06\0\x02\x01\x04\xe7\x07\0\x02\0\x01\x12\ + \x03\x20\x10\x1f\n\x11\n\t\x06\0\x02\x01\x04\xe7\x07\0\x08\x12\x04\x20##\ + \t\nW\n\x04\x06\0\x02\x02\x12\x04&\x04+\x05\x1aI\x20Send\x20a\x20Empty\ + \x20every\x20d\x20Duration\x20to\x20declare\x20that\x20this\x20GameSever\ + \x20is\x20healthy\n\n\x0c\n\x05\x06\0\x02\x02\x01\x12\x03&\x08\x0e\n\x0c\ + \n\x05\x06\0\x02\x02\x05\x12\x03&\x10\x16\n\x0c\n\x05\x06\0\x02\x02\x02\ + \x12\x03&\x17\x1c\n\x0c\n\x05\x06\0\x02\x02\x03\x12\x03&',\n\r\n\x05\x06\ + \0\x02\x02\x04\x12\x04'\x08*\x12\n\x10\n\x08\x06\0\x02\x02\x04\xe7\x07\0\ \x12\x04'\x08*\x12\n\x10\n\t\x06\0\x02\x02\x04\xe7\x07\0\x02\x12\x03'\ \x0f\x20\n\x11\n\n\x06\0\x02\x02\x04\xe7\x07\0\x02\0\x12\x03'\x0f\x20\n\ \x12\n\x0b\x06\0\x02\x02\x04\xe7\x07\0\x02\0\x01\x12\x03'\x10\x1f\n\x11\ @@ -1900,107 +1902,116 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x02\x03\x04\xe7\x07\0\x12\x04.\x080\n\n\x10\n\t\x06\0\x02\x03\x04\xe7\ \x07\0\x02\x12\x03.\x0f\x20\n\x11\n\n\x06\0\x02\x03\x04\xe7\x07\0\x02\0\ \x12\x03.\x0f\x20\n\x12\n\x0b\x06\0\x02\x03\x04\xe7\x07\0\x02\0\x01\x12\ - \x03.\x10\x1f\n\x11\n\t\x06\0\x02\x03\x04\xe7\x07\0\x08\x12\x04.#0\t\n\n\ - \n\x02\x04\0\x12\x044\05\x01\n\n\n\x03\x04\0\x01\x12\x034\x08\r\n\xa2\ - \x01\n\x02\x04\x01\x12\x04:\0c\x01\x1a\x95\x01\x20A\x20GameServer\x20Cus\ - tom\x20Resource\x20Definition\x20object\n\x20We\x20will\x20only\x20expor\ - t\x20those\x20resources\x20that\x20make\x20the\x20most\n\x20sense.\x20Ca\ - n\x20always\x20expand\x20to\x20more\x20as\x20needed.\n\n\n\n\x03\x04\x01\ - \x01\x12\x03:\x08\x12\n\x0b\n\x04\x04\x01\x02\0\x12\x03;\x04\x1f\n\r\n\ - \x05\x04\x01\x02\0\x04\x12\x04;\x04:\x14\n\x0c\n\x05\x04\x01\x02\0\x06\ - \x12\x03;\x04\x0e\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03;\x0f\x1a\n\x0c\n\ - \x05\x04\x01\x02\0\x03\x12\x03;\x1d\x1e\n\x0b\n\x04\x04\x01\x02\x01\x12\ - \x03<\x04\x12\n\r\n\x05\x04\x01\x02\x01\x04\x12\x04<\x04;\x1f\n\x0c\n\ - \x05\x04\x01\x02\x01\x06\x12\x03<\x04\x08\n\x0c\n\x05\x04\x01\x02\x01\ - \x01\x12\x03<\t\r\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03<\x10\x11\n\x0b\ - \n\x04\x04\x01\x02\x02\x12\x03=\x04\x16\n\r\n\x05\x04\x01\x02\x02\x04\ - \x12\x04=\x04<\x12\n\x0c\n\x05\x04\x01\x02\x02\x06\x12\x03=\x04\n\n\x0c\ - \n\x05\x04\x01\x02\x02\x01\x12\x03=\x0b\x11\n\x0c\n\x05\x04\x01\x02\x02\ - \x03\x12\x03=\x14\x15\n=\n\x04\x04\x01\x03\0\x12\x04@\x04L\x05\x1a/\x20r\ - epresentation\x20of\x20the\x20K8s\x20ObjectMeta\x20resource\n\n\x0c\n\ - \x05\x04\x01\x03\0\x01\x12\x03@\x0c\x16\n\r\n\x06\x04\x01\x03\0\x02\0\ - \x12\x03A\x08\x18\n\x0f\n\x07\x04\x01\x03\0\x02\0\x04\x12\x04A\x08@\x18\ - \n\x0e\n\x07\x04\x01\x03\0\x02\0\x05\x12\x03A\x08\x0e\n\x0e\n\x07\x04\ - \x01\x03\0\x02\0\x01\x12\x03A\x0f\x13\n\x0e\n\x07\x04\x01\x03\0\x02\0\ - \x03\x12\x03A\x16\x17\n\r\n\x06\x04\x01\x03\0\x02\x01\x12\x03B\x08\x1d\n\ - \x0f\n\x07\x04\x01\x03\0\x02\x01\x04\x12\x04B\x08A\x18\n\x0e\n\x07\x04\ - \x01\x03\0\x02\x01\x05\x12\x03B\x08\x0e\n\x0e\n\x07\x04\x01\x03\0\x02\ - \x01\x01\x12\x03B\x0f\x18\n\x0e\n\x07\x04\x01\x03\0\x02\x01\x03\x12\x03B\ - \x1b\x1c\n\r\n\x06\x04\x01\x03\0\x02\x02\x12\x03C\x08\x17\n\x0f\n\x07\ - \x04\x01\x03\0\x02\x02\x04\x12\x04C\x08B\x1d\n\x0e\n\x07\x04\x01\x03\0\ - \x02\x02\x05\x12\x03C\x08\x0e\n\x0e\n\x07\x04\x01\x03\0\x02\x02\x01\x12\ - \x03C\x0f\x12\n\x0e\n\x07\x04\x01\x03\0\x02\x02\x03\x12\x03C\x15\x16\n\r\ - \n\x06\x04\x01\x03\0\x02\x03\x12\x03D\x08$\n\x0f\n\x07\x04\x01\x03\0\x02\ - \x03\x04\x12\x04D\x08C\x17\n\x0e\n\x07\x04\x01\x03\0\x02\x03\x05\x12\x03\ - D\x08\x0e\n\x0e\n\x07\x04\x01\x03\0\x02\x03\x01\x12\x03D\x0f\x1f\n\x0e\n\ - \x07\x04\x01\x03\0\x02\x03\x03\x12\x03D\"#\n\r\n\x06\x04\x01\x03\0\x02\ - \x04\x12\x03E\x08\x1d\n\x0f\n\x07\x04\x01\x03\0\x02\x04\x04\x12\x04E\x08\ - D$\n\x0e\n\x07\x04\x01\x03\0\x02\x04\x05\x12\x03E\x08\r\n\x0e\n\x07\x04\ - \x01\x03\0\x02\x04\x01\x12\x03E\x0e\x18\n\x0e\n\x07\x04\x01\x03\0\x02\ - \x04\x03\x12\x03E\x1b\x1c\n<\n\x06\x04\x01\x03\0\x02\x05\x12\x03G\x08%\ - \x1a-\x20timestamp\x20is\x20in\x20Epoch\x20format,\x20unit:\x20seconds\n\ - \n\x0f\n\x07\x04\x01\x03\0\x02\x05\x04\x12\x04G\x08E\x1d\n\x0e\n\x07\x04\ - \x01\x03\0\x02\x05\x05\x12\x03G\x08\r\n\x0e\n\x07\x04\x01\x03\0\x02\x05\ - \x01\x12\x03G\x0e\x20\n\x0e\n\x07\x04\x01\x03\0\x02\x05\x03\x12\x03G#$\n\ - K\n\x06\x04\x01\x03\0\x02\x06\x12\x03I\x08%\x1a<\x20optional\x20deletion\ - \x20timestamp\x20in\x20Epoch\x20format,\x20unit:\x20seconds\n\n\x0f\n\ - \x07\x04\x01\x03\0\x02\x06\x04\x12\x04I\x08G%\n\x0e\n\x07\x04\x01\x03\0\ - \x02\x06\x05\x12\x03I\x08\r\n\x0e\n\x07\x04\x01\x03\0\x02\x06\x01\x12\ - \x03I\x0e\x20\n\x0e\n\x07\x04\x01\x03\0\x02\x06\x03\x12\x03I#$\n\r\n\x06\ - \x04\x01\x03\0\x02\x07\x12\x03J\x08,\n\x0f\n\x07\x04\x01\x03\0\x02\x07\ - \x04\x12\x04J\x08I%\n\x0e\n\x07\x04\x01\x03\0\x02\x07\x06\x12\x03J\x08\ - \x1b\n\x0e\n\x07\x04\x01\x03\0\x02\x07\x01\x12\x03J\x1c'\n\x0e\n\x07\x04\ - \x01\x03\0\x02\x07\x03\x12\x03J*+\n\r\n\x06\x04\x01\x03\0\x02\x08\x12\ - \x03K\x08'\n\x0f\n\x07\x04\x01\x03\0\x02\x08\x04\x12\x04K\x08J,\n\x0e\n\ - \x07\x04\x01\x03\0\x02\x08\x06\x12\x03K\x08\x1b\n\x0e\n\x07\x04\x01\x03\ - \0\x02\x08\x01\x12\x03K\x1c\"\n\x0e\n\x07\x04\x01\x03\0\x02\x08\x03\x12\ - \x03K%&\n\x0c\n\x04\x04\x01\x03\x01\x12\x04N\x04W\x05\n\x0c\n\x05\x04\ - \x01\x03\x01\x01\x12\x03N\x0c\x10\n\r\n\x06\x04\x01\x03\x01\x02\0\x12\ - \x03O\x08\x1a\n\x0f\n\x07\x04\x01\x03\x01\x02\0\x04\x12\x04O\x08N\x12\n\ - \x0e\n\x07\x04\x01\x03\x01\x02\0\x06\x12\x03O\x08\x0e\n\x0e\n\x07\x04\ - \x01\x03\x01\x02\0\x01\x12\x03O\x0f\x15\n\x0e\n\x07\x04\x01\x03\x01\x02\ - \0\x03\x12\x03O\x18\x19\n\x0e\n\x06\x04\x01\x03\x01\x03\0\x12\x04Q\x08V\ - \t\n\x0e\n\x07\x04\x01\x03\x01\x03\0\x01\x12\x03Q\x10\x16\n\x0f\n\x08\ - \x04\x01\x03\x01\x03\0\x02\0\x12\x03R\x0c\x1e\n\x11\n\t\x04\x01\x03\x01\ - \x03\0\x02\0\x04\x12\x04R\x0cQ\x18\n\x10\n\t\x04\x01\x03\x01\x03\0\x02\0\ - \x05\x12\x03R\x0c\x10\n\x10\n\t\x04\x01\x03\x01\x03\0\x02\0\x01\x12\x03R\ - \x11\x19\n\x10\n\t\x04\x01\x03\x01\x03\0\x02\0\x03\x12\x03R\x1c\x1d\n\ - \x0f\n\x08\x04\x01\x03\x01\x03\0\x02\x01\x12\x03S\x0c$\n\x11\n\t\x04\x01\ - \x03\x01\x03\0\x02\x01\x04\x12\x04S\x0cR\x1e\n\x10\n\t\x04\x01\x03\x01\ - \x03\0\x02\x01\x05\x12\x03S\x0c\x11\n\x10\n\t\x04\x01\x03\x01\x03\0\x02\ - \x01\x01\x12\x03S\x12\x1f\n\x10\n\t\x04\x01\x03\x01\x03\0\x02\x01\x03\ - \x12\x03S\"#\n\x0f\n\x08\x04\x01\x03\x01\x03\0\x02\x02\x12\x03T\x0c'\n\ - \x11\n\t\x04\x01\x03\x01\x03\0\x02\x02\x04\x12\x04T\x0cS$\n\x10\n\t\x04\ - \x01\x03\x01\x03\0\x02\x02\x05\x12\x03T\x0c\x11\n\x10\n\t\x04\x01\x03\ - \x01\x03\0\x02\x02\x01\x12\x03T\x12\"\n\x10\n\t\x04\x01\x03\x01\x03\0\ - \x02\x02\x03\x12\x03T%&\n\x0f\n\x08\x04\x01\x03\x01\x03\0\x02\x03\x12\ - \x03U\x0c*\n\x11\n\t\x04\x01\x03\x01\x03\0\x02\x03\x04\x12\x04U\x0cT'\n\ - \x10\n\t\x04\x01\x03\x01\x03\0\x02\x03\x05\x12\x03U\x0c\x11\n\x10\n\t\ - \x04\x01\x03\x01\x03\0\x02\x03\x01\x12\x03U\x12%\n\x10\n\t\x04\x01\x03\ - \x01\x03\0\x02\x03\x03\x12\x03U()\n\x0c\n\x04\x04\x01\x03\x02\x12\x04Y\ - \x04b\x05\n\x0c\n\x05\x04\x01\x03\x02\x01\x12\x03Y\x0c\x12\n\x0e\n\x06\ - \x04\x01\x03\x02\x03\0\x12\x04Z\x08]\t\n\x0e\n\x07\x04\x01\x03\x02\x03\0\ - \x01\x12\x03Z\x10\x14\n\x0f\n\x08\x04\x01\x03\x02\x03\0\x02\0\x12\x03[\ - \x0c\x1c\n\x11\n\t\x04\x01\x03\x02\x03\0\x02\0\x04\x12\x04[\x0cZ\x16\n\ - \x10\n\t\x04\x01\x03\x02\x03\0\x02\0\x05\x12\x03[\x0c\x12\n\x10\n\t\x04\ - \x01\x03\x02\x03\0\x02\0\x01\x12\x03[\x13\x17\n\x10\n\t\x04\x01\x03\x02\ - \x03\0\x02\0\x03\x12\x03[\x1a\x1b\n\x0f\n\x08\x04\x01\x03\x02\x03\0\x02\ - \x01\x12\x03\\\x0c\x1b\n\x11\n\t\x04\x01\x03\x02\x03\0\x02\x01\x04\x12\ - \x04\\\x0c[\x1c\n\x10\n\t\x04\x01\x03\x02\x03\0\x02\x01\x05\x12\x03\\\ - \x0c\x11\n\x10\n\t\x04\x01\x03\x02\x03\0\x02\x01\x01\x12\x03\\\x12\x16\n\ - \x10\n\t\x04\x01\x03\x02\x03\0\x02\x01\x03\x12\x03\\\x19\x1a\n\r\n\x06\ - \x04\x01\x03\x02\x02\0\x12\x03_\x08\x19\n\x0f\n\x07\x04\x01\x03\x02\x02\ - \0\x04\x12\x04_\x08]\t\n\x0e\n\x07\x04\x01\x03\x02\x02\0\x05\x12\x03_\ - \x08\x0e\n\x0e\n\x07\x04\x01\x03\x02\x02\0\x01\x12\x03_\x0f\x14\n\x0e\n\ - \x07\x04\x01\x03\x02\x02\0\x03\x12\x03_\x17\x18\n\r\n\x06\x04\x01\x03\ - \x02\x02\x01\x12\x03`\x08\x1b\n\x0f\n\x07\x04\x01\x03\x02\x02\x01\x04\ - \x12\x04`\x08_\x19\n\x0e\n\x07\x04\x01\x03\x02\x02\x01\x05\x12\x03`\x08\ - \x0e\n\x0e\n\x07\x04\x01\x03\x02\x02\x01\x01\x12\x03`\x0f\x16\n\x0e\n\ - \x07\x04\x01\x03\x02\x02\x01\x03\x12\x03`\x19\x1a\n\r\n\x06\x04\x01\x03\ - \x02\x02\x02\x12\x03a\x08\x20\n\x0e\n\x07\x04\x01\x03\x02\x02\x02\x04\ - \x12\x03a\x08\x10\n\x0e\n\x07\x04\x01\x03\x02\x02\x02\x06\x12\x03a\x11\ - \x15\n\x0e\n\x07\x04\x01\x03\x02\x02\x02\x01\x12\x03a\x16\x1b\n\x0e\n\ - \x07\x04\x01\x03\x02\x02\x02\x03\x12\x03a\x1e\x1fb\x06proto3\ + \x03.\x10\x1f\n\x11\n\t\x06\0\x02\x03\x04\xe7\x07\0\x08\x12\x04.#0\t\nJ\ + \n\x04\x06\0\x02\x04\x12\x043\x047\x05\x1a<\x20Send\x20GameServer\x20det\ + ails\x20whenever\x20the\x20GameServer\x20is\x20updated\n\n\x0c\n\x05\x06\ + \0\x02\x04\x01\x12\x033\x08\x17\n\x0c\n\x05\x06\0\x02\x04\x02\x12\x033\ + \x19\x1e\n\x0c\n\x05\x06\0\x02\x04\x06\x12\x033)/\n\x0c\n\x05\x06\0\x02\ + \x04\x03\x12\x0330:\n\r\n\x05\x06\0\x02\x04\x04\x12\x044\x086\n\n\x10\n\ + \x08\x06\0\x02\x04\x04\xe7\x07\0\x12\x044\x086\n\n\x10\n\t\x06\0\x02\x04\ + \x04\xe7\x07\0\x02\x12\x034\x0f\x20\n\x11\n\n\x06\0\x02\x04\x04\xe7\x07\ + \0\x02\0\x12\x034\x0f\x20\n\x12\n\x0b\x06\0\x02\x04\x04\xe7\x07\0\x02\0\ + \x01\x12\x034\x10\x1f\n\x11\n\t\x06\0\x02\x04\x04\xe7\x07\0\x08\x12\x044\ + #6\t\n\n\n\x02\x04\0\x12\x04:\0;\x01\n\n\n\x03\x04\0\x01\x12\x03:\x08\r\ + \n\xa2\x01\n\x02\x04\x01\x12\x04@\0i\x01\x1a\x95\x01\x20A\x20GameServer\ + \x20Custom\x20Resource\x20Definition\x20object\n\x20We\x20will\x20only\ + \x20export\x20those\x20resources\x20that\x20make\x20the\x20most\n\x20sen\ + se.\x20Can\x20always\x20expand\x20to\x20more\x20as\x20needed.\n\n\n\n\ + \x03\x04\x01\x01\x12\x03@\x08\x12\n\x0b\n\x04\x04\x01\x02\0\x12\x03A\x04\ + \x1f\n\r\n\x05\x04\x01\x02\0\x04\x12\x04A\x04@\x14\n\x0c\n\x05\x04\x01\ + \x02\0\x06\x12\x03A\x04\x0e\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03A\x0f\ + \x1a\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03A\x1d\x1e\n\x0b\n\x04\x04\x01\ + \x02\x01\x12\x03B\x04\x12\n\r\n\x05\x04\x01\x02\x01\x04\x12\x04B\x04A\ + \x1f\n\x0c\n\x05\x04\x01\x02\x01\x06\x12\x03B\x04\x08\n\x0c\n\x05\x04\ + \x01\x02\x01\x01\x12\x03B\t\r\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03B\ + \x10\x11\n\x0b\n\x04\x04\x01\x02\x02\x12\x03C\x04\x16\n\r\n\x05\x04\x01\ + \x02\x02\x04\x12\x04C\x04B\x12\n\x0c\n\x05\x04\x01\x02\x02\x06\x12\x03C\ + \x04\n\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03C\x0b\x11\n\x0c\n\x05\x04\ + \x01\x02\x02\x03\x12\x03C\x14\x15\n=\n\x04\x04\x01\x03\0\x12\x04F\x04R\ + \x05\x1a/\x20representation\x20of\x20the\x20K8s\x20ObjectMeta\x20resourc\ + e\n\n\x0c\n\x05\x04\x01\x03\0\x01\x12\x03F\x0c\x16\n\r\n\x06\x04\x01\x03\ + \0\x02\0\x12\x03G\x08\x18\n\x0f\n\x07\x04\x01\x03\0\x02\0\x04\x12\x04G\ + \x08F\x18\n\x0e\n\x07\x04\x01\x03\0\x02\0\x05\x12\x03G\x08\x0e\n\x0e\n\ + \x07\x04\x01\x03\0\x02\0\x01\x12\x03G\x0f\x13\n\x0e\n\x07\x04\x01\x03\0\ + \x02\0\x03\x12\x03G\x16\x17\n\r\n\x06\x04\x01\x03\0\x02\x01\x12\x03H\x08\ + \x1d\n\x0f\n\x07\x04\x01\x03\0\x02\x01\x04\x12\x04H\x08G\x18\n\x0e\n\x07\ + \x04\x01\x03\0\x02\x01\x05\x12\x03H\x08\x0e\n\x0e\n\x07\x04\x01\x03\0\ + \x02\x01\x01\x12\x03H\x0f\x18\n\x0e\n\x07\x04\x01\x03\0\x02\x01\x03\x12\ + \x03H\x1b\x1c\n\r\n\x06\x04\x01\x03\0\x02\x02\x12\x03I\x08\x17\n\x0f\n\ + \x07\x04\x01\x03\0\x02\x02\x04\x12\x04I\x08H\x1d\n\x0e\n\x07\x04\x01\x03\ + \0\x02\x02\x05\x12\x03I\x08\x0e\n\x0e\n\x07\x04\x01\x03\0\x02\x02\x01\ + \x12\x03I\x0f\x12\n\x0e\n\x07\x04\x01\x03\0\x02\x02\x03\x12\x03I\x15\x16\ + \n\r\n\x06\x04\x01\x03\0\x02\x03\x12\x03J\x08$\n\x0f\n\x07\x04\x01\x03\0\ + \x02\x03\x04\x12\x04J\x08I\x17\n\x0e\n\x07\x04\x01\x03\0\x02\x03\x05\x12\ + \x03J\x08\x0e\n\x0e\n\x07\x04\x01\x03\0\x02\x03\x01\x12\x03J\x0f\x1f\n\ + \x0e\n\x07\x04\x01\x03\0\x02\x03\x03\x12\x03J\"#\n\r\n\x06\x04\x01\x03\0\ + \x02\x04\x12\x03K\x08\x1d\n\x0f\n\x07\x04\x01\x03\0\x02\x04\x04\x12\x04K\ + \x08J$\n\x0e\n\x07\x04\x01\x03\0\x02\x04\x05\x12\x03K\x08\r\n\x0e\n\x07\ + \x04\x01\x03\0\x02\x04\x01\x12\x03K\x0e\x18\n\x0e\n\x07\x04\x01\x03\0\ + \x02\x04\x03\x12\x03K\x1b\x1c\n<\n\x06\x04\x01\x03\0\x02\x05\x12\x03M\ + \x08%\x1a-\x20timestamp\x20is\x20in\x20Epoch\x20format,\x20unit:\x20seco\ + nds\n\n\x0f\n\x07\x04\x01\x03\0\x02\x05\x04\x12\x04M\x08K\x1d\n\x0e\n\ + \x07\x04\x01\x03\0\x02\x05\x05\x12\x03M\x08\r\n\x0e\n\x07\x04\x01\x03\0\ + \x02\x05\x01\x12\x03M\x0e\x20\n\x0e\n\x07\x04\x01\x03\0\x02\x05\x03\x12\ + \x03M#$\nK\n\x06\x04\x01\x03\0\x02\x06\x12\x03O\x08%\x1a<\x20optional\ + \x20deletion\x20timestamp\x20in\x20Epoch\x20format,\x20unit:\x20seconds\ + \n\n\x0f\n\x07\x04\x01\x03\0\x02\x06\x04\x12\x04O\x08M%\n\x0e\n\x07\x04\ + \x01\x03\0\x02\x06\x05\x12\x03O\x08\r\n\x0e\n\x07\x04\x01\x03\0\x02\x06\ + \x01\x12\x03O\x0e\x20\n\x0e\n\x07\x04\x01\x03\0\x02\x06\x03\x12\x03O#$\n\ + \r\n\x06\x04\x01\x03\0\x02\x07\x12\x03P\x08,\n\x0f\n\x07\x04\x01\x03\0\ + \x02\x07\x04\x12\x04P\x08O%\n\x0e\n\x07\x04\x01\x03\0\x02\x07\x06\x12\ + \x03P\x08\x1b\n\x0e\n\x07\x04\x01\x03\0\x02\x07\x01\x12\x03P\x1c'\n\x0e\ + \n\x07\x04\x01\x03\0\x02\x07\x03\x12\x03P*+\n\r\n\x06\x04\x01\x03\0\x02\ + \x08\x12\x03Q\x08'\n\x0f\n\x07\x04\x01\x03\0\x02\x08\x04\x12\x04Q\x08P,\ + \n\x0e\n\x07\x04\x01\x03\0\x02\x08\x06\x12\x03Q\x08\x1b\n\x0e\n\x07\x04\ + \x01\x03\0\x02\x08\x01\x12\x03Q\x1c\"\n\x0e\n\x07\x04\x01\x03\0\x02\x08\ + \x03\x12\x03Q%&\n\x0c\n\x04\x04\x01\x03\x01\x12\x04T\x04]\x05\n\x0c\n\ + \x05\x04\x01\x03\x01\x01\x12\x03T\x0c\x10\n\r\n\x06\x04\x01\x03\x01\x02\ + \0\x12\x03U\x08\x1a\n\x0f\n\x07\x04\x01\x03\x01\x02\0\x04\x12\x04U\x08T\ + \x12\n\x0e\n\x07\x04\x01\x03\x01\x02\0\x06\x12\x03U\x08\x0e\n\x0e\n\x07\ + \x04\x01\x03\x01\x02\0\x01\x12\x03U\x0f\x15\n\x0e\n\x07\x04\x01\x03\x01\ + \x02\0\x03\x12\x03U\x18\x19\n\x0e\n\x06\x04\x01\x03\x01\x03\0\x12\x04W\ + \x08\\\t\n\x0e\n\x07\x04\x01\x03\x01\x03\0\x01\x12\x03W\x10\x16\n\x0f\n\ + \x08\x04\x01\x03\x01\x03\0\x02\0\x12\x03X\x0c\x1e\n\x11\n\t\x04\x01\x03\ + \x01\x03\0\x02\0\x04\x12\x04X\x0cW\x18\n\x10\n\t\x04\x01\x03\x01\x03\0\ + \x02\0\x05\x12\x03X\x0c\x10\n\x10\n\t\x04\x01\x03\x01\x03\0\x02\0\x01\ + \x12\x03X\x11\x19\n\x10\n\t\x04\x01\x03\x01\x03\0\x02\0\x03\x12\x03X\x1c\ + \x1d\n\x0f\n\x08\x04\x01\x03\x01\x03\0\x02\x01\x12\x03Y\x0c$\n\x11\n\t\ + \x04\x01\x03\x01\x03\0\x02\x01\x04\x12\x04Y\x0cX\x1e\n\x10\n\t\x04\x01\ + \x03\x01\x03\0\x02\x01\x05\x12\x03Y\x0c\x11\n\x10\n\t\x04\x01\x03\x01\ + \x03\0\x02\x01\x01\x12\x03Y\x12\x1f\n\x10\n\t\x04\x01\x03\x01\x03\0\x02\ + \x01\x03\x12\x03Y\"#\n\x0f\n\x08\x04\x01\x03\x01\x03\0\x02\x02\x12\x03Z\ + \x0c'\n\x11\n\t\x04\x01\x03\x01\x03\0\x02\x02\x04\x12\x04Z\x0cY$\n\x10\n\ + \t\x04\x01\x03\x01\x03\0\x02\x02\x05\x12\x03Z\x0c\x11\n\x10\n\t\x04\x01\ + \x03\x01\x03\0\x02\x02\x01\x12\x03Z\x12\"\n\x10\n\t\x04\x01\x03\x01\x03\ + \0\x02\x02\x03\x12\x03Z%&\n\x0f\n\x08\x04\x01\x03\x01\x03\0\x02\x03\x12\ + \x03[\x0c*\n\x11\n\t\x04\x01\x03\x01\x03\0\x02\x03\x04\x12\x04[\x0cZ'\n\ + \x10\n\t\x04\x01\x03\x01\x03\0\x02\x03\x05\x12\x03[\x0c\x11\n\x10\n\t\ + \x04\x01\x03\x01\x03\0\x02\x03\x01\x12\x03[\x12%\n\x10\n\t\x04\x01\x03\ + \x01\x03\0\x02\x03\x03\x12\x03[()\n\x0c\n\x04\x04\x01\x03\x02\x12\x04_\ + \x04h\x05\n\x0c\n\x05\x04\x01\x03\x02\x01\x12\x03_\x0c\x12\n\x0e\n\x06\ + \x04\x01\x03\x02\x03\0\x12\x04`\x08c\t\n\x0e\n\x07\x04\x01\x03\x02\x03\0\ + \x01\x12\x03`\x10\x14\n\x0f\n\x08\x04\x01\x03\x02\x03\0\x02\0\x12\x03a\ + \x0c\x1c\n\x11\n\t\x04\x01\x03\x02\x03\0\x02\0\x04\x12\x04a\x0c`\x16\n\ + \x10\n\t\x04\x01\x03\x02\x03\0\x02\0\x05\x12\x03a\x0c\x12\n\x10\n\t\x04\ + \x01\x03\x02\x03\0\x02\0\x01\x12\x03a\x13\x17\n\x10\n\t\x04\x01\x03\x02\ + \x03\0\x02\0\x03\x12\x03a\x1a\x1b\n\x0f\n\x08\x04\x01\x03\x02\x03\0\x02\ + \x01\x12\x03b\x0c\x1b\n\x11\n\t\x04\x01\x03\x02\x03\0\x02\x01\x04\x12\ + \x04b\x0ca\x1c\n\x10\n\t\x04\x01\x03\x02\x03\0\x02\x01\x05\x12\x03b\x0c\ + \x11\n\x10\n\t\x04\x01\x03\x02\x03\0\x02\x01\x01\x12\x03b\x12\x16\n\x10\ + \n\t\x04\x01\x03\x02\x03\0\x02\x01\x03\x12\x03b\x19\x1a\n\r\n\x06\x04\ + \x01\x03\x02\x02\0\x12\x03e\x08\x19\n\x0f\n\x07\x04\x01\x03\x02\x02\0\ + \x04\x12\x04e\x08c\t\n\x0e\n\x07\x04\x01\x03\x02\x02\0\x05\x12\x03e\x08\ + \x0e\n\x0e\n\x07\x04\x01\x03\x02\x02\0\x01\x12\x03e\x0f\x14\n\x0e\n\x07\ + \x04\x01\x03\x02\x02\0\x03\x12\x03e\x17\x18\n\r\n\x06\x04\x01\x03\x02\ + \x02\x01\x12\x03f\x08\x1b\n\x0f\n\x07\x04\x01\x03\x02\x02\x01\x04\x12\ + \x04f\x08e\x19\n\x0e\n\x07\x04\x01\x03\x02\x02\x01\x05\x12\x03f\x08\x0e\ + \n\x0e\n\x07\x04\x01\x03\x02\x02\x01\x01\x12\x03f\x0f\x16\n\x0e\n\x07\ + \x04\x01\x03\x02\x02\x01\x03\x12\x03f\x19\x1a\n\r\n\x06\x04\x01\x03\x02\ + \x02\x02\x12\x03g\x08\x20\n\x0e\n\x07\x04\x01\x03\x02\x02\x02\x04\x12\ + \x03g\x08\x10\n\x0e\n\x07\x04\x01\x03\x02\x02\x02\x06\x12\x03g\x11\x15\n\ + \x0e\n\x07\x04\x01\x03\x02\x02\x02\x01\x12\x03g\x16\x1b\n\x0e\n\x07\x04\ + \x01\x03\x02\x02\x02\x03\x12\x03g\x1e\x1fb\x06proto3\ "; static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy { diff --git a/sdks/rust/src/grpc/sdk_grpc.rs b/sdks/rust/src/grpc/sdk_grpc.rs index fe38d9c8ef..c349f091ab 100644 --- a/sdks/rust/src/grpc/sdk_grpc.rs +++ b/sdks/rust/src/grpc/sdk_grpc.rs @@ -61,6 +61,13 @@ const METHOD_SDK_GET_GAME_SERVER: ::grpcio::Method = ::grpcio::Method { + ty: ::grpcio::MethodType::ServerStreaming, + name: "/stable.agones.dev.sdk.SDK/WatchGameServer", + req_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de }, + resp_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de }, +}; + pub struct SdkClient { client: ::grpcio::Client, } @@ -127,6 +134,14 @@ impl SdkClient { pub fn get_game_server_async(&self, req: &super::sdk::Empty) -> ::grpcio::Result<::grpcio::ClientUnaryReceiver> { self.get_game_server_async_opt(req, ::grpcio::CallOption::default()) } + + pub fn watch_game_server_opt(&self, req: &super::sdk::Empty, opt: ::grpcio::CallOption) -> ::grpcio::Result<::grpcio::ClientSStreamReceiver> { + self.client.server_streaming(&METHOD_SDK_WATCH_GAME_SERVER, req, opt) + } + + pub fn watch_game_server(&self, req: &super::sdk::Empty) -> ::grpcio::Result<::grpcio::ClientSStreamReceiver> { + self.watch_game_server_opt(req, ::grpcio::CallOption::default()) + } pub fn spawn(&self, f: F) where F: ::futures::Future + Send + 'static { self.client.spawn(f) } @@ -137,6 +152,7 @@ pub trait Sdk { fn shutdown(&self, ctx: ::grpcio::RpcContext, req: super::sdk::Empty, sink: ::grpcio::UnarySink); fn health(&self, ctx: ::grpcio::RpcContext, stream: ::grpcio::RequestStream, sink: ::grpcio::ClientStreamingSink); fn get_game_server(&self, ctx: ::grpcio::RpcContext, req: super::sdk::Empty, sink: ::grpcio::UnarySink); + fn watch_game_server(&self, ctx: ::grpcio::RpcContext, req: super::sdk::Empty, sink: ::grpcio::ServerStreamingSink); } pub fn create_sdk(s: S) -> ::grpcio::Service { @@ -157,5 +173,9 @@ pub fn create_sdk(s: S) -> ::grpcio::Service { builder = builder.add_unary_handler(&METHOD_SDK_GET_GAME_SERVER, move |ctx, req, resp| { instance.get_game_server(ctx, req, resp) }); + let instance = s.clone(); + builder = builder.add_server_streaming_handler(&METHOD_SDK_WATCH_GAME_SERVER, move |ctx, req, resp| { + instance.watch_game_server(ctx, req, resp) + }); builder.build() }