agones.dev/agones@v1.53.0/pkg/gameservers/controller_test.go (about)

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gameservers
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"net/http"
    23  	"strconv"
    24  	"testing"
    25  	"time"
    26  
    27  	"agones.dev/agones/pkg/apis/agones"
    28  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    29  	"agones.dev/agones/pkg/cloudproduct/generic"
    30  	"agones.dev/agones/pkg/portallocator"
    31  	agtesting "agones.dev/agones/pkg/testing"
    32  	agruntime "agones.dev/agones/pkg/util/runtime"
    33  	"agones.dev/agones/pkg/util/webhooks"
    34  	"github.com/heptiolabs/healthcheck"
    35  	"github.com/sirupsen/logrus"
    36  	"github.com/stretchr/testify/assert"
    37  	"github.com/stretchr/testify/require"
    38  	"gomodules.xyz/jsonpatch/v2"
    39  	admissionv1 "k8s.io/api/admission/v1"
    40  	corev1 "k8s.io/api/core/v1"
    41  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    42  	"k8s.io/apimachinery/pkg/api/resource"
    43  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    44  	"k8s.io/apimachinery/pkg/labels"
    45  	"k8s.io/apimachinery/pkg/runtime"
    46  	"k8s.io/apimachinery/pkg/runtime/schema"
    47  	"k8s.io/apimachinery/pkg/util/intstr"
    48  	"k8s.io/apimachinery/pkg/util/validation/field"
    49  	"k8s.io/apimachinery/pkg/watch"
    50  	k8stesting "k8s.io/client-go/testing"
    51  	"k8s.io/client-go/tools/cache"
    52  )
    53  
    54  const (
    55  	ipFixture        = "12.12.12.12"
    56  	ipv6Fixture      = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
    57  	nodeFixtureName  = "node1"
    58  	sidecarRunAsUser = 1000
    59  )
    60  
    61  var GameServerKind = metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("GameServer"))
    62  var PodKind = corev1.SchemeGroupVersion.WithKind("Pod")
    63  
    64  func TestControllerSyncGameServer(t *testing.T) {
    65  	t.Parallel()
    66  
    67  	t.Run("Creating a new GameServer", func(t *testing.T) {
    68  		c, mocks := newFakeController()
    69  		updateCount := 0
    70  		podCreated := false
    71  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
    72  			Spec: agonesv1.GameServerSpec{
    73  				Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}},
    74  				Template: corev1.PodTemplateSpec{Spec: corev1.PodSpec{
    75  					Containers: []corev1.Container{{Name: "container", Image: "container/image"}},
    76  				},
    77  				},
    78  			},
    79  		}
    80  
    81  		node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeFixtureName},
    82  			Status: corev1.NodeStatus{Addresses: []corev1.NodeAddress{{Address: ipFixture, Type: corev1.NodeExternalIP}}}}
    83  
    84  		fixture.ApplyDefaults()
    85  
    86  		watchPods := watch.NewFake()
    87  		mocks.KubeClient.AddWatchReactor("pods", k8stesting.DefaultWatchReactor(watchPods, nil))
    88  
    89  		mocks.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
    90  			return true, &corev1.NodeList{Items: []corev1.Node{node}}, nil
    91  		})
    92  		mocks.KubeClient.AddReactor("create", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
    93  			ca := action.(k8stesting.CreateAction)
    94  			pod := ca.GetObject().(*corev1.Pod)
    95  			pod.Spec.NodeName = node.ObjectMeta.Name
    96  			pod.Status.PodIPs = []corev1.PodIP{{IP: ipv6Fixture}}
    97  			podCreated = true
    98  			assert.Equal(t, fixture.ObjectMeta.Name, pod.ObjectMeta.Name)
    99  			watchPods.Add(pod)
   100  			// wait for the change to propagate
   101  			require.Eventually(t, func() bool {
   102  				list, err := c.podLister.List(labels.Everything())
   103  				assert.NoError(t, err)
   104  				return len(list) == 1
   105  			}, 5*time.Second, time.Second)
   106  			return true, pod, nil
   107  		})
   108  		mocks.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   109  			gameServers := &agonesv1.GameServerList{Items: []agonesv1.GameServer{*fixture}}
   110  			return true, gameServers, nil
   111  		})
   112  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   113  			ua := action.(k8stesting.UpdateAction)
   114  			gs := ua.GetObject().(*agonesv1.GameServer)
   115  			updateCount++
   116  			expectedState := agonesv1.GameServerState("notastate")
   117  			switch updateCount {
   118  			case 1:
   119  				expectedState = agonesv1.GameServerStateCreating
   120  			case 2:
   121  				expectedState = agonesv1.GameServerStateStarting
   122  			case 3:
   123  				expectedState = agonesv1.GameServerStateScheduled
   124  			}
   125  
   126  			assert.Equal(t, expectedState, gs.Status.State)
   127  			if expectedState == agonesv1.GameServerStateScheduled {
   128  				assert.Equal(t, ipFixture, gs.Status.Address)
   129  				assert.Equal(t, []corev1.NodeAddress{
   130  					{Address: ipFixture, Type: "ExternalIP"},
   131  					{Address: ipv6Fixture, Type: "PodIP"},
   132  				}, gs.Status.Addresses)
   133  				assert.NotEmpty(t, gs.Status.Ports[0].Port)
   134  			}
   135  
   136  			return true, gs, nil
   137  		})
   138  
   139  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   140  		defer cancel()
   141  
   142  		err := c.portAllocator.Run(ctx)
   143  		assert.Nil(t, err)
   144  
   145  		err = c.syncGameServer(ctx, "default/test")
   146  		assert.Nil(t, err)
   147  		assert.Equal(t, 3, updateCount, "update reactor should fire thrice")
   148  		assert.True(t, podCreated, "pod should be created")
   149  	})
   150  
   151  	t.Run("When a GameServer has been deleted, the sync operation should be a noop", func(t *testing.T) {
   152  		runReconcileDeleteGameServer(t, &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   153  			Spec:   newSingleContainerSpec(),
   154  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}})
   155  	})
   156  }
   157  
   158  func runReconcileDeleteGameServer(t *testing.T, fixture *agonesv1.GameServer) {
   159  	c, mocks := newFakeController()
   160  	agonesWatch := watch.NewFake()
   161  	podAction := false
   162  
   163  	mocks.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(agonesWatch, nil))
   164  	mocks.KubeClient.AddReactor("*", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
   165  		if action.GetVerb() == "update" || action.GetVerb() == "delete" || action.GetVerb() == "create" || action.GetVerb() == "patch" {
   166  			podAction = true
   167  		}
   168  		return false, nil, nil
   169  	})
   170  
   171  	ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   172  	defer cancel()
   173  
   174  	agonesWatch.Delete(fixture)
   175  
   176  	err := c.syncGameServer(ctx, "default/test")
   177  	assert.Nil(t, err, fmt.Sprintf("Shouldn't be an error from syncGameServer: %+v", err))
   178  	assert.False(t, podAction, "Nothing should happen to a Pod")
   179  }
   180  
   181  func TestControllerSyncGameServerWithDevIP(t *testing.T) {
   182  	t.Parallel()
   183  
   184  	templateDevGs := &agonesv1.GameServer{
   185  		ObjectMeta: metav1.ObjectMeta{
   186  			Name:        "test",
   187  			Namespace:   "default",
   188  			Annotations: map[string]string{agonesv1.DevAddressAnnotation: ipFixture},
   189  		},
   190  		Spec: agonesv1.GameServerSpec{
   191  			Ports: []agonesv1.GameServerPort{{ContainerPort: 7777, HostPort: 7777, PortPolicy: agonesv1.Static}},
   192  		},
   193  	}
   194  
   195  	t.Run("Creating a new GameServer", func(t *testing.T) {
   196  		c, mocks := newFakeController()
   197  		updateCount := 0
   198  
   199  		fixture := templateDevGs.DeepCopy()
   200  
   201  		fixture.ApplyDefaults()
   202  
   203  		mocks.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   204  			return false, nil, k8serrors.NewMethodNotSupported(schema.GroupResource{}, "list nodes should not be called")
   205  		})
   206  		mocks.KubeClient.AddReactor("create", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   207  			return false, nil, k8serrors.NewMethodNotSupported(schema.GroupResource{}, "creating a pod with dev mode is not supported")
   208  		})
   209  		mocks.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   210  			gameServers := &agonesv1.GameServerList{Items: []agonesv1.GameServer{*fixture}}
   211  			return true, gameServers, nil
   212  		})
   213  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   214  			ua := action.(k8stesting.UpdateAction)
   215  			gs := ua.GetObject().(*agonesv1.GameServer)
   216  			updateCount++
   217  			expectedState := agonesv1.GameServerStateReady
   218  
   219  			assert.Equal(t, expectedState, gs.Status.State)
   220  			assert.Equal(t, ipFixture, gs.Status.Address)
   221  			assert.Equal(t, []corev1.NodeAddress{{Address: ipFixture, Type: "InternalIP"}}, gs.Status.Addresses)
   222  			assert.NotEmpty(t, gs.Status.Ports[0].Port)
   223  
   224  			return true, gs, nil
   225  		})
   226  
   227  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   228  		defer cancel()
   229  
   230  		err := c.portAllocator.Run(ctx)
   231  		assert.Nil(t, err)
   232  
   233  		err = c.syncGameServer(ctx, "default/test")
   234  		assert.Nil(t, err)
   235  		assert.Equal(t, 1, updateCount, "update reactor should fire once")
   236  	})
   237  
   238  	t.Run("GameServer with ReadyRequest State", func(t *testing.T) {
   239  		c, mocks := newFakeController()
   240  
   241  		updateCount := 0
   242  
   243  		gsFixture := templateDevGs.DeepCopy()
   244  		gsFixture.ApplyDefaults()
   245  		gsFixture.Status.State = agonesv1.GameServerStateRequestReady
   246  
   247  		mocks.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   248  			gameServers := &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gsFixture}}
   249  			return true, gameServers, nil
   250  		})
   251  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   252  			ua := action.(k8stesting.UpdateAction)
   253  			gs := ua.GetObject().(*agonesv1.GameServer)
   254  			updateCount++
   255  
   256  			assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
   257  
   258  			return true, gs, nil
   259  		})
   260  
   261  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   262  		defer cancel()
   263  
   264  		err := c.portAllocator.Run(ctx)
   265  		assert.NoError(t, err, "should not error")
   266  
   267  		err = c.syncGameServer(ctx, "default/test")
   268  		assert.NoError(t, err, "should not error")
   269  		assert.Equal(t, 1, updateCount, "update reactor should fire once")
   270  	})
   271  
   272  	t.Run("Allocated GameServer", func(t *testing.T) {
   273  		c, mocks := newFakeController()
   274  
   275  		fixture := templateDevGs.DeepCopy()
   276  
   277  		fixture.ApplyDefaults()
   278  		fixture.Status.State = agonesv1.GameServerStateAllocated
   279  
   280  		mocks.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   281  			gameServers := &agonesv1.GameServerList{Items: []agonesv1.GameServer{*fixture}}
   282  			return true, gameServers, nil
   283  		})
   284  		mocks.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   285  			require.Fail(t, "should not update")
   286  			return true, nil, nil
   287  		})
   288  
   289  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   290  		defer cancel()
   291  
   292  		err := c.portAllocator.Run(ctx)
   293  		require.NoError(t, err)
   294  
   295  		err = c.syncGameServer(ctx, "default/test")
   296  		require.NoError(t, err)
   297  	})
   298  
   299  	t.Run("When a GameServer has been deleted, the sync operation should be a noop", func(t *testing.T) {
   300  		runReconcileDeleteGameServer(t, &agonesv1.GameServer{
   301  			ObjectMeta: metav1.ObjectMeta{
   302  				Name:        "test",
   303  				Namespace:   "default",
   304  				Annotations: map[string]string{agonesv1.DevAddressAnnotation: ipFixture},
   305  			},
   306  			Spec: agonesv1.GameServerSpec{
   307  				Ports: []agonesv1.GameServerPort{{ContainerPort: 7777, HostPort: 7777, PortPolicy: agonesv1.Static}},
   308  				Template: corev1.PodTemplateSpec{Spec: corev1.PodSpec{
   309  					Containers: []corev1.Container{{Name: "container", Image: "container/image"}},
   310  				},
   311  				},
   312  			},
   313  		})
   314  	})
   315  }
   316  
   317  func TestControllerWatchGameServers(t *testing.T) {
   318  	c, m := newFakeController()
   319  	fixture := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}, Spec: newSingleContainerSpec()}
   320  	fixture.ApplyDefaults()
   321  	pod, err := fixture.Pod(agtesting.FakeAPIHooks{})
   322  	assert.Nil(t, err)
   323  	pod.ObjectMeta.Name = pod.ObjectMeta.GenerateName + "-pod"
   324  
   325  	gsWatch := watch.NewFake()
   326  	podWatch := watch.NewFake()
   327  	m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
   328  	m.KubeClient.AddWatchReactor("pods", k8stesting.DefaultWatchReactor(podWatch, nil))
   329  	m.ExtClient.AddReactor("get", "customresourcedefinitions", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   330  		return true, agtesting.NewEstablishedCRD(), nil
   331  	})
   332  
   333  	received := make(chan string)
   334  	defer close(received)
   335  
   336  	h := func(_ context.Context, name string) error {
   337  		assert.Equal(t, "default/test", name)
   338  		received <- name
   339  		return nil
   340  	}
   341  
   342  	c.workerqueue.SyncHandler = h
   343  	c.creationWorkerQueue.SyncHandler = h
   344  	c.deletionWorkerQueue.SyncHandler = h
   345  
   346  	ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced)
   347  	defer cancel()
   348  
   349  	noStateChange := func(sync cache.InformerSynced) {
   350  		cache.WaitForCacheSync(ctx.Done(), sync)
   351  		select {
   352  		case <-received:
   353  			assert.Fail(t, "Should not be queued")
   354  		default:
   355  		}
   356  	}
   357  
   358  	podSynced := m.KubeInformerFactory.Core().V1().Pods().Informer().HasSynced
   359  	gsSynced := m.AgonesInformerFactory.Agones().V1().GameServers().Informer().HasSynced
   360  
   361  	go func() {
   362  		err := c.Run(ctx, 1)
   363  		assert.Nil(t, err, "Run should not error")
   364  	}()
   365  
   366  	logrus.Info("Adding first fixture")
   367  	gsWatch.Add(&fixture)
   368  	assert.Equal(t, "default/test", <-received)
   369  	podWatch.Add(pod)
   370  	noStateChange(podSynced)
   371  
   372  	// no state change
   373  	gsWatch.Modify(&fixture)
   374  	noStateChange(gsSynced)
   375  
   376  	// add a non game pod
   377  	nonGamePod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}
   378  	podWatch.Add(nonGamePod)
   379  	noStateChange(podSynced)
   380  
   381  	// no state change
   382  	gsWatch.Modify(&fixture)
   383  	noStateChange(gsSynced)
   384  
   385  	// no state change
   386  	gsWatch.Modify(&fixture)
   387  	noStateChange(gsSynced)
   388  
   389  	copyFixture := fixture.DeepCopy()
   390  	copyFixture.Status.State = agonesv1.GameServerStateStarting
   391  	logrus.Info("modify copyFixture")
   392  	gsWatch.Modify(copyFixture)
   393  	assert.Equal(t, "default/test", <-received)
   394  
   395  	// modify a gameserver with a deletion timestamp
   396  	now := metav1.Now()
   397  	deleted := copyFixture.DeepCopy()
   398  	deleted.ObjectMeta.DeletionTimestamp = &now
   399  	gsWatch.Modify(deleted)
   400  	assert.Equal(t, "default/test", <-received)
   401  
   402  	podWatch.Delete(pod)
   403  	assert.Equal(t, "default/test", <-received)
   404  
   405  	// add an unscheduled game pod
   406  	pod, err = fixture.Pod(agtesting.FakeAPIHooks{})
   407  	assert.Nil(t, err)
   408  	pod.ObjectMeta.Name = pod.ObjectMeta.GenerateName + "-pod2"
   409  	podWatch.Add(pod)
   410  	noStateChange(podSynced)
   411  
   412  	// schedule it
   413  	podCopy := pod.DeepCopy()
   414  	podCopy.Spec.NodeName = nodeFixtureName
   415  
   416  	podWatch.Modify(podCopy)
   417  	assert.Equal(t, "default/test", <-received)
   418  }
   419  
   420  func TestControllerCreationMutationHandler(t *testing.T) {
   421  	t.Parallel()
   422  
   423  	type expected struct {
   424  		responseAllowed bool
   425  		patches         []jsonpatch.JsonPatchOperation
   426  		nilPatch        bool
   427  	}
   428  
   429  	var testCases = []struct {
   430  		description string
   431  		fixture     interface{}
   432  		expected    expected
   433  	}{
   434  		{
   435  			description: "OK",
   436  			fixture: &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   437  				Spec: newSingleContainerSpec()},
   438  			expected: expected{
   439  				responseAllowed: true,
   440  				patches: []jsonpatch.JsonPatchOperation{
   441  					{Operation: "add", Path: "/metadata/finalizers", Value: []interface{}{"agones.dev/controller"}},
   442  					{Operation: "add", Path: "/spec/ports/0/protocol", Value: "UDP"}},
   443  			},
   444  		},
   445  		{
   446  			description: "Wrong request object, err expected",
   447  			fixture:     "WRONG DATA",
   448  			expected:    expected{nilPatch: true},
   449  		},
   450  	}
   451  
   452  	ext := newFakeExtensions()
   453  
   454  	for _, tc := range testCases {
   455  		t.Run(tc.description, func(t *testing.T) {
   456  			raw, err := json.Marshal(tc.fixture)
   457  			require.NoError(t, err)
   458  
   459  			review := admissionv1.AdmissionReview{
   460  				Request: &admissionv1.AdmissionRequest{
   461  					Kind:      GameServerKind,
   462  					Operation: admissionv1.Create,
   463  					Object: runtime.RawExtension{
   464  						Raw: raw,
   465  					},
   466  				},
   467  				Response: &admissionv1.AdmissionResponse{Allowed: true},
   468  			}
   469  
   470  			result, err := ext.creationMutationHandler(review)
   471  
   472  			assert.NoError(t, err)
   473  			if tc.expected.nilPatch {
   474  				require.Nil(t, result.Response.PatchType)
   475  			} else {
   476  				assert.True(t, result.Response.Allowed)
   477  				assert.Equal(t, admissionv1.PatchTypeJSONPatch, *result.Response.PatchType)
   478  
   479  				patch := &jsonpatch.ByPath{}
   480  				err = json.Unmarshal(result.Response.Patch, patch)
   481  				require.NoError(t, err)
   482  
   483  				found := false
   484  
   485  				for _, expected := range tc.expected.patches {
   486  					for _, p := range *patch {
   487  						if assert.ObjectsAreEqual(p, expected) {
   488  							found = true
   489  						}
   490  					}
   491  					assert.True(t, found, "Could not find operation %#v in patch %v", expected, *patch)
   492  				}
   493  			}
   494  		})
   495  	}
   496  }
   497  
   498  func TestControllerCreationValidationHandler(t *testing.T) {
   499  	t.Parallel()
   500  
   501  	ext := newFakeExtensions()
   502  
   503  	t.Run("valid gameserver", func(t *testing.T) {
   504  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   505  			Spec: newSingleContainerSpec()}
   506  		fixture.ApplyDefaults()
   507  
   508  		raw, err := json.Marshal(fixture)
   509  		require.NoError(t, err)
   510  		review := admissionv1.AdmissionReview{
   511  			Request: &admissionv1.AdmissionRequest{
   512  				Kind:      GameServerKind,
   513  				Operation: admissionv1.Create,
   514  				Object: runtime.RawExtension{
   515  					Raw: raw,
   516  				},
   517  			},
   518  			Response: &admissionv1.AdmissionResponse{Allowed: true},
   519  		}
   520  
   521  		result, err := ext.creationValidationHandler(review)
   522  		require.NoError(t, err)
   523  		assert.True(t, result.Response.Allowed)
   524  	})
   525  
   526  	t.Run("invalid gameserver", func(t *testing.T) {
   527  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   528  			Spec: agonesv1.GameServerSpec{
   529  				Container: "NOPE!",
   530  				Ports:     []agonesv1.GameServerPort{{ContainerPort: 7777}},
   531  				Template: corev1.PodTemplateSpec{
   532  					Spec: corev1.PodSpec{
   533  						Containers: []corev1.Container{
   534  							{Name: "container", Image: "container/image"},
   535  							{Name: "container2", Image: "container/image"},
   536  						},
   537  					},
   538  				},
   539  			},
   540  		}
   541  		raw, err := json.Marshal(fixture)
   542  		require.NoError(t, err)
   543  		review := admissionv1.AdmissionReview{
   544  			Request: &admissionv1.AdmissionRequest{
   545  				Kind:      GameServerKind,
   546  				Operation: admissionv1.Create,
   547  				Object: runtime.RawExtension{
   548  					Raw: raw,
   549  				},
   550  			},
   551  			Response: &admissionv1.AdmissionResponse{Allowed: true},
   552  		}
   553  
   554  		result, err := ext.creationValidationHandler(review)
   555  		require.NoError(t, err)
   556  		assert.False(t, result.Response.Allowed)
   557  		assert.Equal(t, metav1.StatusFailure, review.Response.Result.Status)
   558  		assert.Equal(t, metav1.StatusReasonInvalid, review.Response.Result.Reason)
   559  		assert.Equal(t, review.Request.Kind.Kind, result.Response.Result.Details.Kind)
   560  		assert.Equal(t, review.Request.Kind.Group, result.Response.Result.Details.Group)
   561  		assert.NotEmpty(t, result.Response.Result.Details.Causes)
   562  	})
   563  
   564  	t.Run("valid request object, error expected", func(t *testing.T) {
   565  		raw, err := json.Marshal("WRONG DATA")
   566  		require.NoError(t, err)
   567  
   568  		review := admissionv1.AdmissionReview{
   569  			Request: &admissionv1.AdmissionRequest{
   570  				Kind:      GameServerKind,
   571  				Operation: admissionv1.Create,
   572  				Object: runtime.RawExtension{
   573  					Raw: raw,
   574  				},
   575  			},
   576  			Response: &admissionv1.AdmissionResponse{Allowed: true},
   577  		}
   578  
   579  		_, err = ext.creationValidationHandler(review)
   580  		if assert.Error(t, err) {
   581  			assert.Equal(t, `error unmarshalling GameServer json after schema validation: "WRONG DATA": json: cannot unmarshal string into Go value of type v1.GameServer`, err.Error())
   582  		}
   583  	})
   584  }
   585  
   586  func TestControllerCreationMutationHandlerPod(t *testing.T) {
   587  	t.Parallel()
   588  	ext := newFakeExtensions()
   589  
   590  	type expected struct {
   591  		patches []jsonpatch.JsonPatchOperation
   592  	}
   593  
   594  	t.Run("valid pod mutation for Passthrough portPolicy, containerPort should be the same as hostPort", func(t *testing.T) {
   595  		gameServerHostPort0 := float64(newPassthroughPortSingleContainerSpec().Spec.Containers[1].Ports[0].HostPort)
   596  		gameServerHostPort2 := float64(newPassthroughPortSingleContainerSpec().Spec.Containers[1].Ports[1].HostPort)
   597  		gameServerHostPort3 := float64(newPassthroughPortSingleContainerSpec().Spec.Containers[2].Ports[0].HostPort)
   598  		fixture := newPassthroughPortSingleContainerSpec()
   599  		raw, err := json.Marshal(fixture)
   600  		require.NoError(t, err)
   601  		review := admissionv1.AdmissionReview{
   602  			Request: &admissionv1.AdmissionRequest{
   603  				Kind:      metav1.GroupVersionKind(PodKind),
   604  				Operation: admissionv1.Create,
   605  				Object: runtime.RawExtension{
   606  					Raw: raw,
   607  				},
   608  			},
   609  			Response: &admissionv1.AdmissionResponse{Allowed: true},
   610  		}
   611  		expected := expected{
   612  			patches: []jsonpatch.JsonPatchOperation{
   613  				{Operation: "replace", Path: "/spec/containers/1/ports/0/containerPort", Value: gameServerHostPort0},
   614  				{Operation: "replace", Path: "/spec/containers/1/ports/1/containerPort", Value: gameServerHostPort2},
   615  				{Operation: "replace", Path: "/spec/containers/2/ports/0/containerPort", Value: gameServerHostPort3}},
   616  		}
   617  
   618  		result, err := ext.creationMutationHandlerPod(review)
   619  		assert.NoError(t, err)
   620  		patch := &jsonpatch.ByPath{}
   621  		err = json.Unmarshal(result.Response.Patch, patch)
   622  		found := false
   623  
   624  		for _, expected := range expected.patches {
   625  			for _, p := range *patch {
   626  				if assert.ObjectsAreEqual(p, expected) {
   627  					found = true
   628  				}
   629  			}
   630  			assert.True(t, found, "Could not find operation %#v in patch %v", expected, *patch)
   631  		}
   632  
   633  		require.NoError(t, err)
   634  
   635  	})
   636  }
   637  
   638  func TestControllerSyncGameServerDeletionTimestamp(t *testing.T) {
   639  	t.Parallel()
   640  
   641  	t.Run("GameServer has a Pod", func(t *testing.T) {
   642  		c, mocks := newFakeController()
   643  		now := metav1.Now()
   644  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default", DeletionTimestamp: &now},
   645  			Spec: newSingleContainerSpec()}
   646  		fixture.ApplyDefaults()
   647  		pod, err := fixture.Pod(agtesting.FakeAPIHooks{})
   648  		assert.Nil(t, err)
   649  
   650  		deleted := false
   651  		mocks.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   652  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
   653  		})
   654  		mocks.KubeClient.AddReactor("delete", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
   655  			deleted = true
   656  			da := action.(k8stesting.DeleteAction)
   657  			assert.Equal(t, pod.ObjectMeta.Name, da.GetName())
   658  			return true, nil, nil
   659  		})
   660  
   661  		ctx, cancel := agtesting.StartInformers(mocks, c.podSynced)
   662  		defer cancel()
   663  
   664  		result, err := c.syncGameServerDeletionTimestamp(ctx, fixture)
   665  		assert.NoError(t, err)
   666  		assert.True(t, deleted, "pod should be deleted")
   667  		assert.Equal(t, fixture, result)
   668  		agtesting.AssertEventContains(t, mocks.FakeRecorder.Events, fmt.Sprintf("%s %s %s", corev1.EventTypeNormal,
   669  			fixture.Status.State, "Deleting Pod "+pod.ObjectMeta.Name))
   670  	})
   671  
   672  	t.Run("Error on deleting pod", func(t *testing.T) {
   673  		c, mocks := newFakeController()
   674  		now := metav1.Now()
   675  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default", DeletionTimestamp: &now},
   676  			Spec: newSingleContainerSpec()}
   677  		fixture.ApplyDefaults()
   678  		pod, err := fixture.Pod(agtesting.FakeAPIHooks{})
   679  		assert.Nil(t, err)
   680  
   681  		mocks.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   682  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
   683  		})
   684  		mocks.KubeClient.AddReactor("delete", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   685  			return true, nil, errors.New("Delete-err")
   686  		})
   687  
   688  		ctx, cancel := agtesting.StartInformers(mocks, c.podSynced)
   689  		defer cancel()
   690  
   691  		_, err = c.syncGameServerDeletionTimestamp(ctx, fixture)
   692  		if assert.Error(t, err) {
   693  			assert.Equal(t, `error deleting pod for GameServer. Name: test, Namespace: default: Delete-err`, err.Error())
   694  		}
   695  	})
   696  
   697  	t.Run("GameServer's Pods have been deleted", func(t *testing.T) {
   698  		c, mocks := newFakeController()
   699  		now := metav1.Now()
   700  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default", DeletionTimestamp: &now},
   701  			Spec: newSingleContainerSpec()}
   702  		fixture.ApplyDefaults()
   703  
   704  		updated := false
   705  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   706  			updated = true
   707  
   708  			ua := action.(k8stesting.UpdateAction)
   709  			gs := ua.GetObject().(*agonesv1.GameServer)
   710  			assert.Equal(t, fixture.ObjectMeta.Name, gs.ObjectMeta.Name)
   711  			assert.Empty(t, gs.ObjectMeta.Finalizers)
   712  
   713  			return true, gs, nil
   714  		})
   715  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   716  		defer cancel()
   717  
   718  		result, err := c.syncGameServerDeletionTimestamp(ctx, fixture)
   719  		assert.Nil(t, err)
   720  		assert.True(t, updated, "gameserver should be updated, to remove the finaliser")
   721  		assert.Equal(t, fixture.ObjectMeta.Name, result.ObjectMeta.Name)
   722  		assert.Empty(t, result.ObjectMeta.Finalizers)
   723  	})
   724  
   725  	t.Run("Local development GameServer", func(t *testing.T) {
   726  		c, mocks := newFakeController()
   727  		now := metav1.Now()
   728  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default",
   729  			Annotations:       map[string]string{agonesv1.DevAddressAnnotation: "1.1.1.1"},
   730  			DeletionTimestamp: &now},
   731  			Spec: newSingleContainerSpec()}
   732  		fixture.ApplyDefaults()
   733  
   734  		updated := false
   735  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   736  			updated = true
   737  
   738  			ua := action.(k8stesting.UpdateAction)
   739  			gs := ua.GetObject().(*agonesv1.GameServer)
   740  			assert.Equal(t, fixture.ObjectMeta.Name, gs.ObjectMeta.Name)
   741  			assert.Empty(t, gs.ObjectMeta.Finalizers)
   742  
   743  			return true, gs, nil
   744  		})
   745  
   746  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   747  		defer cancel()
   748  
   749  		result, err := c.syncGameServerDeletionTimestamp(ctx, fixture)
   750  		assert.Nil(t, err)
   751  		assert.True(t, updated, "gameserver should be updated, to remove the finaliser")
   752  		assert.Equal(t, fixture.ObjectMeta.Name, result.ObjectMeta.Name)
   753  		assert.Empty(t, result.ObjectMeta.Finalizers)
   754  	})
   755  }
   756  
   757  func TestControllerSyncGameServerPortAllocationState(t *testing.T) {
   758  	t.Parallel()
   759  
   760  	t.Run("Gameserver with port allocation state", func(t *testing.T) {
   761  		t.Parallel()
   762  		c, mocks := newFakeController()
   763  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   764  			Spec: agonesv1.GameServerSpec{
   765  				Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}},
   766  				Template: corev1.PodTemplateSpec{
   767  					Spec: corev1.PodSpec{
   768  						Containers: []corev1.Container{{Name: "container", Image: "container/image"}},
   769  					},
   770  				},
   771  			},
   772  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStatePortAllocation},
   773  		}
   774  		fixture.ApplyDefaults()
   775  		mocks.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   776  			return true, &corev1.NodeList{Items: []corev1.Node{{ObjectMeta: metav1.ObjectMeta{Name: nodeFixtureName}}}}, nil
   777  		})
   778  
   779  		updated := false
   780  
   781  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   782  			updated = true
   783  			ua := action.(k8stesting.UpdateAction)
   784  			gs := ua.GetObject().(*agonesv1.GameServer)
   785  			assert.Equal(t, fixture.ObjectMeta.Name, gs.ObjectMeta.Name)
   786  			port := gs.Spec.Ports[0]
   787  			assert.Equal(t, agonesv1.Dynamic, port.PortPolicy)
   788  			assert.NotEqual(t, fixture.Spec.Ports[0].HostPort, port.HostPort)
   789  			assert.True(t, 10 <= port.HostPort && port.HostPort <= 20, "%s not in range", port.HostPort)
   790  
   791  			return true, gs, nil
   792  		})
   793  
   794  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   795  		defer cancel()
   796  		err := c.portAllocator.Run(ctx)
   797  		require.NoError(t, err)
   798  
   799  		result, err := c.syncGameServerPortAllocationState(ctx, fixture)
   800  		require.NoError(t, err, "sync should not error")
   801  		assert.True(t, updated, "update should occur")
   802  		port := result.Spec.Ports[0]
   803  		assert.Equal(t, agonesv1.Dynamic, port.PortPolicy)
   804  		assert.NotEqual(t, fixture.Spec.Ports[0].HostPort, port.HostPort)
   805  		assert.True(t, 10 <= port.HostPort && port.HostPort <= 20, "%s not in range", port.HostPort)
   806  	})
   807  
   808  	t.Run("Error on update", func(t *testing.T) {
   809  		t.Parallel()
   810  		c, mocks := newFakeController()
   811  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   812  			Spec: agonesv1.GameServerSpec{
   813  				Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}},
   814  				Template: corev1.PodTemplateSpec{
   815  					Spec: corev1.PodSpec{
   816  						Containers: []corev1.Container{{Name: "container", Image: "container/image"}},
   817  					},
   818  				},
   819  			},
   820  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStatePortAllocation},
   821  		}
   822  		fixture.ApplyDefaults()
   823  		mocks.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   824  			return true, &corev1.NodeList{Items: []corev1.Node{{ObjectMeta: metav1.ObjectMeta{Name: nodeFixtureName}}}}, nil
   825  		})
   826  
   827  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   828  			ua := action.(k8stesting.UpdateAction)
   829  			gs := ua.GetObject().(*agonesv1.GameServer)
   830  			return true, gs, errors.New("update-err")
   831  		})
   832  
   833  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
   834  		defer cancel()
   835  		err := c.portAllocator.Run(ctx)
   836  		require.NoError(t, err)
   837  
   838  		_, err = c.syncGameServerPortAllocationState(ctx, fixture)
   839  		if assert.Error(t, err) {
   840  			assert.Equal(t, `error updating GameServer test to default values: update-err`, err.Error())
   841  		}
   842  	})
   843  
   844  	t.Run("Gameserver with unknown state", func(t *testing.T) {
   845  		testNoChange(t, "Unknown", func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   846  			return c.syncGameServerPortAllocationState(context.Background(), fixture)
   847  		})
   848  	})
   849  
   850  	t.Run("GameServer with non zero deletion datetime", func(t *testing.T) {
   851  		testWithNonZeroDeletionTimestamp(t, func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   852  			return c.syncGameServerPortAllocationState(context.Background(), fixture)
   853  		})
   854  	})
   855  }
   856  
   857  func TestControllerSyncGameServerCreatingState(t *testing.T) {
   858  	t.Parallel()
   859  
   860  	newFixture := func() *agonesv1.GameServer {
   861  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   862  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
   863  		fixture.ApplyDefaults()
   864  		return fixture
   865  	}
   866  
   867  	t.Run("Testing TCPUDP protocol of static portpolicy", func(t *testing.T) {
   868  		c, m := newFakeController()
   869  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   870  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
   871  		fixture.Spec.Ports[0].Name = "default"
   872  		fixture.Spec.Ports[0].HostPort = 7000
   873  		fixture.Spec.Ports[0].Protocol = agonesv1.ProtocolTCPUDP
   874  		fixture.ApplyDefaults()
   875  		podCreated := false
   876  		gsUpdated := false
   877  
   878  		var pod *corev1.Pod
   879  		m.KubeClient.AddReactor("create", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
   880  			podCreated = true
   881  			ca := action.(k8stesting.CreateAction)
   882  			pod = ca.GetObject().(*corev1.Pod)
   883  			assert.True(t, metav1.IsControlledBy(pod, fixture))
   884  			return true, pod, nil
   885  		})
   886  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   887  			gsUpdated = true
   888  			ua := action.(k8stesting.UpdateAction)
   889  			gs := ua.GetObject().(*agonesv1.GameServer)
   890  			assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
   891  			assert.Len(t, gs.Spec.Ports, 2)
   892  			assert.Equal(t, "default-tcp", gs.Spec.Ports[0].Name)
   893  			assert.Equal(t, corev1.ProtocolTCP, gs.Spec.Ports[0].Protocol)
   894  			assert.Equal(t, "default-udp", gs.Spec.Ports[1].Name)
   895  			assert.Equal(t, corev1.ProtocolUDP, gs.Spec.Ports[1].Protocol)
   896  			return true, gs, nil
   897  		})
   898  
   899  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced)
   900  		defer cancel()
   901  
   902  		gs, err := c.syncGameServerCreatingState(ctx, fixture)
   903  
   904  		assert.NoError(t, err)
   905  		assert.True(t, podCreated, "Pod should have been created")
   906  
   907  		assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
   908  		assert.True(t, gsUpdated, "GameServer should have been updated")
   909  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Pod")
   910  	})
   911  
   912  	t.Run("Testing TCP protocol of static portpolicy", func(t *testing.T) {
   913  		c, m := newFakeController()
   914  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   915  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
   916  		fixture.Spec.Ports[0].Name = "tcp-port"
   917  		fixture.Spec.Ports[0].HostPort = 7000
   918  		fixture.Spec.Ports[0].Protocol = corev1.ProtocolTCP
   919  		fixture.ApplyDefaults()
   920  		podCreated := false
   921  		gsUpdated := false
   922  
   923  		var pod *corev1.Pod
   924  		m.KubeClient.AddReactor("create", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
   925  			podCreated = true
   926  			ca := action.(k8stesting.CreateAction)
   927  			pod = ca.GetObject().(*corev1.Pod)
   928  			assert.True(t, metav1.IsControlledBy(pod, fixture))
   929  			return true, pod, nil
   930  		})
   931  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   932  			gsUpdated = true
   933  			ua := action.(k8stesting.UpdateAction)
   934  			gs := ua.GetObject().(*agonesv1.GameServer)
   935  			assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
   936  			assert.Len(t, gs.Spec.Ports, 1)
   937  			assert.Equal(t, "tcp-port", gs.Spec.Ports[0].Name)
   938  			assert.Equal(t, corev1.ProtocolTCP, gs.Spec.Ports[0].Protocol)
   939  			return true, gs, nil
   940  		})
   941  
   942  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced)
   943  		defer cancel()
   944  
   945  		gs, err := c.syncGameServerCreatingState(ctx, fixture)
   946  
   947  		assert.NoError(t, err)
   948  		assert.True(t, podCreated, "Pod should have been created")
   949  
   950  		assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
   951  		assert.True(t, gsUpdated, "GameServer should have been updated")
   952  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Pod")
   953  	})
   954  
   955  	t.Run("Testing default protocol of static portpolicy", func(t *testing.T) {
   956  		c, m := newFakeController()
   957  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
   958  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
   959  		fixture.Spec.Ports[0].Name = "udp-port"
   960  		fixture.Spec.Ports[0].HostPort = 7000
   961  		fixture.ApplyDefaults()
   962  		podCreated := false
   963  		gsUpdated := false
   964  
   965  		var pod *corev1.Pod
   966  		m.KubeClient.AddReactor("create", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
   967  			podCreated = true
   968  			ca := action.(k8stesting.CreateAction)
   969  			pod = ca.GetObject().(*corev1.Pod)
   970  			assert.True(t, metav1.IsControlledBy(pod, fixture))
   971  			return true, pod, nil
   972  		})
   973  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   974  			gsUpdated = true
   975  			ua := action.(k8stesting.UpdateAction)
   976  			gs := ua.GetObject().(*agonesv1.GameServer)
   977  			assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
   978  			assert.Len(t, gs.Spec.Ports, 1)
   979  			assert.Equal(t, "udp-port", gs.Spec.Ports[0].Name)
   980  			assert.Equal(t, corev1.ProtocolUDP, gs.Spec.Ports[0].Protocol)
   981  			return true, gs, nil
   982  		})
   983  
   984  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced)
   985  		defer cancel()
   986  
   987  		gs, err := c.syncGameServerCreatingState(ctx, fixture)
   988  
   989  		assert.NoError(t, err)
   990  		assert.True(t, podCreated, "Pod should have been created")
   991  
   992  		assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
   993  		assert.True(t, gsUpdated, "GameServer should have been updated")
   994  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Pod")
   995  	})
   996  
   997  	t.Run("Syncing from Created State, with no issues", func(t *testing.T) {
   998  		c, m := newFakeController()
   999  		fixture := newFixture()
  1000  		podCreated := false
  1001  		gsUpdated := false
  1002  
  1003  		var pod *corev1.Pod
  1004  		m.KubeClient.AddReactor("create", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1005  			podCreated = true
  1006  			ca := action.(k8stesting.CreateAction)
  1007  			pod = ca.GetObject().(*corev1.Pod)
  1008  			assert.True(t, metav1.IsControlledBy(pod, fixture))
  1009  			return true, pod, nil
  1010  		})
  1011  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1012  			gsUpdated = true
  1013  			ua := action.(k8stesting.UpdateAction)
  1014  			gs := ua.GetObject().(*agonesv1.GameServer)
  1015  			assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
  1016  			return true, gs, nil
  1017  		})
  1018  
  1019  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced)
  1020  		defer cancel()
  1021  
  1022  		gs, err := c.syncGameServerCreatingState(ctx, fixture)
  1023  
  1024  		assert.NoError(t, err)
  1025  		assert.True(t, podCreated, "Pod should have been created")
  1026  
  1027  		assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
  1028  		assert.True(t, gsUpdated, "GameServer should have been updated")
  1029  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Pod")
  1030  	})
  1031  
  1032  	t.Run("Error on updating gs", func(t *testing.T) {
  1033  		c, m := newFakeController()
  1034  		fixture := newFixture()
  1035  		podCreated := false
  1036  
  1037  		var pod *corev1.Pod
  1038  		m.KubeClient.AddReactor("create", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1039  			podCreated = true
  1040  			ca := action.(k8stesting.CreateAction)
  1041  			pod = ca.GetObject().(*corev1.Pod)
  1042  			assert.True(t, metav1.IsControlledBy(pod, fixture))
  1043  			return true, pod, nil
  1044  		})
  1045  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1046  			ua := action.(k8stesting.UpdateAction)
  1047  			gs := ua.GetObject().(*agonesv1.GameServer)
  1048  			assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
  1049  			return true, gs, errors.New("update-err")
  1050  		})
  1051  
  1052  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced)
  1053  		defer cancel()
  1054  
  1055  		_, err := c.syncGameServerCreatingState(ctx, fixture)
  1056  		require.True(t, podCreated, "Pod should have been created")
  1057  
  1058  		if assert.Error(t, err) {
  1059  			assert.Equal(t, `error updating GameServer test to Starting state: update-err`, err.Error())
  1060  		}
  1061  	})
  1062  
  1063  	t.Run("Previously started sync, created Pod, but didn't move to Starting", func(t *testing.T) {
  1064  		c, m := newFakeController()
  1065  		fixture := newFixture()
  1066  		podCreated := false
  1067  		gsUpdated := false
  1068  		pod, err := fixture.Pod(agtesting.FakeAPIHooks{})
  1069  		assert.Nil(t, err)
  1070  
  1071  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1072  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1073  		})
  1074  		m.KubeClient.AddReactor("create", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1075  			podCreated = true
  1076  			return true, nil, nil
  1077  		})
  1078  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1079  			gsUpdated = true
  1080  			ua := action.(k8stesting.UpdateAction)
  1081  			gs := ua.GetObject().(*agonesv1.GameServer)
  1082  			assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
  1083  			return true, gs, nil
  1084  		})
  1085  
  1086  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced)
  1087  		defer cancel()
  1088  
  1089  		gs, err := c.syncGameServerCreatingState(ctx, fixture)
  1090  		assert.Nil(t, err)
  1091  		assert.Equal(t, agonesv1.GameServerStateStarting, gs.Status.State)
  1092  		assert.False(t, podCreated, "Pod should not have been created")
  1093  		assert.True(t, gsUpdated, "GameServer should have been updated")
  1094  	})
  1095  
  1096  	t.Run("creates an invalid podspec", func(t *testing.T) {
  1097  		c, mocks := newFakeController()
  1098  		fixture := newFixture()
  1099  		podCreated := false
  1100  		gsUpdated := false
  1101  
  1102  		mocks.KubeClient.AddReactor("create", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1103  			podCreated = true
  1104  			return true, nil, k8serrors.NewInvalid(schema.GroupKind{}, "test", field.ErrorList{})
  1105  		})
  1106  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1107  			gsUpdated = true
  1108  			ua := action.(k8stesting.UpdateAction)
  1109  			gs := ua.GetObject().(*agonesv1.GameServer)
  1110  			assert.Equal(t, agonesv1.GameServerStateError, gs.Status.State)
  1111  			return true, gs, nil
  1112  		})
  1113  
  1114  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced, c.podSynced)
  1115  		defer cancel()
  1116  
  1117  		gs, err := c.syncGameServerCreatingState(ctx, fixture)
  1118  		assert.Nil(t, err)
  1119  
  1120  		assert.True(t, podCreated, "attempt should have been made to create a pod")
  1121  		assert.True(t, gsUpdated, "GameServer should be updated")
  1122  		assert.Equal(t, agonesv1.GameServerStateError, gs.Status.State)
  1123  	})
  1124  
  1125  	t.Run("GameServer with unknown state", func(t *testing.T) {
  1126  		testNoChange(t, "Unknown", func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
  1127  			return c.syncGameServerCreatingState(context.Background(), fixture)
  1128  		})
  1129  	})
  1130  
  1131  	t.Run("GameServer with non zero deletion datetime", func(t *testing.T) {
  1132  		testWithNonZeroDeletionTimestamp(t, func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
  1133  			return c.syncGameServerCreatingState(context.Background(), fixture)
  1134  		})
  1135  	})
  1136  }
  1137  
  1138  func TestControllerSyncGameServerStartingState(t *testing.T) {
  1139  	t.Parallel()
  1140  
  1141  	newFixture := func() *agonesv1.GameServer {
  1142  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1143  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateStarting}}
  1144  		fixture.ApplyDefaults()
  1145  		return fixture
  1146  	}
  1147  
  1148  	node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeFixtureName}, Status: corev1.NodeStatus{Addresses: []corev1.NodeAddress{{Address: ipFixture, Type: corev1.NodeExternalIP}}}}
  1149  
  1150  	t.Run("sync from Stating state, with no issues", func(t *testing.T) {
  1151  		c, m := newFakeController()
  1152  		gsFixture := newFixture()
  1153  		gsFixture.ApplyDefaults()
  1154  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1155  		assert.Nil(t, err)
  1156  		pod.Spec.NodeName = nodeFixtureName
  1157  		pod.Status.PodIPs = []corev1.PodIP{{IP: ipv6Fixture}}
  1158  		gsUpdated := false
  1159  
  1160  		m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1161  			return true, &corev1.NodeList{Items: []corev1.Node{node}}, nil
  1162  		})
  1163  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1164  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1165  		})
  1166  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1167  			gsUpdated = true
  1168  			ua := action.(k8stesting.UpdateAction)
  1169  			gs := ua.GetObject().(*agonesv1.GameServer)
  1170  			assert.Equal(t, agonesv1.GameServerStateScheduled, gs.Status.State)
  1171  			return true, gs, nil
  1172  		})
  1173  
  1174  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced, c.nodeSynced)
  1175  		defer cancel()
  1176  
  1177  		gs, err := c.syncGameServerStartingState(ctx, gsFixture)
  1178  		require.NoError(t, err)
  1179  
  1180  		assert.True(t, gsUpdated)
  1181  		assert.Equal(t, gs.Status.NodeName, node.ObjectMeta.Name)
  1182  		assert.Equal(t, gs.Status.Address, ipFixture)
  1183  		assert.Equal(t, []corev1.NodeAddress{
  1184  			{Address: ipFixture, Type: "ExternalIP"},
  1185  			{Address: ipv6Fixture, Type: "PodIP"},
  1186  		}, gs.Status.Addresses)
  1187  
  1188  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Address and port populated")
  1189  		assert.NotEmpty(t, gs.Status.Ports)
  1190  	})
  1191  
  1192  	t.Run("Error on podIPs not populated", func(t *testing.T) {
  1193  		c, m := newFakeController()
  1194  		gsFixture := newFixture()
  1195  		gsFixture.ApplyDefaults()
  1196  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1197  		require.NoError(t, err)
  1198  		pod.Spec.NodeName = nodeFixtureName
  1199  
  1200  		m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1201  			return true, &corev1.NodeList{Items: []corev1.Node{node}}, nil
  1202  		})
  1203  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1204  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1205  		})
  1206  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1207  			ua := action.(k8stesting.UpdateAction)
  1208  			gs := ua.GetObject().(*agonesv1.GameServer)
  1209  			assert.Equal(t, agonesv1.GameServerStateScheduled, gs.Status.State)
  1210  			return true, gs, errors.New("update-err")
  1211  		})
  1212  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced, c.nodeSynced)
  1213  		defer cancel()
  1214  
  1215  		_, err = c.syncGameServerStartingState(ctx, gsFixture)
  1216  		assert.Error(t, err)
  1217  	})
  1218  
  1219  	t.Run("Error on update", func(t *testing.T) {
  1220  		c, m := newFakeController()
  1221  		gsFixture := newFixture()
  1222  		gsFixture.ApplyDefaults()
  1223  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1224  		require.NoError(t, err)
  1225  		pod.Spec.NodeName = nodeFixtureName
  1226  		pod.Status.PodIPs = []corev1.PodIP{{IP: ipv6Fixture}}
  1227  
  1228  		m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1229  			return true, &corev1.NodeList{Items: []corev1.Node{node}}, nil
  1230  		})
  1231  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1232  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1233  		})
  1234  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1235  			ua := action.(k8stesting.UpdateAction)
  1236  			gs := ua.GetObject().(*agonesv1.GameServer)
  1237  			assert.Equal(t, agonesv1.GameServerStateScheduled, gs.Status.State)
  1238  			return true, gs, errors.New("update-err")
  1239  		})
  1240  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced, c.podSynced, c.nodeSynced)
  1241  		defer cancel()
  1242  
  1243  		_, err = c.syncGameServerStartingState(ctx, gsFixture)
  1244  		if assert.Error(t, err) {
  1245  			assert.Equal(t, `error updating GameServer test to Scheduled state: update-err`, err.Error())
  1246  		}
  1247  	})
  1248  
  1249  	t.Run("GameServer with unknown state", func(t *testing.T) {
  1250  		testNoChange(t, "Unknown", func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
  1251  			return c.syncGameServerStartingState(context.Background(), fixture)
  1252  		})
  1253  	})
  1254  
  1255  	t.Run("GameServer with non zero deletion datetime", func(t *testing.T) {
  1256  		testWithNonZeroDeletionTimestamp(t, func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
  1257  			return c.syncGameServerStartingState(context.Background(), fixture)
  1258  		})
  1259  	})
  1260  }
  1261  
  1262  func TestControllerCreateGameServerPod(t *testing.T) {
  1263  	t.Parallel()
  1264  
  1265  	// TODO: remove mutex when "SidecarContainers" moves to stable.
  1266  	agruntime.FeatureTestMutex.Lock()
  1267  	defer agruntime.FeatureTestMutex.Unlock()
  1268  
  1269  	newFixture := func() *agonesv1.GameServer {
  1270  		fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1271  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
  1272  		fixture.ApplyDefaults()
  1273  		return fixture
  1274  	}
  1275  
  1276  	t.Run("create pod, with no issues", func(t *testing.T) {
  1277  		c, m := newFakeController()
  1278  		fixture := newFixture()
  1279  		created := false
  1280  
  1281  		m.KubeClient.AddReactor("create", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1282  			created = true
  1283  			ca := action.(k8stesting.CreateAction)
  1284  			pod := ca.GetObject().(*corev1.Pod)
  1285  
  1286  			assert.Equal(t, fixture.ObjectMeta.Name, pod.ObjectMeta.Name)
  1287  			assert.Equal(t, fixture.ObjectMeta.Namespace, pod.ObjectMeta.Namespace)
  1288  			assert.Equal(t, "sdk-service-account", pod.Spec.ServiceAccountName)
  1289  			assert.Equal(t, "gameserver", pod.ObjectMeta.Labels[agones.GroupName+"/role"])
  1290  			assert.Equal(t, fixture.ObjectMeta.Name, pod.ObjectMeta.Labels[agonesv1.GameServerPodLabel])
  1291  			assert.True(t, metav1.IsControlledBy(pod, fixture))
  1292  
  1293  			// gke.MutateGameServerPod assumes that a non-empty NodeSelector / Tolerations are user
  1294  			// intent. The generic cloudproduct that we use in unit tests does not manipulate these.
  1295  			// So we verify using the generic cloudproduct that NodeSelector/Tolerations are empty
  1296  			// as a change detector - if this test fails, gke.MutateGameServerPod will not work.
  1297  			assert.Empty(t, pod.Spec.NodeSelector)
  1298  			assert.Empty(t, pod.Spec.Tolerations)
  1299  
  1300  			// if sidecar feature enabled, should be 1 container and 1 initContainer, otherwise 2 containers
  1301  			var sidecarContainer corev1.Container
  1302  			var gsContainer corev1.Container
  1303  			if agruntime.FeatureEnabled(agruntime.FeatureSidecarContainers) {
  1304  				assert.Len(t, pod.Spec.Containers, 1, "Should have 1 container")
  1305  				assert.Len(t, pod.Spec.InitContainers, 1, "Should have init container")
  1306  				gsContainer = pod.Spec.Containers[0]
  1307  				sidecarContainer = pod.Spec.InitContainers[0]
  1308  			} else {
  1309  				assert.Len(t, pod.Spec.Containers, 2, "Should have a sidecar container")
  1310  				sidecarContainer = pod.Spec.Containers[0]
  1311  				gsContainer = pod.Spec.Containers[1]
  1312  			}
  1313  
  1314  			assert.Equal(t, sidecarContainer.Image, c.sidecarImage)
  1315  			assert.Equal(t, sidecarContainer.Resources.Limits.Cpu(), &c.sidecarCPULimit)
  1316  			assert.Equal(t, sidecarContainer.Resources.Requests.Cpu(), &c.sidecarCPURequest)
  1317  			assert.Equal(t, sidecarContainer.Resources.Limits.Memory(), &c.sidecarMemoryLimit)
  1318  			assert.Equal(t, sidecarContainer.Resources.Requests.Memory(), &c.sidecarMemoryRequest)
  1319  			assert.Len(t, sidecarContainer.Env, 5, "5 env vars")
  1320  			assert.Equal(t, "GAMESERVER_NAME", sidecarContainer.Env[0].Name)
  1321  			assert.Equal(t, fixture.ObjectMeta.Name, sidecarContainer.Env[0].Value)
  1322  			assert.Equal(t, "POD_NAMESPACE", sidecarContainer.Env[1].Name)
  1323  			assert.Equal(t, "FEATURE_GATES", sidecarContainer.Env[2].Name)
  1324  			assert.Equal(t, "LOG_LEVEL", sidecarContainer.Env[3].Name)
  1325  			assert.Equal(t, "REQUESTS_RATE_LIMIT", sidecarContainer.Env[4].Name)
  1326  			assert.Equal(t, "500ms", sidecarContainer.Env[4].Value)
  1327  			assert.Equal(t, string(fixture.Spec.SdkServer.LogLevel), sidecarContainer.Env[3].Value)
  1328  			assert.Equal(t, *sidecarContainer.SecurityContext.AllowPrivilegeEscalation, false)
  1329  			assert.Equal(t, *sidecarContainer.SecurityContext.RunAsNonRoot, true)
  1330  			assert.Equal(t, *sidecarContainer.SecurityContext.RunAsUser, int64(sidecarRunAsUser))
  1331  
  1332  			assert.Equal(t, fixture.Spec.Ports[0].HostPort, gsContainer.Ports[0].HostPort)
  1333  			assert.Equal(t, fixture.Spec.Ports[0].ContainerPort, gsContainer.Ports[0].ContainerPort)
  1334  			assert.Equal(t, corev1.Protocol("UDP"), gsContainer.Ports[0].Protocol)
  1335  			assert.Equal(t, "/gshealthz", gsContainer.LivenessProbe.HTTPGet.Path)
  1336  			assert.Equal(t, gsContainer.LivenessProbe.HTTPGet.Port, intstr.FromInt(8080))
  1337  			assert.Equal(t, intstr.FromInt(8080), gsContainer.LivenessProbe.HTTPGet.Port)
  1338  			assert.Equal(t, fixture.Spec.Health.InitialDelaySeconds, gsContainer.LivenessProbe.InitialDelaySeconds)
  1339  			assert.Equal(t, fixture.Spec.Health.PeriodSeconds, gsContainer.LivenessProbe.PeriodSeconds)
  1340  			assert.Equal(t, fixture.Spec.Health.FailureThreshold, gsContainer.LivenessProbe.FailureThreshold)
  1341  			assert.Len(t, gsContainer.VolumeMounts, 1)
  1342  			assert.Equal(t, "/var/run/secrets/kubernetes.io/serviceaccount", gsContainer.VolumeMounts[0].MountPath)
  1343  
  1344  			return true, pod, nil
  1345  		})
  1346  
  1347  		gs, err := c.createGameServerPod(context.Background(), fixture)
  1348  		require.NoError(t, err)
  1349  		assert.Equal(t, fixture.Status.State, gs.Status.State)
  1350  		assert.True(t, created)
  1351  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Pod")
  1352  	})
  1353  
  1354  	t.Run("service account", func(t *testing.T) {
  1355  		c, m := newFakeController()
  1356  		fixture := newFixture()
  1357  		fixture.Spec.Template.Spec.ServiceAccountName = "foobar"
  1358  
  1359  		created := false
  1360  
  1361  		m.KubeClient.AddReactor("create", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1362  			created = true
  1363  			ca := action.(k8stesting.CreateAction)
  1364  			pod := ca.GetObject().(*corev1.Pod)
  1365  			if agruntime.FeatureEnabled(agruntime.FeatureSidecarContainers) {
  1366  				assert.Len(t, pod.Spec.InitContainers, 1, "Should have init container")
  1367  			} else {
  1368  				assert.Len(t, pod.Spec.Containers, 2, "Should have a sidecar container")
  1369  			}
  1370  			assert.Empty(t, pod.Spec.Containers[0].VolumeMounts)
  1371  
  1372  			return true, pod, nil
  1373  		})
  1374  
  1375  		_, err := c.createGameServerPod(context.Background(), fixture)
  1376  		assert.Nil(t, err)
  1377  		assert.True(t, created)
  1378  	})
  1379  
  1380  	t.Run("invalid podspec", func(t *testing.T) {
  1381  		c, mocks := newFakeController()
  1382  		fixture := newFixture()
  1383  		podCreated := false
  1384  		gsUpdated := false
  1385  
  1386  		mocks.KubeClient.AddReactor("create", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1387  			podCreated = true
  1388  			return true, nil, k8serrors.NewInvalid(schema.GroupKind{}, "test", field.ErrorList{})
  1389  		})
  1390  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1391  			gsUpdated = true
  1392  			ua := action.(k8stesting.UpdateAction)
  1393  			gs := ua.GetObject().(*agonesv1.GameServer)
  1394  			assert.Equal(t, agonesv1.GameServerStateError, gs.Status.State)
  1395  			return true, gs, nil
  1396  		})
  1397  
  1398  		gs, err := c.createGameServerPod(context.Background(), fixture)
  1399  		require.NoError(t, err)
  1400  
  1401  		assert.True(t, podCreated, "attempt should have been made to create a pod")
  1402  		assert.True(t, gsUpdated, "GameServer should be updated")
  1403  		if assert.NotEmpty(t, gs.Annotations[agonesv1.GameServerErroredAtAnnotation]) {
  1404  			gotTime, err := time.Parse(time.RFC3339, gs.Annotations[agonesv1.GameServerErroredAtAnnotation])
  1405  			require.NoError(t, err)
  1406  			assert.WithinDuration(t, time.Now(), gotTime, time.Second)
  1407  		}
  1408  		assert.Equal(t, agonesv1.GameServerStateError, gs.Status.State)
  1409  	})
  1410  
  1411  	t.Run("forbidden pods creation", func(t *testing.T) {
  1412  		c, mocks := newFakeController()
  1413  		fixture := newFixture()
  1414  		podCreated := false
  1415  		gsUpdated := false
  1416  
  1417  		mocks.KubeClient.AddReactor("create", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1418  			podCreated = true
  1419  			return true, nil, k8serrors.NewForbidden(schema.GroupResource{}, "test", errors.New("test"))
  1420  		})
  1421  		mocks.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1422  			gsUpdated = true
  1423  			ua := action.(k8stesting.UpdateAction)
  1424  			gs := ua.GetObject().(*agonesv1.GameServer)
  1425  			assert.Equal(t, agonesv1.GameServerStateError, gs.Status.State)
  1426  			return true, gs, nil
  1427  		})
  1428  
  1429  		gs, err := c.createGameServerPod(context.Background(), fixture)
  1430  		require.NoError(t, err)
  1431  
  1432  		assert.True(t, podCreated, "attempt should have been made to create a pod")
  1433  		assert.True(t, gsUpdated, "GameServer should be updated")
  1434  		if assert.NotEmpty(t, gs.Annotations[agonesv1.GameServerErroredAtAnnotation]) {
  1435  			gotTime, err := time.Parse(time.RFC3339, gs.Annotations[agonesv1.GameServerErroredAtAnnotation])
  1436  			require.NoError(t, err)
  1437  			assert.WithinDuration(t, time.Now(), gotTime, time.Second)
  1438  		}
  1439  		assert.Equal(t, agonesv1.GameServerStateError, gs.Status.State)
  1440  	})
  1441  }
  1442  
  1443  func TestControllerSyncGameServerRequestReadyState(t *testing.T) {
  1444  	t.Parallel()
  1445  	nodeName := "node"
  1446  	containerID := "1234"
  1447  
  1448  	agruntime.FeatureTestMutex.Lock()
  1449  	defer agruntime.FeatureTestMutex.Unlock()
  1450  
  1451  	t.Run("GameServer with ReadyRequest State", func(t *testing.T) {
  1452  		c, m := newFakeController()
  1453  
  1454  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1455  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1456  		gsFixture.ApplyDefaults()
  1457  		gsFixture.Status.NodeName = nodeName
  1458  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1459  		require.NoError(t, err)
  1460  		pod.Status.ContainerStatuses = []corev1.ContainerStatus{
  1461  			{
  1462  				Name:        gsFixture.Spec.Container,
  1463  				State:       corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
  1464  				ContainerID: containerID,
  1465  			},
  1466  		}
  1467  
  1468  		gsUpdated := false
  1469  		podUpdated := false
  1470  
  1471  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1472  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1473  		})
  1474  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1475  			gsUpdated = true
  1476  			ua := action.(k8stesting.UpdateAction)
  1477  			gs := ua.GetObject().(*agonesv1.GameServer)
  1478  			assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1479  
  1480  			// only set if not using sidecars.
  1481  			if !agruntime.FeatureEnabled(agruntime.FeatureSidecarContainers) {
  1482  				assert.Equal(t, containerID, gs.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1483  			}
  1484  			return true, gs, nil
  1485  		})
  1486  		m.KubeClient.AddReactor("update", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1487  			podUpdated = true
  1488  			ua := action.(k8stesting.UpdateAction)
  1489  			pod := ua.GetObject().(*corev1.Pod)
  1490  			assert.Equal(t, containerID, pod.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1491  			return true, pod, nil
  1492  		})
  1493  
  1494  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1495  		defer cancel()
  1496  
  1497  		gs, err := c.syncGameServerRequestReadyState(ctx, gsFixture)
  1498  		assert.NoError(t, err, "should not error")
  1499  		assert.True(t, gsUpdated, "GameServer wasn't updated")
  1500  		if agruntime.FeatureEnabled(agruntime.FeatureSidecarContainers) {
  1501  			assert.False(t, podUpdated, "Pod was updated")
  1502  		} else {
  1503  			assert.True(t, podUpdated, "Pod was not updated")
  1504  		}
  1505  		assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1506  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "SDK.Ready() complete")
  1507  	})
  1508  
  1509  	t.Run("Error on GameServer update", func(t *testing.T) {
  1510  		require.NoError(t, agruntime.ParseFeatures(string(agruntime.FeatureSidecarContainers)+"=false"))
  1511  
  1512  		c, m := newFakeController()
  1513  
  1514  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1515  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1516  		gsFixture.ApplyDefaults()
  1517  		gsFixture.Status.NodeName = nodeName
  1518  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1519  		require.NoError(t, err)
  1520  		pod.Status.ContainerStatuses = []corev1.ContainerStatus{
  1521  			{
  1522  				Name:        gsFixture.Spec.Container,
  1523  				State:       corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
  1524  				ContainerID: containerID,
  1525  			},
  1526  		}
  1527  		podUpdated := false
  1528  
  1529  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1530  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1531  		})
  1532  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1533  			ua := action.(k8stesting.UpdateAction)
  1534  			gs := ua.GetObject().(*agonesv1.GameServer)
  1535  			assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1536  			assert.Equal(t, containerID, gs.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1537  			return true, gs, errors.New("update-err")
  1538  		})
  1539  		m.KubeClient.AddReactor("update", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1540  			podUpdated = true
  1541  			ua := action.(k8stesting.UpdateAction)
  1542  			pod := ua.GetObject().(*corev1.Pod)
  1543  			assert.Equal(t, containerID, pod.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1544  			return true, pod, nil
  1545  		})
  1546  
  1547  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1548  		defer cancel()
  1549  
  1550  		_, err = c.syncGameServerRequestReadyState(ctx, gsFixture)
  1551  		assert.True(t, podUpdated, "pod was not updated")
  1552  		require.EqualError(t, err, "error setting Ready, Port and address on GameServer test Status: update-err")
  1553  	})
  1554  
  1555  	t.Run("Error on pod update", func(t *testing.T) {
  1556  		c, m := newFakeController()
  1557  
  1558  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1559  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1560  		gsFixture.ApplyDefaults()
  1561  		gsFixture.Status.NodeName = nodeName
  1562  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1563  		require.NoError(t, err)
  1564  		pod.Status.ContainerStatuses = []corev1.ContainerStatus{
  1565  			{
  1566  				Name:        gsFixture.Spec.Container,
  1567  				State:       corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
  1568  				ContainerID: containerID,
  1569  			},
  1570  		}
  1571  		gsUpdated := false
  1572  		podUpdated := false
  1573  
  1574  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1575  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1576  		})
  1577  		m.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1578  			gsUpdated = true
  1579  			return true, nil, nil
  1580  		})
  1581  		m.KubeClient.AddReactor("update", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1582  			podUpdated = true
  1583  			ua := action.(k8stesting.UpdateAction)
  1584  			pod := ua.GetObject().(*corev1.Pod)
  1585  			assert.Equal(t, containerID, pod.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1586  			return true, pod, errors.New("pod-error")
  1587  		})
  1588  
  1589  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1590  		defer cancel()
  1591  
  1592  		_, err = c.syncGameServerRequestReadyState(ctx, gsFixture)
  1593  		assert.True(t, podUpdated, "pod was not updated")
  1594  		assert.False(t, gsUpdated, "GameServer was updated")
  1595  		require.EqualError(t, err, "error updating ready annotation on Pod: test: pod-error")
  1596  	})
  1597  
  1598  	t.Run("Pod annotation already set", func(t *testing.T) {
  1599  		c, m := newFakeController()
  1600  
  1601  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1602  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1603  		gsFixture.ApplyDefaults()
  1604  		gsFixture.Status.NodeName = nodeName
  1605  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1606  		require.NoError(t, err)
  1607  		pod.ObjectMeta.Annotations = map[string]string{agonesv1.GameServerReadyContainerIDAnnotation: containerID}
  1608  		pod.Status.ContainerStatuses = []corev1.ContainerStatus{
  1609  			{
  1610  				Name:        gsFixture.Spec.Container,
  1611  				State:       corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
  1612  				ContainerID: containerID,
  1613  			},
  1614  		}
  1615  		gsUpdated := false
  1616  		podUpdated := false
  1617  
  1618  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1619  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1620  		})
  1621  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1622  			gsUpdated = true
  1623  			ua := action.(k8stesting.UpdateAction)
  1624  			gs := ua.GetObject().(*agonesv1.GameServer)
  1625  			assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1626  			assert.Equal(t, containerID, gs.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1627  			return true, gs, nil
  1628  		})
  1629  		m.KubeClient.AddReactor("update", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1630  			podUpdated = true
  1631  			return true, nil, nil
  1632  		})
  1633  
  1634  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1635  		defer cancel()
  1636  
  1637  		gs, err := c.syncGameServerRequestReadyState(ctx, gsFixture)
  1638  		assert.NoError(t, err, "should not error")
  1639  		assert.True(t, gsUpdated, "GameServer wasn't updated")
  1640  		assert.False(t, podUpdated, "Pod was updated")
  1641  		assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1642  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "SDK.Ready() complete")
  1643  
  1644  	})
  1645  
  1646  	t.Run("GameServer without an Address, but RequestReady State", func(t *testing.T) {
  1647  		c, m := newFakeController()
  1648  
  1649  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1650  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1651  		gsFixture.ApplyDefaults()
  1652  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1653  		assert.Nil(t, err)
  1654  		pod.Spec.NodeName = nodeFixtureName
  1655  		pod.Status.ContainerStatuses = []corev1.ContainerStatus{
  1656  			{
  1657  				Name:        gsFixture.Spec.Container,
  1658  				State:       corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
  1659  				ContainerID: containerID,
  1660  			},
  1661  		}
  1662  		gsUpdated := false
  1663  		podUpdated := false
  1664  
  1665  		ipFixture := "12.12.12.12"
  1666  		nodeFixture := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeFixtureName}, Status: corev1.NodeStatus{Addresses: []corev1.NodeAddress{{Address: ipFixture, Type: corev1.NodeExternalIP}}}}
  1667  
  1668  		m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1669  			return true, &corev1.NodeList{Items: []corev1.Node{nodeFixture}}, nil
  1670  		})
  1671  
  1672  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1673  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1674  		})
  1675  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1676  			gsUpdated = true
  1677  			ua := action.(k8stesting.UpdateAction)
  1678  			gs := ua.GetObject().(*agonesv1.GameServer)
  1679  			assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1680  			assert.Equal(t, containerID, gs.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1681  			return true, gs, nil
  1682  		})
  1683  		m.KubeClient.AddReactor("update", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1684  			podUpdated = true
  1685  			ua := action.(k8stesting.UpdateAction)
  1686  			pod := ua.GetObject().(*corev1.Pod)
  1687  			assert.Equal(t, containerID, pod.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1688  			return true, pod, nil
  1689  		})
  1690  
  1691  		ctx, cancel := agtesting.StartInformers(m, c.podSynced, c.nodeSynced)
  1692  		defer cancel()
  1693  
  1694  		gs, err := c.syncGameServerRequestReadyState(ctx, gsFixture)
  1695  		assert.Nil(t, err, "should not error")
  1696  		assert.True(t, gsUpdated, "GameServer wasn't updated")
  1697  		assert.True(t, podUpdated, "Pod wasn't updated")
  1698  		assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1699  
  1700  		assert.Equal(t, gs.Status.NodeName, nodeFixture.ObjectMeta.Name)
  1701  		assert.Equal(t, gs.Status.Address, ipFixture)
  1702  		assert.Equal(t, []corev1.NodeAddress{{Address: ipFixture, Type: "ExternalIP"}}, gs.Status.Addresses)
  1703  
  1704  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "Address and port populated")
  1705  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "SDK.Ready() complete")
  1706  	})
  1707  
  1708  	t.Run("GameServer with a GameServerReadyContainerIDAnnotation already", func(t *testing.T) {
  1709  		c, m := newFakeController()
  1710  
  1711  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1712  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1713  		gsFixture.ApplyDefaults()
  1714  		gsFixture.Status.NodeName = nodeName
  1715  		gsFixture.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = "4321"
  1716  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1717  		pod.Status.ContainerStatuses = []corev1.ContainerStatus{
  1718  			{
  1719  				Name:        gsFixture.Spec.Container,
  1720  				State:       corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
  1721  				ContainerID: containerID,
  1722  			},
  1723  		}
  1724  		assert.Nil(t, err)
  1725  		gsUpdated := false
  1726  		podUpdated := false
  1727  
  1728  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1729  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1730  		})
  1731  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1732  			gsUpdated = true
  1733  			ua := action.(k8stesting.UpdateAction)
  1734  			gs := ua.GetObject().(*agonesv1.GameServer)
  1735  			assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1736  			assert.NotEqual(t, containerID, gs.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1737  
  1738  			return true, gs, nil
  1739  		})
  1740  		m.KubeClient.AddReactor("update", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1741  			podUpdated = true
  1742  			ua := action.(k8stesting.UpdateAction)
  1743  			pod := ua.GetObject().(*corev1.Pod)
  1744  			assert.NotEqual(t, containerID, pod.Annotations[agonesv1.GameServerReadyContainerIDAnnotation])
  1745  			return true, pod, nil
  1746  		})
  1747  
  1748  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1749  		defer cancel()
  1750  
  1751  		gs, err := c.syncGameServerRequestReadyState(ctx, gsFixture)
  1752  		assert.NoError(t, err, "should not error")
  1753  		assert.True(t, gsUpdated, "GameServer wasn't updated")
  1754  		assert.True(t, podUpdated, "Pod wasn't updated")
  1755  		assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State)
  1756  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "SDK.Ready() complete")
  1757  	})
  1758  
  1759  	for _, s := range []agonesv1.GameServerState{"Unknown", agonesv1.GameServerStateUnhealthy} {
  1760  		name := fmt.Sprintf("GameServer with %s state", s)
  1761  		t.Run(name, func(t *testing.T) {
  1762  			testNoChange(t, s, func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
  1763  				return c.syncGameServerRequestReadyState(context.Background(), fixture)
  1764  			})
  1765  		})
  1766  	}
  1767  
  1768  	t.Run("GameServer whose pod is currently not in a running state, so should retry and not update", func(t *testing.T) {
  1769  		c, m := newFakeController()
  1770  
  1771  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1772  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1773  		gsFixture.ApplyDefaults()
  1774  		gsFixture.Status.NodeName = nodeName
  1775  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1776  		pod.Status.ContainerStatuses = []corev1.ContainerStatus{{Name: gsFixture.Spec.Container}}
  1777  		assert.Nil(t, err)
  1778  		gsUpdated := false
  1779  		podUpdated := false
  1780  
  1781  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1782  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1783  		})
  1784  		m.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1785  			gsUpdated = true
  1786  			return true, nil, nil
  1787  		})
  1788  		m.KubeClient.AddReactor("update", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1789  			podUpdated = true
  1790  			return true, nil, nil
  1791  		})
  1792  
  1793  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1794  		defer cancel()
  1795  
  1796  		_, err = c.syncGameServerRequestReadyState(ctx, gsFixture)
  1797  		assert.EqualError(t, err, "game server container for GameServer test in namespace default is not currently running, try again")
  1798  		assert.False(t, gsUpdated, "GameServer was updated")
  1799  		assert.False(t, podUpdated, "Pod was updated")
  1800  	})
  1801  
  1802  	t.Run("GameServer whose pod is missing ContainerStatuses, so should retry and not update", func(t *testing.T) {
  1803  		c, m := newFakeController()
  1804  
  1805  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1806  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1807  		gsFixture.ApplyDefaults()
  1808  		gsFixture.Status.NodeName = nodeName
  1809  		pod, err := gsFixture.Pod(agtesting.FakeAPIHooks{})
  1810  		assert.Nil(t, err)
  1811  		gsUpdated := false
  1812  		podUpdated := false
  1813  
  1814  		m.KubeClient.AddReactor("list", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1815  			return true, &corev1.PodList{Items: []corev1.Pod{*pod}}, nil
  1816  		})
  1817  		m.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1818  			gsUpdated = true
  1819  			return true, nil, nil
  1820  		})
  1821  		m.KubeClient.AddReactor("update", "pods", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1822  			podUpdated = true
  1823  			return true, nil, nil
  1824  		})
  1825  
  1826  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1827  		defer cancel()
  1828  
  1829  		_, err = c.syncGameServerRequestReadyState(ctx, gsFixture)
  1830  		assert.EqualError(t, err, "game server container for GameServer test in namespace default not present in pod status, try again")
  1831  		assert.False(t, gsUpdated, "GameServer was updated")
  1832  		assert.False(t, podUpdated, "Pod was updated")
  1833  	})
  1834  
  1835  	t.Run("GameServer with non zero deletion datetime", func(t *testing.T) {
  1836  		testWithNonZeroDeletionTimestamp(t, func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
  1837  			return c.syncGameServerRequestReadyState(context.Background(), fixture)
  1838  		})
  1839  	})
  1840  }
  1841  
  1842  func TestMoveToErrorState(t *testing.T) {
  1843  	t.Parallel()
  1844  
  1845  	t.Run("Set GameServer to error state", func(t *testing.T) {
  1846  		c, m := newFakeController()
  1847  
  1848  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1849  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1850  		gsFixture.ApplyDefaults()
  1851  
  1852  		gsUpdated := false
  1853  
  1854  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1855  			gsUpdated = true
  1856  			ua := action.(k8stesting.UpdateAction)
  1857  			gs := ua.GetObject().(*agonesv1.GameServer)
  1858  			assert.Equal(t, agonesv1.GameServerStateError, gs.Status.State)
  1859  			return true, gs, nil
  1860  		})
  1861  
  1862  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1863  		defer cancel()
  1864  
  1865  		res, err := c.moveToErrorState(ctx, gsFixture, "some-data")
  1866  		require.NoError(t, err)
  1867  		require.NotNil(t, res)
  1868  		assert.True(t, gsUpdated)
  1869  		assert.Equal(t, agonesv1.GameServerStateError, res.Status.State)
  1870  	})
  1871  
  1872  	t.Run("Error on update", func(t *testing.T) {
  1873  		c, m := newFakeController()
  1874  
  1875  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1876  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateRequestReady}}
  1877  		gsFixture.ApplyDefaults()
  1878  
  1879  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1880  			ua := action.(k8stesting.UpdateAction)
  1881  			gs := ua.GetObject().(*agonesv1.GameServer)
  1882  			assert.Equal(t, agonesv1.GameServerStateError, gs.Status.State)
  1883  			return true, gs, errors.New("update-err")
  1884  		})
  1885  
  1886  		ctx, cancel := agtesting.StartInformers(m, c.podSynced)
  1887  		defer cancel()
  1888  
  1889  		_, err := c.moveToErrorState(ctx, gsFixture, "some-data")
  1890  		if assert.Error(t, err) {
  1891  			assert.Equal(t, `error moving GameServer test to Error State: update-err`, err.Error())
  1892  		}
  1893  	})
  1894  }
  1895  
  1896  func TestControllerSyncGameServerShutdownState(t *testing.T) {
  1897  	t.Parallel()
  1898  
  1899  	t.Run("GameServer with a Shutdown state", func(t *testing.T) {
  1900  		c, mocks := newFakeController()
  1901  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1902  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateShutdown}}
  1903  		gsFixture.ApplyDefaults()
  1904  		checkDeleted := false
  1905  
  1906  		mocks.AgonesClient.AddReactor("delete", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1907  			checkDeleted = true
  1908  			assert.Equal(t, "default", action.GetNamespace())
  1909  			da := action.(k8stesting.DeleteAction)
  1910  			assert.Equal(t, "test", da.GetName())
  1911  
  1912  			return true, nil, nil
  1913  		})
  1914  
  1915  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
  1916  		defer cancel()
  1917  
  1918  		err := c.syncGameServerShutdownState(ctx, gsFixture)
  1919  		assert.Nil(t, err)
  1920  		assert.True(t, checkDeleted, "GameServer should be deleted")
  1921  		assert.Contains(t, <-mocks.FakeRecorder.Events, "Deletion started")
  1922  	})
  1923  
  1924  	t.Run("Error on delete", func(t *testing.T) {
  1925  		c, mocks := newFakeController()
  1926  		gsFixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1927  			Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateShutdown}}
  1928  		gsFixture.ApplyDefaults()
  1929  
  1930  		mocks.AgonesClient.AddReactor("delete", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1931  			assert.Equal(t, "default", action.GetNamespace())
  1932  			da := action.(k8stesting.DeleteAction)
  1933  			assert.Equal(t, "test", da.GetName())
  1934  
  1935  			return true, nil, errors.New("delete-err")
  1936  		})
  1937  
  1938  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
  1939  		defer cancel()
  1940  
  1941  		err := c.syncGameServerShutdownState(ctx, gsFixture)
  1942  		if assert.Error(t, err) {
  1943  			assert.Equal(t, `error deleting Game Server test: delete-err`, err.Error())
  1944  		}
  1945  	})
  1946  
  1947  	t.Run("GameServer with unknown state", func(t *testing.T) {
  1948  		testNoChange(t, "Unknown", func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
  1949  			return fixture, c.syncGameServerShutdownState(context.Background(), fixture)
  1950  		})
  1951  	})
  1952  
  1953  	t.Run("GameServer with non zero deletion datetime", func(t *testing.T) {
  1954  		testWithNonZeroDeletionTimestamp(t, func(c *Controller, fixture *agonesv1.GameServer) (*agonesv1.GameServer, error) {
  1955  			return fixture, c.syncGameServerShutdownState(context.Background(), fixture)
  1956  		})
  1957  	})
  1958  }
  1959  
  1960  func TestControllerGameServerPod(t *testing.T) {
  1961  	t.Parallel()
  1962  
  1963  	agruntime.FeatureTestMutex.Lock()
  1964  	defer agruntime.FeatureTestMutex.Unlock()
  1965  	require.NoError(t, agruntime.ParseFeatures(string(agruntime.FeatureSidecarContainers)+"=false"))
  1966  
  1967  	setup := func() (*Controller, *agonesv1.GameServer, *watch.FakeWatcher, context.Context, context.CancelFunc) {
  1968  		c, mocks := newFakeController()
  1969  		fakeWatch := watch.NewFake()
  1970  		mocks.KubeClient.AddWatchReactor("pods", k8stesting.DefaultWatchReactor(fakeWatch, nil))
  1971  		ctx, cancel := agtesting.StartInformers(mocks, c.gameServerSynced)
  1972  		gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gameserver",
  1973  			Namespace: defaultNs, UID: "1234"}, Spec: newSingleContainerSpec()}
  1974  		gs.ApplyDefaults()
  1975  		return c, gs, fakeWatch, ctx, cancel
  1976  	}
  1977  
  1978  	t.Run("no pod exists", func(t *testing.T) {
  1979  		c, gs, _, _, cancel := setup()
  1980  		defer cancel()
  1981  
  1982  		require.Never(t, func() bool {
  1983  			list, err := c.podLister.List(labels.Everything())
  1984  			assert.NoError(t, err)
  1985  			return len(list) > 0
  1986  		}, time.Second, 100*time.Millisecond)
  1987  		_, err := c.gameServerPod(gs)
  1988  		assert.Error(t, err)
  1989  		assert.True(t, k8serrors.IsNotFound(err))
  1990  	})
  1991  
  1992  	t.Run("a pod exists", func(t *testing.T) {
  1993  		c, gs, fakeWatch, _, cancel := setup()
  1994  
  1995  		defer cancel()
  1996  		pod, err := gs.Pod(agtesting.FakeAPIHooks{})
  1997  		require.NoError(t, err)
  1998  
  1999  		fakeWatch.Add(pod.DeepCopy())
  2000  		require.Eventually(t, func() bool {
  2001  			list, err := c.podLister.List(labels.Everything())
  2002  			assert.NoError(t, err)
  2003  			return len(list) == 1
  2004  		}, 5*time.Second, time.Second)
  2005  
  2006  		pod2, err := c.gameServerPod(gs)
  2007  		require.NoError(t, err)
  2008  		assert.Equal(t, pod, pod2)
  2009  
  2010  		fakeWatch.Delete(pod.DeepCopy())
  2011  		require.Eventually(t, func() bool {
  2012  			list, err := c.podLister.List(labels.Everything())
  2013  			assert.NoError(t, err)
  2014  			return len(list) == 0
  2015  		}, 5*time.Second, time.Second)
  2016  		_, err = c.gameServerPod(gs)
  2017  		assert.Error(t, err)
  2018  		assert.True(t, k8serrors.IsNotFound(err))
  2019  	})
  2020  
  2021  	t.Run("a pod exists, but isn't owned by the gameserver", func(t *testing.T) {
  2022  		c, gs, fakeWatch, ctx, cancel := setup()
  2023  		defer cancel()
  2024  
  2025  		pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: gs.ObjectMeta.Name, Labels: map[string]string{agonesv1.GameServerPodLabel: gs.ObjectMeta.Name, "owned": "false"}}}
  2026  		fakeWatch.Add(pod.DeepCopy())
  2027  
  2028  		// gate
  2029  		cache.WaitForCacheSync(ctx.Done(), c.podSynced)
  2030  		pod, err := c.podGetter.Pods(defaultNs).Get(ctx, pod.ObjectMeta.Name, metav1.GetOptions{})
  2031  		require.NoError(t, err)
  2032  		assert.NotNil(t, pod)
  2033  
  2034  		_, err = c.gameServerPod(gs)
  2035  		assert.Error(t, err)
  2036  		assert.True(t, k8serrors.IsNotFound(err))
  2037  	})
  2038  
  2039  	t.Run("dev gameserver pod", func(t *testing.T) {
  2040  		c, _ := newFakeController()
  2041  
  2042  		gs := &agonesv1.GameServer{
  2043  			ObjectMeta: metav1.ObjectMeta{Name: "gameserver", Namespace: defaultNs,
  2044  				Annotations: map[string]string{
  2045  					agonesv1.DevAddressAnnotation: "1.1.1.1",
  2046  				},
  2047  				UID: "1234"},
  2048  
  2049  			Spec: newSingleContainerSpec()}
  2050  
  2051  		pod, err := c.gameServerPod(gs)
  2052  		require.NoError(t, err)
  2053  		assert.Empty(t, pod.ObjectMeta.Name)
  2054  	})
  2055  }
  2056  
  2057  func TestControllerAddGameServerHealthCheck(t *testing.T) {
  2058  	c, _ := newFakeController()
  2059  	fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  2060  		Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
  2061  	fixture.ApplyDefaults()
  2062  
  2063  	assert.False(t, fixture.Spec.Health.Disabled)
  2064  	pod, err := fixture.Pod(agtesting.FakeAPIHooks{})
  2065  	require.NoError(t, err)
  2066  	err = c.addGameServerHealthCheck(fixture, pod)
  2067  
  2068  	assert.NoError(t, err)
  2069  	assert.Len(t, pod.Spec.Containers, 1)
  2070  	probe := pod.Spec.Containers[0].LivenessProbe
  2071  	require.NotNil(t, probe)
  2072  	assert.Equal(t, "/gshealthz", probe.HTTPGet.Path)
  2073  	assert.Equal(t, intstr.IntOrString{IntVal: 8080}, probe.HTTPGet.Port)
  2074  	assert.Equal(t, fixture.Spec.Health.FailureThreshold, probe.FailureThreshold)
  2075  	assert.Equal(t, fixture.Spec.Health.InitialDelaySeconds, probe.InitialDelaySeconds)
  2076  	assert.Equal(t, fixture.Spec.Health.PeriodSeconds, probe.PeriodSeconds)
  2077  }
  2078  
  2079  func TestControllerAddSDKServerEnvVars(t *testing.T) {
  2080  
  2081  	t.Run("legacy game server without ports set", func(t *testing.T) {
  2082  		// For backwards compatibility, verify that no variables are set if the ports
  2083  		// are not set on the game server.
  2084  		c, _ := newFakeController()
  2085  		gs := &agonesv1.GameServer{
  2086  			ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "1234"},
  2087  			Spec:       newSingleContainerSpec(),
  2088  		}
  2089  		gs.ApplyDefaults()
  2090  		gs.Spec.SdkServer = agonesv1.SdkServer{}
  2091  		pod, err := gs.Pod(agtesting.FakeAPIHooks{})
  2092  		require.NoError(t, err)
  2093  		before := pod.DeepCopy()
  2094  		c.addSDKServerEnvVars(gs, pod)
  2095  		assert.Equal(t, before, pod, "Error: pod unexpectedly modified. before = %v, after = %v", before, pod)
  2096  	})
  2097  
  2098  	t.Run("game server without any environment", func(t *testing.T) {
  2099  		c, _ := newFakeController()
  2100  		gs := &agonesv1.GameServer{
  2101  			ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "2345"},
  2102  			Spec:       newSingleContainerSpec(),
  2103  		}
  2104  		gs.ApplyDefaults()
  2105  		pod, err := gs.Pod(agtesting.FakeAPIHooks{})
  2106  		require.NoError(t, err)
  2107  		c.addSDKServerEnvVars(gs, pod)
  2108  		assert.Len(t, pod.Spec.Containers, 1, "Expected 1 container, found %d", len(pod.Spec.Containers))
  2109  		assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))})
  2110  		assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))})
  2111  	})
  2112  
  2113  	t.Run("game server without any conflicting env vars", func(t *testing.T) {
  2114  		c, _ := newFakeController()
  2115  		gs := &agonesv1.GameServer{
  2116  			ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "3456"},
  2117  			Spec: agonesv1.GameServerSpec{
  2118  				Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}},
  2119  				Template: corev1.PodTemplateSpec{
  2120  					Spec: corev1.PodSpec{
  2121  						Containers: []corev1.Container{
  2122  							{
  2123  								Name:  "container",
  2124  								Image: "container/image",
  2125  								Env:   []corev1.EnvVar{{Name: "one", Value: "value"}, {Name: "two", Value: "value"}},
  2126  							},
  2127  						},
  2128  					},
  2129  				},
  2130  			},
  2131  		}
  2132  		gs.ApplyDefaults()
  2133  		pod, err := gs.Pod(agtesting.FakeAPIHooks{})
  2134  		require.NoError(t, err)
  2135  		c.addSDKServerEnvVars(gs, pod)
  2136  		assert.Len(t, pod.Spec.Containers, 1, "Expected 1 container, found %d", len(pod.Spec.Containers))
  2137  		assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))})
  2138  		assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))})
  2139  	})
  2140  
  2141  	t.Run("game server with conflicting env vars", func(t *testing.T) {
  2142  		c, _ := newFakeController()
  2143  		gs := &agonesv1.GameServer{
  2144  			ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "4567"},
  2145  			Spec: agonesv1.GameServerSpec{
  2146  				Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}},
  2147  				Template: corev1.PodTemplateSpec{
  2148  					Spec: corev1.PodSpec{
  2149  						Containers: []corev1.Container{
  2150  							{
  2151  								Name:  "container",
  2152  								Image: "container/image",
  2153  								Env:   []corev1.EnvVar{{Name: grpcPortEnvVar, Value: "value"}, {Name: httpPortEnvVar, Value: "value"}},
  2154  							},
  2155  						},
  2156  					},
  2157  				},
  2158  			},
  2159  		}
  2160  		gs.ApplyDefaults()
  2161  		pod, err := gs.Pod(agtesting.FakeAPIHooks{})
  2162  		require.NoError(t, err)
  2163  		c.addSDKServerEnvVars(gs, pod)
  2164  		assert.Len(t, pod.Spec.Containers, 1, "Expected 1 container, found %d", len(pod.Spec.Containers))
  2165  		assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))})
  2166  		assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))})
  2167  	})
  2168  
  2169  	t.Run("game server with multiple containers", func(t *testing.T) {
  2170  		c, _ := newFakeController()
  2171  		gs := &agonesv1.GameServer{
  2172  			ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "5678"},
  2173  			Spec: agonesv1.GameServerSpec{
  2174  				Container: "container1",
  2175  				Ports:     []agonesv1.GameServerPort{{ContainerPort: 7777}},
  2176  				Template: corev1.PodTemplateSpec{
  2177  					Spec: corev1.PodSpec{
  2178  						Containers: []corev1.Container{
  2179  							{
  2180  								Name:  "container1",
  2181  								Image: "container/gameserver",
  2182  							},
  2183  							{
  2184  								Name:  "container2",
  2185  								Image: "container/image2",
  2186  								Env:   []corev1.EnvVar{{Name: "one", Value: "value"}, {Name: "two", Value: "value"}},
  2187  							},
  2188  							{
  2189  								Name:  "container3",
  2190  								Image: "container/image2",
  2191  								Env:   []corev1.EnvVar{{Name: grpcPortEnvVar, Value: "value"}, {Name: httpPortEnvVar, Value: "value"}},
  2192  							},
  2193  						},
  2194  					},
  2195  				},
  2196  			},
  2197  		}
  2198  		gs.ApplyDefaults()
  2199  		pod, err := gs.Pod(agtesting.FakeAPIHooks{})
  2200  		require.NoError(t, err)
  2201  		c.addSDKServerEnvVars(gs, pod)
  2202  		for _, c := range pod.Spec.Containers {
  2203  			assert.Contains(t, c.Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))})
  2204  			assert.Contains(t, c.Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))})
  2205  		}
  2206  	})
  2207  
  2208  	t.Run("environment variables not applied to the sdkserver container", func(t *testing.T) {
  2209  		c, _ := newFakeController()
  2210  		gs := &agonesv1.GameServer{
  2211  			ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "5678"},
  2212  			Spec: agonesv1.GameServerSpec{
  2213  				Container: "container1",
  2214  				Ports:     []agonesv1.GameServerPort{{ContainerPort: 7777}},
  2215  				Template: corev1.PodTemplateSpec{
  2216  					Spec: corev1.PodSpec{
  2217  						Containers: []corev1.Container{
  2218  							{
  2219  								Name:  "container1",
  2220  								Image: "container/gameserver",
  2221  							},
  2222  							{
  2223  								Name:  "container2",
  2224  								Image: "container/image2",
  2225  								Env:   []corev1.EnvVar{{Name: "one", Value: "value"}, {Name: "two", Value: "value"}},
  2226  							},
  2227  							{
  2228  								Name:  "container3",
  2229  								Image: "container/image2",
  2230  								Env:   []corev1.EnvVar{{Name: grpcPortEnvVar, Value: "value"}, {Name: httpPortEnvVar, Value: "value"}},
  2231  							},
  2232  						},
  2233  					},
  2234  				},
  2235  			},
  2236  		}
  2237  		gs.ApplyDefaults()
  2238  		sidecar := c.sidecar(gs)
  2239  		pod, err := gs.Pod(agtesting.FakeAPIHooks{}, sidecar)
  2240  		require.NoError(t, err)
  2241  		c.addSDKServerEnvVars(gs, pod)
  2242  		for _, c := range pod.Spec.Containers {
  2243  			if c.Name == sdkserverSidecarName {
  2244  				assert.NotContains(t, c.Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))})
  2245  				assert.NotContains(t, c.Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))})
  2246  			} else {
  2247  				assert.Contains(t, c.Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))})
  2248  				assert.Contains(t, c.Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))})
  2249  			}
  2250  		}
  2251  	})
  2252  }
  2253  
  2254  // testNoChange runs a test with a state that doesn't exist, to ensure a handler
  2255  // doesn't do process anything beyond the state it is meant to handle.
  2256  func testNoChange(t *testing.T, state agonesv1.GameServerState, f func(*Controller, *agonesv1.GameServer) (*agonesv1.GameServer, error)) {
  2257  	c, mocks := newFakeController()
  2258  	fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  2259  		Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: state}}
  2260  	fixture.ApplyDefaults()
  2261  	updated := false
  2262  	mocks.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  2263  		updated = true
  2264  		return true, nil, nil
  2265  	})
  2266  
  2267  	result, err := f(c, fixture)
  2268  	require.NoError(t, err)
  2269  	assert.False(t, updated, "update should occur")
  2270  	assert.Equal(t, fixture, result)
  2271  }
  2272  
  2273  // testWithNonZeroDeletionTimestamp runs a test with a given state, but
  2274  // the DeletionTimestamp set to Now()
  2275  func testWithNonZeroDeletionTimestamp(t *testing.T, f func(*Controller, *agonesv1.GameServer) (*agonesv1.GameServer, error)) {
  2276  	c, mocks := newFakeController()
  2277  	now := metav1.Now()
  2278  	fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default", DeletionTimestamp: &now},
  2279  		Spec: newSingleContainerSpec(), Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateShutdown}}
  2280  	fixture.ApplyDefaults()
  2281  	updated := false
  2282  	mocks.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  2283  		updated = true
  2284  		return true, nil, nil
  2285  	})
  2286  
  2287  	result, err := f(c, fixture)
  2288  	require.NoError(t, err)
  2289  	assert.False(t, updated, "update should occur")
  2290  	assert.Equal(t, fixture, result)
  2291  }
  2292  
  2293  // newFakeController returns a controller, backed by the fake Clientset
  2294  func newFakeController() (*Controller, agtesting.Mocks) {
  2295  	m := agtesting.NewMocks()
  2296  	c := NewController(
  2297  		generic.New(),
  2298  		healthcheck.NewHandler(),
  2299  		map[string]portallocator.PortRange{agonesv1.DefaultPortRange: {MinPort: 10, MaxPort: 20}},
  2300  		"sidecar:dev", false,
  2301  		resource.MustParse("0.05"), resource.MustParse("0.1"),
  2302  		resource.MustParse("50Mi"), resource.MustParse("100Mi"), sidecarRunAsUser, 500*time.Millisecond, "sdk-service-account",
  2303  		m.KubeClient, m.KubeInformerFactory, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory)
  2304  	c.recorder = m.FakeRecorder
  2305  	return c, m
  2306  }
  2307  
  2308  // newFakeExtensions return a fake extensions struct
  2309  func newFakeExtensions() *Extensions {
  2310  	return NewExtensions(generic.New(), webhooks.NewWebHook(http.NewServeMux()))
  2311  }
  2312  
  2313  func newSingleContainerSpec() agonesv1.GameServerSpec {
  2314  	return agonesv1.GameServerSpec{
  2315  		Ports: []agonesv1.GameServerPort{{ContainerPort: 7777, HostPort: 9999, PortPolicy: agonesv1.Static}},
  2316  		Template: corev1.PodTemplateSpec{
  2317  			Spec: corev1.PodSpec{
  2318  				Containers: []corev1.Container{{Name: "container", Image: "container/image"}},
  2319  			},
  2320  		},
  2321  	}
  2322  }
  2323  
  2324  // Assume container ports 0 and 1 are Passthrough ports for "example-server" container and container port 0 for "example-server-two"
  2325  // The annotation would look like autopilot.gke.io/passthrough-port-assignment: '{"example-server":["0","1"], "example-server-two":[0]}'
  2326  func newPassthroughPortSingleContainerSpec() corev1.Pod {
  2327  	passthroughContainerPortMap := "{\"example-server\":[0,1],\"example-server-two\":[0]}"
  2328  	return corev1.Pod{
  2329  		ObjectMeta: metav1.ObjectMeta{
  2330  			Annotations: map[string]string{agonesv1.PassthroughPortAssignmentAnnotation: passthroughContainerPortMap},
  2331  		},
  2332  		Spec: corev1.PodSpec{
  2333  			Containers: []corev1.Container{
  2334  				{Name: "agones-gameserver-sidecar",
  2335  					Image: "container/image",
  2336  					Env:   []corev1.EnvVar{{Name: passthroughPortEnvVar, Value: "TRUE"}}},
  2337  				{Name: "example-server",
  2338  					Image: "container2/image",
  2339  					Ports: []corev1.ContainerPort{
  2340  						{HostPort: 7777, ContainerPort: 5555},
  2341  						{HostPort: 7776, ContainerPort: 7797},
  2342  						{HostPort: 7775, ContainerPort: 7793}},
  2343  					Env: []corev1.EnvVar{{Name: passthroughPortEnvVar, Value: "TRUE"}}},
  2344  				{Name: "example-server-two",
  2345  					Image: "container3/image",
  2346  					Ports: []corev1.ContainerPort{
  2347  						{HostPort: 7745, ContainerPort: 7983},
  2348  						{HostPort: 7312, ContainerPort: 7364}},
  2349  					Env: []corev1.EnvVar{{Name: passthroughPortEnvVar, Value: "TRUE"}}}},
  2350  		},
  2351  	}
  2352  }