agones.dev/agones@v1.53.0/pkg/fleets/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  // nolint:goconst
    16  package fleets
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"net/http"
    22  	"testing"
    23  	"time"
    24  
    25  	"agones.dev/agones/pkg/apis"
    26  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    27  	v1 "agones.dev/agones/pkg/apis/agones/v1"
    28  	applyconfigurations "agones.dev/agones/pkg/client/applyconfiguration/agones/v1"
    29  	agonesv1clientset "agones.dev/agones/pkg/client/clientset/versioned/typed/agones/v1"
    30  	agonesv1client "agones.dev/agones/pkg/client/listers/agones/v1"
    31  	"agones.dev/agones/pkg/cloudproduct/generic"
    32  	agtesting "agones.dev/agones/pkg/testing"
    33  	utilruntime "agones.dev/agones/pkg/util/runtime"
    34  	"agones.dev/agones/pkg/util/webhooks"
    35  	"github.com/heptiolabs/healthcheck"
    36  	"github.com/pkg/errors"
    37  	"github.com/sirupsen/logrus"
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/require"
    40  	"gomodules.xyz/jsonpatch/v2"
    41  	admissionv1 "k8s.io/api/admission/v1"
    42  	appsv1 "k8s.io/api/apps/v1"
    43  	autoscalingv1 "k8s.io/api/autoscaling/v1"
    44  	corev1 "k8s.io/api/core/v1"
    45  	"k8s.io/apimachinery/pkg/api/resource"
    46  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    47  	"k8s.io/apimachinery/pkg/labels"
    48  	"k8s.io/apimachinery/pkg/runtime"
    49  	"k8s.io/apimachinery/pkg/types"
    50  	"k8s.io/apimachinery/pkg/util/intstr"
    51  	"k8s.io/apimachinery/pkg/watch"
    52  	k8stesting "k8s.io/client-go/testing"
    53  	"k8s.io/client-go/tools/cache"
    54  )
    55  
    56  func TestControllerSyncFleet(t *testing.T) {
    57  	t.Parallel()
    58  
    59  	t.Run("no gameserverset, create it", func(t *testing.T) {
    60  		f := defaultFixture()
    61  		c, m := newFakeController()
    62  
    63  		created := false
    64  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
    65  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
    66  		})
    67  
    68  		m.AgonesClient.AddReactor("create", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
    69  			ca := action.(k8stesting.CreateAction)
    70  			gsSet := ca.GetObject().(*agonesv1.GameServerSet)
    71  
    72  			created = true
    73  			assert.True(t, metav1.IsControlledBy(gsSet, f))
    74  			assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas)
    75  
    76  			return true, gsSet, nil
    77  		})
    78  
    79  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced)
    80  		defer cancel()
    81  
    82  		err := c.syncFleet(ctx, "default/fleet-1")
    83  		assert.Nil(t, err)
    84  		assert.True(t, created, "gameserverset should have been created")
    85  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "CreatingGameServerSet")
    86  	})
    87  
    88  	t.Run("gameserverset with the same number of replicas", func(t *testing.T) {
    89  		t.Parallel()
    90  		f := defaultFixture()
    91  		f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
    92  		c, m := newFakeController()
    93  		gsSet := f.GameServerSet()
    94  
    95  		gsSet.ObjectMeta.Name = "gsSet1"
    96  		gsSet.ObjectMeta.UID = "4321"
    97  		gsSet.Spec.Replicas = f.Spec.Replicas
    98  
    99  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   100  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   101  		})
   102  
   103  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   104  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   105  		})
   106  
   107  		m.AgonesClient.AddReactor("create", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   108  			assert.FailNow(t, "gameserverset should not be created")
   109  			return true, nil, nil
   110  		})
   111  
   112  		m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   113  			assert.FailNow(t, "gameserverset should not have been updated")
   114  			return true, nil, nil
   115  		})
   116  
   117  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   118  		defer cancel()
   119  
   120  		err := c.syncFleet(ctx, "default/fleet-1")
   121  		assert.Nil(t, err)
   122  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   123  	})
   124  
   125  	t.Run("gameserverset with different number of replicas", func(t *testing.T) {
   126  		if utilruntime.FeatureEnabled(utilruntime.FeatureRollingUpdateFix) {
   127  			t.SkipNow()
   128  		}
   129  
   130  		f := defaultFixture()
   131  		f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
   132  		c, m := newFakeController()
   133  		gsSet := f.GameServerSet()
   134  		gsSet.ObjectMeta.Name = "gsSet1"
   135  		gsSet.ObjectMeta.UID = "1234"
   136  		gsSet.Spec.Replicas = f.Spec.Replicas + 10
   137  		updated := false
   138  
   139  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   140  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   141  		})
   142  
   143  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   144  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   145  		})
   146  
   147  		m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   148  			updated = true
   149  
   150  			ua := action.(k8stesting.UpdateAction)
   151  			gsSet := ua.GetObject().(*agonesv1.GameServerSet)
   152  			assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas)
   153  
   154  			return true, gsSet, nil
   155  		})
   156  
   157  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   158  		defer cancel()
   159  
   160  		err := c.syncFleet(ctx, "default/fleet-1")
   161  		assert.Nil(t, err)
   162  		assert.True(t, updated, "gameserverset should have been updated")
   163  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet")
   164  	})
   165  
   166  	t.Run("gameserverset with different scheduling", func(t *testing.T) {
   167  		f := defaultFixture()
   168  		f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
   169  		c, m := newFakeController()
   170  		gsSet := f.GameServerSet()
   171  		gsSet.ObjectMeta.Name = "gsSet1"
   172  		gsSet.ObjectMeta.UID = "1234"
   173  		gsSet.Spec.Replicas = f.Spec.Replicas
   174  		gsSet.Spec.Scheduling = apis.Distributed
   175  		updated := false
   176  
   177  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   178  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   179  		})
   180  
   181  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   182  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   183  		})
   184  
   185  		m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   186  			updated = true
   187  
   188  			ua := action.(k8stesting.UpdateAction)
   189  			gsSet := ua.GetObject().(*agonesv1.GameServerSet)
   190  			assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas)
   191  			assert.Equal(t, f.Spec.Scheduling, gsSet.Spec.Scheduling)
   192  			return true, gsSet, nil
   193  		})
   194  
   195  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   196  		defer cancel()
   197  
   198  		err := c.syncFleet(ctx, "default/fleet-1")
   199  		assert.Nil(t, err)
   200  		assert.True(t, updated, "gameserverset should have been updated")
   201  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet")
   202  	})
   203  
   204  	t.Run("gameserverset with different image details", func(t *testing.T) {
   205  		if utilruntime.FeatureEnabled(utilruntime.FeatureRollingUpdateFix) {
   206  			t.SkipNow()
   207  		}
   208  
   209  		f := defaultFixture()
   210  		f.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType
   211  		f.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 5555}}
   212  		f.Status.ReadyReplicas = 5
   213  		c, m := newFakeController()
   214  		gsSet := f.GameServerSet()
   215  		gsSet.ObjectMeta.Name = "gsSet1"
   216  		gsSet.ObjectMeta.UID = "4321"
   217  		gsSet.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 7777}}
   218  		gsSet.Spec.Replicas = f.Spec.Replicas
   219  		gsSet.Spec.Scheduling = f.Spec.Scheduling
   220  		gsSet.Status.Replicas = 5
   221  		updated := false
   222  		created := false
   223  
   224  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   225  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   226  		})
   227  
   228  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   229  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   230  		})
   231  
   232  		m.AgonesClient.AddReactor("create", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   233  			created = true
   234  			ca := action.(k8stesting.CreateAction)
   235  			gsSet := ca.GetObject().(*agonesv1.GameServerSet)
   236  			assert.Equal(t, int32(2), gsSet.Spec.Replicas)
   237  			assert.Equal(t, f.Spec.Template.Spec.Ports[0].HostPort, gsSet.Spec.Template.Spec.Ports[0].HostPort)
   238  
   239  			return true, gsSet, nil
   240  		})
   241  
   242  		m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
   243  			updated = true
   244  			ua := action.(k8stesting.UpdateAction)
   245  			// separate update of status subresource
   246  			if ua.GetSubresource() != "" {
   247  				assert.Equal(t, ua.GetSubresource(), "status")
   248  				return true, nil, nil
   249  			}
   250  			// update main resource
   251  			gsSet := ua.GetObject().(*agonesv1.GameServerSet)
   252  			assert.Equal(t, int32(4), gsSet.Spec.Replicas)
   253  			assert.Equal(t, "gsSet1", gsSet.ObjectMeta.Name)
   254  
   255  			return true, gsSet, nil
   256  		})
   257  
   258  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   259  		defer cancel()
   260  
   261  		err := c.syncFleet(ctx, "default/fleet-1")
   262  		assert.Nil(t, err)
   263  		assert.True(t, updated, "gameserverset should have been updated")
   264  		assert.True(t, created, "gameserverset should have been created")
   265  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet")
   266  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "CreatingGameServerSet")
   267  	})
   268  
   269  	t.Run("fleet marked for deletion shouldn't take any action on gameserver sets", func(t *testing.T) {
   270  		f := defaultFixture()
   271  		f.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType
   272  		f.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 5555}}
   273  		f.Status.ReadyReplicas = 5
   274  		f.DeletionTimestamp = &metav1.Time{
   275  			Time: time.Now(),
   276  		}
   277  
   278  		c, m := newFakeController()
   279  		gsSet := f.GameServerSet()
   280  		gsSet.ObjectMeta.Name = "gsSet1"
   281  		gsSet.ObjectMeta.UID = "4321"
   282  		gsSet.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 7777}}
   283  		gsSet.Spec.Replicas = f.Spec.Replicas
   284  		gsSet.Spec.Scheduling = f.Spec.Scheduling
   285  		gsSet.Status.Replicas = 5
   286  
   287  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   288  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   289  		})
   290  
   291  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   292  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   293  		})
   294  
   295  		m.AgonesClient.AddReactor("create", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   296  			assert.FailNow(t, "gameserverset should not have been created")
   297  			return false, nil, nil
   298  		})
   299  
   300  		m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   301  			assert.FailNow(t, "gameserverset should not have been updated")
   302  			return false, nil, nil
   303  		})
   304  
   305  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   306  		defer cancel()
   307  
   308  		err := c.syncFleet(ctx, "default/fleet-1")
   309  		assert.Nil(t, err)
   310  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
   311  	})
   312  
   313  	t.Run("error on getting fleet", func(t *testing.T) {
   314  		c, _ := newFakeController()
   315  		c.fleetLister = &fakeFleetListerWithErr{}
   316  
   317  		err := c.syncFleet(context.Background(), "default/fleet-1")
   318  		assert.EqualError(t, err, "error retrieving fleet fleet-1 from namespace default: err-from-namespace-lister")
   319  	})
   320  
   321  	t.Run("error on getting list of GS", func(t *testing.T) {
   322  		f := defaultFixture()
   323  		c, m := newFakeController()
   324  		c.gameServerSetLister = &fakeGSSListerWithErr{}
   325  
   326  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   327  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   328  		})
   329  
   330  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   331  		defer cancel()
   332  
   333  		err := c.syncFleet(ctx, "default/fleet-1")
   334  		assert.EqualError(t, err, "error listing gameserversets for fleet fleet-1: random-err")
   335  	})
   336  
   337  	t.Run("fleet not found", func(t *testing.T) {
   338  		c, _ := newFakeController()
   339  
   340  		err := c.syncFleet(context.Background(), "default/fleet-1")
   341  		assert.Nil(t, err)
   342  	})
   343  
   344  	t.Run("fleet invalid strategy type", func(t *testing.T) {
   345  		f := defaultFixture()
   346  		c, m := newFakeController()
   347  		f.Spec.Strategy.Type = "invalid-strategy-type"
   348  
   349  		gsSet := f.GameServerSet()
   350  		// make gsSet.Spec.Template and f.Spec.Template different in order to make 'rest' list not empty
   351  		gsSet.Spec.Template.Name = "qqqqqqqqqqqqqqqqqqq"
   352  		// make sure there is at least one replica, or the logic will escape before the check.
   353  		gsSet.Spec.Replicas = 1
   354  
   355  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   356  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   357  		})
   358  
   359  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   360  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   361  		})
   362  
   363  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   364  		defer cancel()
   365  
   366  		err := c.syncFleet(ctx, "default/fleet-1")
   367  		assert.EqualError(t, err, "unexpected deployment strategy type: invalid-strategy-type")
   368  	})
   369  
   370  	t.Run("error on deleteEmptyGameServerSets", func(t *testing.T) {
   371  		f := defaultFixture()
   372  		c, m := newFakeController()
   373  
   374  		gsSet := f.GameServerSet()
   375  		// make gsSet.Spec.Template and f.Spec.Template different in order to make 'rest' list not empty
   376  		gsSet.Spec.Template.Name = "qqqqqqqqqqqqqqqqqqq"
   377  
   378  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   379  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   380  		})
   381  
   382  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   383  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   384  		})
   385  
   386  		m.AgonesClient.AddReactor("delete", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   387  			return true, nil, errors.New("random-err")
   388  		})
   389  
   390  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   391  		defer cancel()
   392  
   393  		err := c.syncFleet(ctx, "default/fleet-1")
   394  		assert.EqualError(t, err, "error updating gameserverset : random-err")
   395  	})
   396  
   397  	t.Run("error on upsertGameServerSet", func(t *testing.T) {
   398  		f := defaultFixture()
   399  		c, m := newFakeController()
   400  
   401  		gsSet := f.GameServerSet()
   402  
   403  		m.AgonesClient.AddReactor("list", "fleets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   404  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   405  		})
   406  
   407  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   408  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   409  		})
   410  
   411  		m.AgonesClient.AddReactor("create", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   412  			return true, nil, errors.New("random-err")
   413  		})
   414  
   415  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   416  		defer cancel()
   417  
   418  		err := c.syncFleet(ctx, "default/fleet-1")
   419  		assert.EqualError(t, err, "error creating gameserverset for fleet fleet-1: random-err")
   420  	})
   421  }
   422  
   423  func TestControllerCreationValidationHandler(t *testing.T) {
   424  	t.Parallel()
   425  
   426  	ext := newFakeExtensions()
   427  
   428  	t.Run("invalid JSON", func(t *testing.T) {
   429  		raw, err := json.Marshal([]byte(`1`))
   430  		require.NoError(t, err)
   431  		review := getAdmissionReview(raw)
   432  
   433  		_, err = ext.creationValidationHandler(review)
   434  		assert.EqualError(t, err, "error unmarshalling Fleet json after schema validation: \"MQ==\": json: cannot unmarshal string into Go value of type v1.Fleet")
   435  	})
   436  
   437  	t.Run("invalid fleet", func(t *testing.T) {
   438  		fixture := agonesv1.Fleet{}
   439  
   440  		raw, err := json.Marshal(fixture)
   441  		require.NoError(t, err)
   442  		review := getAdmissionReview(raw)
   443  
   444  		result, err := ext.creationValidationHandler(review)
   445  		require.NoError(t, err)
   446  		assert.False(t, result.Response.Allowed)
   447  		assert.Equal(t, "Failure", result.Response.Result.Status)
   448  	})
   449  
   450  	t.Run("valid fleet", func(t *testing.T) {
   451  		gsSpec := *defaultGSSpec()
   452  		f := defaultFixture()
   453  		f.Spec.Template = gsSpec
   454  
   455  		raw, err := json.Marshal(f)
   456  		require.NoError(t, err)
   457  		review := getAdmissionReview(raw)
   458  
   459  		result, err := ext.creationValidationHandler(review)
   460  		require.NoError(t, err)
   461  		assert.True(t, result.Response.Allowed)
   462  	})
   463  }
   464  
   465  func TestControllerCreationMutationHandler(t *testing.T) {
   466  	t.Parallel()
   467  
   468  	t.Run("ok scenario", func(t *testing.T) {
   469  		ext := newFakeExtensions()
   470  		fixture := agonesv1.Fleet{}
   471  
   472  		raw, err := json.Marshal(fixture)
   473  		require.NoError(t, err)
   474  		review := getAdmissionReview(raw)
   475  
   476  		result, err := ext.creationMutationHandler(review)
   477  		require.NoError(t, err)
   478  		assert.True(t, result.Response.Allowed)
   479  		assert.Equal(t, admissionv1.PatchTypeJSONPatch, *result.Response.PatchType)
   480  
   481  		patch := &jsonpatch.ByPath{}
   482  		err = json.Unmarshal(result.Response.Patch, patch)
   483  		require.NoError(t, err)
   484  
   485  		assertContains := func(patch *jsonpatch.ByPath, op jsonpatch.JsonPatchOperation) {
   486  			found := false
   487  			for _, p := range *patch {
   488  				if assert.ObjectsAreEqualValues(p, op) {
   489  					found = true
   490  				}
   491  			}
   492  
   493  			assert.True(t, found, "Could not find operation %#v in patch %v", op, *patch)
   494  		}
   495  
   496  		assertContains(patch, jsonpatch.JsonPatchOperation{Operation: "add", Path: "/spec/strategy/type", Value: "RollingUpdate"})
   497  	})
   498  
   499  	t.Run("invalid JSON", func(t *testing.T) {
   500  		ext := newFakeExtensions()
   501  		raw, err := json.Marshal([]byte(`1`))
   502  		require.NoError(t, err)
   503  		review := getAdmissionReview(raw)
   504  
   505  		result, err := ext.creationMutationHandler(review)
   506  		assert.NoError(t, err)
   507  		require.Nil(t, result.Response.PatchType)
   508  	})
   509  }
   510  
   511  func TestControllerRun(t *testing.T) {
   512  	t.Parallel()
   513  
   514  	fleet := defaultFixture()
   515  	c, m := newFakeController()
   516  	received := make(chan string)
   517  	defer close(received)
   518  
   519  	m.ExtClient.AddReactor("get", "customresourcedefinitions", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   520  		return true, agtesting.NewEstablishedCRD(), nil
   521  	})
   522  
   523  	fleetWatch := watch.NewFake()
   524  	m.AgonesClient.AddWatchReactor("fleets", k8stesting.DefaultWatchReactor(fleetWatch, nil))
   525  
   526  	gsSetWatch := watch.NewFake()
   527  	m.AgonesClient.AddWatchReactor("gameserversets", k8stesting.DefaultWatchReactor(gsSetWatch, nil))
   528  
   529  	c.workerqueue.SyncHandler = func(_ context.Context, name string) error {
   530  		received <- name
   531  		return nil
   532  	}
   533  
   534  	ctx, cancel := agtesting.StartInformers(m, c.fleetSynced)
   535  	defer cancel()
   536  
   537  	go func() {
   538  		err := c.Run(ctx, 1)
   539  		assert.Nil(t, err)
   540  	}()
   541  
   542  	f := func() string {
   543  		select {
   544  		case result := <-received:
   545  			return result
   546  		case <-time.After(3 * time.Second):
   547  			assert.FailNow(t, "timeout occurred")
   548  		}
   549  		return ""
   550  	}
   551  
   552  	expected, err := cache.MetaNamespaceKeyFunc(fleet)
   553  	require.NoError(t, err)
   554  
   555  	// test adding fleet
   556  	fleetWatch.Add(fleet.DeepCopy())
   557  	assert.Equal(t, expected, f())
   558  
   559  	// test updating fleet
   560  	fCopy := fleet.DeepCopy()
   561  	fCopy.Spec.Replicas += 10
   562  	fleetWatch.Modify(fCopy)
   563  	assert.Equal(t, expected, f())
   564  
   565  	// test add/update of gameserver set
   566  	gsSet := fleet.GameServerSet()
   567  	gsSet.ObjectMeta.Name = "gs1"
   568  	gsSet.ObjectMeta.GenerateName = ""
   569  	gsSetWatch.Add(gsSet)
   570  	assert.Equal(t, expected, f())
   571  
   572  	gsSet.Spec.Replicas += 10
   573  	gsSetWatch.Modify(gsSet)
   574  	assert.Equal(t, expected, f())
   575  }
   576  
   577  func TestControllerUpdateFleetStatus(t *testing.T) {
   578  	t.Parallel()
   579  
   580  	t.Run("update, no errors", func(t *testing.T) {
   581  		fleet := defaultFixture()
   582  		c, m := newFakeController()
   583  
   584  		gsSet1 := fleet.GameServerSet()
   585  		gsSet1.ObjectMeta.Name = "gsSet1"
   586  		gsSet1.Status.Replicas = 3
   587  		gsSet1.Status.ReadyReplicas = 2
   588  		gsSet1.Status.ReservedReplicas = 4
   589  		gsSet1.Status.AllocatedReplicas = 1
   590  
   591  		gsSet2 := fleet.GameServerSet()
   592  		// nolint:goconst
   593  		gsSet2.ObjectMeta.Name = "gsSet2"
   594  		gsSet2.Status.Replicas = 5
   595  		gsSet2.Status.ReadyReplicas = 5
   596  		gsSet2.Status.ReservedReplicas = 3
   597  		gsSet2.Status.AllocatedReplicas = 2
   598  
   599  		m.AgonesClient.AddReactor("list", "gameserversets",
   600  			func(_ k8stesting.Action) (bool, runtime.Object, error) {
   601  				return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet1, *gsSet2}}, nil
   602  			})
   603  
   604  		updated := false
   605  		m.AgonesClient.AddReactor("update", "fleets",
   606  			func(action k8stesting.Action) (bool, runtime.Object, error) {
   607  				updated = true
   608  				ua := action.(k8stesting.UpdateAction)
   609  				fleet := ua.GetObject().(*agonesv1.Fleet)
   610  
   611  				assert.Equal(t, gsSet1.Status.Replicas+gsSet2.Status.Replicas, fleet.Status.Replicas)
   612  				assert.Equal(t, gsSet1.Status.ReadyReplicas+gsSet2.Status.ReadyReplicas, fleet.Status.ReadyReplicas)
   613  				assert.Equal(t, gsSet1.Status.ReservedReplicas+gsSet2.Status.ReservedReplicas, fleet.Status.ReservedReplicas)
   614  				assert.Equal(t, gsSet1.Status.AllocatedReplicas+gsSet2.Status.AllocatedReplicas, fleet.Status.AllocatedReplicas)
   615  				return true, fleet, nil
   616  			})
   617  
   618  		ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   619  		defer cancel()
   620  
   621  		err := c.updateFleetStatus(ctx, fleet)
   622  		assert.Nil(t, err)
   623  		assert.True(t, updated)
   624  	})
   625  
   626  	t.Run("list gameservers returns an error", func(t *testing.T) {
   627  		fleet := defaultFixture()
   628  		c, _ := newFakeController()
   629  		c.gameServerSetLister = &fakeGSSListerWithErr{}
   630  
   631  		err := c.updateFleetStatus(context.Background(), fleet)
   632  		assert.EqualError(t, err, "error listing gameserversets for fleet fleet-1: random-err")
   633  	})
   634  
   635  	t.Run("fleets getter returns an error", func(t *testing.T) {
   636  		fleet := defaultFixture()
   637  		c, _ := newFakeController()
   638  
   639  		c.fleetGetter = &fakeFleetsGetterWithErr{}
   640  
   641  		err := c.updateFleetStatus(context.Background(), fleet)
   642  
   643  		assert.EqualError(t, err, "err-from-fleet-getter")
   644  	})
   645  
   646  }
   647  
   648  func TestControllerUpdateFleetPlayerStatus(t *testing.T) {
   649  	t.Parallel()
   650  
   651  	utilruntime.FeatureTestMutex.Lock()
   652  	defer utilruntime.FeatureTestMutex.Unlock()
   653  
   654  	require.NoError(t, utilruntime.ParseFeatures(string(utilruntime.FeaturePlayerTracking)+"=true"))
   655  
   656  	fleet := defaultFixture()
   657  	c, m := newFakeController()
   658  
   659  	gsSet1 := fleet.GameServerSet()
   660  	gsSet1.ObjectMeta.Name = "gsSet1"
   661  	gsSet1.Status.Players = &agonesv1.AggregatedPlayerStatus{
   662  		Count:    5,
   663  		Capacity: 10,
   664  	}
   665  
   666  	gsSet2 := fleet.GameServerSet()
   667  	gsSet2.ObjectMeta.Name = "gsSet2"
   668  	gsSet2.Status.Players = &agonesv1.AggregatedPlayerStatus{
   669  		Count:    10,
   670  		Capacity: 20,
   671  	}
   672  
   673  	m.AgonesClient.AddReactor("list", "gameserversets",
   674  		func(_ k8stesting.Action) (bool, runtime.Object, error) {
   675  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet1, *gsSet2}}, nil
   676  		})
   677  
   678  	updated := false
   679  	m.AgonesClient.AddReactor("update", "fleets",
   680  		func(action k8stesting.Action) (bool, runtime.Object, error) {
   681  			updated = true
   682  			ua := action.(k8stesting.UpdateAction)
   683  			fleet := ua.GetObject().(*agonesv1.Fleet)
   684  
   685  			assert.Equal(t, gsSet1.Status.Players.Count+gsSet2.Status.Players.Count, fleet.Status.Players.Count)
   686  			assert.Equal(t, gsSet1.Status.Players.Capacity+gsSet2.Status.Players.Capacity, fleet.Status.Players.Capacity)
   687  
   688  			return true, fleet, nil
   689  		})
   690  
   691  	ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   692  	defer cancel()
   693  
   694  	err := c.updateFleetStatus(ctx, fleet)
   695  	assert.Nil(t, err)
   696  	assert.True(t, updated)
   697  }
   698  
   699  // nolint:dupl // Linter errors on lines are duplicate of TestControllerUpdateFleetListStatus
   700  func TestControllerUpdateFleetCounterStatus(t *testing.T) {
   701  	t.Parallel()
   702  
   703  	utilruntime.FeatureTestMutex.Lock()
   704  	defer utilruntime.FeatureTestMutex.Unlock()
   705  
   706  	require.NoError(t, utilruntime.ParseFeatures(string(utilruntime.FeatureCountsAndLists)+"=true"))
   707  
   708  	fleet := defaultFixture()
   709  	c, m := newFakeController()
   710  
   711  	gsSet1 := fleet.GameServerSet()
   712  	gsSet1.ObjectMeta.Name = "gsSet1"
   713  	gsSet1.Status.Counters = map[string]agonesv1.AggregatedCounterStatus{
   714  		"fullCounter": {
   715  			AllocatedCount:    9223372036854775807,
   716  			AllocatedCapacity: 9223372036854775807,
   717  			Capacity:          9223372036854775807,
   718  			Count:             9223372036854775807,
   719  		},
   720  		"anotherCounter": {
   721  			AllocatedCount:    11,
   722  			AllocatedCapacity: 20,
   723  			Capacity:          100,
   724  			Count:             42,
   725  		},
   726  	}
   727  
   728  	gsSet2 := fleet.GameServerSet()
   729  	gsSet2.ObjectMeta.Name = "gsSet2"
   730  	gsSet2.Status.Counters = map[string]agonesv1.AggregatedCounterStatus{
   731  		"fullCounter": {
   732  			AllocatedCount:    100,
   733  			AllocatedCapacity: 100,
   734  			Capacity:          100,
   735  			Count:             100,
   736  		},
   737  		"anotherCounter": {
   738  			AllocatedCount:    0,
   739  			AllocatedCapacity: 0,
   740  			Capacity:          100,
   741  			Count:             0,
   742  		},
   743  		"thirdCounter": {
   744  			AllocatedCount:    21,
   745  			AllocatedCapacity: 30,
   746  			Capacity:          400,
   747  			Count:             21,
   748  		},
   749  	}
   750  
   751  	m.AgonesClient.AddReactor("list", "gameserversets",
   752  		func(_ k8stesting.Action) (bool, runtime.Object, error) {
   753  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet1, *gsSet2}}, nil
   754  		})
   755  
   756  	updated := false
   757  	m.AgonesClient.AddReactor("update", "fleets",
   758  		func(action k8stesting.Action) (bool, runtime.Object, error) {
   759  			updated = true
   760  			ua := action.(k8stesting.UpdateAction)
   761  			fleet := ua.GetObject().(*agonesv1.Fleet)
   762  
   763  			assert.Equal(t, int64(9223372036854775807), fleet.Status.Counters["fullCounter"].AllocatedCount)
   764  			assert.Equal(t, int64(9223372036854775807), fleet.Status.Counters["fullCounter"].AllocatedCapacity)
   765  			assert.Equal(t, int64(9223372036854775807), fleet.Status.Counters["fullCounter"].Capacity)
   766  			assert.Equal(t, int64(9223372036854775807), fleet.Status.Counters["fullCounter"].Count)
   767  
   768  			assert.Equal(t, int64(11), fleet.Status.Counters["anotherCounter"].AllocatedCount)
   769  			assert.Equal(t, int64(20), fleet.Status.Counters["anotherCounter"].AllocatedCapacity)
   770  			assert.Equal(t, int64(200), fleet.Status.Counters["anotherCounter"].Capacity)
   771  			assert.Equal(t, int64(42), fleet.Status.Counters["anotherCounter"].Count)
   772  
   773  			assert.Equal(t, int64(21), fleet.Status.Counters["thirdCounter"].AllocatedCount)
   774  			assert.Equal(t, int64(30), fleet.Status.Counters["thirdCounter"].AllocatedCapacity)
   775  			assert.Equal(t, int64(400), fleet.Status.Counters["thirdCounter"].Capacity)
   776  			assert.Equal(t, int64(21), fleet.Status.Counters["thirdCounter"].Count)
   777  
   778  			return true, fleet, nil
   779  		})
   780  
   781  	ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   782  	defer cancel()
   783  
   784  	err := c.updateFleetStatus(ctx, fleet)
   785  	assert.Nil(t, err)
   786  	assert.True(t, updated)
   787  }
   788  
   789  // nolint:dupl // Linter errors on lines are duplicate of TestControllerUpdateFleetCounterStatus
   790  func TestControllerUpdateFleetListStatus(t *testing.T) {
   791  	t.Parallel()
   792  
   793  	utilruntime.FeatureTestMutex.Lock()
   794  	defer utilruntime.FeatureTestMutex.Unlock()
   795  
   796  	require.NoError(t, utilruntime.ParseFeatures(string(utilruntime.FeatureCountsAndLists)+"=true"))
   797  
   798  	fleet := defaultFixture()
   799  	c, m := newFakeController()
   800  
   801  	gsSet1 := fleet.GameServerSet()
   802  	gsSet1.ObjectMeta.Name = "gsSet1"
   803  	gsSet1.Status.Lists = map[string]agonesv1.AggregatedListStatus{
   804  		"fullList": {
   805  			AllocatedCount:    1000,
   806  			AllocatedCapacity: 1000,
   807  			Capacity:          1000,
   808  			Count:             1000,
   809  		},
   810  		"anotherList": {
   811  			AllocatedCount:    11,
   812  			AllocatedCapacity: 100,
   813  			Capacity:          100,
   814  			Count:             11,
   815  		},
   816  		"thirdList": {
   817  			AllocatedCount:    1,
   818  			AllocatedCapacity: 20,
   819  			Capacity:          30,
   820  			Count:             4,
   821  		},
   822  	}
   823  
   824  	gsSet2 := fleet.GameServerSet()
   825  	gsSet2.ObjectMeta.Name = "gsSet2"
   826  	gsSet2.Status.Lists = map[string]agonesv1.AggregatedListStatus{
   827  		"fullList": {
   828  			AllocatedCount:    200,
   829  			AllocatedCapacity: 200,
   830  			Capacity:          200,
   831  			Count:             200,
   832  		},
   833  		"anotherList": {
   834  			AllocatedCount:    1,
   835  			AllocatedCapacity: 10,
   836  			Capacity:          100,
   837  			Count:             11,
   838  		},
   839  	}
   840  
   841  	m.AgonesClient.AddReactor("list", "gameserversets",
   842  		func(_ k8stesting.Action) (bool, runtime.Object, error) {
   843  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet1, *gsSet2}}, nil
   844  		})
   845  
   846  	updated := false
   847  	m.AgonesClient.AddReactor("update", "fleets",
   848  		func(action k8stesting.Action) (bool, runtime.Object, error) {
   849  			updated = true
   850  			ua := action.(k8stesting.UpdateAction)
   851  			fleet := ua.GetObject().(*agonesv1.Fleet)
   852  
   853  			assert.Equal(t, int64(1200), fleet.Status.Lists["fullList"].AllocatedCount)
   854  			assert.Equal(t, int64(1200), fleet.Status.Lists["fullList"].AllocatedCapacity)
   855  			assert.Equal(t, int64(1200), fleet.Status.Lists["fullList"].Capacity)
   856  			assert.Equal(t, int64(1200), fleet.Status.Lists["fullList"].Count)
   857  
   858  			assert.Equal(t, int64(12), fleet.Status.Lists["anotherList"].AllocatedCount)
   859  			assert.Equal(t, int64(110), fleet.Status.Lists["anotherList"].AllocatedCapacity)
   860  			assert.Equal(t, int64(200), fleet.Status.Lists["anotherList"].Capacity)
   861  			assert.Equal(t, int64(22), fleet.Status.Lists["anotherList"].Count)
   862  
   863  			assert.Equal(t, int64(1), fleet.Status.Lists["thirdList"].AllocatedCount)
   864  			assert.Equal(t, int64(20), fleet.Status.Lists["thirdList"].AllocatedCapacity)
   865  			assert.Equal(t, int64(30), fleet.Status.Lists["thirdList"].Capacity)
   866  			assert.Equal(t, int64(4), fleet.Status.Lists["thirdList"].Count)
   867  
   868  			return true, fleet, nil
   869  		})
   870  
   871  	ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   872  	defer cancel()
   873  
   874  	err := c.updateFleetStatus(ctx, fleet)
   875  	assert.Nil(t, err)
   876  	assert.True(t, updated)
   877  }
   878  
   879  // Test that the aggregated Counters and Lists are removed from the Fleet status if the
   880  // FeatureCountsAndLists flag is set to false.
   881  func TestFleetDropCountsAndListsStatus(t *testing.T) {
   882  	t.Parallel()
   883  
   884  	utilruntime.FeatureTestMutex.Lock()
   885  	defer utilruntime.FeatureTestMutex.Unlock()
   886  
   887  	f := defaultFixture()
   888  	defaultFleetName := "default/fleet-1"
   889  	c, m := newFakeController()
   890  
   891  	gss := f.GameServerSet()
   892  	gss.ObjectMeta.Name = "gsSet1"
   893  	gss.ObjectMeta.UID = "4321"
   894  	gss.Spec.Replicas = f.Spec.Replicas
   895  	gss.Status.Counters = map[string]agonesv1.AggregatedCounterStatus{
   896  		"aCounter": {
   897  			AllocatedCount:    1,
   898  			AllocatedCapacity: 10,
   899  			Capacity:          1000,
   900  			Count:             100,
   901  		},
   902  	}
   903  	gss.Status.Lists = map[string]agonesv1.AggregatedListStatus{
   904  		"aList": {
   905  			AllocatedCount:    10,
   906  			AllocatedCapacity: 100,
   907  			Capacity:          10000,
   908  			Count:             1000,
   909  		},
   910  	}
   911  
   912  	flag := ""
   913  	updated := false
   914  
   915  	m.AgonesClient.AddReactor("list", "gameserversets",
   916  		func(_ k8stesting.Action) (bool, runtime.Object, error) {
   917  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gss}}, nil
   918  		})
   919  
   920  	m.AgonesClient.AddReactor("list", "fleets",
   921  		func(_ k8stesting.Action) (bool, runtime.Object, error) {
   922  			return true, &agonesv1.FleetList{Items: []agonesv1.Fleet{*f}}, nil
   923  		})
   924  
   925  	m.AgonesClient.AddReactor("update", "fleets",
   926  		func(action k8stesting.Action) (bool, runtime.Object, error) {
   927  			ua := action.(k8stesting.UpdateAction)
   928  			fleet := ua.GetObject().(*agonesv1.Fleet)
   929  			updated = true
   930  
   931  			switch flag {
   932  			case string(utilruntime.FeatureCountsAndLists) + "=true":
   933  				assert.Equal(t, gss.Status.Counters, fleet.Status.Counters)
   934  				assert.Equal(t, gss.Status.Lists, fleet.Status.Lists)
   935  			case string(utilruntime.FeatureCountsAndLists) + "=false":
   936  				assert.Nil(t, fleet.Status.Counters)
   937  				assert.Nil(t, fleet.Status.Lists)
   938  			default:
   939  				return false, fleet, errors.Errorf("Flag string(utilruntime.FeatureCountsAndLists) should be set")
   940  			}
   941  			return true, fleet, nil
   942  		})
   943  
   944  	ctx, cancel := agtesting.StartInformers(m, c.fleetSynced, c.gameServerSetSynced)
   945  	defer cancel()
   946  
   947  	// Expect starting fleet to have Aggregated Counter and List Statuses
   948  	flag = string(utilruntime.FeatureCountsAndLists) + "=true"
   949  	require.NoError(t, utilruntime.ParseFeatures(flag))
   950  	err := c.syncFleet(ctx, defaultFleetName)
   951  	assert.NoError(t, err)
   952  	assert.True(t, updated)
   953  
   954  	updated = false
   955  	flag = string(utilruntime.FeatureCountsAndLists) + "=false"
   956  	require.NoError(t, utilruntime.ParseFeatures(flag))
   957  	err = c.syncFleet(ctx, defaultFleetName)
   958  	assert.NoError(t, err)
   959  	assert.True(t, updated)
   960  
   961  	updated = false
   962  	flag = string(utilruntime.FeatureCountsAndLists) + "=true"
   963  	require.NoError(t, utilruntime.ParseFeatures(flag))
   964  	err = c.syncFleet(ctx, defaultFleetName)
   965  	assert.NoError(t, err)
   966  	assert.True(t, updated)
   967  }
   968  
   969  func TestControllerFilterGameServerSetByActive(t *testing.T) {
   970  	t.Parallel()
   971  
   972  	f := defaultFixture()
   973  	c, _ := newFakeController()
   974  	// the same GameServer Template
   975  	gsSet1 := f.GameServerSet()
   976  	gsSet1.ObjectMeta.Name = "gsSet1"
   977  
   978  	// different GameServer Template
   979  	gsSet2 := f.GameServerSet()
   980  	gsSet2.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 9999}}
   981  
   982  	// one active
   983  	active, rest := c.filterGameServerSetByActive(f, []*agonesv1.GameServerSet{gsSet1, gsSet2})
   984  	assert.Equal(t, gsSet1, active)
   985  	assert.Equal(t, []*agonesv1.GameServerSet{gsSet2}, rest)
   986  
   987  	// none active
   988  	gsSet1.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{HostPort: 9999}}
   989  	active, rest = c.filterGameServerSetByActive(f, []*agonesv1.GameServerSet{gsSet1, gsSet2})
   990  	assert.Nil(t, active)
   991  	assert.Equal(t, []*agonesv1.GameServerSet{gsSet1, gsSet2}, rest)
   992  }
   993  
   994  func TestControllerRecreateDeployment(t *testing.T) {
   995  	t.Parallel()
   996  
   997  	f := defaultFixture()
   998  	f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
   999  	f.Spec.Replicas = 10
  1000  	gsSet1 := f.GameServerSet()
  1001  	gsSet1.ObjectMeta.Name = "gsSet1"
  1002  	gsSet1.Spec.Replicas = 10
  1003  	gsSet2 := f.GameServerSet()
  1004  	gsSet2.ObjectMeta.Name = "gsSet2"
  1005  	gsSet2.Spec.Replicas = 0
  1006  	gsSet2.Status.AllocatedReplicas = 1
  1007  
  1008  	t.Run("ok scenario", func(t *testing.T) {
  1009  		c, m := newFakeController()
  1010  		updated := false
  1011  		m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1012  			updated = true
  1013  			ua := action.(k8stesting.UpdateAction)
  1014  			gsSet := ua.GetObject().(*agonesv1.GameServerSet)
  1015  			assert.Equal(t, gsSet1.ObjectMeta.Name, gsSet.ObjectMeta.Name)
  1016  			assert.Equal(t, int32(0), gsSet.Spec.Replicas)
  1017  
  1018  			return true, gsSet, nil
  1019  		})
  1020  
  1021  		replicas, err := c.recreateDeployment(context.Background(), f, []*agonesv1.GameServerSet{gsSet1, gsSet2})
  1022  
  1023  		require.NoError(t, err)
  1024  		assert.True(t, updated)
  1025  		assert.Equal(t, f.Spec.Replicas-1, replicas)
  1026  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet")
  1027  	})
  1028  
  1029  	t.Run("error on update", func(t *testing.T) {
  1030  		c, m := newFakeController()
  1031  		m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1032  			return true, nil, errors.New("random-err")
  1033  		})
  1034  
  1035  		_, err := c.recreateDeployment(context.Background(), f, []*agonesv1.GameServerSet{gsSet1, gsSet2})
  1036  
  1037  		assert.EqualError(t, err, "error updating gameserverset gsSet1: random-err")
  1038  	})
  1039  }
  1040  
  1041  func TestControllerApplyDeploymentStrategy(t *testing.T) {
  1042  	t.Parallel()
  1043  
  1044  	type expected struct {
  1045  		inactiveReplicas int32
  1046  		replicas         int32
  1047  	}
  1048  
  1049  	fixtures := map[string]struct {
  1050  		strategyType         appsv1.DeploymentStrategyType
  1051  		gsSet1StatusReplicas int32
  1052  		gsSet2StatusReplicas int32
  1053  		expected             expected
  1054  	}{
  1055  		string(appsv1.RecreateDeploymentStrategyType): {
  1056  			strategyType:         appsv1.RecreateDeploymentStrategyType,
  1057  			gsSet1StatusReplicas: 0,
  1058  			gsSet2StatusReplicas: 0,
  1059  			expected: expected{
  1060  				inactiveReplicas: 0,
  1061  				replicas:         10,
  1062  			},
  1063  		},
  1064  		string(appsv1.RollingUpdateDeploymentStrategyType): {
  1065  			strategyType:         appsv1.RollingUpdateDeploymentStrategyType,
  1066  			gsSet1StatusReplicas: 10,
  1067  			gsSet2StatusReplicas: 1,
  1068  			expected: expected{
  1069  				inactiveReplicas: 8,
  1070  				replicas:         2,
  1071  			},
  1072  		},
  1073  	}
  1074  
  1075  	for k, v := range fixtures {
  1076  		t.Run(k, func(t *testing.T) {
  1077  			if utilruntime.FeatureEnabled(utilruntime.FeatureRollingUpdateFix) {
  1078  				t.SkipNow()
  1079  			}
  1080  
  1081  			f := defaultFixture()
  1082  			f.Spec.Strategy.Type = v.strategyType
  1083  			f.Spec.Replicas = 10
  1084  
  1085  			gsSet1 := f.GameServerSet()
  1086  			gsSet1.ObjectMeta.Name = "gsSet1"
  1087  			gsSet1.Spec.Replicas = 10
  1088  			gsSet1.Status.Replicas = v.gsSet1StatusReplicas
  1089  
  1090  			gsSet2 := f.GameServerSet()
  1091  			gsSet2.ObjectMeta.Name = "gsSet2"
  1092  			gsSet2.Spec.Replicas = 0
  1093  			gsSet2.Status.Replicas = v.gsSet2StatusReplicas
  1094  
  1095  			c, m := newFakeController()
  1096  
  1097  			updated := false
  1098  			m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1099  				updated = true
  1100  				ua := action.(k8stesting.UpdateAction)
  1101  				gsSet := ua.GetObject().(*agonesv1.GameServerSet)
  1102  				assert.Equal(t, gsSet1.ObjectMeta.Name, gsSet.ObjectMeta.Name)
  1103  				assert.Equal(t, v.expected.inactiveReplicas, gsSet.Spec.Replicas)
  1104  
  1105  				return true, gsSet, nil
  1106  			})
  1107  
  1108  			replicas, err := c.applyDeploymentStrategy(context.Background(), f, f.GameServerSet(), []*agonesv1.GameServerSet{gsSet1, gsSet2})
  1109  			require.NoError(t, err)
  1110  			assert.True(t, updated, "update should happen")
  1111  			assert.Equal(t, v.expected.replicas, replicas)
  1112  		})
  1113  	}
  1114  
  1115  	t.Run("a single gameserverset", func(t *testing.T) {
  1116  		f := defaultFixture()
  1117  		f.Spec.Replicas = 10
  1118  
  1119  		gsSet1 := f.GameServerSet()
  1120  		gsSet1.ObjectMeta.Name = "gsSet1"
  1121  
  1122  		c, _ := newFakeController()
  1123  
  1124  		replicas, err := c.applyDeploymentStrategy(context.Background(), f, f.GameServerSet(), []*agonesv1.GameServerSet{})
  1125  		require.NoError(t, err)
  1126  		assert.Equal(t, f.Spec.Replicas, replicas)
  1127  	})
  1128  
  1129  	t.Run("rest gameservers that are already scaled down", func(t *testing.T) {
  1130  		f := defaultFixture()
  1131  		f.Spec.Replicas = 10
  1132  
  1133  		gsSet1 := f.GameServerSet()
  1134  		gsSet1.ObjectMeta.Name = "gsSet1"
  1135  		gsSet1.Spec.Replicas = 0
  1136  		gsSet1.Status.AllocatedReplicas = 1
  1137  
  1138  		c, _ := newFakeController()
  1139  
  1140  		replicas, err := c.applyDeploymentStrategy(context.Background(), f, f.GameServerSet(), []*agonesv1.GameServerSet{gsSet1})
  1141  		require.NoError(t, err)
  1142  		assert.Equal(t, int32(9), replicas)
  1143  	})
  1144  }
  1145  
  1146  func TestControllerUpsertGameServerSet(t *testing.T) {
  1147  	t.Parallel()
  1148  	f := defaultFixture()
  1149  	replicas := int32(10)
  1150  
  1151  	t.Run("insert", func(t *testing.T) {
  1152  		c, m := newFakeController()
  1153  		gsSet := f.GameServerSet()
  1154  		created := false
  1155  		m.AgonesClient.AddReactor("create", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1156  			created = true
  1157  			ca := action.(k8stesting.CreateAction)
  1158  			gsSet := ca.GetObject().(*agonesv1.GameServerSet)
  1159  			assert.Equal(t, replicas, gsSet.Spec.Replicas)
  1160  
  1161  			return true, gsSet, nil
  1162  		})
  1163  
  1164  		err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas)
  1165  		assert.Nil(t, err)
  1166  
  1167  		assert.True(t, created, "Should be created")
  1168  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "CreatingGameServerSet")
  1169  	})
  1170  
  1171  	t.Run("update", func(t *testing.T) {
  1172  		c, m := newFakeController()
  1173  		gsSet := f.GameServerSet()
  1174  		gsSet.ObjectMeta.UID = "1234"
  1175  		gsSet.Spec.Replicas = replicas + 10
  1176  		update := false
  1177  
  1178  		m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1179  			update = true
  1180  			ca := action.(k8stesting.UpdateAction)
  1181  			gsSet := ca.GetObject().(*agonesv1.GameServerSet)
  1182  			assert.Equal(t, replicas, gsSet.Spec.Replicas)
  1183  
  1184  			return true, gsSet, nil
  1185  		})
  1186  
  1187  		err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas)
  1188  		assert.Nil(t, err)
  1189  
  1190  		assert.True(t, update, "Should be updated")
  1191  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet")
  1192  	})
  1193  
  1194  	t.Run("error updating gss replicas", func(t *testing.T) {
  1195  		c, m := newFakeController()
  1196  		gsSet := f.GameServerSet()
  1197  		gsSet.ObjectMeta.UID = "1234"
  1198  		gsSet.Spec.Replicas = replicas + 10
  1199  
  1200  		m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1201  			return true, nil, errors.New("random-err")
  1202  		})
  1203  
  1204  		err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas)
  1205  
  1206  		assert.EqualError(t, err, "error updating replicas for gameserverset for fleet fleet-1: random-err")
  1207  	})
  1208  
  1209  	t.Run("error on gs status update", func(t *testing.T) {
  1210  		c, m := newFakeController()
  1211  		gsSet := f.GameServerSet()
  1212  
  1213  		m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1214  			return true, nil, errors.New("random-err")
  1215  		})
  1216  
  1217  		err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas)
  1218  
  1219  		assert.EqualError(t, err, "error updating status of gameserverset for fleet fleet-1: random-err")
  1220  	})
  1221  
  1222  	t.Run("nothing happens, nil is returned", func(t *testing.T) {
  1223  		t.Parallel()
  1224  
  1225  		c, m := newFakeController()
  1226  		gsSet := f.GameServerSet()
  1227  		gsSet.ObjectMeta.UID = "1234"
  1228  		gsSet.Spec.Replicas = replicas
  1229  
  1230  		m.AgonesClient.AddReactor("create", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1231  			assert.FailNow(t, "should not create")
  1232  			return false, nil, nil
  1233  		})
  1234  		m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1235  			assert.FailNow(t, "should not update")
  1236  			return false, nil, nil
  1237  		})
  1238  
  1239  		err := c.upsertGameServerSet(context.Background(), f, gsSet, replicas)
  1240  		assert.Nil(t, err)
  1241  		agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
  1242  	})
  1243  
  1244  	t.Run("update Priorities", func(t *testing.T) {
  1245  		utilruntime.FeatureTestMutex.Lock()
  1246  		defer utilruntime.FeatureTestMutex.Unlock()
  1247  		require.NoError(t, utilruntime.ParseFeatures(string(utilruntime.FeatureCountsAndLists)+"=true"))
  1248  
  1249  		c, m := newFakeController()
  1250  		// Default GameServerSet has no Priorities
  1251  		gsSet := f.GameServerSet()
  1252  		gsSet.ObjectMeta.UID = "1234"
  1253  		// Add Priorities to the Fleet
  1254  		f.Spec.Priorities = []agonesv1.Priority{
  1255  			{
  1256  				Type:  "List",
  1257  				Key:   "Baz",
  1258  				Order: "Ascending",
  1259  			}}
  1260  		update := false
  1261  
  1262  		m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1263  			update = true
  1264  			ca := action.(k8stesting.UpdateAction)
  1265  			gsSet := ca.GetObject().(*agonesv1.GameServerSet)
  1266  			assert.Equal(t, agonesv1.Priority{Type: "List", Key: "Baz", Order: "Ascending"}, gsSet.Spec.Priorities[0])
  1267  			return true, gsSet, nil
  1268  		})
  1269  
  1270  		// Update Priorities on the GameServerSet to match the Fleet
  1271  		err := c.upsertGameServerSet(context.Background(), f, gsSet, gsSet.Spec.Replicas)
  1272  		assert.Nil(t, err)
  1273  
  1274  		assert.True(t, update, "Should be updated")
  1275  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "UpdatingGameServerSet")
  1276  	})
  1277  }
  1278  
  1279  func TestResourcesRequestsAndLimits(t *testing.T) {
  1280  	t.Parallel()
  1281  
  1282  	gsSpec := *defaultGSSpec()
  1283  	c, _ := newFakeController()
  1284  	f := defaultFixture()
  1285  	f.Spec.Template = gsSpec
  1286  	f.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("1000m")
  1287  
  1288  	// Semantically equal definition, 1 == 1000m CPU
  1289  	gsSet1 := f.GameServerSet()
  1290  	gsSet1.Spec.Template = gsSpec
  1291  	gsSet1.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("1")
  1292  
  1293  	// Absolutely different GameServer Spec, 1.1 CPU
  1294  	gsSet3 := f.GameServerSet()
  1295  	gsSet3.Spec.Template = *gsSpec.DeepCopy()
  1296  	gsSet3.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("1.1")
  1297  	active, rest := c.filterGameServerSetByActive(f, []*agonesv1.GameServerSet{gsSet1, gsSet3})
  1298  	assert.Equal(t, gsSet1, active)
  1299  	assert.Equal(t, []*agonesv1.GameServerSet{gsSet3}, rest)
  1300  
  1301  	gsSet2 := f.GameServerSet()
  1302  	gsSet2.Spec.Template = *gsSpec.DeepCopy()
  1303  	gsSet2.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("1000m")
  1304  	active, rest = c.filterGameServerSetByActive(f, []*agonesv1.GameServerSet{gsSet2, gsSet3})
  1305  	assert.Equal(t, gsSet2, active)
  1306  	assert.Equal(t, []*agonesv1.GameServerSet{gsSet3}, rest)
  1307  }
  1308  
  1309  func TestControllerDeleteEmptyGameServerSets(t *testing.T) {
  1310  	t.Parallel()
  1311  
  1312  	f := defaultFixture()
  1313  	gsSet1 := f.GameServerSet()
  1314  	gsSet1.ObjectMeta.Name = "gsSet1"
  1315  	gsSet1.Spec.Replicas = 10
  1316  	gsSet1.Status.Replicas = 10
  1317  	gsSet2 := f.GameServerSet()
  1318  	gsSet2.ObjectMeta.Name = "gsSet2"
  1319  	gsSet2.Spec.Replicas = 0
  1320  	gsSet2.Status.Replicas = 0
  1321  
  1322  	c, m := newFakeController()
  1323  	deleted := false
  1324  
  1325  	m.AgonesClient.AddReactor("delete", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1326  		deleted = true
  1327  		da := action.(k8stesting.DeleteAction)
  1328  		assert.Equal(t, gsSet2.ObjectMeta.Name, da.GetName())
  1329  		return true, nil, nil
  1330  	})
  1331  
  1332  	err := c.deleteEmptyGameServerSets(context.Background(), f, []*agonesv1.GameServerSet{gsSet1, gsSet2})
  1333  	assert.Nil(t, err)
  1334  	assert.True(t, deleted, "delete should happen")
  1335  }
  1336  
  1337  func TestControllerRollingUpdateDeploymentNoInactiveGSSNoErrors(t *testing.T) {
  1338  	t.Parallel()
  1339  
  1340  	f := defaultFixture()
  1341  
  1342  	f.Spec.Replicas = 100
  1343  
  1344  	active := f.GameServerSet()
  1345  	active.ObjectMeta.Name = "active"
  1346  
  1347  	c, _ := newFakeController()
  1348  
  1349  	replicas, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{})
  1350  	assert.Nil(t, err)
  1351  	assert.Equal(t, int32(25), replicas)
  1352  }
  1353  
  1354  // Test when replicas is negative value(0 replicas - 1 allocated = -1)
  1355  func TestControllerRollingUpdateDeploymentNegativeReplica(t *testing.T) {
  1356  	t.Parallel()
  1357  
  1358  	// Create Fleet with replicas: 5
  1359  	f := defaultFixture()
  1360  	f.Status.Replicas = 5
  1361  	// Allocate 1 gameserver
  1362  	f.Status.AllocatedReplicas = 1
  1363  	f.Status.ReadyReplicas = 4
  1364  
  1365  	// Edit fleet spec.template.spec and create new gameserverset
  1366  	f.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{
  1367  		ContainerPort: 6000,
  1368  		Name:          "gameport",
  1369  		PortPolicy:    agonesv1.Dynamic,
  1370  		Protocol:      corev1.ProtocolUDP,
  1371  	}}
  1372  
  1373  	// old gameserverset has only allocated gameserver
  1374  	inactive := f.GameServerSet()
  1375  	inactive.ObjectMeta.Name = "inactive"
  1376  	inactive.Spec.Replicas = 0
  1377  	inactive.Status.ReadyReplicas = 0
  1378  	inactive.Status.Replicas = 1
  1379  	inactive.Status.AllocatedReplicas = 1
  1380  
  1381  	// new gameserverset has 4 gameserver(replicas:5 - sumAllocated:1)
  1382  	active := f.GameServerSet()
  1383  	active.ObjectMeta.Name = "active"
  1384  	active.Spec.Replicas = 4
  1385  	active.Status.ReadyReplicas = 4
  1386  	active.Status.Replicas = 4
  1387  	active.Status.AllocatedReplicas = 0
  1388  
  1389  	c, m := newFakeController()
  1390  
  1391  	// triggered inside rollingUpdateRest
  1392  	m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1393  		ca := action.(k8stesting.UpdateAction)
  1394  		gsSet := ca.GetObject().(*agonesv1.GameServerSet)
  1395  		assert.Equal(t, int32(4), gsSet.Spec.Replicas)
  1396  		assert.Equal(t, int32(5), f.Spec.Replicas)
  1397  
  1398  		return true, nil, errors.Errorf("error updating replicas for gameserverset for fleet %s", f.Name)
  1399  	})
  1400  
  1401  	// assert the active gameserverset's replicas when active and inactive gameserversets exist
  1402  	expected := f.Spec.Replicas - f.Status.AllocatedReplicas
  1403  	replicas, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive})
  1404  	f.Status.ReadyReplicas = replicas
  1405  	assert.NoError(t, err)
  1406  	assert.Equal(t, expected, replicas)
  1407  
  1408  	// happened scale down to 0 by manual operation
  1409  	f.Spec.Replicas = 0
  1410  	// rolling update to scale 0
  1411  	replicas, err = c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive})
  1412  	f.Status.ReadyReplicas = replicas
  1413  	// assert no error, when fleet replicas is negative value(0 replicas - 1 allocated = -1)
  1414  	assert.NoError(t, err)
  1415  	// assert replicas 0, after user scales replicas to 0
  1416  	assert.Equal(t, int32(0), replicas)
  1417  }
  1418  
  1419  func TestControllerRollingUpdateDeploymentGSSUpdateFailedErrExpected(t *testing.T) {
  1420  	t.Parallel()
  1421  
  1422  	f := defaultFixture()
  1423  	f.Spec.Replicas = 75
  1424  	f.Status.ReadyReplicas = 75
  1425  
  1426  	active := f.GameServerSet()
  1427  	active.ObjectMeta.Name = "active"
  1428  	active.Spec.Replicas = 75
  1429  	active.Status.ReadyReplicas = 75
  1430  	active.Status.Replicas = 75
  1431  
  1432  	inactive := f.GameServerSet()
  1433  	inactive.ObjectMeta.Name = "inactive"
  1434  	inactive.Spec.Replicas = 10
  1435  	inactive.Status.ReadyReplicas = 10
  1436  	inactive.Status.Replicas = 10
  1437  	inactive.Status.AllocatedReplicas = 5
  1438  
  1439  	c, m := newFakeController()
  1440  
  1441  	// triggered inside rollingUpdateRest
  1442  	m.AgonesClient.AddReactor("update", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
  1443  		return true, nil, errors.New("random-err")
  1444  	})
  1445  
  1446  	_, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive})
  1447  	assert.EqualError(t, err, "error updating gameserverset inactive: random-err")
  1448  }
  1449  
  1450  func TestRollingUpdateOnReady(t *testing.T) {
  1451  	type expected struct {
  1452  		inactiveSpecReplicas int32
  1453  		replicas             int32
  1454  		updated              bool
  1455  	}
  1456  
  1457  	fixtures := map[string]struct {
  1458  		activeStatusReadyReplicas   int32
  1459  		inactiveStatusReadyReplicas int32
  1460  		allocatedReplicas           int32
  1461  		expected                    expected
  1462  	}{
  1463  		"not enough Ready GameServers - do not scale down rest GameServerSet": {
  1464  			activeStatusReadyReplicas:   10,
  1465  			inactiveStatusReadyReplicas: 10,
  1466  			expected: expected{
  1467  				updated:              false,
  1468  				inactiveSpecReplicas: 0,
  1469  				replicas:             75,
  1470  			},
  1471  		},
  1472  		"enough Ready GameServers - scale down rest GameServerSet to Allocated": {
  1473  			activeStatusReadyReplicas:   70,
  1474  			inactiveStatusReadyReplicas: 5,
  1475  			allocatedReplicas:           5,
  1476  			expected: expected{
  1477  				updated:              true,
  1478  				inactiveSpecReplicas: 5,
  1479  				replicas:             70,
  1480  			},
  1481  		},
  1482  		"enough Ready GameServers - scale down rest GameServerSet to 0": {
  1483  			activeStatusReadyReplicas:   70,
  1484  			inactiveStatusReadyReplicas: 10,
  1485  			allocatedReplicas:           0,
  1486  			expected: expected{
  1487  				updated:              true,
  1488  				inactiveSpecReplicas: 0,
  1489  				replicas:             75,
  1490  			},
  1491  		},
  1492  		"scale down rest GameServerSet to > 0": {
  1493  			// 75 - 19 = 56 is minimum number of gameservers
  1494  			// scaling 58 - 56 = -2 gameservers
  1495  			// initial 10 - 2 = 8
  1496  			activeStatusReadyReplicas:   50,
  1497  			inactiveStatusReadyReplicas: 8,
  1498  			allocatedReplicas:           0,
  1499  			expected: expected{
  1500  				updated:              true,
  1501  				inactiveSpecReplicas: 8,
  1502  				replicas:             75,
  1503  			},
  1504  		},
  1505  	}
  1506  
  1507  	for k, v := range fixtures {
  1508  		t.Run(k, func(t *testing.T) {
  1509  			c, m := newFakeController()
  1510  
  1511  			f := defaultFixture()
  1512  			f.Spec.Replicas = 75
  1513  			f.Status.ReadyReplicas = v.activeStatusReadyReplicas + v.inactiveStatusReadyReplicas
  1514  
  1515  			active := f.GameServerSet()
  1516  			active.ObjectMeta.Name = "active"
  1517  			active.Spec.Replicas = 75
  1518  			active.Status.Replicas = 75
  1519  			active.Status.ReadyReplicas = v.activeStatusReadyReplicas
  1520  
  1521  			inactive := f.GameServerSet()
  1522  			inactive.ObjectMeta.Name = "inactive"
  1523  			inactive.Spec.Replicas = 10
  1524  			inactive.Status.Replicas = 10
  1525  			inactive.Status.ReadyReplicas = v.inactiveStatusReadyReplicas
  1526  			inactive.Status.AllocatedReplicas = v.allocatedReplicas
  1527  			updated := false
  1528  			// triggered inside rollingUpdateRest
  1529  			m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1530  				updated = true
  1531  				ua := action.(k8stesting.UpdateAction)
  1532  				gsSet := ua.GetObject().(*agonesv1.GameServerSet)
  1533  				assert.Equal(t, inactive.ObjectMeta.Name, gsSet.ObjectMeta.Name)
  1534  				assert.Equal(t, v.expected.inactiveSpecReplicas, gsSet.Spec.Replicas)
  1535  
  1536  				return true, gsSet, nil
  1537  			})
  1538  
  1539  			replicas, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive})
  1540  			require.NoError(t, err, "no error")
  1541  
  1542  			assert.Equal(t, v.expected.replicas, replicas)
  1543  			assert.Equal(t, v.expected.updated, updated)
  1544  			if updated {
  1545  				agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet")
  1546  			} else {
  1547  				agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
  1548  			}
  1549  		})
  1550  	}
  1551  
  1552  }
  1553  
  1554  func TestControllerRollingUpdateDeployment(t *testing.T) {
  1555  	t.Cleanup(func() {
  1556  		utilruntime.FeatureTestMutex.Lock()
  1557  		defer utilruntime.FeatureTestMutex.Unlock()
  1558  		require.NoError(t, utilruntime.ParseFeatures(""))
  1559  	})
  1560  
  1561  	type expected struct {
  1562  		inactiveSpecReplicas int32
  1563  		replicas             int32
  1564  		updated              bool
  1565  		err                  string
  1566  	}
  1567  
  1568  	fixtures := map[string]struct {
  1569  		features                         string
  1570  		fleetSpecReplicas                int32
  1571  		activeSpecReplicas               int32
  1572  		activeStatusReplicas             int32
  1573  		readyReplicas                    int32
  1574  		inactiveSpecReplicas             int32
  1575  		inactiveStatusReplicas           int32
  1576  		inactiveStatusReadyReplicas      int32
  1577  		inactiveStatusAllocationReplicas int32
  1578  		nilMaxSurge                      bool
  1579  		nilMaxUnavailable                bool
  1580  		expected                         expected
  1581  	}{
  1582  		"nil MaxUnavailable, err expected": {
  1583  			fleetSpecReplicas:           100,
  1584  			activeSpecReplicas:          0,
  1585  			activeStatusReplicas:        0,
  1586  			inactiveSpecReplicas:        100,
  1587  			inactiveStatusReplicas:      100,
  1588  			inactiveStatusReadyReplicas: 100,
  1589  			nilMaxUnavailable:           true,
  1590  			expected: expected{
  1591  				err: "error parsing MaxUnavailable value: fleet-1: nil value for IntOrString",
  1592  			},
  1593  		},
  1594  		"nil MaxSurge, err expected": {
  1595  			fleetSpecReplicas:           100,
  1596  			activeSpecReplicas:          0,
  1597  			activeStatusReplicas:        0,
  1598  			inactiveSpecReplicas:        100,
  1599  			inactiveStatusReplicas:      100,
  1600  			inactiveStatusReadyReplicas: 100,
  1601  			nilMaxSurge:                 true,
  1602  			expected: expected{
  1603  				err: "error parsing MaxSurge value: fleet-1: nil value for IntOrString",
  1604  			},
  1605  		},
  1606  		"full inactive, empty inactive": {
  1607  			fleetSpecReplicas:           100,
  1608  			activeSpecReplicas:          0,
  1609  			activeStatusReplicas:        0,
  1610  			inactiveSpecReplicas:        100,
  1611  			inactiveStatusReplicas:      100,
  1612  			inactiveStatusReadyReplicas: 100,
  1613  			expected: expected{
  1614  				inactiveSpecReplicas: 70,
  1615  				replicas:             25,
  1616  				updated:              true,
  1617  			},
  1618  		},
  1619  		"almost empty inactive with allocated, almost full active": {
  1620  			fleetSpecReplicas:                100,
  1621  			activeSpecReplicas:               75,
  1622  			activeStatusReplicas:             75,
  1623  			inactiveSpecReplicas:             10,
  1624  			inactiveStatusReplicas:           10,
  1625  			inactiveStatusReadyReplicas:      10,
  1626  			inactiveStatusAllocationReplicas: 5,
  1627  
  1628  			expected: expected{
  1629  				inactiveSpecReplicas: 0,
  1630  				replicas:             95,
  1631  				updated:              true,
  1632  			},
  1633  		},
  1634  		"attempt to drive replicas over the max surge": {
  1635  			features:                    "RollingUpdateFix=false",
  1636  			fleetSpecReplicas:           100,
  1637  			activeSpecReplicas:          25,
  1638  			activeStatusReplicas:        25,
  1639  			inactiveSpecReplicas:        95,
  1640  			inactiveStatusReplicas:      95,
  1641  			inactiveStatusReadyReplicas: 95,
  1642  			expected: expected{
  1643  				inactiveSpecReplicas: 45,
  1644  				replicas:             30,
  1645  				updated:              true,
  1646  			},
  1647  		},
  1648  		"test smalled numbers of active and allocated": {
  1649  			fleetSpecReplicas:                5,
  1650  			activeSpecReplicas:               0,
  1651  			activeStatusReplicas:             0,
  1652  			inactiveSpecReplicas:             5,
  1653  			inactiveStatusReplicas:           5,
  1654  			inactiveStatusReadyReplicas:      5,
  1655  			inactiveStatusAllocationReplicas: 2,
  1656  			expected: expected{
  1657  				inactiveSpecReplicas: 4,
  1658  				replicas:             2,
  1659  				updated:              true,
  1660  			},
  1661  		},
  1662  		"activeSpecReplicas >= (fleetSpecReplicas - inactiveStatusAllocationReplicas)": {
  1663  			fleetSpecReplicas:                75,
  1664  			activeSpecReplicas:               75,
  1665  			activeStatusReplicas:             75,
  1666  			inactiveSpecReplicas:             10,
  1667  			inactiveStatusReplicas:           10,
  1668  			inactiveStatusReadyReplicas:      10,
  1669  			inactiveStatusAllocationReplicas: 5,
  1670  			expected: expected{
  1671  				inactiveSpecReplicas: 0,
  1672  				replicas:             70,
  1673  				updated:              true,
  1674  			},
  1675  		},
  1676  		"rolling update does not remove all ready replicas": {
  1677  			features:                         "RollingUpdateFix=true",
  1678  			fleetSpecReplicas:                100,
  1679  			activeSpecReplicas:               0,
  1680  			activeStatusReplicas:             0,
  1681  			inactiveSpecReplicas:             100,
  1682  			inactiveStatusReplicas:           100,
  1683  			inactiveStatusReadyReplicas:      10,
  1684  			inactiveStatusAllocationReplicas: 90,
  1685  			expected: expected{
  1686  				inactiveSpecReplicas: 97,
  1687  				replicas:             10,
  1688  				updated:              true,
  1689  			},
  1690  		},
  1691  		"rolling update stops scaling fully allocated inactive": {
  1692  			features:                         "RollingUpdateFix=true",
  1693  			fleetSpecReplicas:                100,
  1694  			activeSpecReplicas:               50,
  1695  			activeStatusReplicas:             50,
  1696  			inactiveSpecReplicas:             50,
  1697  			inactiveStatusReplicas:           50,
  1698  			inactiveStatusReadyReplicas:      0,
  1699  			inactiveStatusAllocationReplicas: 50,
  1700  			expected: expected{
  1701  				inactiveSpecReplicas: 0,
  1702  				replicas:             50,
  1703  				updated:              true,
  1704  			},
  1705  		},
  1706  		"rolling update scales down with fleet spec replicas = 0": {
  1707  			features:                         "RollingUpdateFix=true",
  1708  			fleetSpecReplicas:                0,
  1709  			activeSpecReplicas:               0,
  1710  			activeStatusReplicas:             0,
  1711  			inactiveSpecReplicas:             3,
  1712  			inactiveStatusReplicas:           3,
  1713  			inactiveStatusReadyReplicas:      3,
  1714  			inactiveStatusAllocationReplicas: 0,
  1715  			expected: expected{
  1716  				inactiveSpecReplicas: 0,
  1717  				replicas:             0,
  1718  				updated:              true,
  1719  			},
  1720  		},
  1721  	}
  1722  
  1723  	for k, v := range fixtures {
  1724  		t.Run(k, func(t *testing.T) {
  1725  			utilruntime.FeatureTestMutex.Lock()
  1726  			defer utilruntime.FeatureTestMutex.Unlock()
  1727  			require.NoError(t, utilruntime.ParseFeatures(v.features))
  1728  
  1729  			f := defaultFixture()
  1730  
  1731  			mu := intstr.FromString("30%")
  1732  			f.Spec.Strategy.RollingUpdate.MaxUnavailable = &mu
  1733  			f.Spec.Replicas = v.fleetSpecReplicas
  1734  
  1735  			// Inactive GameServerSet is downscaled second time only after
  1736  			// ReadyReplicas has raised.
  1737  			f.Status.ReadyReplicas = v.activeStatusReplicas + v.inactiveStatusReadyReplicas
  1738  
  1739  			if v.nilMaxSurge {
  1740  				f.Spec.Strategy.RollingUpdate.MaxSurge = nil
  1741  			} else {
  1742  				assert.Equal(t, "25%", f.Spec.Strategy.RollingUpdate.MaxSurge.String())
  1743  			}
  1744  
  1745  			if v.nilMaxUnavailable {
  1746  				f.Spec.Strategy.RollingUpdate.MaxUnavailable = nil
  1747  			} else {
  1748  				assert.Equal(t, "30%", f.Spec.Strategy.RollingUpdate.MaxUnavailable.String())
  1749  			}
  1750  
  1751  			active := f.GameServerSet()
  1752  			active.ObjectMeta.Name = "active"
  1753  			active.Spec.Replicas = v.activeSpecReplicas
  1754  			active.Status.Replicas = v.activeStatusReplicas
  1755  			active.Status.ReadyReplicas = v.activeStatusReplicas
  1756  
  1757  			inactive := f.GameServerSet()
  1758  			inactive.ObjectMeta.Name = "inactive"
  1759  			inactive.Spec.Replicas = v.inactiveSpecReplicas
  1760  			inactive.Status.Replicas = v.inactiveStatusReplicas
  1761  			inactive.Status.ReadyReplicas = v.inactiveStatusReadyReplicas
  1762  			inactive.Status.AllocatedReplicas = v.inactiveStatusAllocationReplicas
  1763  
  1764  			logrus.WithField("inactive", inactive).Info("Setting up the initial inactive")
  1765  
  1766  			updated := false
  1767  			c, m := newFakeController()
  1768  
  1769  			m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1770  				updated = true
  1771  				ua := action.(k8stesting.UpdateAction)
  1772  				gsSet := ua.GetObject().(*agonesv1.GameServerSet)
  1773  				assert.Equal(t, inactive.ObjectMeta.Name, gsSet.ObjectMeta.Name)
  1774  				assert.Equal(t, v.expected.inactiveSpecReplicas, gsSet.Spec.Replicas)
  1775  
  1776  				return true, gsSet, nil
  1777  			})
  1778  
  1779  			replicas, err := c.rollingUpdateDeployment(context.Background(), f, active, []*agonesv1.GameServerSet{inactive})
  1780  
  1781  			if v.expected.err != "" {
  1782  				assert.EqualError(t, err, v.expected.err)
  1783  			} else {
  1784  				require.NoError(t, err)
  1785  				assert.Equal(t, v.expected.replicas, replicas)
  1786  				assert.Equal(t, v.expected.updated, updated)
  1787  				if updated {
  1788  					agtesting.AssertEventContains(t, m.FakeRecorder.Events, "ScalingGameServerSet")
  1789  				} else {
  1790  					agtesting.AssertNoEvent(t, m.FakeRecorder.Events)
  1791  				}
  1792  			}
  1793  		})
  1794  	}
  1795  }
  1796  
  1797  // newFakeController returns a controller, backed by the fake Clientset
  1798  func newFakeController() (*Controller, agtesting.Mocks) {
  1799  	m := agtesting.NewMocks()
  1800  	c := NewController(healthcheck.NewHandler(), m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory)
  1801  	c.recorder = m.FakeRecorder
  1802  	return c, m
  1803  }
  1804  
  1805  // newFakeExtensions returns a fake extensions struct
  1806  func newFakeExtensions() *Extensions {
  1807  	return NewExtensions(generic.New(), webhooks.NewWebHook(http.NewServeMux()))
  1808  }
  1809  
  1810  func defaultFixture() *agonesv1.Fleet {
  1811  	f := &agonesv1.Fleet{
  1812  		ObjectMeta: metav1.ObjectMeta{
  1813  			Name:      "fleet-1",
  1814  			Namespace: "default",
  1815  			UID:       "1234",
  1816  		},
  1817  		Spec: agonesv1.FleetSpec{
  1818  			Replicas:   5,
  1819  			Scheduling: apis.Packed,
  1820  			Template:   agonesv1.GameServerTemplateSpec{},
  1821  		},
  1822  	}
  1823  	f.ApplyDefaults()
  1824  	return f
  1825  }
  1826  
  1827  func defaultGSSpec() *agonesv1.GameServerTemplateSpec {
  1828  	return &agonesv1.GameServerTemplateSpec{
  1829  		Spec: agonesv1.GameServerSpec{
  1830  			Container: "udp-server",
  1831  			Ports: []agonesv1.GameServerPort{{
  1832  				ContainerPort: 7654,
  1833  				Name:          "gameport",
  1834  				PortPolicy:    agonesv1.Dynamic,
  1835  				Protocol:      corev1.ProtocolUDP,
  1836  			}},
  1837  			Template: corev1.PodTemplateSpec{
  1838  				Spec: corev1.PodSpec{
  1839  					Containers: []corev1.Container{{
  1840  						Name:            "udp-server",
  1841  						Image:           "gcr.io/images/new:0.2",
  1842  						ImagePullPolicy: corev1.PullIfNotPresent,
  1843  						Resources: corev1.ResourceRequirements{
  1844  							Requests: corev1.ResourceList{
  1845  								corev1.ResourceCPU:    resource.MustParse("30m"),
  1846  								corev1.ResourceMemory: resource.MustParse("32Mi"),
  1847  							},
  1848  							Limits: corev1.ResourceList{
  1849  								corev1.ResourceCPU:    resource.MustParse("30m"),
  1850  								corev1.ResourceMemory: resource.MustParse("32Mi"),
  1851  							},
  1852  						},
  1853  					}},
  1854  				},
  1855  			},
  1856  		},
  1857  	}
  1858  }
  1859  
  1860  func getAdmissionReview(raw []byte) admissionv1.AdmissionReview {
  1861  	gvk := metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("Fleet"))
  1862  
  1863  	return admissionv1.AdmissionReview{
  1864  		Request: &admissionv1.AdmissionRequest{
  1865  			Kind:      gvk,
  1866  			Operation: admissionv1.Create,
  1867  			Object: runtime.RawExtension{
  1868  				Raw: raw,
  1869  			},
  1870  		},
  1871  		Response: &admissionv1.AdmissionResponse{Allowed: true},
  1872  	}
  1873  }
  1874  
  1875  // MOCKS SECTION
  1876  
  1877  type fakeGSSListerWithErr struct {
  1878  }
  1879  
  1880  // GameServerSetLister interface implementation
  1881  func (fgsl *fakeGSSListerWithErr) List(_ labels.Selector) (ret []*v1.GameServerSet, err error) {
  1882  	return nil, errors.New("random-err")
  1883  }
  1884  
  1885  // GameServerSetLister interface implementation
  1886  func (fgsl *fakeGSSListerWithErr) Get(_ string) (ret *v1.GameServerSet, err error) {
  1887  	return nil, errors.New("random-err")
  1888  }
  1889  
  1890  func (fgsl *fakeGSSListerWithErr) GameServerSets(_ string) agonesv1client.GameServerSetNamespaceLister {
  1891  	return fgsl
  1892  }
  1893  
  1894  type fakeFleetsGetterWithErr struct{}
  1895  
  1896  // FleetsGetter interface implementation
  1897  func (ffg *fakeFleetsGetterWithErr) Fleets(_ string) agonesv1clientset.FleetInterface {
  1898  	return &fakeFleetsGetterWithErr{}
  1899  }
  1900  
  1901  func (ffg *fakeFleetsGetterWithErr) Create(_ context.Context, _ *v1.Fleet, _ metav1.CreateOptions) (*v1.Fleet, error) {
  1902  	panic("not implemented")
  1903  }
  1904  
  1905  func (ffg *fakeFleetsGetterWithErr) Update(_ context.Context, _ *v1.Fleet, _ metav1.UpdateOptions) (*v1.Fleet, error) {
  1906  	panic("not implemented")
  1907  }
  1908  
  1909  func (ffg *fakeFleetsGetterWithErr) UpdateStatus(_ context.Context, _ *v1.Fleet, _ metav1.UpdateOptions) (*v1.Fleet, error) {
  1910  	panic("not implemented")
  1911  }
  1912  
  1913  func (ffg *fakeFleetsGetterWithErr) Delete(_ context.Context, _ string, _ metav1.DeleteOptions) error {
  1914  	panic("not implemented")
  1915  }
  1916  
  1917  func (ffg *fakeFleetsGetterWithErr) DeleteCollection(_ context.Context, _ metav1.DeleteOptions, _ metav1.ListOptions) error {
  1918  	panic("not implemented")
  1919  }
  1920  
  1921  func (ffg *fakeFleetsGetterWithErr) Get(_ context.Context, _ string, _ metav1.GetOptions) (*v1.Fleet, error) {
  1922  	return nil, errors.New("err-from-fleet-getter")
  1923  }
  1924  
  1925  func (ffg *fakeFleetsGetterWithErr) List(_ context.Context, _ metav1.ListOptions) (*v1.FleetList, error) {
  1926  	panic("not implemented")
  1927  }
  1928  
  1929  func (ffg *fakeFleetsGetterWithErr) Watch(_ context.Context, _ metav1.ListOptions) (watch.Interface, error) {
  1930  	panic("not implemented")
  1931  }
  1932  
  1933  func (ffg *fakeFleetsGetterWithErr) Patch(_ context.Context, _ string, _ types.PatchType, _ []byte, _ metav1.PatchOptions, _ ...string) (result *v1.Fleet, err error) {
  1934  	panic("not implemented")
  1935  }
  1936  
  1937  func (ffg *fakeFleetsGetterWithErr) Apply(_ context.Context, _ *applyconfigurations.FleetApplyConfiguration, _ metav1.ApplyOptions) (*v1.Fleet, error) {
  1938  	panic("not implemented")
  1939  }
  1940  
  1941  func (ffg *fakeFleetsGetterWithErr) ApplyStatus(_ context.Context, _ *applyconfigurations.FleetApplyConfiguration, _ metav1.ApplyOptions) (*v1.Fleet, error) {
  1942  	panic("not implemented")
  1943  }
  1944  
  1945  func (ffg *fakeFleetsGetterWithErr) GetScale(_ context.Context, _ string, _ metav1.GetOptions) (*autoscalingv1.Scale, error) {
  1946  	panic("not implemented")
  1947  }
  1948  
  1949  func (ffg *fakeFleetsGetterWithErr) UpdateScale(_ context.Context, _ string, _ *autoscalingv1.Scale, _ metav1.UpdateOptions) (*autoscalingv1.Scale, error) {
  1950  	panic("not implemented")
  1951  }
  1952  
  1953  type fakeFleetListerWithErr struct{}
  1954  
  1955  // FleetLister interface implementation
  1956  func (ffl *fakeFleetListerWithErr) List(_ labels.Selector) (ret []*v1.Fleet, err error) {
  1957  	return nil, errors.New("err-from-fleet-lister")
  1958  }
  1959  
  1960  func (ffl *fakeFleetListerWithErr) Fleets(_ string) agonesv1client.FleetNamespaceLister {
  1961  	return &fakeFleetNamespaceListerWithErr{}
  1962  }
  1963  
  1964  type fakeFleetNamespaceListerWithErr struct{}
  1965  
  1966  // FleetNamespaceLister interface implementation
  1967  func (ffnl *fakeFleetNamespaceListerWithErr) List(_ labels.Selector) (ret []*v1.Fleet, err error) {
  1968  	return nil, errors.New("err-from-namespace-lister")
  1969  }
  1970  
  1971  func (ffnl *fakeFleetNamespaceListerWithErr) Get(_ string) (*v1.Fleet, error) {
  1972  	return nil, errors.New("err-from-namespace-lister")
  1973  }