From 4b4b9f21cc4408c2b2ed7741817267b4ab7d3c9e Mon Sep 17 00:00:00 2001 From: Mark Mandel Date: Mon, 3 Feb 2020 18:13:07 -0800 Subject: [PATCH 1/2] CRD implementation of alpha player tracking Update to CRD implementation, and pkg/api/gameserver.go and default value implementation. Work on #1033 --- .../crds/_gameserverspecvalidation.yaml | 26 +++ install/yaml/install.yaml | 78 +++++++ pkg/apis/agones/v1/gameserver.go | 42 +++- pkg/apis/agones/v1/gameserver_test.go | 35 +++- pkg/apis/agones/v1/zz_generated.deepcopy.go | 74 +++++++ pkg/util/runtime/features.go | 6 +- pkg/util/runtime/features_test.go | 6 +- .../Reference/agones_crd_api_reference.html | 194 ++++++++++++++++++ 8 files changed, 445 insertions(+), 16 deletions(-) diff --git a/install/helm/agones/templates/crds/_gameserverspecvalidation.yaml b/install/helm/agones/templates/crds/_gameserverspecvalidation.yaml index cd794389bf..1ed768d7f5 100644 --- a/install/helm/agones/templates/crds/_gameserverspecvalidation.yaml +++ b/install/helm/agones/templates/crds/_gameserverspecvalidation.yaml @@ -148,4 +148,30 @@ properties: type: integer minimum: 1 maximum: 2147483648 + alpha: + type: object + title: Alpha properties for the GameServer + properties: + players: + type: object + title: Configuration of player capacity + properties: + initialCapacity: + type: integer + title: The intial player capacity that this Game Server has + minimum: 0 + webhook: + type: object + title: webhook to call when a player connects or disconnects + properties: + service: + properties: + name: + type: string + namespace: + type: string + path: + type: string + url: + type: string {{- end }} \ No newline at end of file diff --git a/install/yaml/install.yaml b/install/yaml/install.yaml index b152629678..e7a15f8197 100644 --- a/install/yaml/install.yaml +++ b/install/yaml/install.yaml @@ -394,6 +394,32 @@ spec: type: integer minimum: 1 maximum: 2147483648 + alpha: + type: object + title: Alpha properties for the GameServer + properties: + players: + type: object + title: Configuration of player capacity + properties: + initialCapacity: + type: integer + title: The intial player capacity that this Game Server has + minimum: 0 + webhook: + type: object + title: webhook to call when a player connects or disconnects + properties: + service: + properties: + name: + type: string + namespace: + type: string + path: + type: string + url: + type: string subresources: # status enables the status subresource. status: {} @@ -680,6 +706,32 @@ spec: type: integer minimum: 1 maximum: 2147483648 + alpha: + type: object + title: Alpha properties for the GameServer + properties: + players: + type: object + title: Configuration of player capacity + properties: + initialCapacity: + type: integer + title: The intial player capacity that this Game Server has + minimum: 0 + webhook: + type: object + title: webhook to call when a player connects or disconnects + properties: + service: + properties: + name: + type: string + namespace: + type: string + path: + type: string + url: + type: string --- # Source: agones/templates/crds/gameserverallocationpolicy.yaml @@ -982,6 +1034,32 @@ spec: type: integer minimum: 1 maximum: 2147483648 + alpha: + type: object + title: Alpha properties for the GameServer + properties: + players: + type: object + title: Configuration of player capacity + properties: + initialCapacity: + type: integer + title: The intial player capacity that this Game Server has + minimum: 0 + webhook: + type: object + title: webhook to call when a player connects or disconnects + properties: + service: + properties: + name: + type: string + namespace: + type: string + path: + type: string + url: + type: string subresources: # status enables the status subresource. status: {} diff --git a/pkg/apis/agones/v1/gameserver.go b/pkg/apis/agones/v1/gameserver.go index bdc38d9a0a..e5012d9464 100644 --- a/pkg/apis/agones/v1/gameserver.go +++ b/pkg/apis/agones/v1/gameserver.go @@ -19,12 +19,14 @@ import ( "fmt" "net" - "github.com/mattbaird/jsonpatch" + "agones.dev/agones/pkg/util/runtime" "agones.dev/agones/pkg" "agones.dev/agones/pkg/apis" "agones.dev/agones/pkg/apis/agones" + "github.com/mattbaird/jsonpatch" "github.com/pkg/errors" + admregv1b "k8s.io/api/admissionregistration/v1beta1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -150,6 +152,20 @@ type GameServerSpec struct { SdkServer SdkServer `json:"sdkServer,omitempty"` // Template describes the Pod that will be created for the GameServer Template corev1.PodTemplateSpec `json:"template"` + // AlphaSpec describes the alpha properties for the GameServer + Alpha AlphaSpec `json:"alpha,omitempty"` +} + +// AlphaSpec is the alpha properties of the GameServer +type AlphaSpec struct { + Players PlayersSpec `json:"players"` +} + +// PlayersSpec tracks the initial player capacity, and what webhooks to send events to when there are +// connection/disconnection events. +type PlayersSpec struct { + InitialCapacity int64 `json:"initialCapacity,omitempty"` + Webhook *admregv1b.WebhookClientConfig `json:"webhook,omitempty"` } // GameServerState is the state for the GameServer @@ -210,6 +226,7 @@ type GameServerStatus struct { Address string `json:"address"` NodeName string `json:"nodeName"` ReservedUntil *metav1.Time `json:"reservedUntil"` + Alpha AlphaStatus `json:"alpha"` } // GameServerStatusPort shows the port that was allocated to a @@ -219,6 +236,17 @@ type GameServerStatusPort struct { Port int32 `json:"port"` } +// AlphaStatus is the alpha status values for a GameServer +type AlphaStatus struct { + Players PlayerStatus `json:"players"` +} + +// PlayerStatus stores the current player capacity values +type PlayerStatus struct { + Count int64 `json:"count"` + Capacity int64 `json:"capacity"` +} + // ApplyDefaults applies default values to the GameServer if they are not already populated func (gs *GameServer) ApplyDefaults() { // VersionAnnotation is the annotation that stores @@ -230,7 +258,7 @@ func (gs *GameServer) ApplyDefaults() { gs.ObjectMeta.Finalizers = append(gs.ObjectMeta.Finalizers, agones.GroupName) gs.Spec.ApplyDefaults() - gs.applyStateDefaults() + gs.applyStatusDefaults() } // ApplyDefaults applies default values to the GameServerSpec if they are not already populated @@ -277,15 +305,19 @@ func (gss *GameServerSpec) applyHealthDefaults() { } } -// applyStateDefaults applies state defaults -func (gs *GameServer) applyStateDefaults() { +// applyStatusDefaults applies Status defaults +func (gs *GameServer) applyStatusDefaults() { if gs.Status.State == "" { gs.Status.State = GameServerStateCreating - // applyStateDefaults() should be called after applyPortDefaults() + // applyStatusDefaults() should be called after applyPortDefaults() if gs.HasPortPolicy(Dynamic) || gs.HasPortPolicy(Passthrough) { gs.Status.State = GameServerStatePortAllocation } } + + if runtime.FeatureEnabled(runtime.FeaturePlayerTracking) { + gs.Status.Alpha.Players.Capacity = gs.Spec.Alpha.Players.InitialCapacity + } } // applyPortDefaults applies default values for all ports diff --git a/pkg/apis/agones/v1/gameserver_test.go b/pkg/apis/agones/v1/gameserver_test.go index a0cf23768f..a62c3e7df1 100644 --- a/pkg/apis/agones/v1/gameserver_test.go +++ b/pkg/apis/agones/v1/gameserver_test.go @@ -17,8 +17,11 @@ package v1 import ( "fmt" "strings" + "sync" "testing" + "agones.dev/agones/pkg/util/runtime" + "agones.dev/agones/pkg" "agones.dev/agones/pkg/apis" "agones.dev/agones/pkg/apis/agones" @@ -62,21 +65,25 @@ func TestGameServerApplyDefaults(t *testing.T) { t.Parallel() type expected struct { - protocol corev1.Protocol - state GameServerState - policy PortPolicy - health Health - scheduling apis.SchedulingStrategy - sdkServer SdkServer + protocol corev1.Protocol + state GameServerState + policy PortPolicy + health Health + scheduling apis.SchedulingStrategy + sdkServer SdkServer + alphaPlayerCapacity int64 } data := map[string]struct { - gameServer GameServer - container string - expected expected + gameServer GameServer + container string + featureFlags string + expected expected }{ "set basic defaults on a very simple gameserver": { + featureFlags: runtime.FeaturePlayerTracking + "=true", gameServer: GameServer{ Spec: GameServerSpec{ + Alpha: AlphaSpec{Players: PlayersSpec{InitialCapacity: 10}}, Ports: []GameServerPort{{ContainerPort: 999}}, Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{Containers: []corev1.Container{ @@ -100,6 +107,7 @@ func TestGameServerApplyDefaults(t *testing.T) { GRPCPort: 9357, HTTPPort: 9358, }, + alphaPlayerCapacity: 10, }, }, "defaults on passthrough": { @@ -180,6 +188,7 @@ func TestGameServerApplyDefaults(t *testing.T) { gameServer: GameServer{ Spec: GameServerSpec{ Ports: []GameServerPort{{PortPolicy: Static}}, + Alpha: AlphaSpec{Players: PlayersSpec{InitialCapacity: 10}}, Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "testing", Image: "testing/image"}}}}}, }, @@ -319,8 +328,15 @@ func TestGameServerApplyDefaults(t *testing.T) { }, } + // otherwise the race condition detector is not happy. + mtx := sync.Mutex{} for name, test := range data { t.Run(name, func(t *testing.T) { + mtx.Lock() + err := runtime.ParseFeatures(test.featureFlags) + mtx.Unlock() + assert.NoError(t, err) + test.gameServer.ApplyDefaults() assert.Equal(t, pkg.Version, test.gameServer.Annotations[VersionAnnotation]) @@ -333,6 +349,7 @@ func TestGameServerApplyDefaults(t *testing.T) { assert.Equal(t, test.expected.scheduling, test.gameServer.Spec.Scheduling) assert.Equal(t, test.expected.health, test.gameServer.Spec.Health) assert.Equal(t, test.expected.sdkServer, test.gameServer.Spec.SdkServer) + assert.Equal(t, test.expected.alphaPlayerCapacity, test.gameServer.Status.Alpha.Players.Capacity) }) } } diff --git a/pkg/apis/agones/v1/zz_generated.deepcopy.go b/pkg/apis/agones/v1/zz_generated.deepcopy.go index 840f0f1482..bb4f9ebeea 100644 --- a/pkg/apis/agones/v1/zz_generated.deepcopy.go +++ b/pkg/apis/agones/v1/zz_generated.deepcopy.go @@ -21,9 +21,44 @@ package v1 import ( + v1beta1 "k8s.io/api/admissionregistration/v1beta1" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AlphaSpec) DeepCopyInto(out *AlphaSpec) { + *out = *in + in.Players.DeepCopyInto(&out.Players) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlphaSpec. +func (in *AlphaSpec) DeepCopy() *AlphaSpec { + if in == nil { + return nil + } + out := new(AlphaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AlphaStatus) DeepCopyInto(out *AlphaStatus) { + *out = *in + out.Players = in.Players + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlphaStatus. +func (in *AlphaStatus) DeepCopy() *AlphaStatus { + if in == nil { + return nil + } + out := new(AlphaStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Fleet) DeepCopyInto(out *Fleet) { *out = *in @@ -301,6 +336,7 @@ func (in *GameServerSpec) DeepCopyInto(out *GameServerSpec) { out.Health = in.Health out.SdkServer = in.SdkServer in.Template.DeepCopyInto(&out.Template) + in.Alpha.DeepCopyInto(&out.Alpha) return } @@ -326,6 +362,7 @@ func (in *GameServerStatus) DeepCopyInto(out *GameServerStatus) { in, out := &in.ReservedUntil, &out.ReservedUntil *out = (*in).DeepCopy() } + out.Alpha = in.Alpha return } @@ -389,6 +426,43 @@ func (in *Health) DeepCopy() *Health { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlayerStatus) DeepCopyInto(out *PlayerStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlayerStatus. +func (in *PlayerStatus) DeepCopy() *PlayerStatus { + if in == nil { + return nil + } + out := new(PlayerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlayersSpec) DeepCopyInto(out *PlayersSpec) { + *out = *in + if in.Webhook != nil { + in, out := &in.Webhook, &out.Webhook + *out = new(v1beta1.WebhookClientConfig) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlayersSpec. +func (in *PlayersSpec) DeepCopy() *PlayersSpec { + if in == nil { + return nil + } + out := new(PlayersSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SdkServer) DeepCopyInto(out *SdkServer) { *out = *in diff --git a/pkg/util/runtime/features.go b/pkg/util/runtime/features.go index 3aaaa79875..08e8bbf7b5 100644 --- a/pkg/util/runtime/features.go +++ b/pkg/util/runtime/features.go @@ -28,13 +28,17 @@ const ( // FeatureExample is an example feature gate flag, used for testing and demonstrative purposes FeatureExample Feature = "Example" + + // FeaturePlayerTracking is a feature flag to enable/disable player tracking features. + FeaturePlayerTracking = "PlayerTracking" ) var ( // featureDefaults is a map of all Feature Gates that are // operational in Agones, and what their default configuration is. featureDefaults = map[Feature]bool{ - FeatureExample: true, + FeatureExample: true, + FeaturePlayerTracking: false, } // featureGates is the storage of what features are enabled diff --git a/pkg/util/runtime/features_test.go b/pkg/util/runtime/features_test.go index 70d5fe39e6..70c44c1dd4 100644 --- a/pkg/util/runtime/features_test.go +++ b/pkg/util/runtime/features_test.go @@ -26,7 +26,11 @@ import ( func TestFeatures(t *testing.T) { t.Parallel() - featureDefaults[Feature("Test")] = false + // stable feature flag state + featureDefaults = map[Feature]bool{ + FeatureExample: true, + Feature("Test"): false, + } t.Run("invalid Feature gate", func(t *testing.T) { err := ParseFeatures("Foo") diff --git a/site/content/en/docs/Reference/agones_crd_api_reference.html b/site/content/en/docs/Reference/agones_crd_api_reference.html index 28b3704ce4..4d5d0fc188 100644 --- a/site/content/en/docs/Reference/agones_crd_api_reference.html +++ b/site/content/en/docs/Reference/agones_crd_api_reference.html @@ -2534,6 +2534,19 @@

