Skip to content

Commit

Permalink
Add validation of the fleet underlying gameserver
Browse files Browse the repository at this point in the history
Now we perform additional validation on the Gameserver specification.
For googleforgames#765.
  • Loading branch information
aLekSer committed May 17, 2019
1 parent c0723c2 commit c2a3f8f
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 50 deletions.
8 changes: 8 additions & 0 deletions pkg/apis/stable/v1alpha1/fleet.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ func (f *Fleet) Validate() ([]metav1.StatusCause, bool) {
})
}

// check gameserver specification in a fleet
gsSpec := f.Spec.Template.Spec
gsSpec.ApplyDefaults()
gsCauses, ok := gsSpec.Validate("")
if !ok {
causes = append(causes, gsCauses...)
}

return causes, len(causes) == 0
}

Expand Down
41 changes: 40 additions & 1 deletion pkg/apis/stable/v1alpha1/fleet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,30 @@ func TestSumStatusAllocatedReplicas(t *testing.T) {
assert.Equal(t, int32(5), SumStatusAllocatedReplicas([]*GameServerSet{gsSet1, gsSet2}))
}

func TestFleetGameserverSpec(t *testing.T) {
f := defaultFleet()
f.Spec.Template.Spec.Template =
corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Name: "container", Image: "myimage"}, {Name: "container2", Image: "myimage"}},
},
}
causes, ok := f.Validate()

assert.False(t, ok)
assert.Len(t, causes, 2)
assert.Equal(t, "container", causes[0].Field)

f.Spec.Template.Spec.Container = "testing"
causes, ok = f.Validate()

assert.False(t, ok)
assert.Len(t, causes, 1)
assert.Equal(t, "Could not find a container named testing", causes[0].Message)
}

