Skip to content

Commit

Permalink
GameServerSet Implementation
Browse files Browse the repository at this point in the history
GameServerSets are the basic building block for Fleets.
GameServerSets will be allow Fleet migrations to occur, similarly
to how ReplicaSets allow Deployments to migrate one image type
to another.

This has not been formally documented, as this will likely be an
internal CRD, and not (widely) used externally.

Parent ticket: googleforgames#70
  • Loading branch information
markmandel committed Apr 10, 2018
1 parent 794dcdb commit cdfab72
Show file tree
Hide file tree
Showing 25 changed files with 1,913 additions and 19 deletions.
Empty file added build/install.yaml
Empty file.
24 changes: 17 additions & 7 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"agones.dev/agones/pkg/client/clientset/versioned"
"agones.dev/agones/pkg/client/informers/externalversions"
"agones.dev/agones/pkg/gameservers"
"agones.dev/agones/pkg/gameserversets"
"agones.dev/agones/pkg/util/runtime"
"agones.dev/agones/pkg/util/signals"
"agones.dev/agones/pkg/util/webhooks"
Expand Down Expand Up @@ -128,7 +129,8 @@ func main() {
agonesInformerFactory := externalversions.NewSharedInformerFactory(agonesClient, 30*time.Second)
kubeInformationFactory := informers.NewSharedInformerFactory(kubeClient, 30*time.Second)

c := gameservers.NewController(wh, health, minPort, maxPort, sidecarImage, alwaysPullSidecar, kubeClient, kubeInformationFactory, extClient, agonesClient, agonesInformerFactory)
gsController := gameservers.NewController(wh, health, minPort, maxPort, sidecarImage, alwaysPullSidecar, kubeClient, kubeInformationFactory, extClient, agonesClient, agonesInformerFactory)
gsSetController := gameserversets.NewController(wh, health, kubeClient, extClient, agonesClient, agonesInformerFactory)

stop := signals.NewStopChannel()

Expand All @@ -140,6 +142,18 @@ func main() {
logger.WithError(err).Fatal("could not run webhook server")
}
}()
go func() {
err = gsController.Run(2, stop)
if err != nil {
logger.WithError(err).Fatal("Could not run gameserver controller")
}
}()
go func() {
err = gsSetController.Run(2, stop)
if err != nil {
logger.WithError(err).Fatal("Could not run gameserverset controller")
}
}()
go func() {
logger.Info("Starting health check...")
srv := &http.Server{
Expand All @@ -158,10 +172,6 @@ func main() {
}
}()

err = c.Run(2, stop)
if err != nil {
logger.WithError(err).Fatal("Could not run gameserver controller")
}

logger.Info("Shut down gameserver controller")
<-stop
logger.Info("Shut down agones controllers")
}
33 changes: 33 additions & 0 deletions examples/cpp-simple/gameserverset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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.

# Usually you would define a Fleet rather than a GameServerSet
# directly. This is here mostly for testing purposes

apiVersion: "stable.agones.dev/v1alpha1"
kind: GameServerSet
metadata:
name: cpp-simple
spec:
replicas: 5
template:
spec:
containerPort: 7654
template:
spec:
health:
initialDelaySeconds: 15
containers:
- name: cpp-simple
image: gcr.io/agones-images/cpp-simple-server:0.1
31 changes: 31 additions & 0 deletions examples/simple-udp/server/gameserverset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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.

# Usually you would define a Fleet rather than a GameServerSet
# directly. This is here mostly for testing purposes

