Skip to content

Commit cf14773

Browse files
committed
Changes to the GameServer configuration are reflected in the local sdk server
This uses [fsnotify](https://github.com/fsnotify/fsnotify) to track changes to the config file that can be passed in. Changes to the file update the stored configuration, as well as updating `WatchGameServer()` events.
1 parent e3e7cf4 commit cf14773

5 files changed

Lines changed: 209 additions & 66 deletions

File tree

cmd/sdk-server/main.go

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"strings"
2525

2626
"agones.dev/agones/pkg"
27-
"agones.dev/agones/pkg/apis/stable/v1alpha1"
2827
"agones.dev/agones/pkg/client/clientset/versioned"
2928
"agones.dev/agones/pkg/gameservers"
3029
"agones.dev/agones/pkg/sdk"
@@ -36,7 +35,6 @@ import (
3635
"github.com/spf13/viper"
3736
"golang.org/x/net/context"
3837
"google.golang.org/grpc"
39-
"k8s.io/apimachinery/pkg/util/yaml"
4038
"k8s.io/client-go/kubernetes"
4139
"k8s.io/client-go/rest"
4240
)
@@ -134,40 +132,24 @@ func main() {
134132
}
135133

136134
func registerLocal(grpcServer *grpc.Server, ctlConf config) error {
137-
var local *gameservers.LocalSDKServer
138-
if ctlConf.LocalFile != "" {
139-
path, err := filepath.Abs(ctlConf.LocalFile)
140-
if err != nil {
141-
return err
142-
}
143-
144-
if _, err = os.Stat(path); os.IsNotExist(err) {
145-
return errors.Errorf("Could not find file: %s", path)
146-
}
135+
filePath := ""
147136

148-
logger.WithField("path", path).Info("Reading GameServer configuration")
149-
reader, err := os.Open(path) // nolint: gosec
137+
if ctlConf.LocalFile != "" {
138+
var err error
139+
filePath, err = filepath.Abs(ctlConf.LocalFile)
150140
if err != nil {
151141
return err
152142
}
153143

154-
var gs v1alpha1.GameServer
155-
// 4096 is the number of bytes the YAMLOrJSONDecoder goes looking
156-
// into the file to determine if it's JSON or YAML
157-
// (JSON == has whitespace followed by an open brace).
158-
// The Kubernetes uses 4096 bytes as its default, so that's what we'll
159-
// use as well.
160-
// https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/podnodeselector/admission.go#L86
161-
decoder := yaml.NewYAMLOrJSONDecoder(reader, 4096)
162-
err = decoder.Decode(&gs)
163-
if err != nil {
164-
return err
144+
if _, err = os.Stat(filePath); os.IsNotExist(err) {
145+
return errors.Errorf("Could not find file: %s", filePath)
165146
}
166-
local = gameservers.NewLocalSDKServer(&gs)
167-
} else {
168-
local = gameservers.NewLocalSDKServer(nil)
169147
}
170148

149+
local, err := gameservers.NewLocalSDKServer(filePath)
150+
if err != nil {
151+
return err
152+
}
171153
sdk.RegisterSDKServer(grpcServer, local)
172154

173155
return nil

examples/simple-udp/gameserver.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ spec:
2525
spec:
2626
containers:
2727
- name: simple-udp
28-
image: gcr.io/agones-images/udp-server:0.4
28+
image: gcr.io/agones-images/udp-server:0.4
29+
status:
30+
state: Allocate

pkg/gameservers/localsdk.go

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@ package gameservers
1616

1717
import (
1818
"io"
19+
"os"
1920
"sync"
2021
"time"
2122

2223
"agones.dev/agones/pkg/apis/stable/v1alpha1"
2324
"agones.dev/agones/pkg/sdk"
25+
"github.com/fsnotify/fsnotify"
2426
"github.com/pkg/errors"
2527
"github.com/sirupsen/logrus"
2628
"golang.org/x/net/context"
29+
"k8s.io/apimachinery/pkg/util/yaml"
2730
)
2831

2932
var (
@@ -52,23 +55,52 @@ var (
5255
// is being run for local development, and doesn't connect to the
5356
// Kubernetes cluster
5457
type LocalSDKServer struct {
58+
gsMutex sync.RWMutex
5559
gs *sdk.GameServer
56-
watchPeriod time.Duration
5760
update chan struct{}
5861
updateObservers sync.Map
5962
}
6063

6164
// NewLocalSDKServer returns the default LocalSDKServer
62-
func NewLocalSDKServer(gs *v1alpha1.GameServer) *LocalSDKServer {
65+
// TODO: update all the tests at a later date
66+
func NewLocalSDKServer(filePath string) (*LocalSDKServer, error) {
6367
l := &LocalSDKServer{
68+
gsMutex: sync.RWMutex{},
6469
gs: defaultGs,
65-
watchPeriod: 5 * time.Second,
6670
update: make(chan struct{}),
6771
updateObservers: sync.Map{},
6872
}
6973

70-
if gs != nil {
71-
l.gs = convert(gs)
74+
if filePath != "" {
75+
err := l.setGameServerFromFilePath(filePath)
76+
if err != nil {
77+
return l, err
78+
}
79+
80+
watcher, err := fsnotify.NewWatcher()
81+
if err != nil {
82+
return l, err
83+
}
84+
85+
go func() {
86+
for event := range watcher.Events {
87+
if event.Op == fsnotify.Write {
88+
logrus.WithField("event", event).Info("File has been changed!")
89+
err := l.setGameServerFromFilePath(filePath)
90+
if err != nil {
91+
logrus.WithError(err).Error("error setting GameServer from file")
92+
continue
93+
}
94+
logrus.Info("Sending watched GameServer!")
95+
l.update <- struct{}{}
96+
}
97+
}
98+
}()
99+
100+
err = watcher.Add(filePath)
101+
if err != nil {
102+
logrus.WithError(err).WithField("filePath", filePath).Error("error adding watcher")
103+
}
72104
}
73105

74106
go func() {
@@ -81,7 +113,7 @@ func NewLocalSDKServer(gs *v1alpha1.GameServer) *LocalSDKServer {
81113
}
82114
}()
83115

84-
return l
116+
return l, nil
85117
}
86118

87119
// Ready logs that the Ready request has been received
@@ -114,6 +146,8 @@ func (l *LocalSDKServer) Health(stream sdk.SDK_HealthServer) error {
114146
// SetLabel applies a Label to the backing GameServer metadata
115147
func (l *LocalSDKServer) SetLabel(_ context.Context, kv *sdk.KeyValue) (*sdk.Empty, error) {
116148
logrus.WithField("values", kv).Info("Setting label")
149+
l.gsMutex.Lock()
150+
defer l.gsMutex.Unlock()
117151

118152
if l.gs.ObjectMeta == nil {
119153
l.gs.ObjectMeta = &sdk.GameServer_ObjectMeta{}
@@ -130,6 +164,8 @@ func (l *LocalSDKServer) SetLabel(_ context.Context, kv *sdk.KeyValue) (*sdk.Emp
130164
// SetAnnotation applies a Annotation to the backing GameServer metadata
131165
func (l *LocalSDKServer) SetAnnotation(_ context.Context, kv *sdk.KeyValue) (*sdk.Empty, error) {
132166
logrus.WithField("values", kv).Info("Setting annotation")
167+
l.gsMutex.Lock()
168+
defer l.gsMutex.Unlock()
133169

134170
if l.gs.ObjectMeta == nil {
135171
l.gs.ObjectMeta = &sdk.GameServer_ObjectMeta{}
@@ -146,6 +182,8 @@ func (l *LocalSDKServer) SetAnnotation(_ context.Context, kv *sdk.KeyValue) (*sd
146182
// GetGameServer returns a dummy game server.
147183
func (l *LocalSDKServer) GetGameServer(context.Context, *sdk.Empty) (*sdk.GameServer, error) {
148184
logrus.Info("getting GameServer details")
185+
l.gsMutex.RLock()
186+
defer l.gsMutex.RUnlock()
149187
return l.gs, nil
150188
}
151189

@@ -156,24 +194,14 @@ func (l *LocalSDKServer) WatchGameServer(_ *sdk.Empty, stream sdk.SDK_WatchGameS
156194

157195
defer func() {
158196
l.updateObservers.Delete(observer)
159-
close(observer)
160197
}()
161198

162199
l.updateObservers.Store(observer, true)
163200

164-
// on connect, send 3 events, as advertised
165-
go func() {
166-
times := 3
167-
168-
for i := 0; i < times; i++ {
169-
logrus.Info("Sending watched GameServer!")
170-
l.update <- struct{}{}
171-
time.Sleep(l.watchPeriod)
172-
}
173-
}()
174-
175201
for range observer {
202+
l.gsMutex.RLock()
176203
err := stream.Send(l.gs)
204+
l.gsMutex.RUnlock()
177205
if err != nil {
178206
logrus.WithError(err).Error("error sending gameserver")
179207
return err
@@ -182,3 +210,40 @@ func (l *LocalSDKServer) WatchGameServer(_ *sdk.Empty, stream sdk.SDK_WatchGameS
182210

183211
return nil
184212
}
213+
214+
// Close tears down all the things
215+
func (l *LocalSDKServer) Close() {
216+
l.updateObservers.Range(func(observer, _ interface{}) bool {
217+
close(observer.(chan struct{}))
218+
return true
219+
})
220+
}
221+
222+
func (l *LocalSDKServer) setGameServerFromFilePath(filePath string) error {
223+
logrus.WithField("filePath", filePath).Info("Reading GameServer configuration")
224+
225+
reader, err := os.Open(filePath) // nolint: gosec
226+
defer reader.Close() // nolint: megacheck
227+
228+
if err != nil {
229+
return err
230+
}
231+
232+
var gs v1alpha1.GameServer
233+
// 4096 is the number of bytes the YAMLOrJSONDecoder goes looking
234+
// into the file to determine if it's JSON or YAML
235+
// (JSON == has whitespace followed by an open brace).
236+
// The Kubernetes uses 4096 bytes as its default, so that's what we'll
237+
// use as well.
238+
// https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/podnodeselector/admission.go#L86
239+
decoder := yaml.NewYAMLOrJSONDecoder(reader, 4096)
240+
err = decoder.Decode(&gs)
241+
if err != nil {
242+
return err
243+
}
244+
245+
l.gsMutex.Lock()
246+
defer l.gsMutex.Unlock()
247+
l.gs = convert(&gs)
248+
return nil
249+
}

0 commit comments

Comments
 (0)