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
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+players
+
+
+PlayersSpec
+
+
+ |
+
+ |
+
+
+
+AlphaStatus
+
+
+(Appears on:
+GameServerStatus)
+
+
+
AlphaStatus is the alpha status values for a GameServer
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+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
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+count
+
+int64
+
+ |
+
+ |
+
+
+
+capacity
+
+int64
+
+ |
+
+ |
+
+
+
+
+
+(Appears on:
+AlphaSpec)
+
+
+
PlayersSpec tracks the initial player capacity, and what webhooks to send events to when there are
+connection/disconnection events.
+
+
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 is the alpha properties of the GameServer
+AlphaSpec contains the alpha properties of the GameServer.
@@ -3183,7 +3183,7 @@ GameServerSpec
- AlphaSpec describes the alpha properties for the GameServer
+Alpha describes the alpha properties for the GameServer.
|
@@ -3456,7 +3456,7 @@ GameServerTemplateSpec
- AlphaSpec describes the alpha properties for the GameServer
+Alpha describes the alpha properties for the GameServer.
|