diff --git a/build/install.yaml b/build/install.yaml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cmd/controller/main.go b/cmd/controller/main.go index adfa6c0cc4..e6fca0c4e3 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -25,6 +25,7 @@ import ( "agones.dev/agones/pkg" "agones.dev/agones/pkg/client/clientset/versioned" "agones.dev/agones/pkg/client/informers/externalversions" + "agones.dev/agones/pkg/fleets" "agones.dev/agones/pkg/gameservers" "agones.dev/agones/pkg/gameserversets" "agones.dev/agones/pkg/util/runtime" @@ -41,12 +42,13 @@ import ( ) const ( - sidecarFlag = "sidecar" - pullSidecarFlag = "always-pull-sidecar" - minPortFlag = "min-port" - maxPortFlag = "max-port" - certFileFlag = "cert-file" - keyFileFlag = "key-file" + sidecarFlag = "sidecar" + pullSidecarFlag = "always-pull-sidecar" + minPortFlag = "min-port" + maxPortFlag = "max-port" + certFileFlag = "cert-file" + keyFileFlag = "key-file" + controllerThreadiness = 2 ) var ( @@ -131,6 +133,7 @@ func main() { gsController := gameservers.NewController(wh, health, minPort, maxPort, sidecarImage, alwaysPullSidecar, kubeClient, kubeInformationFactory, extClient, agonesClient, agonesInformerFactory) gsSetController := gameserversets.NewController(wh, health, kubeClient, extClient, agonesClient, agonesInformerFactory) + fleetController := fleets.NewController(health, kubeClient, extClient, agonesClient, agonesInformerFactory) stop := signals.NewStopChannel() @@ -143,17 +146,23 @@ func main() { } }() go func() { - err = gsController.Run(2, stop) + err = gsController.Run(controllerThreadiness, stop) if err != nil { logger.WithError(err).Fatal("Could not run gameserver controller") } }() go func() { - err = gsSetController.Run(2, stop) + err = gsSetController.Run(controllerThreadiness, stop) if err != nil { logger.WithError(err).Fatal("Could not run gameserverset controller") } }() + go func() { + err = fleetController.Run(controllerThreadiness, stop) + if err != nil { + logger.WithError(err).Fatal("Could not run fleet controller") + } + }() go func() { logger.Info("Starting health check...") srv := &http.Server{ diff --git a/examples/cpp-simple/fleet.yaml b/examples/cpp-simple/fleet.yaml new file mode 100644 index 0000000000..b59205157b --- /dev/null +++ b/examples/cpp-simple/fleet.yaml @@ -0,0 +1,28 @@ +# Copyright 2017 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. + +apiVersion: "stable.agones.dev/v1alpha1" +kind: Fleet +metadata: + # generate a unique name + # will need to be created with `kubectl create` + generateName: cpp-simple- +spec: + containerPort: 7654 + template: + spec: + containers: + - name: cpp-simple + image: gcr.io/agones-images/cpp-simple-server:0.1 + # imagePullPolicy: Always # add for development \ No newline at end of file diff --git a/examples/simple-udp/server/fleet.yaml b/examples/simple-udp/server/fleet.yaml new file mode 100644 index 0000000000..6971c79746 --- /dev/null +++ b/examples/simple-udp/server/fleet.yaml @@ -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: Fleet +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 \ No newline at end of file diff --git a/install/helm/agones/templates/crds/fleet.yaml b/install/helm/agones/templates/crds/fleet.yaml new file mode 100644 index 0000000000..4648fee42f --- /dev/null +++ b/install/helm/agones/templates/crds/fleet.yaml @@ -0,0 +1,41 @@ +# 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. + +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: fleets.stable.agones.dev +spec: + group: stable.agones.dev + version: v1alpha1 + scope: Namespaced + names: + kind: Fleet + plural: fleets + shortNames: + - flt + singular: fleet + validation: + openAPIV3Schema: + properties: + spec: + required: + - replicas + - template + properties: + replicas: + type: integer + minimum: 0 + template: + {{- include "gameserver.validation" . | indent 14 }} \ No newline at end of file diff --git a/install/helm/agones/templates/serviceaccounts/controller.yaml b/install/helm/agones/templates/serviceaccounts/controller.yaml index 3d4c6ae872..497f31e9c3 100644 --- a/install/helm/agones/templates/serviceaccounts/controller.yaml +++ b/install/helm/agones/templates/serviceaccounts/controller.yaml @@ -47,7 +47,7 @@ rules: resources: ["customresourcedefinitions"] verbs: ["get"] - apiGroups: ["stable.agones.dev"] - resources: ["gameservers", "gameserversets"] + resources: ["gameservers", "gameserversets", "fleets"] verbs: ["create", "delete", "get", "list", "update", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/install/yaml/install.yaml b/install/yaml/install.yaml index 58639cd4f4..c87ea21db2 100644 --- a/install/yaml/install.yaml +++ b/install/yaml/install.yaml @@ -74,7 +74,7 @@ rules: resources: ["customresourcedefinitions"] verbs: ["get"] - apiGroups: ["stable.agones.dev"] - resources: ["gameservers", "gameserversets"] + resources: ["gameservers", "gameserversets", "fleets"] verbs: ["create", "delete", "get", "list", "update", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 @@ -156,6 +156,139 @@ roleRef: kind: ClusterRole name: agones-sdk --- +# Source: agones/templates/crds/fleet.yaml +# 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. + +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: fleets.stable.agones.dev +spec: + group: stable.agones.dev + version: v1alpha1 + scope: Namespaced + names: + kind: Fleet + plural: fleets + shortNames: + - flt + singular: fleet + validation: + openAPIV3Schema: + properties: + spec: + required: + - replicas + - template + properties: + replicas: + type: integer + minimum: 0 + template: + required: + - spec + properties: + spec: + required: + - containerPort + - template + properties: + template: + type: object + required: + - spec + properties: + spec: + type: object + required: + - containers + properties: + containers: + type: array + items: + type: object + required: + - image + properties: + name: + type: string + minLength: 0 + maxLength: 63 + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + image: + type: string + minLength: 1 + minItems: 1 + container: + title: The container name running the gameserver + description: if there is more than one container, specify which one is the game server + type: string + minLength: 0 + maxLength: 63 + pattern: "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" + portPolicy: + title: the port policy that will be applied to the game server + description: | + portPolicy has two options: + - "dynamic" (default) the system allocates a free hostPort for the gameserver, for game clients to connect to + - "static", user defines the hostPort that the game client will connect to. Then onus is on the user to ensure that the + port is available. When static is the policy specified, `hostPort` is required to be populated + type: string + enum: + - dynamic + - static + protocol: + title: Protocol being used. Defaults to UDP. TCP is the only other option + type: string + enum: + - UDP + - TCP + containerPort: + title: The port that is being opened on the game server process + type: integer + minimum: 0 + maximum: 65535 + hostPort: + title: The port exposed on the host + description: Only required when `portPolicy` is "static". Overwritten when portPolicy is "dynamic". + type: integer + minimum: 0 + maximum: 65535 + health: + type: object + title: Health checking for the running game server + properties: + disabled: + title: Disable health checking. defaults to false, but can be set to true + type: boolean + initialDelaySeconds: + title: Number of seconds after the container has started before health check is initiated. Defaults to 5 seconds + type: integer + minimum: 0 + maximum: 2147483648 + periodSeconds: + title: How long before the server is considered not healthy + type: integer + minimum: 0 + maximum: 2147483648 + failureThreshold: + title: Minimum consecutive failures for the health probe to be considered failed after having succeeded. + type: integer + minimum: 1 + maximum: 2147483648 +--- # Source: agones/templates/crds/gameserver.yaml # Copyright 2018 Google Inc. All Rights Reserved. # diff --git a/pkg/apis/stable/v1alpha1/fleet.go b/pkg/apis/stable/v1alpha1/fleet.go new file mode 100644 index 0000000000..0d0f48de74 --- /dev/null +++ b/pkg/apis/stable/v1alpha1/fleet.go @@ -0,0 +1,93 @@ +// 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 ( + "agones.dev/agones/pkg/apis/stable" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + FleetGameServerSetLabel = stable.GroupName + "/fleet" +) + +// +genclient +// +genclient:noStatus +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Fleet is the data structure for a gameserver resource +type Fleet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FleetSpec `json:"spec"` + Status FleetStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// FleetList is a list of GameServer resources +type FleetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []Fleet `json:"items"` +} + +// FleetSpec is the spec for a Fleet +type FleetSpec 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 Fleet + Template GameServerTemplateSpec `json:"template"` +} + +// FleetStatus is the status of a GameServerSet +type FleetStatus 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"` +} + +// Fleet returns a single GameServerSet for this Fleet definition +func (f *Fleet) GameServerSet() *GameServerSet { + gsSet := &GameServerSet{ + ObjectMeta: *f.Spec.Template.ObjectMeta.DeepCopy(), + Spec: GameServerSetSpec{ + Replicas: f.Spec.Replicas, + Template: f.Spec.Template, + }, + } + + // Switch to GenerateName, so that we always get a Unique name for the GameServerSet, and there + // can be no collisions + gsSet.ObjectMeta.GenerateName = f.ObjectMeta.Name + "-" + gsSet.ObjectMeta.Name = "" + gsSet.ObjectMeta.Namespace = f.ObjectMeta.Namespace + gsSet.ObjectMeta.ResourceVersion = "" + gsSet.ObjectMeta.UID = "" + + ref := metav1.NewControllerRef(f, SchemeGroupVersion.WithKind("Fleet")) + gsSet.ObjectMeta.OwnerReferences = append(gsSet.ObjectMeta.OwnerReferences, *ref) + + if gsSet.ObjectMeta.Labels == nil { + gsSet.ObjectMeta.Labels = make(map[string]string, 1) + } + + gsSet.ObjectMeta.Labels[FleetGameServerSetLabel] = f.ObjectMeta.Name + + return gsSet +} diff --git a/pkg/apis/stable/v1alpha1/fleet_test.go b/pkg/apis/stable/v1alpha1/fleet_test.go new file mode 100644 index 0000000000..130bfe61cc --- /dev/null +++ b/pkg/apis/stable/v1alpha1/fleet_test.go @@ -0,0 +1,55 @@ +// 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 ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestFleetGameServerSetGameServer(t *testing.T) { + f := Fleet{ + ObjectMeta: v1.ObjectMeta{ + Name: "test", + Namespace: "namespace", + UID: "1234", + }, + Spec: FleetSpec{ + Replicas: 10, + Template: GameServerTemplateSpec{ + Spec: GameServerSpec{ + ContainerPort: 1234, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "container", Image: "myimage"}}, + }, + }, + }, + }, + }, + } + + gsSet := f.GameServerSet() + assert.Equal(t, "", gsSet.ObjectMeta.Name) + assert.Equal(t, f.ObjectMeta.Namespace, gsSet.ObjectMeta.Namespace) + assert.Equal(t, f.ObjectMeta.Name+"-", gsSet.ObjectMeta.GenerateName) + assert.Equal(t, f.ObjectMeta.Name, gsSet.ObjectMeta.Labels[FleetGameServerSetLabel]) + assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas) + assert.Equal(t, f.Spec.Template, gsSet.Spec.Template) + assert.True(t, v1.IsControlledBy(gsSet, &f)) +} diff --git a/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go index 689331671f..5d5e5c3b41 100644 --- a/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go @@ -24,6 +24,102 @@ import ( 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 *Fleet) DeepCopyInto(out *Fleet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Fleet. +func (in *Fleet) DeepCopy() *Fleet { + if in == nil { + return nil + } + out := new(Fleet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Fleet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FleetList) DeepCopyInto(out *FleetList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Fleet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FleetList. +func (in *FleetList) DeepCopy() *FleetList { + if in == nil { + return nil + } + out := new(FleetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FleetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FleetSpec) DeepCopyInto(out *FleetSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FleetSpec. +func (in *FleetSpec) DeepCopy() *FleetSpec { + if in == nil { + return nil + } + out := new(FleetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FleetStatus) DeepCopyInto(out *FleetStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FleetStatus. +func (in *FleetStatus) DeepCopy() *FleetStatus { + if in == nil { + return nil + } + out := new(FleetStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GameServer) DeepCopyInto(out *GameServer) { *out = *in @@ -88,28 +184,28 @@ func (in *GameServerList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (gsSet *GameServerSet) DeepCopyInto(out *GameServerSet) { - *out = *gsSet - out.TypeMeta = gsSet.TypeMeta - gsSet.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - gsSet.Spec.DeepCopyInto(&out.Spec) - out.Status = gsSet.Status +func (in *GameServerSet) DeepCopyInto(out *GameServerSet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GameServerSet. -func (gsSet *GameServerSet) DeepCopy() *GameServerSet { - if gsSet == nil { +func (in *GameServerSet) DeepCopy() *GameServerSet { + if in == nil { return nil } out := new(GameServerSet) - gsSet.DeepCopyInto(out) + in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (gsSet *GameServerSet) DeepCopyObject() runtime.Object { - if c := gsSet.DeepCopy(); c != nil { +func (in *GameServerSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { return c } else { return nil diff --git a/pkg/client/clientset/versioned/typed/stable/v1alpha1/fake/fake_fleet.go b/pkg/client/clientset/versioned/typed/stable/v1alpha1/fake/fake_fleet.go new file mode 100644 index 0000000000..e0f862f1dd --- /dev/null +++ b/pkg/client/clientset/versioned/typed/stable/v1alpha1/fake/fake_fleet.go @@ -0,0 +1,125 @@ +// 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. + +// This code was autogenerated. Do not edit directly. +package fake + +import ( + v1alpha1 "agones.dev/agones/pkg/apis/stable/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeFleets implements FleetInterface +type FakeFleets struct { + Fake *FakeStableV1alpha1 + ns string +} + +var fleetsResource = schema.GroupVersionResource{Group: "stable.agones.dev", Version: "v1alpha1", Resource: "fleets"} + +var fleetsKind = schema.GroupVersionKind{Group: "stable.agones.dev", Version: "v1alpha1", Kind: "Fleet"} + +// Get takes name of the fleet, and returns the corresponding fleet object, and an error if there is any. +func (c *FakeFleets) Get(name string, options v1.GetOptions) (result *v1alpha1.Fleet, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(fleetsResource, c.ns, name), &v1alpha1.Fleet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Fleet), err +} + +// List takes label and field selectors, and returns the list of Fleets that match those selectors. +func (c *FakeFleets) List(opts v1.ListOptions) (result *v1alpha1.FleetList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(fleetsResource, fleetsKind, c.ns, opts), &v1alpha1.FleetList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.FleetList{} + for _, item := range obj.(*v1alpha1.FleetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested fleets. +func (c *FakeFleets) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(fleetsResource, c.ns, opts)) + +} + +// Create takes the representation of a fleet and creates it. Returns the server's representation of the fleet, and an error, if there is any. +func (c *FakeFleets) Create(fleet *v1alpha1.Fleet) (result *v1alpha1.Fleet, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(fleetsResource, c.ns, fleet), &v1alpha1.Fleet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Fleet), err +} + +// Update takes the representation of a fleet and updates it. Returns the server's representation of the fleet, and an error, if there is any. +func (c *FakeFleets) Update(fleet *v1alpha1.Fleet) (result *v1alpha1.Fleet, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(fleetsResource, c.ns, fleet), &v1alpha1.Fleet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Fleet), err +} + +// Delete takes name of the fleet and deletes it. Returns an error if one occurs. +func (c *FakeFleets) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(fleetsResource, c.ns, name), &v1alpha1.Fleet{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeFleets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(fleetsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.FleetList{}) + return err +} + +// Patch applies the patch and returns the patched fleet. +func (c *FakeFleets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Fleet, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(fleetsResource, c.ns, name, data, subresources...), &v1alpha1.Fleet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Fleet), err +} diff --git a/pkg/client/clientset/versioned/typed/stable/v1alpha1/fake/fake_stable_client.go b/pkg/client/clientset/versioned/typed/stable/v1alpha1/fake/fake_stable_client.go index f0681bea1f..41eb46535b 100644 --- a/pkg/client/clientset/versioned/typed/stable/v1alpha1/fake/fake_stable_client.go +++ b/pkg/client/clientset/versioned/typed/stable/v1alpha1/fake/fake_stable_client.go @@ -25,6 +25,10 @@ type FakeStableV1alpha1 struct { *testing.Fake } +func (c *FakeStableV1alpha1) Fleets(namespace string) v1alpha1.FleetInterface { + return &FakeFleets{c, namespace} +} + func (c *FakeStableV1alpha1) GameServers(namespace string) v1alpha1.GameServerInterface { return &FakeGameServers{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/stable/v1alpha1/fleet.go b/pkg/client/clientset/versioned/typed/stable/v1alpha1/fleet.go new file mode 100644 index 0000000000..8f3ab2d0ac --- /dev/null +++ b/pkg/client/clientset/versioned/typed/stable/v1alpha1/fleet.go @@ -0,0 +1,154 @@ +// 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. + +// This code was autogenerated. Do not edit directly. +package v1alpha1 + +import ( + v1alpha1 "agones.dev/agones/pkg/apis/stable/v1alpha1" + scheme "agones.dev/agones/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// FleetsGetter has a method to return a FleetInterface. +// A group's client should implement this interface. +type FleetsGetter interface { + Fleets(namespace string) FleetInterface +} + +// FleetInterface has methods to work with Fleet resources. +type FleetInterface interface { + Create(*v1alpha1.Fleet) (*v1alpha1.Fleet, error) + Update(*v1alpha1.Fleet) (*v1alpha1.Fleet, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.Fleet, error) + List(opts v1.ListOptions) (*v1alpha1.FleetList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Fleet, err error) + FleetExpansion +} + +// fleets implements FleetInterface +type fleets struct { + client rest.Interface + ns string +} + +// newFleets returns a Fleets +func newFleets(c *StableV1alpha1Client, namespace string) *fleets { + return &fleets{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the fleet, and returns the corresponding fleet object, and an error if there is any. +func (c *fleets) Get(name string, options v1.GetOptions) (result *v1alpha1.Fleet, err error) { + result = &v1alpha1.Fleet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("fleets"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Fleets that match those selectors. +func (c *fleets) List(opts v1.ListOptions) (result *v1alpha1.FleetList, err error) { + result = &v1alpha1.FleetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("fleets"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested fleets. +func (c *fleets) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("fleets"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a fleet and creates it. Returns the server's representation of the fleet, and an error, if there is any. +func (c *fleets) Create(fleet *v1alpha1.Fleet) (result *v1alpha1.Fleet, err error) { + result = &v1alpha1.Fleet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("fleets"). + Body(fleet). + Do(). + Into(result) + return +} + +// Update takes the representation of a fleet and updates it. Returns the server's representation of the fleet, and an error, if there is any. +func (c *fleets) Update(fleet *v1alpha1.Fleet) (result *v1alpha1.Fleet, err error) { + result = &v1alpha1.Fleet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("fleets"). + Name(fleet.Name). + Body(fleet). + Do(). + Into(result) + return +} + +// Delete takes name of the fleet and deletes it. Returns an error if one occurs. +func (c *fleets) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("fleets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *fleets) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("fleets"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched fleet. +func (c *fleets) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Fleet, err error) { + result = &v1alpha1.Fleet{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("fleets"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/stable/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/stable/v1alpha1/generated_expansion.go index 69f3013015..4771f5085e 100644 --- a/pkg/client/clientset/versioned/typed/stable/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/stable/v1alpha1/generated_expansion.go @@ -15,6 +15,8 @@ // This code was autogenerated. Do not edit directly. package v1alpha1 +type FleetExpansion interface{} + type GameServerExpansion interface{} type GameServerSetExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/stable/v1alpha1/stable_client.go b/pkg/client/clientset/versioned/typed/stable/v1alpha1/stable_client.go index 7b57ac2a08..401945b176 100644 --- a/pkg/client/clientset/versioned/typed/stable/v1alpha1/stable_client.go +++ b/pkg/client/clientset/versioned/typed/stable/v1alpha1/stable_client.go @@ -24,6 +24,7 @@ import ( type StableV1alpha1Interface interface { RESTClient() rest.Interface + FleetsGetter GameServersGetter GameServerSetsGetter } @@ -33,6 +34,10 @@ type StableV1alpha1Client struct { restClient rest.Interface } +func (c *StableV1alpha1Client) Fleets(namespace string) FleetInterface { + return newFleets(c, namespace) +} + func (c *StableV1alpha1Client) GameServers(namespace string) GameServerInterface { return newGameServers(c, namespace) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 78fde1793f..0c5c3683f6 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -53,6 +53,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=stable.agones.dev, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("fleets"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Stable().V1alpha1().Fleets().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("gameservers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Stable().V1alpha1().GameServers().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("gameserversets"): diff --git a/pkg/client/informers/externalversions/stable/v1alpha1/fleet.go b/pkg/client/informers/externalversions/stable/v1alpha1/fleet.go new file mode 100644 index 0000000000..8f3d954e0b --- /dev/null +++ b/pkg/client/informers/externalversions/stable/v1alpha1/fleet.go @@ -0,0 +1,89 @@ +// 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. + +// This code was autogenerated. Do not edit directly. + +// This file was automatically generated by informer-gen + +package v1alpha1 + +import ( + time "time" + + stable_v1alpha1 "agones.dev/agones/pkg/apis/stable/v1alpha1" + versioned "agones.dev/agones/pkg/client/clientset/versioned" + internalinterfaces "agones.dev/agones/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "agones.dev/agones/pkg/client/listers/stable/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// FleetInformer provides access to a shared informer and lister for +// Fleets. +type FleetInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.FleetLister +} + +type fleetInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewFleetInformer constructs a new informer for Fleet type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFleetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredFleetInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredFleetInformer constructs a new informer for Fleet type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredFleetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.StableV1alpha1().Fleets(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.StableV1alpha1().Fleets(namespace).Watch(options) + }, + }, + &stable_v1alpha1.Fleet{}, + resyncPeriod, + indexers, + ) +} + +func (f *fleetInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredFleetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *fleetInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&stable_v1alpha1.Fleet{}, f.defaultInformer) +} + +func (f *fleetInformer) Lister() v1alpha1.FleetLister { + return v1alpha1.NewFleetLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/stable/v1alpha1/interface.go b/pkg/client/informers/externalversions/stable/v1alpha1/interface.go index 00dea0a3b0..881befccf4 100644 --- a/pkg/client/informers/externalversions/stable/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/stable/v1alpha1/interface.go @@ -24,6 +24,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // Fleets returns a FleetInformer. + Fleets() FleetInformer // GameServers returns a GameServerInformer. GameServers() GameServerInformer // GameServerSets returns a GameServerSetInformer. @@ -41,6 +43,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// Fleets returns a FleetInformer. +func (v *version) Fleets() FleetInformer { + return &fleetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // GameServers returns a GameServerInformer. func (v *version) GameServers() GameServerInformer { return &gameServerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/listers/stable/v1alpha1/expansion_generated.go b/pkg/client/listers/stable/v1alpha1/expansion_generated.go index aea2de3929..3f954364fd 100644 --- a/pkg/client/listers/stable/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/stable/v1alpha1/expansion_generated.go @@ -18,6 +18,14 @@ package v1alpha1 +// FleetListerExpansion allows custom methods to be added to +// FleetLister. +type FleetListerExpansion interface{} + +// FleetNamespaceListerExpansion allows custom methods to be added to +// FleetNamespaceLister. +type FleetNamespaceListerExpansion interface{} + // GameServerListerExpansion allows custom methods to be added to // GameServerLister. type GameServerListerExpansion interface{} diff --git a/pkg/client/listers/stable/v1alpha1/fleet.go b/pkg/client/listers/stable/v1alpha1/fleet.go new file mode 100644 index 0000000000..1569e58ee4 --- /dev/null +++ b/pkg/client/listers/stable/v1alpha1/fleet.go @@ -0,0 +1,94 @@ +// 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. + +// This code was autogenerated. Do not edit directly. + +// This file was automatically generated by lister-gen + +package v1alpha1 + +import ( + v1alpha1 "agones.dev/agones/pkg/apis/stable/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// FleetLister helps list Fleets. +type FleetLister interface { + // List lists all Fleets in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.Fleet, err error) + // Fleets returns an object that can list and get Fleets. + Fleets(namespace string) FleetNamespaceLister + FleetListerExpansion +} + +// fleetLister implements the FleetLister interface. +type fleetLister struct { + indexer cache.Indexer +} + +// NewFleetLister returns a new FleetLister. +func NewFleetLister(indexer cache.Indexer) FleetLister { + return &fleetLister{indexer: indexer} +} + +// List lists all Fleets in the indexer. +func (s *fleetLister) List(selector labels.Selector) (ret []*v1alpha1.Fleet, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Fleet)) + }) + return ret, err +} + +// Fleets returns an object that can list and get Fleets. +func (s *fleetLister) Fleets(namespace string) FleetNamespaceLister { + return fleetNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// FleetNamespaceLister helps list and get Fleets. +type FleetNamespaceLister interface { + // List lists all Fleets in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.Fleet, err error) + // Get retrieves the Fleet from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.Fleet, error) + FleetNamespaceListerExpansion +} + +// fleetNamespaceLister implements the FleetNamespaceLister +// interface. +type fleetNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Fleets in the indexer for a given namespace. +func (s fleetNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Fleet, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Fleet)) + }) + return ret, err +} + +// Get retrieves the Fleet from the indexer for a given namespace and name. +func (s fleetNamespaceLister) Get(name string) (*v1alpha1.Fleet, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("fleet"), name) + } + return obj.(*v1alpha1.Fleet), nil +} diff --git a/pkg/fleets/controller.go b/pkg/fleets/controller.go new file mode 100644 index 0000000000..2a08f75c6b --- /dev/null +++ b/pkg/fleets/controller.go @@ -0,0 +1,249 @@ +// 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 fleets + +import ( + "agones.dev/agones/pkg/apis/stable" + stablev1alpha1 "agones.dev/agones/pkg/apis/stable/v1alpha1" + "agones.dev/agones/pkg/client/clientset/versioned" + getterv1alpha1 "agones.dev/agones/pkg/client/clientset/versioned/typed/stable/v1alpha1" + "agones.dev/agones/pkg/client/informers/externalversions" + listerv1alpha1 "agones.dev/agones/pkg/client/listers/stable/v1alpha1" + "agones.dev/agones/pkg/util/crd" + "agones.dev/agones/pkg/util/runtime" + "agones.dev/agones/pkg/util/workerqueue" + "github.com/heptiolabs/healthcheck" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + extclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" +) + +// Controller is a the GameServerSet controller +type Controller struct { + logger *logrus.Entry + crdGetter v1beta1.CustomResourceDefinitionInterface + gameServerSetGetter getterv1alpha1.GameServerSetsGetter + gameServerSetLister listerv1alpha1.GameServerSetLister + gameServerSetSynced cache.InformerSynced + fleetGetter getterv1alpha1.FleetsGetter + fleetLister listerv1alpha1.FleetLister + fleetSynced cache.InformerSynced + workerqueue *workerqueue.WorkerQueue + recorder record.EventRecorder +} + +// NewController returns a new fleets crd controller +func NewController( + health healthcheck.Handler, + kubeClient kubernetes.Interface, + extClient extclientset.Interface, + agonesClient versioned.Interface, + agonesInformerFactory externalversions.SharedInformerFactory) *Controller { + + gameServerSets := agonesInformerFactory.Stable().V1alpha1().GameServerSets() + gsSetInformer := gameServerSets.Informer() + + fleets := agonesInformerFactory.Stable().V1alpha1().Fleets() + fInformer := fleets.Informer() + + c := &Controller{ + crdGetter: extClient.ApiextensionsV1beta1().CustomResourceDefinitions(), + gameServerSetGetter: agonesClient.StableV1alpha1(), + gameServerSetLister: gameServerSets.Lister(), + gameServerSetSynced: gsSetInformer.HasSynced, + fleetGetter: agonesClient.StableV1alpha1(), + fleetLister: fleets.Lister(), + fleetSynced: fInformer.HasSynced, + } + + c.logger = runtime.NewLoggerWithType(c) + c.workerqueue = workerqueue.NewWorkerQueue(c.syncFleet, c.logger, stable.GroupName+".FleetController") + health.AddLivenessCheck("fleet-workerqueue", healthcheck.Check(c.workerqueue.Healthy)) + + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(c.logger.Infof) + eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) + c.recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "fleet-controller"}) + + fInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.workerqueue.Enqueue, + UpdateFunc: func(_, newObj interface{}) { + c.workerqueue.Enqueue(newObj) + }, + }) + + gsSetInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.gameServerSetEventHandler, + UpdateFunc: func(_, newObj interface{}) { + gsSet := newObj.(*stablev1alpha1.GameServerSet) + // ignore if already being deleted + if gsSet.ObjectMeta.DeletionTimestamp.IsZero() { + c.gameServerSetEventHandler(gsSet) + } + }, + }) + + return c +} + +// Run the Fleet controller. Will block until stop is closed. +// Runs threadiness number workers to process the rate limited queue +func (c *Controller) Run(threadiness int, stop <-chan struct{}) error { + err := crd.WaitForEstablishedCRD(c.crdGetter, "fleets.stable.agones.dev", c.logger) + if err != nil { + return err + } + + c.logger.Info("Wait for cache sync") + if !cache.WaitForCacheSync(stop, c.gameServerSetSynced, c.fleetSynced) { + return errors.New("failed to wait for caches to sync") + } + + c.workerqueue.Run(threadiness, stop) + return nil +} + +// gameServerSetEventHandler enqueues the owning Fleet for this GameServerSet, +// assuming that it has one +func (c *Controller) gameServerSetEventHandler(obj interface{}) { + gsSet := obj.(*stablev1alpha1.GameServerSet) + ref := metav1.GetControllerOf(gsSet) + if ref == nil { + return + } + + fleet, err := c.fleetLister.Fleets(gsSet.ObjectMeta.Namespace).Get(ref.Name) + if err != nil { + if k8serrors.IsNotFound(err) { + c.logger.WithField("ref", ref).Info("Owner Fleet no longer available for syncing") + } else { + runtime.HandleError(c.logger.WithField("fleet", fleet.ObjectMeta.Name).WithField("ref", ref), + errors.Wrap(err, "error retrieving GameServerSet owner")) + } + return + } + c.workerqueue.Enqueue(fleet) +} + +// syncFleet synchronised the fleet CRDs and configures/updates +// backing GameServerSets +func (c *Controller) syncFleet(key string) error { + c.logger.WithField("key", key).Info("Synchronising") + + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + // don't return an error, as we don't want this retried + runtime.HandleError(c.logger.WithField("key", key), errors.Wrapf(err, "invalid resource key")) + return nil + } + + fleet, err := c.fleetLister.Fleets(namespace).Get(name) + if err != nil { + if k8serrors.IsNotFound(err) { + c.logger.WithField("key", key).Info("Fleet is no longer available for syncing") + return nil + } + return errors.Wrapf(err, "error retrieving fleet %s from namespace %s", name, namespace) + } + + list, err := c.listGameServerSets(fleet) + if err != nil { + return err + } + + var activeGsSet *stablev1alpha1.GameServerSet + + // if there isn't a GameServerSet yet, then create one + if len(list) == 0 { + activeGsSet = fleet.GameServerSet() + activeGsSet, err = c.gameServerSetGetter.GameServerSets(fleet.ObjectMeta.Namespace).Create(activeGsSet) + if err != nil { + return errors.Wrapf(err, "error creating gameserverset for fleet %s", fleet.ObjectMeta.Name) + } + + c.recorder.Eventf(fleet, corev1.EventTypeNormal, "CreatingGameServerSet", + "Created GameServerSet %s", activeGsSet.ObjectMeta.Name) + + } else { + // for now, we're ignoring any change to the template - will handle on the next PR + // therefore, we are going to assume for the moment, that there is only ever one + // GameServerSet for a Fleet - we will handle multiple GameServerSets in the next PR + activeGsSet = list[0] + } + + // if the replica count has changed, then update the GameServerSet + if fleet.Spec.Replicas != activeGsSet.Spec.Replicas { + gsSetCopy := activeGsSet.DeepCopy() + gsSetCopy.Spec.Replicas = fleet.Spec.Replicas + + if gsSetCopy, err = c.gameServerSetGetter.GameServerSets(fleet.ObjectMeta.Namespace).Update(gsSetCopy); err != nil { + return errors.Wrapf(err, "error updating replicas for gameserverset for fleet %s", fleet.ObjectMeta.Name) + } + c.recorder.Eventf(fleet, corev1.EventTypeNormal, "ScalingGameServerSet", + "Scaling GameServerSet %s to %d", gsSetCopy.ObjectMeta.Name, gsSetCopy.Spec.Replicas) + } + + return c.updateFleetStatus(fleet) +} + +// listGameServerSets lists all the GameServerSets for a given +// Fleet +func (c *Controller) listGameServerSets(f *stablev1alpha1.Fleet) ([]*stablev1alpha1.GameServerSet, error) { + list, err := c.gameServerSetLister.List(labels.SelectorFromSet(labels.Set{stablev1alpha1.FleetGameServerSetLabel: f.ObjectMeta.Name})) + if err != nil { + return list, errors.Wrapf(err, "error listing gameserversets for fleet %s", f.ObjectMeta.Name) + } + + var result []*stablev1alpha1.GameServerSet + for _, gsSet := range list { + if metav1.IsControlledBy(gsSet, f) { + result = append(result, gsSet) + } + } + + return result, nil +} + +// updateFleetStatus gets the GameServerSets for this Fleet and then +// calculates the counts for the status, and updates the Fleet +func (c *Controller) updateFleetStatus(fleet *stablev1alpha1.Fleet) error { + list, err := c.listGameServerSets(fleet) + if err != nil { + return err + } + + fCopy := fleet.DeepCopy() + fCopy.Status.Replicas = 0 + fCopy.Status.ReadyReplicas = 0 + + for _, gsSet := range list { + fCopy.Status.Replicas += gsSet.Status.Replicas + fCopy.Status.ReadyReplicas += gsSet.Status.ReadyReplicas + } + + _, err = c.fleetGetter.Fleets(fCopy.Namespace).Update(fCopy) + return errors.Wrapf(err, "error updating status of fleet %s", fCopy.ObjectMeta.Name) +} diff --git a/pkg/fleets/controller_test.go b/pkg/fleets/controller_test.go new file mode 100644 index 0000000000..a52067ab47 --- /dev/null +++ b/pkg/fleets/controller_test.go @@ -0,0 +1,295 @@ +// 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 fleets + +import ( + "sort" + "testing" + "time" + + "agones.dev/agones/pkg/apis/stable/v1alpha1" + agtesting "agones.dev/agones/pkg/testing" + "github.com/heptiolabs/healthcheck" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + k8stesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/cache" +) + +func TestControllerListGameServerSets(t *testing.T) { + t.Parallel() + + f := defaultFixture() + + gsSet1 := f.GameServerSet() + gsSet1.ObjectMeta.Name = "gsSet1" + gsSet2 := f.GameServerSet() + gsSet2.ObjectMeta.Name = "gsSet2" + gsSet3 := f.GameServerSet() + gsSet3.ObjectMeta.Name = "gsSet3" + gsSet3.ObjectMeta.Labels = nil + gsSet4 := f.GameServerSet() + gsSet4.ObjectMeta.Name = "gsSet4" + gsSet4.ObjectMeta.OwnerReferences = nil + + c, m := newFakeController() + + m.AgonesClient.AddReactor("list", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, &v1alpha1.GameServerSetList{Items: []v1alpha1.GameServerSet{*gsSet1, *gsSet2, *gsSet3, *gsSet4}}, nil + }) + + _, cancel := agtesting.StartInformers(m) + defer cancel() + + list, err := c.listGameServerSets(f) + assert.Nil(t, err) + + // sort of stable ordering + sort.SliceStable(list, func(i, j int) bool { + return list[i].ObjectMeta.Name < list[j].ObjectMeta.Name + }) + assert.Equal(t, []*v1alpha1.GameServerSet{gsSet1, gsSet2}, list) +} + +func TestControllerSyncFleet(t *testing.T) { + t.Parallel() + + t.Run("no gameserverset, create it", func(t *testing.T) { + f := defaultFixture() + c, m := newFakeController() + + created := false + m.AgonesClient.AddReactor("list", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, &v1alpha1.FleetList{Items: []v1alpha1.Fleet{*f}}, nil + }) + + m.AgonesClient.AddReactor("create", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { + ca := action.(k8stesting.CreateAction) + gsSet := ca.GetObject().(*v1alpha1.GameServerSet) + + created = true + assert.True(t, metav1.IsControlledBy(gsSet, f)) + + return true, gsSet, nil + }) + + _, cancel := agtesting.StartInformers(m) + defer cancel() + + err := c.syncFleet("default/fleet-1") + assert.Nil(t, err) + assert.True(t, created, "gameserverset should have been created") + assert.Contains(t, <-m.FakeRecorder.Events, "CreatingGameServerSet") + }) + + t.Run("gamserverset with the same number of replicas", func(t *testing.T) { + t.Parallel() + f := defaultFixture() + c, m := newFakeController() + gsSet := f.GameServerSet() + gsSet.ObjectMeta.Name = "gsSet1" + updated := false + + m.AgonesClient.AddReactor("list", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, &v1alpha1.FleetList{Items: []v1alpha1.Fleet{*f}}, nil + }) + + m.AgonesClient.AddReactor("list", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, &v1alpha1.GameServerSetList{Items: []v1alpha1.GameServerSet{*gsSet}}, nil + }) + + m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { + updated = true + return true, nil, nil + }) + + _, cancel := agtesting.StartInformers(m) + defer cancel() + + err := c.syncFleet("default/fleet-1") + assert.Nil(t, err) + assert.False(t, updated, "gameserverset should not have been updated") + + select { + case <-m.FakeRecorder.Events: + assert.FailNow(t, "there should be no events") + case <-time.After(time.Second): + } + }) + + t.Run("gameserverset with different number of replicas", func(t *testing.T) { + f := defaultFixture() + c, m := newFakeController() + gsSet := f.GameServerSet() + gsSet.ObjectMeta.Name = "gsSet1" + gsSet.Spec.Replicas = f.Spec.Replicas + 10 + updated := false + + m.AgonesClient.AddReactor("list", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, &v1alpha1.FleetList{Items: []v1alpha1.Fleet{*f}}, nil + }) + + m.AgonesClient.AddReactor("list", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, &v1alpha1.GameServerSetList{Items: []v1alpha1.GameServerSet{*gsSet}}, nil + }) + + m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) { + updated = true + + ua := action.(k8stesting.UpdateAction) + gsSet := ua.GetObject().(*v1alpha1.GameServerSet) + assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas) + + return true, gsSet, nil + }) + + _, cancel := agtesting.StartInformers(m) + defer cancel() + + err := c.syncFleet("default/fleet-1") + assert.Nil(t, err) + assert.True(t, updated, "gameserverset should have been updated") + assert.Contains(t, <-m.FakeRecorder.Events, "ScalingGameServerSet") + }) +} + +func TestControllerRun(t *testing.T) { + t.Parallel() + + fleet := defaultFixture() + c, m := newFakeController() + received := make(chan string) + defer close(received) + + m.ExtClient.AddReactor("get", "customresourcedefinitions", func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, agtesting.NewEstablishedCRD(), nil + }) + + fleetWatch := watch.NewFake() + m.AgonesClient.AddWatchReactor("fleets", k8stesting.DefaultWatchReactor(fleetWatch, nil)) + + gsSetWatch := watch.NewFake() + m.AgonesClient.AddWatchReactor("gameserversets", k8stesting.DefaultWatchReactor(gsSetWatch, nil)) + + c.workerqueue.SyncHandler = func(name string) error { + received <- name + return nil + } + + stop, cancel := agtesting.StartInformers(m, c.fleetSynced) + defer cancel() + + go func() { + err := c.Run(1, stop) + assert.Nil(t, err) + }() + + f := func() string { + select { + case result := <-received: + return result + case <-time.After(3 * time.Second): + assert.FailNow(t, "timeout occurred") + } + return "" + } + + expected, err := cache.MetaNamespaceKeyFunc(fleet) + assert.Nil(t, err) + + // test adding fleet + fleetWatch.Add(fleet.DeepCopy()) + assert.Equal(t, expected, f()) + + // test updating fleet + fCopy := fleet.DeepCopy() + fCopy.Spec.Replicas = fCopy.Spec.Replicas + 10 + fleetWatch.Modify(fCopy) + assert.Equal(t, expected, f()) + + // test add/update of gameserver set + gsSet := fleet.GameServerSet() + gsSet.ObjectMeta.Name = "gs1" + gsSet.ObjectMeta.GenerateName = "" + gsSetWatch.Add(gsSet) + assert.Equal(t, expected, f()) + + gsSet.Spec.Replicas += 10 + gsSetWatch.Modify(gsSet) + assert.Equal(t, expected, f()) +} + +func TestControllerUpdateFleetStatus(t *testing.T) { + t.Parallel() + + fleet := defaultFixture() + c, m := newFakeController() + + gsSet1 := fleet.GameServerSet() + gsSet1.ObjectMeta.Name = "gsSet1" + gsSet1.Status.Replicas = 3 + gsSet1.Status.ReadyReplicas = 2 + + gsSet2 := fleet.GameServerSet() + gsSet2.ObjectMeta.Name = "gsSet2" + gsSet2.Status.Replicas = 5 + gsSet2.Status.ReadyReplicas = 5 + + m.AgonesClient.AddReactor("list", "gameserversets", + func(action k8stesting.Action) (bool, runtime.Object, error) { + return true, &v1alpha1.GameServerSetList{Items: []v1alpha1.GameServerSet{*gsSet1, *gsSet2}}, nil + }) + + m.AgonesClient.AddReactor("update", "fleets", + func(action k8stesting.Action) (bool, runtime.Object, error) { + ua := action.(k8stesting.UpdateAction) + fleet := ua.GetObject().(*v1alpha1.Fleet) + + assert.Equal(t, gsSet1.Status.Replicas+gsSet2.Status.Replicas, fleet.Status.Replicas) + assert.Equal(t, gsSet1.Status.ReadyReplicas+gsSet2.Status.ReadyReplicas, fleet.Status.ReadyReplicas) + return true, fleet, nil + }) + + _, cancel := agtesting.StartInformers(m, c.fleetSynced) + defer cancel() + + err := c.updateFleetStatus(fleet) + assert.Nil(t, err) +} + +// newFakeController returns a controller, backed by the fake Clientset +func newFakeController() (*Controller, agtesting.Mocks) { + m := agtesting.NewMocks() + c := NewController(healthcheck.NewHandler(), m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory) + c.recorder = m.FakeRecorder + return c, m +} + +func defaultFixture() *v1alpha1.Fleet { + f := &v1alpha1.Fleet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fleet-1", + Namespace: "default", + UID: "1234", + }, + Spec: v1alpha1.FleetSpec{ + Replicas: 5, + Template: v1alpha1.GameServerTemplateSpec{}, + }, + } + return f +} diff --git a/pkg/fleets/doc.go b/pkg/fleets/doc.go new file mode 100644 index 0000000000..80ba858a9c --- /dev/null +++ b/pkg/fleets/doc.go @@ -0,0 +1,17 @@ +// 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 fleets handles management of the +// Fleet Custom Resource Definition +package fleets diff --git a/pkg/gameservers/controller_test.go b/pkg/gameservers/controller_test.go index 501bc76980..becf511e1f 100644 --- a/pkg/gameservers/controller_test.go +++ b/pkg/gameservers/controller_test.go @@ -30,7 +30,6 @@ import ( "github.com/stretchr/testify/assert" admv1beta1 "k8s.io/api/admission/v1beta1" corev1 "k8s.io/api/core/v1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -143,7 +142,7 @@ func TestControllerWatchGameServers(t *testing.T) { mocks.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil)) mocks.KubeClient.AddWatchReactor("pods", k8stesting.DefaultWatchReactor(podWatch, nil)) mocks.ExtClient.AddReactor("get", "customresourcedefinitions", func(action k8stesting.Action) (bool, runtime.Object, error) { - return true, newEstablishedCRD(), nil + return true, agtesting.NewEstablishedCRD(), nil }) received := make(chan string) @@ -833,14 +832,3 @@ func newFakeController() (*Controller, agtesting.Mocks) { c.recorder = m.FakeRecorder return c, m } - -func newEstablishedCRD() *v1beta1.CustomResourceDefinition { - return &v1beta1.CustomResourceDefinition{ - Status: v1beta1.CustomResourceDefinitionStatus{ - Conditions: []v1beta1.CustomResourceDefinitionCondition{{ - Type: v1beta1.Established, - Status: v1beta1.ConditionTrue, - }}, - }, - } -} diff --git a/pkg/gameservers/health_test.go b/pkg/gameservers/health_test.go index 54dfe1bb47..071aaa163a 100644 --- a/pkg/gameservers/health_test.go +++ b/pkg/gameservers/health_test.go @@ -19,13 +19,13 @@ import ( "time" "agones.dev/agones/pkg/apis/stable/v1alpha1" + agtesting "agones.dev/agones/pkg/testing" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" k8stesting "k8s.io/client-go/testing" - agtesting "agones.dev/agones/pkg/testing" ) func TestHealthControllerFailedContainer(t *testing.T) { diff --git a/pkg/gameservers/portallocator_test.go b/pkg/gameservers/portallocator_test.go index 642523eb29..78ebce192f 100644 --- a/pkg/gameservers/portallocator_test.go +++ b/pkg/gameservers/portallocator_test.go @@ -21,6 +21,7 @@ import ( "sync" "agones.dev/agones/pkg/apis/stable/v1alpha1" + agtesting "agones.dev/agones/pkg/testing" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" @@ -29,7 +30,6 @@ import ( "k8s.io/apimachinery/pkg/watch" k8stesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" - agtesting "agones.dev/agones/pkg/testing" ) var ( diff --git a/pkg/gameservers/sdkserver_test.go b/pkg/gameservers/sdkserver_test.go index 4b67227ab1..8f222dfe3c 100644 --- a/pkg/gameservers/sdkserver_test.go +++ b/pkg/gameservers/sdkserver_test.go @@ -22,6 +22,7 @@ import ( "agones.dev/agones/pkg/apis/stable/v1alpha1" "agones.dev/agones/pkg/sdk" + agtesting "agones.dev/agones/pkg/testing" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "golang.org/x/net/context" @@ -29,7 +30,6 @@ import ( "k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/wait" k8stesting "k8s.io/client-go/testing" - agtesting "agones.dev/agones/pkg/testing" ) func TestSidecarRun(t *testing.T) { diff --git a/pkg/gameserversets/controller_test.go b/pkg/gameserversets/controller_test.go index b3bfc6e360..0cbc07cc1e 100644 --- a/pkg/gameserversets/controller_test.go +++ b/pkg/gameserversets/controller_test.go @@ -23,18 +23,17 @@ import ( "encoding/json" "agones.dev/agones/pkg/apis/stable/v1alpha1" + agtesting "agones.dev/agones/pkg/testing" "agones.dev/agones/pkg/util/webhooks" "github.com/heptiolabs/healthcheck" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" admv1beta1 "k8s.io/api/admission/v1beta1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" k8stesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" - agtesting "agones.dev/agones/pkg/testing" ) func TestControllerWatchGameServers(t *testing.T) { @@ -46,7 +45,7 @@ func TestControllerWatchGameServers(t *testing.T) { defer close(received) m.ExtClient.AddReactor("get", "customresourcedefinitions", func(action k8stesting.Action) (bool, runtime.Object, error) { - return true, newEstablishedCRD(), nil + return true, agtesting.NewEstablishedCRD(), nil }) gsSetWatch := watch.NewFake() m.AgonesClient.AddWatchReactor("gameserversets", k8stesting.DefaultWatchReactor(gsSetWatch, nil)) @@ -527,14 +526,3 @@ func newFakeController() (*Controller, agtesting.Mocks) { c.recorder = m.FakeRecorder return c, m } - -func newEstablishedCRD() *v1beta1.CustomResourceDefinition { - return &v1beta1.CustomResourceDefinition{ - Status: v1beta1.CustomResourceDefinitionStatus{ - Conditions: []v1beta1.CustomResourceDefinitionCondition{{ - Type: v1beta1.Established, - Status: v1beta1.ConditionTrue, - }}, - }, - } -} diff --git a/pkg/gameserversets/helper_test.go b/pkg/gameserversets/helper_test.go new file mode 100644 index 0000000000..00487ba72b --- /dev/null +++ b/pkg/gameserversets/helper_test.go @@ -0,0 +1,66 @@ +// 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 gameserversets + +import ( + "context" + "time" + + agonesfake "agones.dev/agones/pkg/client/clientset/versioned/fake" + "agones.dev/agones/pkg/client/informers/externalversions" + "github.com/sirupsen/logrus" + extfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" + kubefake "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" +) + +// holder for all my fakes and mocks +type mocks struct { + kubeClient *kubefake.Clientset + extClient *extfake.Clientset + agonesClient *agonesfake.Clientset + agonesInformerFactory externalversions.SharedInformerFactory + fakeRecorder *record.FakeRecorder +} + +func newMocks() mocks { + kubeClient := &kubefake.Clientset{} + extClient := &extfake.Clientset{} + agonesClient := &agonesfake.Clientset{} + agonesInformerFactory := externalversions.NewSharedInformerFactory(agonesClient, 30*time.Second) + m := mocks{ + kubeClient: kubeClient, + extClient: extClient, + agonesClient: agonesClient, + agonesInformerFactory: agonesInformerFactory, + fakeRecorder: record.NewFakeRecorder(10), + } + return m +} + +func startInformers(mocks mocks, sync ...cache.InformerSynced) (<-chan struct{}, context.CancelFunc) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + stop := ctx.Done() + + mocks.agonesInformerFactory.Start(stop) + + logrus.Info("Wait for cache sync") + if !cache.WaitForCacheSync(stop, sync...) { + panic("Cache never synced") + } + + return stop, cancel +} diff --git a/pkg/testing/controller.go b/pkg/testing/controller.go index f731e96fac..9c50325f53 100644 --- a/pkg/testing/controller.go +++ b/pkg/testing/controller.go @@ -21,6 +21,7 @@ import ( agonesfake "agones.dev/agones/pkg/client/clientset/versioned/fake" "agones.dev/agones/pkg/client/informers/externalversions" "github.com/sirupsen/logrus" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" extfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" "k8s.io/client-go/informers" kubefake "k8s.io/client-go/kubernetes/fake" @@ -69,3 +70,14 @@ func StartInformers(mocks Mocks, sync ...cache.InformerSynced) (<-chan struct{}, return stop, cancel } + +func NewEstablishedCRD() *v1beta1.CustomResourceDefinition { + return &v1beta1.CustomResourceDefinition{ + Status: v1beta1.CustomResourceDefinitionStatus{ + Conditions: []v1beta1.CustomResourceDefinitionCondition{{ + Type: v1beta1.Established, + Status: v1beta1.ConditionTrue, + }}, + }, + } +}