func TestFleetName(t *testing.T) {
f := Fleet{}
f := defaultFleet()

nameLen := validation.LabelValueMaxLength + 1
bytes := make([]byte, nameLen)
Expand Down Expand Up @@ -130,3 +152,20 @@ func TestSumStatusReplicas(t *testing.T) {

assert.Equal(t, int32(30), SumStatusReplicas(fixture))
}

func defaultFleet() *Fleet {
gs := GameServer{
Spec: GameServerSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "testing", Image: "testing/image"}}}}},
}
return &Fleet{
ObjectMeta: metav1.ObjectMeta{GenerateName: "simple-fleet-", Namespace: "defaultNs"},
Spec: FleetSpec{
Replicas: 2,
Template: GameServerTemplateSpec{
Spec: gs.Spec,
},
},
}
}
118 changes: 71 additions & 47 deletions pkg/apis/stable/v1alpha1/gameserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,31 +187,36 @@ type GameServerStatusPort struct {
func (gs *GameServer) ApplyDefaults() {
gs.ObjectMeta.Finalizers = append(gs.ObjectMeta.Finalizers, stable.GroupName)

gs.applyContainerDefaults()
gs.applyPortDefaults()
gs.Spec.ApplyDefaults()
gs.applyStateDefaults()
gs.applyHealthDefaults()
gs.applySchedulingDefaults()
}

// ApplyDefaults applies default values to the GameServerSpec if they are not already populated
func (gss *GameServerSpec) ApplyDefaults() {
gss.applyContainerDefaults()
gss.applyPortDefaults()
gss.applyHealthDefaults()
gss.applySchedulingDefaults()
}

// applyContainerDefaults applues the container defaults
func (gs *GameServer) applyContainerDefaults() {
if len(gs.Spec.Template.Spec.Containers) == 1 {
gs.Spec.Container = gs.Spec.Template.Spec.Containers[0].Name
func (gss *GameServerSpec) applyContainerDefaults() {
if len(gss.Template.Spec.Containers) == 1 {
gss.Container = gss.Template.Spec.Containers[0].Name
}
}

// applyHealthDefaults applies health checking defaults
func (gs *GameServer) applyHealthDefaults() {
if !gs.Spec.Health.Disabled {
if gs.Spec.Health.PeriodSeconds <= 0 {
gs.Spec.Health.PeriodSeconds = 5
func (gss *GameServerSpec) applyHealthDefaults() {
if !gss.Health.Disabled {
if gss.Health.PeriodSeconds <= 0 {
gss.Health.PeriodSeconds = 5
}
if gs.Spec.Health.FailureThreshold <= 0 {
gs.Spec.Health.FailureThreshold = 3
if gss.Health.FailureThreshold <= 0 {
gss.Health.FailureThreshold = 3
}
if gs.Spec.Health.InitialDelaySeconds <= 0 {
gs.Spec.Health.InitialDelaySeconds = 5
if gss.Health.InitialDelaySeconds <= 0 {
gss.Health.InitialDelaySeconds = 5
}
}
}
Expand All @@ -220,50 +225,40 @@ func (gs *GameServer) applyHealthDefaults() {
func (gs *GameServer) applyStateDefaults() {
if gs.Status.State == "" {
gs.Status.State = GameServerStateCreating
// applyStateDefaults() should be called after applyPortDefaults()
if gs.HasPortPolicy(Dynamic) {
gs.Status.State = GameServerStatePortAllocation
}
}
}

// applyPortDefaults applies default values for all ports
func (gs *GameServer) applyPortDefaults() {
for i, p := range gs.Spec.Ports {
func (gss *GameServerSpec) applyPortDefaults() {
for i, p := range gss.Ports {
// basic spec
if p.PortPolicy == "" {
gs.Spec.Ports[i].PortPolicy = Dynamic
gss.Ports[i].PortPolicy = Dynamic
}

if p.Protocol == "" {
gs.Spec.Ports[i].Protocol = "UDP"
gss.Ports[i].Protocol = "UDP"
}
}
}

func (gs *GameServer) applySchedulingDefaults() {
if gs.Spec.Scheduling == "" {
gs.Spec.Scheduling = apis.Packed
func (gss *GameServerSpec) applySchedulingDefaults() {
if gss.Scheduling == "" {
gss.Scheduling = apis.Packed
}
}

// Validate validates the GameServer configuration.
// If a GameServer is invalid there will be > 0 values in
// Validate validates the GameServerSpec configuration.
// devAddress is a specific IP address used for local Gameservers, for fleets "" is used
// If a GameServer Spec is invalid there will be > 0 values in
// the returned array
func (gs *GameServer) Validate() ([]metav1.StatusCause, bool) {
func (gss GameServerSpec) Validate(devAddress string) ([]metav1.StatusCause, bool) {
var causes []metav1.StatusCause

// Long GS name would produce an invalid Label for Pod
if len(gs.Name) > validation.LabelValueMaxLength {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Field: fmt.Sprintf("Name"),
Message: fmt.Sprintf("Length of Gameserver '%s' name should be no more than 63 characters.", gs.ObjectMeta.Name),
})
}

// make sure the host port is specified if this is a development server
devAddress, hasDevAddress := gs.GetDevAddress()
if hasDevAddress {
if devAddress != "" {
// verify that the value is a valid IP address.
if net.ParseIP(devAddress) == nil {
causes = append(causes, metav1.StatusCause{
Expand All @@ -273,7 +268,7 @@ func (gs *GameServer) Validate() ([]metav1.StatusCause, bool) {
})
}

for _, p := range gs.Spec.Ports {
for _, p := range gss.Ports {
if p.HostPort == 0 {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueRequired,
Expand All @@ -291,7 +286,7 @@ func (gs *GameServer) Validate() ([]metav1.StatusCause, bool) {
}
} else {
// make sure a name is specified when there is multiple containers in the pod.
if len(gs.Spec.Container) == 0 && len(gs.Spec.Template.Spec.Containers) > 1 {
if len(gss.Container) == 0 && len(gss.Template.Spec.Containers) > 1 {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Field: "container",
Expand All @@ -300,7 +295,7 @@ func (gs *GameServer) Validate() ([]metav1.StatusCause, bool) {
}

// no host port when using dynamic PortPolicy
for _, p := range gs.Spec.Ports {
for _, p := range gss.Ports {
if p.HostPort > 0 && p.PortPolicy == Dynamic {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Expand All @@ -311,7 +306,7 @@ func (gs *GameServer) Validate() ([]metav1.StatusCause, bool) {
}

// make sure the container value points to a valid container
_, _, err := gs.FindGameServerContainer()
_, _, err := gss.FindGameServerContainer()
if err != nil {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Expand All @@ -320,7 +315,29 @@ func (gs *GameServer) Validate() ([]metav1.StatusCause, bool) {
})
}
}
return causes, len(causes) == 0

}

// Validate validates the GameServer configuration.
// If a GameServer is invalid there will be > 0 values in
// the returned array
func (gs *GameServer) Validate() ([]metav1.StatusCause, bool) {
var causes []metav1.StatusCause

// Long GS name would produce an invalid Label for Pod
if len(gs.Name) > validation.LabelValueMaxLength {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Field: fmt.Sprintf("Name"),
Message: fmt.Sprintf("Length of Gameserver '%s' name should be no more than 63 characters.", gs.ObjectMeta.Name),
})
}

// make sure the host port is specified if this is a development server
devAddress, _ := gs.GetDevAddress()
gssCauses, _ := gs.Spec.Validate(devAddress)
causes = append(causes, gssCauses...)
return causes, len(causes) == 0
}

Expand All @@ -341,16 +358,23 @@ func (gs *GameServer) IsBeingDeleted() bool {
}

// FindGameServerContainer returns the container that is specified in
// spec.gameServer.container. Returns the index and the value.
// gameServer.Spec.Container. Returns the index and the value.
// Returns an error if not found
func (gs *GameServer) FindGameServerContainer() (int, corev1.Container, error) {
for i, c := range gs.Spec.Template.Spec.Containers {
if c.Name == gs.Spec.Container {
func (gss *GameServerSpec) FindGameServerContainer() (int, corev1.Container, error) {
for i, c := range gss.Template.Spec.Containers {
if c.Name == gss.Container {
return i, c, nil
}
}

return -1, corev1.Container{}, errors.Errorf("Could not find a container named %s", gs.Spec.Container)
return -1, corev1.Container{}, errors.Errorf("Could not find a container named %s", gss.Container)
}

// FindGameServerContainer returns the container that is specified in
// gameServer.Spec.Container. Returns the index and the value.
// Returns an error if not found
func (gs *GameServer) FindGameServerContainer() (int, corev1.Container, error) {
return gs.Spec.FindGameServerContainer()
}

// ApplyToPodGameServerContainer applies func(v1.Container) to the pod's gameserver container
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/fleetautoscaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func TestAutoscalerBasicFunctions(t *testing.T) {

// check that we are able to scale
framework.WaitForFleetAutoScalerCondition(t, fas, func(fas *v1alpha1.FleetAutoscaler) bool {
return fas.Status.ScalingLimited == false
return !fas.Status.ScalingLimited
})

// patch autoscaler to a maxReplicas count equal to current replicas count
Expand All @@ -113,7 +113,7 @@ func TestAutoscalerBasicFunctions(t *testing.T) {

// check that we are not able to scale
framework.WaitForFleetAutoScalerCondition(t, fas, func(fas *v1alpha1.FleetAutoscaler) bool {
return fas.Status.ScalingLimited == true
return fas.Status.ScalingLimited
})

// delete the allocated GameServer and watch the fleet scale down
Expand Down

0 comments on commit c2a3f8f

Please sign in to comment.