diff --git a/docs/fleet_spec.md b/docs/fleet_spec.md index ff437359fa..30f9bd9894 100644 --- a/docs/fleet_spec.md +++ b/docs/fleet_spec.md @@ -79,6 +79,14 @@ spec: # The name of the fleet to allocate from. Must be an existing Fleet in the same namespace # as this FleetAllocation fleetName: fleet-example + # Custom metadata that is added to game server status in the moment of allocation + # You can use this to tell the server necessary session data + # ⚠️⚠️⚠️ **This is currently a development feature and has not been released** ⚠️⚠️⚠️ + metadata: + labels: + mode: deathmatch + annotations: + map: garden22 ``` We recommend using `metadata > generateName`, to declare to Kubernetes that a unique @@ -88,3 +96,6 @@ The `spec` field is the actual `FleetAllocation` specification and it is compose - `fleetName` is the name of an existing Fleet. If this doesn't exist, and error will be returned when the `FleetAllocation` is created +- `metadata` is an optional list of custom labels and/or annotations that will be used to patch + the game server's metadata in the moment of allocation. + ⚠️⚠️⚠️ **This is currently a development feature and has not been released** ⚠️⚠️⚠️ diff --git a/examples/fleetallocation.yaml b/examples/fleetallocation.yaml index e5d064d7d6..ae2c654e6d 100644 --- a/examples/fleetallocation.yaml +++ b/examples/fleetallocation.yaml @@ -28,4 +28,11 @@ metadata: spec: # The name of the fleet to allocate from. Must be an existing Fleet in the same namespace # as this FleetAllocation - fleetName: fleet-example \ No newline at end of file + fleetName: fleet-example + # Custom metadata that is added to game server status in the moment of allocation + # You can use this to tell the server necessary session data + metadata: + labels: + mode: deathmatch + annotations: + map: garden22 diff --git a/pkg/apis/stable/v1alpha1/fleetallocation.go b/pkg/apis/stable/v1alpha1/fleetallocation.go index 47d5b8a1ef..4a12c504fb 100644 --- a/pkg/apis/stable/v1alpha1/fleetallocation.go +++ b/pkg/apis/stable/v1alpha1/fleetallocation.go @@ -42,7 +42,14 @@ type FleetAllocationList struct { // FleetAllocationSpec is the spec for a Fleet // Allocation type FleetAllocationSpec struct { - FleetName string `json:"fleetName"` + FleetName string `json:"fleetName"` + MetaPatch FleetAllocationMeta `json:"metadata,omitempty"` +} + +// FleetAllocationMeta is the metadata used to patch the GameServer metadata on allocation +type FleetAllocationMeta struct { + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` } // FleetAllocationStatus will contain the diff --git a/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go index 903c0a8fb3..6fd9485633 100644 --- a/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go @@ -57,7 +57,7 @@ func (in *FleetAllocation) DeepCopyInto(out *FleetAllocation) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } @@ -113,9 +113,40 @@ func (in *FleetAllocationList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FleetAllocationMeta) DeepCopyInto(out *FleetAllocationMeta) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FleetAllocationMeta. +func (in *FleetAllocationMeta) DeepCopy() *FleetAllocationMeta { + if in == nil { + return nil + } + out := new(FleetAllocationMeta) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FleetAllocationSpec) DeepCopyInto(out *FleetAllocationSpec) { *out = *in + in.MetaPatch.DeepCopyInto(&out.MetaPatch) return } diff --git a/pkg/fleetallocation/controller.go b/pkg/fleetallocation/controller.go index db9740a40f..0b597553ef 100644 --- a/pkg/fleetallocation/controller.go +++ b/pkg/fleetallocation/controller.go @@ -134,7 +134,7 @@ func (c *Controller) creationMutationHandler(review admv1beta1.AdmissionReview) return review, errors.Wrapf(err, "error retrieving fleet %s", fa.Name) } - gs, err := c.allocate(fleet) + gs, err := c.allocate(fleet, &fa.Spec.MetaPatch) if err != nil { review.Response.Allowed = false review.Response.Result = &metav1.Status{ @@ -250,7 +250,7 @@ func (c *Controller) mutationValidationHandler(review admv1beta1.AdmissionReview } // allocate allocated a GameServer from a given Fleet -func (c *Controller) allocate(f *stablev1alpha1.Fleet) (*stablev1alpha1.GameServer, error) { +func (c *Controller) allocate(f *stablev1alpha1.Fleet, fam *stablev1alpha1.FleetAllocationMeta) (*stablev1alpha1.GameServer, error) { var allocation *stablev1alpha1.GameServer // can only allocate one at a time, as we don't want two separate processes // trying to allocate the same GameServer to different clients @@ -280,6 +280,10 @@ func (c *Controller) allocate(f *stablev1alpha1.Fleet) (*stablev1alpha1.GameServ gsCopy := allocation.DeepCopy() gsCopy.Status.State = stablev1alpha1.Allocated + if fam != nil { + c.patchMetadata(gsCopy, fam) + } + gs, err := c.gameServerGetter.GameServers(f.ObjectMeta.Namespace).Update(gsCopy) if err != nil { return gs, errors.Wrapf(err, "error updating GameServer %s", gsCopy.ObjectMeta.Name) @@ -288,3 +292,25 @@ func (c *Controller) allocate(f *stablev1alpha1.Fleet) (*stablev1alpha1.GameServ return gs, nil } + +// patch the labels and annotations of an allocated GameServer with metadata from a FleetAllocation +func (c *Controller) patchMetadata(gs *stablev1alpha1.GameServer, fam *stablev1alpha1.FleetAllocationMeta) { + // patch ObjectMeta labels + if fam.Labels != nil { + if gs.ObjectMeta.Labels == nil { + gs.ObjectMeta.Labels = make(map[string]string, len(fam.Labels)) + } + for key, value := range fam.Labels { + gs.ObjectMeta.Labels[key] = value + } + } + // apply annotations patch + if fam.Annotations != nil { + if gs.ObjectMeta.Annotations == nil { + gs.ObjectMeta.Annotations = make(map[string]string, len(fam.Annotations)) + } + for key, value := range fam.Annotations { + gs.ObjectMeta.Annotations[key] = value + } + } +} diff --git a/pkg/fleetallocation/controller_test.go b/pkg/fleetallocation/controller_test.go index 4c11b6a0c5..55702e3387 100644 --- a/pkg/fleetallocation/controller_test.go +++ b/pkg/fleetallocation/controller_test.go @@ -144,6 +144,10 @@ func TestControllerAllocate(t *testing.T) { f, gsSet, gsList := defaultFixtures(4) c, m := newFakeController() n := metav1.Now() + l := map[string]string{"mode": "deathmatch"} + a := map[string]string{"map": "searide"} + fam := &v1alpha1.FleetAllocationMeta{Labels: l, Annotations: a} + gsList[3].ObjectMeta.DeletionTimestamp = &n m.AgonesClient.AddReactor("list", "fleets", func(action k8stesting.Action) (bool, runtime.Object, error) { @@ -172,25 +176,35 @@ func TestControllerAllocate(t *testing.T) { _, cancel := agtesting.StartInformers(m) defer cancel() - gs, err := c.allocate(f) + gs, err := c.allocate(f, fam) assert.Nil(t, err) assert.Equal(t, v1alpha1.Allocated, gs.Status.State) assert.True(t, updated) + for key, value := range fam.Labels { + v, ok := gs.ObjectMeta.Labels[key] + assert.True(t, ok) + assert.Equal(t, v, value) + } + for key, value := range fam.Annotations { + v, ok := gs.ObjectMeta.Annotations[key] + assert.True(t, ok) + assert.Equal(t, v, value) + } updated = false - gs, err = c.allocate(f) + gs, err = c.allocate(f, nil) assert.Nil(t, err) assert.Equal(t, v1alpha1.Allocated, gs.Status.State) assert.True(t, updated) updated = false - gs, err = c.allocate(f) + gs, err = c.allocate(f, nil) assert.Nil(t, err) assert.Equal(t, v1alpha1.Allocated, gs.Status.State) assert.True(t, updated) updated = false - _, err = c.allocate(f) + _, err = c.allocate(f, nil) assert.NotNil(t, err) assert.Equal(t, ErrNoGameServerReady, err) assert.False(t, updated) @@ -230,7 +244,7 @@ func TestControllerAllocateMutex(t *testing.T) { allocate := func() { defer wg.Done() for i := 1; i <= 10; i++ { - _, err := c.allocate(f) + _, err := c.allocate(f, nil) assert.Nil(t, err) } }