GameServer

Template describes the Pod that will be created for the GameServer

+ + +alpha
+ + +AlphaSpec + + + + +

AlphaSpec describes the alpha properties for the GameServer

+ + @@ -2662,6 +2675,68 @@

GameServerSet +

AlphaSpec +

+

+(Appears on: +GameServerSpec) +

+

+

AlphaSpec is the alpha properties of the GameServer

+

+ + + + + + + + + + + + + +
FieldDescription
+players
+ + +PlayersSpec + + +
+
+

AlphaStatus +

+

+(Appears on: +GameServerStatus) +

+

+

AlphaStatus is the alpha status values for a GameServer

+

+ + + + + + + + + + + + + +
FieldDescription
+players
+ + +PlayerStatus + + +
+

FleetSpec

@@ -3098,6 +3173,19 @@

GameServerSpec

Template describes the Pod that will be created for the GameServer

+ + +alpha
+ + +AlphaSpec + + + + +

AlphaSpec describes the alpha properties for the GameServer

+ +

GameServerState @@ -3183,6 +3271,18 @@

GameServerStatus + + +alpha
+ + +AlphaStatus + + + + + +

GameServerStatusPort @@ -3346,6 +3446,19 @@

GameServerTemplateSpec

Template describes the Pod that will be created for the GameServer

