Skip to content

Commit

Permalink
Ability to pass GameServer yaml/json to local sdk server
Browse files Browse the repository at this point in the history
To be able to work locally, you need to be able to specify your
local `GameServer` configuration, as it likely will have application specific
configuration in it -- or maybe you want to specify what state it's in.

This commit allow you to specify the local resource as either yaml/json
through a `-f` or `--file` flag.

Closes googleforgames#296
  • Loading branch information
markmandel committed Aug 26, 2018
1 parent d6d5d10 commit e784314
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 130 deletions.
58 changes: 53 additions & 5 deletions cmd/sdk-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,24 @@ import (
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strings"

"agones.dev/agones/pkg"
"agones.dev/agones/pkg/apis/stable/v1alpha1"
"agones.dev/agones/pkg/client/clientset/versioned"
"agones.dev/agones/pkg/gameservers"
"agones.dev/agones/pkg/sdk"
"agones.dev/agones/pkg/util/runtime"
"agones.dev/agones/pkg/util/signals"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"golang.org/x/net/context"
"google.golang.org/grpc"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
Expand All @@ -46,6 +51,7 @@ const (

// Flags (that can also be env vars)
localFlag = "local"
fileFlag = "file"
addressFlag = "address"
)

Expand Down Expand Up @@ -81,7 +87,10 @@ func main() {
defer cancel()

if ctlConf.IsLocal {
sdk.RegisterSDKServer(grpcServer, gameservers.NewLocalSDKServer())
err = registerLocal(grpcServer, ctlConf)
if err != nil {
logger.WithError(err).Fatal("Could not start local sdk server")
}
} else {
var config *rest.Config
config, err = rest.InClusterConfig()
Expand Down Expand Up @@ -124,6 +133,41 @@ func main() {
logger.Info("shutting down sdk server")
}

func registerLocal(grpcServer *grpc.Server, ctlConf config) error {
var local *gameservers.LocalSDKServer
if ctlConf.LocalFile != "" {
path, err := filepath.Abs(ctlConf.LocalFile)
if err != nil {
return err
}

if _, err = os.Stat(path); os.IsNotExist(err) {
return errors.Errorf("Could not find file: %s", path)
}

logger.WithField("path", path).Info("Reading GameServer configuration")
reader, err := os.Open(path) // nolint: gosec
if err != nil {
return err
}

var gs v1alpha1.GameServer
// 4096 is what Kubernetes uses
decoder := yaml.NewYAMLOrJSONDecoder(reader, 4096)
err = decoder.Decode(&gs)
if err != nil {
return err
}
local = gameservers.NewLocalSDKServer(&gs)
} else {
local = gameservers.NewLocalSDKServer(nil)
}

sdk.RegisterSDKServer(grpcServer, local)

return nil
}

// runGrpc runs the grpc service
func runGrpc(grpcServer *grpc.Server, lis net.Listener) {
logger.Info("Starting SDKServer grpc service...")
Expand Down Expand Up @@ -157,9 +201,11 @@ func runGateway(ctx context.Context, grpcEndpoint string, mux *gwruntime.ServeMu
// a configuration structure
func parseEnvFlags() config {
viper.SetDefault(localFlag, false)
viper.SetDefault(fileFlag, "")
viper.SetDefault(addressFlag, "localhost")
pflag.Bool(localFlag, viper.GetBool(localFlag),
"Set this, or LOCAL env, to 'true' to run this binary in local development mode. Defaults to 'false'")
pflag.StringP(fileFlag, "f", viper.GetString(fileFlag), "Set this, or FILE env var to the path of a local yaml or json file that contains your GameServer resoure configuration")
pflag.String(addressFlag, viper.GetString(addressFlag), "The Address to bind the server grpcPort to. Defaults to 'localhost")
pflag.Parse()

Expand All @@ -170,13 +216,15 @@ func parseEnvFlags() config {
runtime.Must(viper.BindPFlags(pflag.CommandLine))

return config{
IsLocal: viper.GetBool(localFlag),
Address: viper.GetString(addressFlag),
IsLocal: viper.GetBool(localFlag),
Address: viper.GetString(addressFlag),
LocalFile: viper.GetString(fileFlag),
}
}

// config is all the configuration for this program
type config struct {
Address string
IsLocal bool
Address string
IsLocal bool
LocalFile string
}
19 changes: 14 additions & 5 deletions pkg/gameservers/localsdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"io"
"time"

"agones.dev/agones/pkg/apis/stable/v1alpha1"
"agones.dev/agones/pkg/sdk"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
Expand All @@ -27,7 +28,7 @@ import (
var (
_ sdk.SDKServer = &LocalSDKServer{}

fixture = &sdk.GameServer{
defaultGs = &sdk.GameServer{
ObjectMeta: &sdk.GameServer_ObjectMeta{
Name: "local",
Namespace: "default",
Expand All @@ -50,14 +51,22 @@ var (
// is being run for local development, and doesn't connect to the
// Kubernetes cluster
type LocalSDKServer struct {
gs *sdk.GameServer
watchPeriod time.Duration
}

// NewLocalSDKServer returns the default LocalSDKServer
func NewLocalSDKServer() *LocalSDKServer {
return &LocalSDKServer{
func NewLocalSDKServer(gs *v1alpha1.GameServer) *LocalSDKServer {
lss := &LocalSDKServer{
watchPeriod: 5 * time.Second,
gs: defaultGs,
}

if gs != nil {
lss.gs = convert(gs)
}

return lss
}

// Ready logs that the Ready request has been received
Expand Down Expand Up @@ -90,7 +99,7 @@ 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")
return fixture, nil
return l.gs, nil
}

// WatchGameServer will return a dummy GameServer (with no changes), 3 times, every 5 seconds
Expand All @@ -100,7 +109,7 @@ func (l *LocalSDKServer) WatchGameServer(_ *sdk.Empty, stream sdk.SDK_WatchGameS

for i := 0; i < times; i++ {
logrus.Info("Sending watched GameServer!")
err := stream.Send(fixture)
err := stream.Send(l.gs)
if err != nil {
logrus.WithError(err).Error("error sending gameserver")
return err
Expand Down
22 changes: 18 additions & 4 deletions pkg/gameservers/localsdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ import (
"testing"
"time"

"agones.dev/agones/pkg/apis/stable/v1alpha1"
"agones.dev/agones/pkg/sdk"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestLocal(t *testing.T) {
ctx := context.Background()
e := &sdk.Empty{}
l := NewLocalSDKServer()
l := NewLocalSDKServer(nil)

_, err := l.Ready(ctx, e)
assert.Nil(t, err, "Ready should not error")
Expand All @@ -53,14 +55,26 @@ func TestLocal(t *testing.T) {
gs, err := l.GetGameServer(ctx, e)
assert.Nil(t, err)

assert.Equal(t, fixture, gs)
assert.Equal(t, defaultGs, gs)
}

func TestLocalSDKWithGameServer(t *testing.T) {
ctx := context.Background()
e := &sdk.Empty{}

fixture := &v1alpha1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "stuff"}}
l := NewLocalSDKServer(fixture.DeepCopy())
gs, err := l.GetGameServer(ctx, e)
assert.Nil(t, err)

assert.Equal(t, fixture.ObjectMeta.Name, gs.ObjectMeta.Name)
}

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

e := &sdk.Empty{}
l := NewLocalSDKServer()
l := NewLocalSDKServer(nil)
l.watchPeriod = time.Second

stream := newGameServerMockStream()
Expand All @@ -70,7 +84,7 @@ func TestLocalSDKServerWatchGameServer(t *testing.T) {
for i := 0; i < 3; i++ {
select {
case msg := <-stream.msgs:
assert.Equal(t, fixture, msg)
assert.Equal(t, defaultGs, msg)
case <-time.After(2 * l.watchPeriod):
assert.FailNow(t, "timeout on receiving messagess")
}
Expand Down
65 changes: 65 additions & 0 deletions pkg/gameservers/sdk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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.

package gameservers

import (
"agones.dev/agones/pkg/apis/stable/v1alpha1"
"agones.dev/agones/pkg/sdk"
)

// convert converts a K8s GameServer object, into a gRPC SDK GameServer object
func convert(gs *v1alpha1.GameServer) *sdk.GameServer {
meta := gs.ObjectMeta
status := gs.Status
health := gs.Spec.Health
result := &sdk.GameServer{
ObjectMeta: &sdk.GameServer_ObjectMeta{
Name: meta.Name,
Namespace: meta.Namespace,
Uid: string(meta.UID),
ResourceVersion: meta.ResourceVersion,
Generation: meta.Generation,
CreationTimestamp: meta.CreationTimestamp.Unix(),
Annotations: meta.Annotations,
Labels: meta.Labels,
},
Spec: &sdk.GameServer_Spec{
Health: &sdk.GameServer_Spec_Health{
Disabled: health.Disabled,
PeriodSeconds: health.PeriodSeconds,
FailureThreshold: health.FailureThreshold,
InitialDelaySeconds: health.InitialDelaySeconds,
},
},
Status: &sdk.GameServer_Status{
State: string(status.State),
Address: status.Address,
},
}
if meta.DeletionTimestamp != nil {
result.ObjectMeta.DeletionTimestamp = meta.DeletionTimestamp.Unix()
}

// loop around and add all the ports
for _, p := range status.Ports {
grpcPort := &sdk.GameServer_Status_Port{
Name: p.Name,
Port: p.Port,
}
result.Status.Ports = append(result.Status.Ports, grpcPort)
}

return result
}
87 changes: 87 additions & 0 deletions pkg/gameservers/sdk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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.

package gameservers

import (
"testing"

"agones.dev/agones/pkg/apis/stable/v1alpha1"
"agones.dev/agones/pkg/sdk"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1"
)

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

fixture := &v1alpha1.GameServer{
ObjectMeta: v1.ObjectMeta{
CreationTimestamp: v1.Now(),
Namespace: "default",
Name: "test",
Labels: map[string]string{"foo": "bar"},
Annotations: map[string]string{"stuff": "things"},
UID: "1234",
},
Spec: v1alpha1.GameServerSpec{
Health: v1alpha1.Health{
Disabled: false,
InitialDelaySeconds: 10,
FailureThreshold: 15,
PeriodSeconds: 20,
},
},
Status: v1alpha1.GameServerStatus{
NodeName: "george",
Address: "127.0.0.1",
State: "Ready",
Ports: []v1alpha1.GameServerStatusPort{
{Name: "default", Port: 12345},
{Name: "beacon", Port: 123123},
},
},
}

eq := func(t *testing.T, fixture *v1alpha1.GameServer, sdkGs *sdk.GameServer) {
assert.Equal(t, fixture.ObjectMeta.Name, sdkGs.ObjectMeta.Name)
assert.Equal(t, fixture.ObjectMeta.Namespace, sdkGs.ObjectMeta.Namespace)
assert.Equal(t, fixture.ObjectMeta.CreationTimestamp.Unix(), sdkGs.ObjectMeta.CreationTimestamp)
assert.Equal(t, string(fixture.ObjectMeta.UID), sdkGs.ObjectMeta.Uid)
assert.Equal(t, fixture.ObjectMeta.Labels, sdkGs.ObjectMeta.Labels)
assert.Equal(t, fixture.ObjectMeta.Annotations, sdkGs.ObjectMeta.Annotations)
assert.Equal(t, fixture.Spec.Health.Disabled, sdkGs.Spec.Health.Disabled)
assert.Equal(t, fixture.Spec.Health.InitialDelaySeconds, sdkGs.Spec.Health.InitialDelaySeconds)
assert.Equal(t, fixture.Spec.Health.FailureThreshold, sdkGs.Spec.Health.FailureThreshold)
assert.Equal(t, fixture.Spec.Health.PeriodSeconds, sdkGs.Spec.Health.PeriodSeconds)
assert.Equal(t, fixture.Status.Address, sdkGs.Status.Address)
assert.Equal(t, string(fixture.Status.State), sdkGs.Status.State)
assert.Len(t, sdkGs.Status.Ports, len(fixture.Status.Ports))
for i, fp := range fixture.Status.Ports {
p := sdkGs.Status.Ports[i]
assert.Equal(t, fp.Name, p.Name)
assert.Equal(t, fp.Port, p.Port)
}
}

sdkGs := convert(fixture)
eq(t, fixture, sdkGs)
assert.Zero(t, sdkGs.ObjectMeta.DeletionTimestamp)

now := v1.Now()
fixture.DeletionTimestamp = &now
sdkGs = convert(fixture)
eq(t, fixture, sdkGs)
assert.Equal(t, fixture.ObjectMeta.DeletionTimestamp.Unix(), sdkGs.ObjectMeta.DeletionTimestamp)
}
Loading

0 comments on commit e784314

Please sign in to comment.