apiVersion: "stable.agones.dev/v1alpha1"
kind: GameServerSet
metadata:
name: simple-udp
spec:
replicas: 2
template:
spec:
containerPort: 7654
template:
spec:
containers:
- name: simple-udp
image: gcr.io/agones-images/udp-server:0.1
40 changes: 38 additions & 2 deletions install/yaml/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,34 @@ spec:
minimum: 1
maximum: 2147483648
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: gameserversets.stable.agones.dev
spec:
group: stable.agones.dev
version: v1alpha1
scope: Namespaced
names:
kind: GameServerSet
plural: gameserversets
shortNames:
- gss
- gsset
singular: gameserverset
validation:
openAPIV3Schema:
properties:
spec:
required:
- replicas
- template
properties:
replicas:
type: integer
minimum: 0
template:
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
Expand Down Expand Up @@ -218,6 +246,14 @@ webhooks:
- "v1alpha1"
operations:
- CREATE
- apiGroups:
- stable.agones.dev
resources:
- "gameserversets"
apiVersions:
- "v1alpha1"
operations:
- UPDATE
---
# Service account, secret, role and rolebinding for sidecar (agones-sdk) pod
apiVersion: v1
Expand Down Expand Up @@ -296,8 +332,8 @@ rules:
resources: ["customresourcedefinitions"]
verbs: ["get"]
- apiGroups: ["stable.agones.dev"]
resources: ["gameservers"]
verbs: ["delete", "get", "list", "update", "watch"]
resources: ["gameservers", "gameserversets"]
verbs: ["create", "delete", "get", "list", "update", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const (
Error State = "Error"
// Unhealthy is when the GameServer has failed its health checks
Unhealthy State = "Unhealthy"
// Allocated is when the GameServer has been allocated to a session
Allocated State = "Allocated"

// Static PortPolicy means that the user defines the hostPort to be used
// in the configuration.
Expand Down Expand Up @@ -85,6 +87,22 @@ type GameServer struct {
Status GameServerStatus `json:"status"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// GameServerList is a list of GameServer resources
type GameServerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

Items []GameServer `json:"items"`
}

// GameServerTemplateSpec is a template for GameServers
type GameServerTemplateSpec struct {
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec GameServerSpec `json:"spec"`
}

// GameServerSpec is the spec for a GameServer resource
type GameServerSpec struct {
// Container specifies which Pod container is the game server. Only required if there is more than one
Expand Down Expand Up @@ -135,16 +153,6 @@ type GameServerStatus struct {
NodeName string `json:"nodeName"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// GameServerList is a list of GameServer resources
type GameServerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

Items []GameServer `json:"items"`
}

// ApplyDefaults applies default values to the GameServer if they are not already populated
func (gs *GameServer) ApplyDefaults() {
gs.ObjectMeta.Finalizers = append(gs.ObjectMeta.Finalizers, stable.GroupName)
Expand Down
File renamed without changes.
112 changes: 112 additions & 0 deletions pkg/apis/stable/v1alpha1/gameserverset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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 v1alpha1

import (
"reflect"

"agones.dev/agones/pkg/apis/stable"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// GameServerSetGameServerLabel is the label that the name of the GameServerSet
// is set on the GameServer the GameServerSet controls
GameServerSetGameServerLabel = stable.GroupName + "/gameserverset"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// GameServerSet is the data structure a set of GameServers
// This matches philosophically with the relationship between
// Depoyments and ReplicaSets
type GameServerSet struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec GameServerSetSpec `json:"spec"`
Status GameServerSetStatus `json:"status"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// GameServerSetList is a list of GameServerSet resources
type GameServerSetList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

Items []GameServerSet `json:"items"`
}

// GameServerSetSpec the specification for
type GameServerSetSpec struct {
// Replicas are the number of GameServers that should be in this set
Replicas int32 `json:"replicas"`
// Template the GameServer template to apply for this GameServerSet
Template GameServerTemplateSpec `json:"template"`
}

// GameServerSetStatus is the status of a GameServerSet
type GameServerSetStatus struct {
// Replicas the total number of current GameServer replicas
Replicas int32 `json:"replicas"`
// ReadyReplicas are the number of Ready GameServer replicas
ReadyReplicas int32 `json:"readyReplicas"`
}

// ValidateUpdate validates when updates occur. The argument
// is the new GameServerSet, being passed into the old GameServerSet
func (gsSet *GameServerSet) ValidateUpdate(new *GameServerSet) (bool, []metav1.StatusCause) {
var causes []metav1.StatusCause
if !reflect.DeepEqual(gsSet.Spec.Template, new.Spec.Template) {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Field: "template",
Message: "template values cannot be updated after creation",
})
}

return len(causes) == 0, causes
}

// GameServer returns a single GameServer derived
// from the GameSever template
func (gsSet *GameServerSet) GameServer() *GameServer {
gs := &GameServer{
ObjectMeta: *gsSet.Spec.Template.ObjectMeta.DeepCopy(),
Spec: *gsSet.Spec.Template.Spec.DeepCopy(),
}

// Switch to GenerateName, so that we always get a Unique name for the GameServer, and there
// can be no collisions
gs.ObjectMeta.GenerateName = gsSet.ObjectMeta.Name + "-"
gs.ObjectMeta.Name = ""
gs.ObjectMeta.Namespace = gsSet.ObjectMeta.Namespace
gs.ObjectMeta.ResourceVersion = ""
gs.ObjectMeta.UID = ""

ref := metav1.NewControllerRef(gsSet, SchemeGroupVersion.WithKind("GameServerSet"))
gs.ObjectMeta.OwnerReferences = append(gs.ObjectMeta.OwnerReferences, *ref)

if gs.ObjectMeta.Labels == nil {
gs.ObjectMeta.Labels = make(map[string]string, 1)
}

gs.ObjectMeta.Labels[GameServerSetGameServerLabel] = gsSet.ObjectMeta.Name

return gs
}
Loading

0 comments on commit cdfab72

Please sign in to comment.