+ + +alpha
+ + +AlphaSpec + + + + +

AlphaSpec describes the alpha properties for the GameServer

+ + @@ -3414,6 +3527,87 @@

Health +

PlayerStatus +

+

+(Appears on: +AlphaStatus) +

+

+

PlayerStatus stores the current player capacity values

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+count
+ +int64 + +
+
+capacity
+ +int64 + +
+
+

PlayersSpec +

+

+(Appears on: +AlphaSpec) +

+

+

PlayersSpec tracks the initial player capacity, and what webhooks to send events to when there are +connection/disconnection events.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+initialCapacity
+ +int64 + +
+
+webhook
+ + +Kubernetes admissionregistration/v1beta1.WebhookClientConfig + + +
+

PortPolicy (string alias)

From 559230a740087fcfb3f70faa82901f2b7e9a9a2d Mon Sep 17 00:00:00 2001 From: Mark Mandel Date: Fri, 7 Feb 2020 18:20:48 -0800 Subject: [PATCH 2/2] Including feature flag locking. Required to allow tests to pass with race checking enabled. --- .../crds/_gameserverspecvalidation.yaml | 4 +++- install/yaml/install.yaml | 12 +++++++++--- pkg/apis/agones/v1/gameserver.go | 7 +++---- pkg/apis/agones/v1/gameserver_test.go | 11 ++++------- pkg/util/runtime/features.go | 17 +++++++++++++++++ pkg/util/runtime/features_test.go | 3 +++ .../Reference/agones_crd_api_reference.html | 8 ++++---- 7 files changed, 43 insertions(+), 19 deletions(-) diff --git a/install/helm/agones/templates/crds/_gameserverspecvalidation.yaml b/install/helm/agones/templates/crds/_gameserverspecvalidation.yaml index 1ed768d7f5..10fc7495d3 100644 --- a/install/helm/agones/templates/crds/_gameserverspecvalidation.yaml +++ b/install/helm/agones/templates/crds/_gameserverspecvalidation.yaml @@ -158,7 +158,7 @@ properties: properties: initialCapacity: type: integer - title: The intial player capacity that this Game Server has + title: The initial player capacity of this Game Server minimum: 0 webhook: type: object @@ -174,4 +174,6 @@ properties: type: string url: type: string + caBundle: + type: string {{- end }} \ No newline at end of file diff --git a/install/yaml/install.yaml b/install/yaml/install.yaml index e7a15f8197..74641e086a 100644 --- a/install/yaml/install.yaml +++ b/install/yaml/install.yaml @@ -404,7 +404,7 @@ spec: properties: initialCapacity: type: integer - title: The intial player capacity that this Game Server has + title: The initial player capacity of this Game Server minimum: 0 webhook: type: object @@ -420,6 +420,8 @@ spec: type: string url: type: string + caBundle: + type: string subresources: # status enables the status subresource. status: {} @@ -716,7 +718,7 @@ spec: properties: initialCapacity: type: integer - title: The intial player capacity that this Game Server has + title: The initial player capacity of this Game Server minimum: 0 webhook: type: object @@ -732,6 +734,8 @@ spec: type: string url: type: string + caBundle: + type: string --- # Source: agones/templates/crds/gameserverallocationpolicy.yaml @@ -1044,7 +1048,7 @@ spec: properties: initialCapacity: type: integer - title: The intial player capacity that this Game Server has + title: The initial player capacity of this Game Server minimum: 0 webhook: type: object @@ -1060,6 +1064,8 @@ spec: type: string url: type: string + caBundle: + type: string subresources: # status enables the status subresource. status: {} diff --git a/pkg/apis/agones/v1/gameserver.go b/pkg/apis/agones/v1/gameserver.go index e5012d9464..e24c0820f8 100644 --- a/pkg/apis/agones/v1/gameserver.go +++ b/pkg/apis/agones/v1/gameserver.go @@ -19,11 +19,10 @@ import ( "fmt" "net" - "agones.dev/agones/pkg/util/runtime" - "agones.dev/agones/pkg" "agones.dev/agones/pkg/apis" "agones.dev/agones/pkg/apis/agones" + "agones.dev/agones/pkg/util/runtime" "github.com/mattbaird/jsonpatch" "github.com/pkg/errors" admregv1b "k8s.io/api/admissionregistration/v1beta1" @@ -152,11 +151,11 @@ type GameServerSpec struct { SdkServer SdkServer `json:"sdkServer,omitempty"` // Template describes the Pod that will be created for the GameServer Template corev1.PodTemplateSpec `json:"template"` - // AlphaSpec describes the alpha properties for the GameServer + // Alpha describes the alpha properties for the GameServer. Alpha AlphaSpec `json:"alpha,omitempty"` } -// AlphaSpec is the alpha properties of the GameServer +// AlphaSpec contains the alpha properties of the GameServer. type AlphaSpec struct { Players PlayersSpec `json:"players"` } diff --git a/pkg/apis/agones/v1/gameserver_test.go b/pkg/apis/agones/v1/gameserver_test.go index a62c3e7df1..b99e0965f1 100644 --- a/pkg/apis/agones/v1/gameserver_test.go +++ b/pkg/apis/agones/v1/gameserver_test.go @@ -17,14 +17,12 @@ package v1 import ( "fmt" "strings" - "sync" "testing" - "agones.dev/agones/pkg/util/runtime" - "agones.dev/agones/pkg" "agones.dev/agones/pkg/apis" "agones.dev/agones/pkg/apis/agones" + "agones.dev/agones/pkg/util/runtime" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -328,13 +326,12 @@ func TestGameServerApplyDefaults(t *testing.T) { }, } - // otherwise the race condition detector is not happy. - mtx := sync.Mutex{} + runtime.FeatureTestMutex.Lock() + defer runtime.FeatureTestMutex.Unlock() + for name, test := range data { t.Run(name, func(t *testing.T) { - mtx.Lock() err := runtime.ParseFeatures(test.featureFlags) - mtx.Unlock() assert.NoError(t, err) test.gameServer.ApplyDefaults() diff --git a/pkg/util/runtime/features.go b/pkg/util/runtime/features.go index 08e8bbf7b5..ed227515b1 100644 --- a/pkg/util/runtime/features.go +++ b/pkg/util/runtime/features.go @@ -17,6 +17,7 @@ package runtime import ( "net/url" "strconv" + "sync" "github.com/pkg/errors" "github.com/spf13/pflag" @@ -44,6 +45,14 @@ var ( // featureGates is the storage of what features are enabled // or disabled. featureGates map[Feature]bool + + // featureMutex ensures that updates to featureGates don't happen at the same time as reads. + // this is mostly to protect tests which can change gates in parallel. + featureMutex = sync.RWMutex{} + + // FeatureTestMutex is a mutex to be shared between tests to ensure that a test that involves changing featureGates + // cannot accidentally run at the same time as another test that also changing feature flags. + FeatureTestMutex sync.Mutex ) // Feature is a type for defining feature gates. @@ -70,6 +79,9 @@ func ParseFeaturesFromEnv() error { // ParseFeatures parses the url encoded query string of features and stores the value // for later retrieval func ParseFeatures(queryString string) error { + featureMutex.Lock() + defer featureMutex.Unlock() + features := map[Feature]bool{} // copy the defaults into this map for k, v := range featureDefaults { @@ -101,12 +113,17 @@ func ParseFeatures(queryString string) error { // FeatureEnabled returns if a Feature is enabled or not func FeatureEnabled(feature Feature) bool { + featureMutex.RLock() + defer featureMutex.RUnlock() return featureGates[feature] } // EncodeFeatures returns the feature set as a URL encoded query string func EncodeFeatures() string { values := url.Values{} + featureMutex.RLock() + defer featureMutex.RUnlock() + for k, v := range featureGates { values.Add(string(k), strconv.FormatBool(v)) } diff --git a/pkg/util/runtime/features_test.go b/pkg/util/runtime/features_test.go index 70c44c1dd4..25219887fc 100644 --- a/pkg/util/runtime/features_test.go +++ b/pkg/util/runtime/features_test.go @@ -26,6 +26,9 @@ import ( func TestFeatures(t *testing.T) { t.Parallel() + FeatureTestMutex.Lock() + defer FeatureTestMutex.Unlock() + // stable feature flag state featureDefaults = map[Feature]bool{ FeatureExample: true, diff --git a/site/content/en/docs/Reference/agones_crd_api_reference.html b/site/content/en/docs/Reference/agones_crd_api_reference.html index 4d5d0fc188..c8fd1861cd 100644 --- a/site/content/en/docs/Reference/agones_crd_api_reference.html +++ b/site/content/en/docs/Reference/agones_crd_api_reference.html @@ -2544,7 +2544,7 @@

GameServer -

AlphaSpec describes the alpha properties for the GameServer

+

Alpha describes the alpha properties for the GameServer.

@@ -2682,7 +2682,7 @@

AlphaSpec GameServerSpec)

-

AlphaSpec is the alpha properties of the GameServer

+

AlphaSpec contains the alpha properties of the GameServer.

@@ -3183,7 +3183,7 @@

GameServerSpec

@@ -3456,7 +3456,7 @@

GameServerTemplateSpec

-

AlphaSpec describes the alpha properties for the GameServer

+

Alpha describes the alpha properties for the GameServer.

-

AlphaSpec describes the alpha properties for the GameServer

+

Alpha describes the alpha properties for the GameServer.