Permalink
Browse files

Implement SDK SetLabel and SetAnnotation functionality

This implements new functions in the SDK:

- SetLabel(key, value) - that lets you set a label on the backing `GameServer`
- SetAnnotation(key, value) - that lets you set an annotation on the backing
`GameServer`

All keys are prefixed with "stable.agones.dev/sdk-" to maintain isolation.

Closes #279
  • Loading branch information...
markmandel committed Aug 14, 2018
1 parent d6d5d10 commit 9ff84ac23d29e2231fb5911bb8e332c67055d28a
View
@@ -0,0 +1,38 @@
# Copyright 2018 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# project specific
.*
.idea
*.zip
/release
bin
/docs
*.md
*.amd64
# Created by .ignore support plugin (hsz.mobi)
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
View
@@ -16,6 +16,7 @@
!.gitignore
!.helmignore
!.gitattributes
!.dockerignore
*.iml
bin
*.o
View
@@ -47,7 +47,7 @@ GCP_CLUSTER_ZONE ?= us-west1-c
MINIKUBE_PROFILE ?= agones
# Game Server image to use while doing end-to-end tests
GS_TEST_IMAGE ?= gcr.io/agones-images/udp-server:0.1
GS_TEST_IMAGE ?= gcr.io/agones-images/udp-server:0.3
# Directory that this Makefile is in.
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
@@ -119,7 +119,7 @@ test-go:
# Runs end-to-end tests on the current configured cluster
# For minikube user the minikube-test-e2e targets
test-e2e: $(ensure-build-image)
$(DOCKER_RUN) go test -v $(agones_package)/test/e2e/... \
$(DOCKER_RUN) go test -v -race $(agones_package)/test/e2e/... \
--kubeconfig /root/.kube/config \
--gameserver-image=$(GS_TEST_IMAGE)
View
@@ -62,6 +62,28 @@ Call when the GameServer session is over and it's time to shut down
$ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:59358/shutdown
```
### Set Label
Apply a Label with the prefix "stable.agones.dev/sdk-" to the backing `GameServer` metadata.
See the SDK [SetLabel](../sdks/README.md#setlabelkey-value) documentation for restrictions.
#### Example
```bash
$ curl -d '{"key": "foo", "value": "bar"}' -H "Content-Type: application/json" -X PUT http://localhost:59358/metadata/label
```
### Set Annotation
Apply a Annotation with the prefix "stable.agones.dev/sdk-" to the backing `GameServer` metadata
#### Example
```bash
$ curl -d '{"key": "foo", "value": "bar"}' -H "Content-Type: application/json" -X PUT http://localhost:59358/metadata/annotation
```
### GameServer
Call when you want to retrieve the backing `GameServer` configuration details
@@ -59,8 +59,22 @@ int main() {
std::thread health (doHealth, sdk);
std::thread watch (watchUpdates, sdk);
std::cout << "Setting a label" << std::endl;
grpc::Status status = sdk->SetLabel("test-label", "test-value");
if (!status.ok()) {
std::cout << "Could not run SetLabel(): "+ status.error_message() + ". Exiting!" << std::endl;
return -1;
}
std::cout << "Setting an annotation" << std::endl;
status = sdk->SetAnnotation("test-annotation", "test value");
if (!status.ok()) {
std::cout << "Could not run SetAnnotation(): "+ status.error_message() + ". Exiting!" << std::endl;
return -1;
}
std::cout << "Marking server as ready..." << std::endl;
grpc::Status status = sdk->Ready();
status = sdk->Ready();
if (!status.ok()) {
std::cout << "Could not run Ready(): "+ status.error_message() + ". Exiting!" << std::endl;
return -1;
@@ -16,16 +16,17 @@
package main
import (
"encoding/json"
"flag"
"log"
"net"
"os"
"strings"
"time"
"agones.dev/agones/sdks/go"
"encoding/json"
coresdk "agones.dev/agones/pkg/sdk"
"agones.dev/agones/sdks/go"
"strconv"
)
// main starts a UDP server that received 1024 byte sized packets at at time
@@ -67,23 +68,12 @@ func main() {
func readWriteLoop(conn net.PacketConn, stop chan struct{}, s *sdk.SDK) {
b := make([]byte, 1024)
for {
n, sender, err := conn.ReadFrom(b)
if err != nil {
log.Fatalf("Could not read from udp stream: %v", err)
}
txt := strings.TrimSpace(string(b[:n]))
log.Printf("Received packet from %v: %v", sender.String(), txt)
sender, txt := readPacket(conn, b)
switch txt {
// shuts down the gameserver
case "EXIT":
log.Printf("Received EXIT command. Exiting.")
// This tells Agones to shutdown this Game Server
shutdownErr := s.Shutdown()
if shutdownErr != nil {
log.Printf("Could not shutdown")
}
os.Exit(0)
exit(s)
// turns off the health pings
case "UNHEALTHY":
@@ -94,14 +84,46 @@ func readWriteLoop(conn net.PacketConn, stop chan struct{}, s *sdk.SDK) {
case "WATCH":
watchGameServerEvents(s)
}
// echo it back
ack := "ACK: " + txt + "\n"
if _, err = conn.WriteTo([]byte(ack), sender); err != nil {
log.Fatalf("Could not write to udp stream: %v", err)
case "LABEL":
setLabel(s)
case "ANNOTATION":
setAnnotation(s)
}
ack(conn, sender, txt)
}
}
// readPacket reads a string from the connection
func readPacket(conn net.PacketConn, b []byte) (net.Addr, string) {
n, sender, err := conn.ReadFrom(b)
if err != nil {
log.Fatalf("Could not read from udp stream: %v", err)
}
txt := strings.TrimSpace(string(b[:n]))
log.Printf("Received packet from %v: %v", sender.String(), txt)
return sender, txt
}
// ack echoes it back, with an ACK
func ack(conn net.PacketConn, sender net.Addr, txt string) {
ack := "ACK: " + txt + "\n"
if _, err := conn.WriteTo([]byte(ack), sender); err != nil {
log.Fatalf("Could not write to udp stream: %v", err)
}
}
// exit shutdowns the server
func exit(s *sdk.SDK) {
log.Printf("Received EXIT command. Exiting.")
// This tells Agones to shutdown this Game Server
shutdownErr := s.Shutdown()
if shutdownErr != nil {
log.Printf("Could not shutdown")
}
os.Exit(0)
}
// writes the GameServer name to the connection UDP stream
@@ -138,6 +160,25 @@ func watchGameServerEvents(s *sdk.SDK) {
}
}
// setAnnotation sets a given annotation
func setAnnotation(s *sdk.SDK) {
log.Print("Setting annotation")
err := s.SetAnnotation("timestamp", time.Now().UTC().String())
if err != nil {
log.Fatalf("could not set annotation: %v", err)
}
}
// setLabel sets a given label
func setLabel(s *sdk.SDK) {
log.Print("Setting label")
// label values can only be alpha, - and .
err := s.SetLabel("timestamp", strconv.FormatInt(time.Now().Unix(), 10))
if err != nil {
log.Fatalf("could not set label: %v", err)
}
}
// doHealth sends the regular Health Pings
func doHealth(sdk *sdk.SDK, stop <-chan struct{}) {
tick := time.Tick(2 * time.Second)
@@ -17,6 +17,7 @@ package gameservers
import (
"io"
"time"
"sync"
"agones.dev/agones/pkg/sdk"
"github.com/pkg/errors"
@@ -50,14 +51,30 @@ var (
// is being run for local development, and doesn't connect to the
// Kubernetes cluster
type LocalSDKServer struct {
watchPeriod time.Duration
watchPeriod time.Duration
update chan struct{}
updateObservers sync.Map
}
// NewLocalSDKServer returns the default LocalSDKServer
func NewLocalSDKServer() *LocalSDKServer {
return &LocalSDKServer{
watchPeriod: 5 * time.Second,
l := &LocalSDKServer{
watchPeriod: 5 * time.Second,
update: make(chan struct{}),
updateObservers: sync.Map{},
}
go func() {
for value := range l.update {
logrus.Info("gameserver update received")
l.updateObservers.Range(func(observer, _ interface{}) bool {
observer.(chan struct{}) <- value
return true
})
}
}()
return l
}
// Ready logs that the Ready request has been received
@@ -87,6 +104,22 @@ func (l *LocalSDKServer) Health(stream sdk.SDK_HealthServer) error {
}
}
// SetLabel applies a Label to the backing GameServer metadata
func (l *LocalSDKServer) SetLabel(_ context.Context, kv *sdk.KeyValue) (*sdk.Empty, error) {
logrus.WithField("values", kv).Info("Setting label")
fixture.ObjectMeta.Labels[metadataPrefix+kv.Key] = kv.Value
l.update <- struct{}{}
return &sdk.Empty{}, nil
}
// SetAnnotation applies a Annotation to the backing GameServer metadata
func (l *LocalSDKServer) SetAnnotation(_ context.Context, kv *sdk.KeyValue) (*sdk.Empty, error) {
logrus.WithField("values", kv).Info("Setting annotation")
fixture.ObjectMeta.Annotations[metadataPrefix+kv.Key] = kv.Value
l.update <- struct{}{}
return &sdk.Empty{}, nil
}
// GetGameServer returns a dummy game server.
func (l *LocalSDKServer) GetGameServer(context.Context, *sdk.Empty) (*sdk.GameServer, error) {
logrus.Info("getting GameServer details")
@@ -96,17 +129,32 @@ func (l *LocalSDKServer) GetGameServer(context.Context, *sdk.Empty) (*sdk.GameSe
// 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
observer := make(chan struct{})
defer func() {
l.updateObservers.Delete(observer)
close(observer)
}()
l.updateObservers.Store(observer, true)
// on connect, send 3 events, as advertised
go func() {
times := 3
for i := 0; i < times; i++ {
logrus.Info("Sending watched GameServer!")
for i := 0; i < times; i++ {
logrus.Info("Sending watched GameServer!")
l.update <- struct{}{}
time.Sleep(l.watchPeriod)
}
}()
for range observer {
err := stream.Send(fixture)
if err != nil {
logrus.WithError(err).Error("error sending gameserver")
return err
}
time.Sleep(l.watchPeriod)
}
return nil
Oops, something went wrong.

0 comments on commit 9ff84ac

Please sign in to comment.