agones.dev/agones@v1.53.0/pkg/gameserversets/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 gameserversets
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"math/rand"
    22  	"net/http"
    23  	"strconv"
    24  	"testing"
    25  	"time"
    26  
    27  	"agones.dev/agones/pkg/apis"
    28  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    29  	"agones.dev/agones/pkg/cloudproduct/generic"
    30  	"agones.dev/agones/pkg/gameservers"
    31  	agtesting "agones.dev/agones/pkg/testing"
    32  	utilruntime "agones.dev/agones/pkg/util/runtime"
    33  	"agones.dev/agones/pkg/util/webhooks"
    34  	"github.com/heptiolabs/healthcheck"
    35  	"github.com/pkg/errors"
    36  	"github.com/sirupsen/logrus"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  	admissionv1 "k8s.io/api/admission/v1"
    40  	corev1 "k8s.io/api/core/v1"
    41  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    42  	"k8s.io/apimachinery/pkg/runtime"
    43  	"k8s.io/apimachinery/pkg/watch"
    44  	k8stesting "k8s.io/client-go/testing"
    45  	"k8s.io/client-go/tools/cache"
    46  )
    47  
    48  func gsWithState(st agonesv1.GameServerState) *agonesv1.GameServer {
    49  	return &agonesv1.GameServer{Status: agonesv1.GameServerStatus{State: st}}
    50  }
    51  
    52  func gsPendingDeletionWithState(st agonesv1.GameServerState) *agonesv1.GameServer {
    53  	return &agonesv1.GameServer{
    54  		ObjectMeta: metav1.ObjectMeta{
    55  			DeletionTimestamp: &deletionTime,
    56  		},
    57  		Status: agonesv1.GameServerStatus{State: st},
    58  	}
    59  }
    60  
    61  const (
    62  	maxTestCreationsPerBatch = 3
    63  	maxTestDeletionsPerBatch = 3
    64  	maxTestPendingPerBatch   = 3
    65  )
    66  
    67  func TestComputeReconciliationAction(t *testing.T) {
    68  	t.Parallel()
    69  
    70  	cases := []struct {
    71  		desc                   string
    72  		list                   []*agonesv1.GameServer
    73  		targetReplicaCount     int
    74  		wantNumServersToAdd    int
    75  		wantNumServersToDelete int
    76  		wantIsPartial          bool
    77  		priorities             []agonesv1.Priority
    78  	}{
    79  		{
    80  			desc: "Empty",
    81  		},
    82  		{
    83  			desc: "AddServers",
    84  			list: []*agonesv1.GameServer{
    85  				gsWithState(agonesv1.GameServerStateReady),
    86  			},
    87  			targetReplicaCount:  3,
    88  			wantNumServersToAdd: 2,
    89  		},
    90  		{
    91  			// 1 ready servers, target is 30 but we can only create 3 at a time.
    92  			desc: "AddServersPartial",
    93  			list: []*agonesv1.GameServer{
    94  				gsWithState(agonesv1.GameServerStateReady),
    95  			},
    96  			targetReplicaCount:  30,
    97  			wantNumServersToAdd: 3,
    98  			wantIsPartial:       true, // max 3 creations per action
    99  		},
   100  		{
   101  			// 0 ready servers, target is 30 but we can only have 3 in-flight.
   102  			desc: "AddServersExceedsInFlightLimit",
   103  			list: []*agonesv1.GameServer{
   104  				gsWithState(agonesv1.GameServerStateCreating),
   105  				gsWithState(agonesv1.GameServerStatePortAllocation),
   106  			},
   107  			targetReplicaCount:  30,
   108  			wantNumServersToAdd: 1,
   109  			wantIsPartial:       true,
   110  		}, {
   111  			desc: "DeleteServers",
   112  			list: []*agonesv1.GameServer{
   113  				gsWithState(agonesv1.GameServerStateReady),
   114  				gsWithState(agonesv1.GameServerStateReserved),
   115  				gsWithState(agonesv1.GameServerStateReady),
   116  			},
   117  			targetReplicaCount:     1,
   118  			wantNumServersToDelete: 2,
   119  		},
   120  		{
   121  			// 6 ready servers, target is 1 but we can only delete 3 at a time.
   122  			desc: "DeleteServerPartial",
   123  			list: []*agonesv1.GameServer{
   124  				gsWithState(agonesv1.GameServerStateReady),
   125  				gsWithState(agonesv1.GameServerStateReady),
   126  				gsWithState(agonesv1.GameServerStateReady),
   127  				gsWithState(agonesv1.GameServerStateReady),
   128  				gsWithState(agonesv1.GameServerStateReady),
   129  				gsWithState(agonesv1.GameServerStateReady),
   130  			},
   131  			targetReplicaCount:     1,
   132  			wantNumServersToDelete: 3,
   133  			wantIsPartial:          true, // max 3 deletions per action
   134  		},
   135  		{
   136  			desc: "DeleteIgnoresAllocatedServers",
   137  			list: []*agonesv1.GameServer{
   138  				gsWithState(agonesv1.GameServerStateReady),
   139  				gsWithState(agonesv1.GameServerStateAllocated),
   140  				gsWithState(agonesv1.GameServerStateAllocated),
   141  			},
   142  			targetReplicaCount:     0,
   143  			wantNumServersToDelete: 1,
   144  		},
   145  		{
   146  			desc: "DeleteIgnoresReservedServers",
   147  			list: []*agonesv1.GameServer{
   148  				gsWithState(agonesv1.GameServerStateReady),
   149  				gsWithState(agonesv1.GameServerStateReserved),
   150  				gsWithState(agonesv1.GameServerStateReserved),
   151  			},
   152  			targetReplicaCount:     0,
   153  			wantNumServersToDelete: 1,
   154  		},
   155  		{
   156  			desc: "CreateWhileDeletionsPending",
   157  			list: []*agonesv1.GameServer{
   158  				// 2 being deleted, one ready, target is 4, we add 3 more.
   159  				gsPendingDeletionWithState(agonesv1.GameServerStateUnhealthy),
   160  				gsPendingDeletionWithState(agonesv1.GameServerStateUnhealthy),
   161  				gsWithState(agonesv1.GameServerStateReady),
   162  			},
   163  			targetReplicaCount:  4,
   164  			wantNumServersToAdd: 3,
   165  		},
   166  		{
   167  			desc: "PendingDeletionsCountTowardsTargetReplicaCount",
   168  			list: []*agonesv1.GameServer{
   169  				// 6 being deleted now, we want 10 but that would exceed in-flight limit by a lot.
   170  				gsWithState(agonesv1.GameServerStateCreating),
   171  				gsWithState(agonesv1.GameServerStatePortAllocation),
   172  				gsWithState(agonesv1.GameServerStateCreating),
   173  				gsWithState(agonesv1.GameServerStatePortAllocation),
   174  				gsWithState(agonesv1.GameServerStateCreating),
   175  				gsWithState(agonesv1.GameServerStatePortAllocation),
   176  			},
   177  			targetReplicaCount:  10,
   178  			wantNumServersToAdd: 0,
   179  			wantIsPartial:       true,
   180  		},
   181  		{
   182  			desc: "DeletingUnhealthyGameServers",
   183  			list: []*agonesv1.GameServer{
   184  				gsWithState(agonesv1.GameServerStateReady),
   185  				gsWithState(agonesv1.GameServerStateUnhealthy),
   186  				gsWithState(agonesv1.GameServerStateUnhealthy),
   187  			},
   188  			targetReplicaCount:     3,
   189  			wantNumServersToAdd:    2,
   190  			wantNumServersToDelete: 2,
   191  		},
   192  		{
   193  			desc: "DeletingErrorGameServers",
   194  			list: []*agonesv1.GameServer{
   195  				gsWithState(agonesv1.GameServerStateReady),
   196  				gsWithState(agonesv1.GameServerStateError),
   197  				gsWithState(agonesv1.GameServerStateError),
   198  			},
   199  			targetReplicaCount:     3,
   200  			wantNumServersToAdd:    2,
   201  			wantNumServersToDelete: 2,
   202  		},
   203  		{
   204  			desc: "DeletingPartialGameServers",
   205  			list: []*agonesv1.GameServer{
   206  				gsWithState(agonesv1.GameServerStateReady),
   207  				gsWithState(agonesv1.GameServerStateUnhealthy),
   208  				gsWithState(agonesv1.GameServerStateError),
   209  				gsWithState(agonesv1.GameServerStateUnhealthy),
   210  				gsWithState(agonesv1.GameServerStateError),
   211  				gsWithState(agonesv1.GameServerStateUnhealthy),
   212  				gsWithState(agonesv1.GameServerStateError),
   213  			},
   214  			targetReplicaCount:     3,
   215  			wantNumServersToAdd:    2,
   216  			wantNumServersToDelete: 3,
   217  			wantIsPartial:          true,
   218  		},
   219  	}
   220  
   221  	for _, tc := range cases {
   222  		t.Run(tc.desc, func(t *testing.T) {
   223  			toAdd, toDelete, isPartial := computeReconciliationAction(apis.Distributed, tc.list, map[string]gameservers.NodeCount{},
   224  				tc.targetReplicaCount, maxTestCreationsPerBatch, maxTestDeletionsPerBatch, maxTestPendingPerBatch, tc.priorities)
   225  
   226  			assert.Equal(t, tc.wantNumServersToAdd, toAdd, "# of GameServers to add")
   227  			assert.Len(t, toDelete, tc.wantNumServersToDelete, "# of GameServers to delete")
   228  			assert.Equal(t, tc.wantIsPartial, isPartial, "is partial reconciliation")
   229  		})
   230  	}
   231  
   232  	t.Run("test packed scale down", func(t *testing.T) {
   233  		list := []*agonesv1.GameServer{
   234  			{ObjectMeta: metav1.ObjectMeta{Name: "gs1"}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, NodeName: "node3"}},
   235  			{ObjectMeta: metav1.ObjectMeta{Name: "gs2"}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, NodeName: "node1"}},
   236  			{ObjectMeta: metav1.ObjectMeta{Name: "gs3"}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, NodeName: "node3"}},
   237  			{ObjectMeta: metav1.ObjectMeta{Name: "gs4"}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, NodeName: ""}},
   238  		}
   239  
   240  		counts := map[string]gameservers.NodeCount{"node1": {Ready: 1}, "node3": {Ready: 2}}
   241  		toAdd, toDelete, isPartial := computeReconciliationAction(apis.Packed, list, counts, 2,
   242  			1000, 1000, 1000, nil)
   243  
   244  		assert.Empty(t, toAdd)
   245  		assert.False(t, isPartial, "shouldn't be partial")
   246  
   247  		assert.Len(t, toDelete, 2)
   248  		assert.Equal(t, "gs4", toDelete[0].ObjectMeta.Name)
   249  		assert.Equal(t, "gs2", toDelete[1].ObjectMeta.Name)
   250  	})
   251  
   252  	t.Run("test distributed scale down", func(t *testing.T) {
   253  		now := metav1.Now()
   254  
   255  		list := []*agonesv1.GameServer{
   256  			{ObjectMeta: metav1.ObjectMeta{Name: "gs1",
   257  				CreationTimestamp: metav1.Time{Time: now.Add(10 * time.Second)}}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}},
   258  			{ObjectMeta: metav1.ObjectMeta{Name: "gs2",
   259  				CreationTimestamp: now}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}},
   260  			{ObjectMeta: metav1.ObjectMeta{Name: "gs3",
   261  				CreationTimestamp: metav1.Time{Time: now.Add(40 * time.Second)}}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}},
   262  			{ObjectMeta: metav1.ObjectMeta{Name: "gs4",
   263  				CreationTimestamp: metav1.Time{Time: now.Add(30 * time.Second)}}, Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}},
   264  		}
   265  
   266  		toAdd, toDelete, isPartial := computeReconciliationAction(apis.Distributed, list, map[string]gameservers.NodeCount{},
   267  			2, 1000, 1000, 1000, nil)
   268  
   269  		assert.Empty(t, toAdd)
   270  		assert.False(t, isPartial, "shouldn't be partial")
   271  
   272  		assert.Len(t, toDelete, 2)
   273  		assert.Equal(t, "gs2", toDelete[0].ObjectMeta.Name)
   274  		assert.Equal(t, "gs1", toDelete[1].ObjectMeta.Name)
   275  	})
   276  }
   277  
   278  func TestComputeStatus(t *testing.T) {
   279  	t.Parallel()
   280  
   281  	t.Run("compute status", func(t *testing.T) {
   282  		utilruntime.FeatureTestMutex.Lock()
   283  		defer utilruntime.FeatureTestMutex.Unlock()
   284  
   285  		require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=false", utilruntime.FeatureCountsAndLists)))
   286  		gsSet := defaultFixture()
   287  		cases := []struct {
   288  			list       []*agonesv1.GameServer
   289  			wantStatus agonesv1.GameServerSetStatus
   290  		}{
   291  			{[]*agonesv1.GameServer{}, agonesv1.GameServerSetStatus{}},
   292  			{[]*agonesv1.GameServer{
   293  				gsWithState(agonesv1.GameServerStateCreating),
   294  				gsWithState(agonesv1.GameServerStateReady),
   295  			}, agonesv1.GameServerSetStatus{ReadyReplicas: 1, Replicas: 2}},
   296  			{[]*agonesv1.GameServer{
   297  				gsWithState(agonesv1.GameServerStateAllocated),
   298  				gsWithState(agonesv1.GameServerStateAllocated),
   299  				gsWithState(agonesv1.GameServerStateCreating),
   300  				gsWithState(agonesv1.GameServerStateReady),
   301  			}, agonesv1.GameServerSetStatus{ReadyReplicas: 1, AllocatedReplicas: 2, Replicas: 4}},
   302  			{
   303  				list: []*agonesv1.GameServer{
   304  					gsWithState(agonesv1.GameServerStateReserved),
   305  					gsWithState(agonesv1.GameServerStateReserved),
   306  					gsWithState(agonesv1.GameServerStateReady),
   307  				},
   308  				wantStatus: agonesv1.GameServerSetStatus{Replicas: 3, ReadyReplicas: 1, ReservedReplicas: 2},
   309  			},
   310  		}
   311  
   312  		for _, tc := range cases {
   313  			assert.Equal(t, tc.wantStatus, computeStatus(gsSet, tc.list))
   314  		}
   315  	})
   316  
   317  	t.Run("player tracking", func(t *testing.T) {
   318  		utilruntime.FeatureTestMutex.Lock()
   319  		defer utilruntime.FeatureTestMutex.Unlock()
   320  
   321  		require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeaturePlayerTracking)))
   322  
   323  		gsSet := defaultFixture()
   324  		var list []*agonesv1.GameServer
   325  		gs1 := gsWithState(agonesv1.GameServerStateAllocated)
   326  		gs1.Status.Players = &agonesv1.PlayerStatus{Count: 5, Capacity: 10}
   327  		gs2 := gsWithState(agonesv1.GameServerStateReserved)
   328  		gs2.Status.Players = &agonesv1.PlayerStatus{Count: 10, Capacity: 15}
   329  		gs3 := gsWithState(agonesv1.GameServerStateCreating)
   330  		gs3.Status.Players = &agonesv1.PlayerStatus{Count: 20, Capacity: 30}
   331  		gs4 := gsWithState(agonesv1.GameServerStateReady)
   332  		gs4.Status.Players = &agonesv1.PlayerStatus{Count: 15, Capacity: 30}
   333  		list = append(list, gs1, gs2, gs3, gs4)
   334  
   335  		expected := agonesv1.GameServerSetStatus{
   336  			Replicas:          4,
   337  			ReadyReplicas:     1,
   338  			ReservedReplicas:  1,
   339  			AllocatedReplicas: 1,
   340  			Players: &agonesv1.AggregatedPlayerStatus{
   341  				Count:    30,
   342  				Capacity: 55,
   343  			},
   344  			Counters: map[string]agonesv1.AggregatedCounterStatus{},
   345  			Lists:    map[string]agonesv1.AggregatedListStatus{},
   346  		}
   347  
   348  		assert.Equal(t, expected, computeStatus(gsSet, list))
   349  	})
   350  
   351  	t.Run("counters", func(t *testing.T) {
   352  		utilruntime.FeatureTestMutex.Lock()
   353  		defer utilruntime.FeatureTestMutex.Unlock()
   354  
   355  		require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists)))
   356  
   357  		gsSet := defaultFixture()
   358  		var list []*agonesv1.GameServer
   359  		gs1 := gsWithState(agonesv1.GameServerStateAllocated)
   360  		gs1.Status.Counters = map[string]agonesv1.CounterStatus{
   361  			"firstCounter":  {Count: 5, Capacity: 10},
   362  			"secondCounter": {Count: 100, Capacity: 1000},
   363  			"fullCounter":   {Count: 9223372036854775807, Capacity: 9223372036854775807},
   364  		}
   365  		gs2 := gsWithState(agonesv1.GameServerStateReserved)
   366  		gs2.Status.Counters = map[string]agonesv1.CounterStatus{
   367  			"firstCounter": {Count: 10, Capacity: 15},
   368  			"fullCounter":  {Count: 9223372036854775807, Capacity: 9223372036854775807},
   369  		}
   370  		gs3 := gsWithState(agonesv1.GameServerStateCreating)
   371  		gs3.Status.Counters = map[string]agonesv1.CounterStatus{
   372  			"firstCounter":  {Count: 20, Capacity: 30},
   373  			"secondCounter": {Count: 100, Capacity: 1000},
   374  			"fullCounter":   {Count: 9223372036854775807, Capacity: 9223372036854775807},
   375  		}
   376  		gs4 := gsWithState(agonesv1.GameServerStateReady)
   377  		gs4.Status.Counters = map[string]agonesv1.CounterStatus{
   378  			"firstCounter":  {Count: 15, Capacity: 30},
   379  			"secondCounter": {Count: 20, Capacity: 200},
   380  			"fullCounter":   {Count: 9223372036854775807, Capacity: 9223372036854775807},
   381  		}
   382  		list = append(list, gs1, gs2, gs3, gs4)
   383  
   384  		expected := agonesv1.GameServerSetStatus{
   385  			Replicas:          4,
   386  			ReadyReplicas:     1,
   387  			ReservedReplicas:  1,
   388  			AllocatedReplicas: 1,
   389  			Counters: map[string]agonesv1.AggregatedCounterStatus{
   390  				"firstCounter": {
   391  					AllocatedCount:    5,
   392  					AllocatedCapacity: 10,
   393  					Count:             50,
   394  					Capacity:          85,
   395  				},
   396  				"secondCounter": {
   397  					AllocatedCount:    100,
   398  					AllocatedCapacity: 1000,
   399  					Count:             220,
   400  					Capacity:          2200,
   401  				},
   402  				"fullCounter": {
   403  					AllocatedCount:    9223372036854775807,
   404  					AllocatedCapacity: 9223372036854775807,
   405  					Count:             9223372036854775807,
   406  					Capacity:          9223372036854775807,
   407  				},
   408  			},
   409  			Lists: map[string]agonesv1.AggregatedListStatus{},
   410  		}
   411  
   412  		assert.Equal(t, expected, computeStatus(gsSet, list))
   413  	})
   414  
   415  	t.Run("counters with no gameservers", func(t *testing.T) {
   416  		utilruntime.FeatureTestMutex.Lock()
   417  		defer utilruntime.FeatureTestMutex.Unlock()
   418  
   419  		require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists)))
   420  
   421  		gsSet := defaultFixture()
   422  		gsSet.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
   423  			"firstCounter":  {Capacity: 10, Count: 1},
   424  			"secondCounter": {Capacity: 10, Count: 1},
   425  		}
   426  		var list []*agonesv1.GameServer
   427  
   428  		expected := agonesv1.GameServerSetStatus{
   429  			Replicas:          0,
   430  			ReadyReplicas:     0,
   431  			ReservedReplicas:  0,
   432  			AllocatedReplicas: 0,
   433  			Lists:             map[string]agonesv1.AggregatedListStatus{},
   434  			Counters: map[string]agonesv1.AggregatedCounterStatus{
   435  				"firstCounter": {
   436  					AllocatedCount:    0,
   437  					AllocatedCapacity: 0,
   438  					Capacity:          0,
   439  					Count:             0,
   440  				},
   441  				"secondCounter": {
   442  					AllocatedCount:    0,
   443  					AllocatedCapacity: 0,
   444  					Capacity:          0,
   445  					Count:             0,
   446  				},
   447  			},
   448  		}
   449  
   450  		assert.Equal(t, expected, computeStatus(gsSet, list))
   451  	})
   452  
   453  	t.Run("lists", func(t *testing.T) {
   454  		utilruntime.FeatureTestMutex.Lock()
   455  		defer utilruntime.FeatureTestMutex.Unlock()
   456  
   457  		require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists)))
   458  
   459  		gsSet := defaultFixture()
   460  		var list []*agonesv1.GameServer
   461  		gs1 := gsWithState(agonesv1.GameServerStateAllocated)
   462  		gs1.Status.Lists = map[string]agonesv1.ListStatus{
   463  			"firstList":  {Capacity: 10, Values: []string{"a", "b"}},
   464  			"secondList": {Capacity: 1000, Values: []string{"1", "2"}},
   465  		}
   466  		gs2 := gsWithState(agonesv1.GameServerStateReserved)
   467  		gs2.Status.Lists = map[string]agonesv1.ListStatus{
   468  			"firstList": {Capacity: 15, Values: []string{"c"}},
   469  		}
   470  		gs3 := gsWithState(agonesv1.GameServerStateCreating)
   471  		gs3.Status.Lists = map[string]agonesv1.ListStatus{
   472  			"firstList":  {Capacity: 30, Values: []string{"d"}},
   473  			"secondList": {Capacity: 1000, Values: []string{"3"}},
   474  		}
   475  		gs4 := gsWithState(agonesv1.GameServerStateReady)
   476  		gs4.Status.Lists = map[string]agonesv1.ListStatus{
   477  			"firstList":  {Capacity: 30},
   478  			"secondList": {Capacity: 100, Values: []string{"4", "5", "6"}},
   479  		}
   480  		list = append(list, gs1, gs2, gs3, gs4)
   481  
   482  		expected := agonesv1.GameServerSetStatus{
   483  			Replicas:          4,
   484  			ReadyReplicas:     1,
   485  			ReservedReplicas:  1,
   486  			AllocatedReplicas: 1,
   487  			Counters:          map[string]agonesv1.AggregatedCounterStatus{},
   488  			Lists: map[string]agonesv1.AggregatedListStatus{
   489  				"firstList": {
   490  					AllocatedCount:    2,
   491  					AllocatedCapacity: 10,
   492  					Capacity:          85,
   493  					Count:             4,
   494  				},
   495  				"secondList": {
   496  					AllocatedCount:    2,
   497  					AllocatedCapacity: 1000,
   498  					Capacity:          2100,
   499  					Count:             6,
   500  				},
   501  			},
   502  		}
   503  
   504  		assert.Equal(t, expected, computeStatus(gsSet, list))
   505  	})
   506  
   507  	t.Run("lists with no gameservers", func(t *testing.T) {
   508  		utilruntime.FeatureTestMutex.Lock()
   509  		defer utilruntime.FeatureTestMutex.Unlock()
   510  
   511  		require.NoError(t, utilruntime.ParseFeatures(fmt.Sprintf("%s=true", utilruntime.FeatureCountsAndLists)))
   512  
   513  		gsSet := defaultFixture()
   514  		gsSet.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
   515  			"firstList":  {Capacity: 10, Values: []string{"a", "b"}},
   516  			"secondList": {Capacity: 1000, Values: []string{"1", "2"}},
   517  		}
   518  		var list []*agonesv1.GameServer
   519  
   520  		expected := agonesv1.GameServerSetStatus{
   521  			Replicas:          0,
   522  			ReadyReplicas:     0,
   523  			ReservedReplicas:  0,
   524  			AllocatedReplicas: 0,
   525  			Counters:          map[string]agonesv1.AggregatedCounterStatus{},
   526  			Lists: map[string]agonesv1.AggregatedListStatus{
   527  				"firstList": {
   528  					AllocatedCount:    0,
   529  					AllocatedCapacity: 0,
   530  					Capacity:          0,
   531  					Count:             0,
   532  				},
   533  				"secondList": {
   534  					AllocatedCount:    0,
   535  					AllocatedCapacity: 0,
   536  					Capacity:          0,
   537  					Count:             0,
   538  				},
   539  			},
   540  		}
   541  
   542  		assert.Equal(t, expected, computeStatus(gsSet, list))
   543  	})
   544  }
   545  
   546  // Test that the aggregated Counters and Lists are removed from the Game Server Set status if the
   547  // FeatureCountsAndLists flag is set to false.
   548  func TestGameServerSetDropCountsAndListsStatus(t *testing.T) {
   549  	t.Parallel()
   550  	utilruntime.FeatureTestMutex.Lock()
   551  	defer utilruntime.FeatureTestMutex.Unlock()
   552  
   553  	gss := defaultFixture()
   554  	c, m := newFakeController()
   555  
   556  	list := createGameServers(gss, 2)
   557  	list[0].Status.Counters = map[string]agonesv1.CounterStatus{
   558  		"firstCounter": {Count: 5, Capacity: 10},
   559  	}
   560  	list[1].Status.Lists = map[string]agonesv1.ListStatus{
   561  		"firstList": {Capacity: 100, Values: []string{"4", "5", "6"}},
   562  	}
   563  	gsList := []*agonesv1.GameServer{&list[0], &list[1]}
   564  
   565  	expectedCounterStatus := map[string]agonesv1.AggregatedCounterStatus{
   566  		"firstCounter": {
   567  			AllocatedCount:    0,
   568  			AllocatedCapacity: 0,
   569  			Capacity:          10,
   570  			Count:             5,
   571  		},
   572  	}
   573  	expectedListStatus := map[string]agonesv1.AggregatedListStatus{
   574  		"firstList": {
   575  			AllocatedCount:    0,
   576  			AllocatedCapacity: 0,
   577  			Capacity:          100,
   578  			Count:             3,
   579  		},
   580  	}
   581  
   582  	flag := ""
   583  	updated := false
   584  
   585  	m.AgonesClient.AddReactor("update", "gameserversets",
   586  		func(action k8stesting.Action) (bool, runtime.Object, error) {
   587  			updated = true
   588  			ua := action.(k8stesting.UpdateAction)
   589  			gsSet := ua.GetObject().(*agonesv1.GameServerSet)
   590  
   591  			switch flag {
   592  			case string(utilruntime.FeatureCountsAndLists) + "=true":
   593  				assert.Equal(t, expectedCounterStatus, gsSet.Status.Counters)
   594  				assert.Equal(t, expectedListStatus, gsSet.Status.Lists)
   595  			case string(utilruntime.FeatureCountsAndLists) + "=false":
   596  				assert.Nil(t, gsSet.Status.Counters)
   597  				assert.Nil(t, gsSet.Status.Lists)
   598  			default:
   599  				return false, nil, errors.Errorf("Flag string(utilruntime.FeatureCountsAndLists) should be set")
   600  			}
   601  
   602  			return true, gsSet, nil
   603  		})
   604  
   605  	// Expect starting fleet to have Aggregated Counter and List Statuses
   606  	flag = string(utilruntime.FeatureCountsAndLists) + "=true"
   607  	require.NoError(t, utilruntime.ParseFeatures(flag))
   608  	err := c.syncGameServerSetStatus(context.Background(), gss, gsList)
   609  	assert.Nil(t, err)
   610  	assert.True(t, updated)
   611  
   612  	updated = false
   613  	flag = string(utilruntime.FeatureCountsAndLists) + "=false"
   614  	require.NoError(t, utilruntime.ParseFeatures(flag))
   615  	err = c.syncGameServerSetStatus(context.Background(), gss, gsList)
   616  	assert.Nil(t, err)
   617  	assert.True(t, updated)
   618  
   619  	updated = false
   620  	flag = string(utilruntime.FeatureCountsAndLists) + "=true"
   621  	require.NoError(t, utilruntime.ParseFeatures(flag))
   622  	err = c.syncGameServerSetStatus(context.Background(), gss, gsList)
   623  	assert.Nil(t, err)
   624  	assert.True(t, updated)
   625  }
   626  
   627  func TestControllerWatchGameServers(t *testing.T) {
   628  	t.Parallel()
   629  	utilruntime.FeatureTestMutex.Lock()
   630  	defer utilruntime.FeatureTestMutex.Unlock()
   631  
   632  	gsSet := defaultFixture()
   633  
   634  	c, m := newFakeController()
   635  
   636  	received := make(chan string)
   637  	defer close(received)
   638  
   639  	m.ExtClient.AddReactor("get", "customresourcedefinitions", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   640  		return true, agtesting.NewEstablishedCRD(), nil
   641  	})
   642  	gsSetWatch := watch.NewFake()
   643  	m.AgonesClient.AddWatchReactor("gameserversets", k8stesting.DefaultWatchReactor(gsSetWatch, nil))
   644  	gsWatch := watch.NewFake()
   645  	m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
   646  
   647  	c.workerqueue.SyncHandler = func(_ context.Context, name string) error {
   648  		received <- name
   649  		return nil
   650  	}
   651  
   652  	ctx, cancel := agtesting.StartInformers(m, c.gameServerSynced)
   653  	defer cancel()
   654  
   655  	go func() {
   656  		err := c.Run(ctx, 1)
   657  		assert.Nil(t, err)
   658  	}()
   659  
   660  	f := func() string {
   661  		select {
   662  		case result := <-received:
   663  			return result
   664  		case <-time.After(3 * time.Second):
   665  			assert.FailNow(t, "timeout occurred")
   666  		}
   667  		return ""
   668  	}
   669  
   670  	expected, err := cache.MetaNamespaceKeyFunc(gsSet)
   671  	require.NoError(t, err)
   672  
   673  	// gsSet add
   674  	logrus.Info("adding gsSet")
   675  	gsSetWatch.Add(gsSet.DeepCopy())
   676  
   677  	assert.Equal(t, expected, f())
   678  	// gsSet update
   679  	logrus.Info("modify gsSet")
   680  	gsSetCopy := gsSet.DeepCopy()
   681  	gsSetCopy.Spec.Replicas = 5
   682  	gsSetWatch.Modify(gsSetCopy)
   683  	assert.Equal(t, expected, f())
   684  
   685  	gs := gsSet.GameServer()
   686  	gs.ObjectMeta.Name = "test-gs"
   687  	// gs add
   688  	logrus.Info("add gs")
   689  	gsWatch.Add(gs.DeepCopy())
   690  	assert.Equal(t, expected, f())
   691  
   692  	// gs update
   693  	gsCopy := gs.DeepCopy()
   694  	now := metav1.Now()
   695  	gsCopy.ObjectMeta.DeletionTimestamp = &now
   696  
   697  	logrus.Info("modify gs - noop")
   698  	gsWatch.Modify(gsCopy.DeepCopy())
   699  	select {
   700  	case <-received:
   701  		assert.Fail(t, "Should be no value")
   702  	case <-time.After(time.Second):
   703  	}
   704  
   705  	gsCopy = gs.DeepCopy()
   706  	gsCopy.Status.State = agonesv1.GameServerStateUnhealthy
   707  	logrus.Info("modify gs - unhealthy")
   708  	gsWatch.Modify(gsCopy.DeepCopy())
   709  	assert.Equal(t, expected, f())
   710  
   711  	// gs delete
   712  	logrus.Info("delete gs")
   713  	gsWatch.Delete(gsCopy.DeepCopy())
   714  	assert.Equal(t, expected, f())
   715  }
   716  
   717  func TestSyncGameServerSet(t *testing.T) {
   718  	t.Parallel()
   719  
   720  	t.Run("gameservers are not recreated when set is marked for deletion", func(t *testing.T) {
   721  		gsSet := defaultFixture()
   722  		gsSet.DeletionTimestamp = &metav1.Time{
   723  			Time: time.Now(),
   724  		}
   725  		list := createGameServers(gsSet, 5)
   726  
   727  		// mark some as shutdown
   728  		list[0].Status.State = agonesv1.GameServerStateShutdown
   729  		list[1].Status.State = agonesv1.GameServerStateShutdown
   730  
   731  		c, m := newFakeController()
   732  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   733  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   734  		})
   735  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   736  			return true, &agonesv1.GameServerList{Items: list}, nil
   737  		})
   738  		m.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   739  			assert.FailNow(t, "gameserver should not update")
   740  			return false, nil, nil
   741  		})
   742  		m.AgonesClient.AddReactor("create", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   743  			assert.FailNow(t, "new gameservers should not be created")
   744  
   745  			return false, nil, nil
   746  		})
   747  
   748  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced)
   749  		defer cancel()
   750  
   751  		c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck
   752  	})
   753  
   754  	t.Run("adding and deleting unhealthy gameservers", func(t *testing.T) {
   755  		gsSet := defaultFixture()
   756  		list := createGameServers(gsSet, 5)
   757  
   758  		// make some as unhealthy
   759  		list[0].Status.State = agonesv1.GameServerStateUnhealthy
   760  
   761  		updated := false
   762  		count := 0
   763  
   764  		c, m := newFakeController()
   765  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   766  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   767  		})
   768  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   769  			return true, &agonesv1.GameServerList{Items: list}, nil
   770  		})
   771  
   772  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   773  			ua := action.(k8stesting.UpdateAction)
   774  			gs := ua.GetObject().(*agonesv1.GameServer)
   775  			assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown)
   776  
   777  			updated = true
   778  			assert.Equal(t, "test-0", gs.GetName())
   779  			return true, nil, nil
   780  		})
   781  		m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   782  			ca := action.(k8stesting.CreateAction)
   783  			gs := ca.GetObject().(*agonesv1.GameServer)
   784  
   785  			assert.True(t, metav1.IsControlledBy(gs, gsSet))
   786  			count++
   787  			return true, gs, nil
   788  		})
   789  
   790  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced)
   791  		defer cancel()
   792  
   793  		c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck
   794  
   795  		assert.Equal(t, 6, count)
   796  		assert.True(t, updated, "A game servers should have been updated")
   797  	})
   798  
   799  	t.Run("adding and deleting errored gameservers", func(t *testing.T) {
   800  		gsSet := defaultFixture()
   801  		list := createGameServers(gsSet, 5)
   802  
   803  		// make some as unhealthy
   804  		list[0].Annotations = map[string]string{agonesv1.GameServerErroredAtAnnotation: time.Now().Add(-30 * time.Second).UTC().Format(time.RFC3339)}
   805  		list[0].Status.State = agonesv1.GameServerStateError
   806  
   807  		updated := false
   808  		count := 0
   809  
   810  		c, m := newFakeController()
   811  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   812  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   813  		})
   814  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   815  			return true, &agonesv1.GameServerList{Items: list}, nil
   816  		})
   817  
   818  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   819  			ua := action.(k8stesting.UpdateAction)
   820  			gs := ua.GetObject().(*agonesv1.GameServer)
   821  			assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown)
   822  
   823  			updated = true
   824  			assert.Equal(t, "test-0", gs.GetName())
   825  			return true, nil, nil
   826  		})
   827  		m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   828  			ca := action.(k8stesting.CreateAction)
   829  			gs := ca.GetObject().(*agonesv1.GameServer)
   830  
   831  			assert.True(t, metav1.IsControlledBy(gs, gsSet))
   832  			count++
   833  			return true, gs, nil
   834  		})
   835  
   836  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced)
   837  		defer cancel()
   838  
   839  		c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck
   840  
   841  		assert.Equal(t, 6, count)
   842  		assert.True(t, updated, "A game servers should have been updated")
   843  	})
   844  
   845  	t.Run("adding and delay deleting errored gameservers", func(t *testing.T) {
   846  		gsSet := defaultFixture()
   847  		list := createGameServers(gsSet, 5)
   848  
   849  		// make some as unhealthy
   850  		list[0].Annotations = map[string]string{agonesv1.GameServerErroredAtAnnotation: time.Now().UTC().Format(time.RFC3339)}
   851  		list[0].Status.State = agonesv1.GameServerStateError
   852  
   853  		updated := false
   854  		count := 0
   855  
   856  		c, m := newFakeController()
   857  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   858  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   859  		})
   860  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   861  			return true, &agonesv1.GameServerList{Items: list}, nil
   862  		})
   863  
   864  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   865  			ua := action.(k8stesting.UpdateAction)
   866  			gs := ua.GetObject().(*agonesv1.GameServer)
   867  			assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown)
   868  
   869  			updated = true
   870  			assert.Equal(t, "test-0", gs.GetName())
   871  			return true, nil, nil
   872  		})
   873  		m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   874  			ca := action.(k8stesting.CreateAction)
   875  			gs := ca.GetObject().(*agonesv1.GameServer)
   876  
   877  			assert.True(t, metav1.IsControlledBy(gs, gsSet))
   878  			count++
   879  			return true, gs, nil
   880  		})
   881  
   882  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced)
   883  		defer cancel()
   884  
   885  		c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck
   886  
   887  		assert.Equal(t, 5, count)
   888  		assert.False(t, updated, "A game servers should not have been updated")
   889  	})
   890  
   891  	t.Run("removing gamservers", func(t *testing.T) {
   892  		gsSet := defaultFixture()
   893  		list := createGameServers(gsSet, 15)
   894  		count := 0
   895  
   896  		c, m := newFakeController()
   897  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   898  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   899  		})
   900  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   901  			return true, &agonesv1.GameServerList{Items: list}, nil
   902  		})
   903  		m.AgonesClient.AddReactor("update", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   904  			count++
   905  			return true, nil, nil
   906  		})
   907  
   908  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced)
   909  		defer cancel()
   910  
   911  		c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name) // nolint: errcheck
   912  
   913  		assert.Equal(t, 5, count)
   914  	})
   915  
   916  	t.Run("Starting GameServers get deleted first", func(t *testing.T) {
   917  		gsSet := defaultFixture()
   918  		list := createGameServers(gsSet, 12)
   919  
   920  		list[0].Status.State = agonesv1.GameServerStateStarting
   921  		list[1].Status.State = agonesv1.GameServerStateCreating
   922  
   923  		rand.Shuffle(len(list), func(i, j int) {
   924  			list[i], list[j] = list[j], list[i]
   925  		})
   926  
   927  		var deleted []string
   928  
   929  		c, m := newFakeController()
   930  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   931  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   932  		})
   933  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   934  			return true, &agonesv1.GameServerList{Items: list}, nil
   935  		})
   936  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   937  			ua := action.(k8stesting.UpdateAction)
   938  			gs := ua.GetObject().(*agonesv1.GameServer)
   939  			require.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown)
   940  
   941  			deleted = append(deleted, gs.ObjectMeta.Name)
   942  			return true, nil, nil
   943  		})
   944  
   945  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced)
   946  		defer cancel()
   947  		require.NoError(t, c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name))
   948  
   949  		require.Len(t, deleted, 2)
   950  		require.ElementsMatchf(t, []string{"test-0", "test-1"}, deleted, "should be the non-ready GameServers")
   951  	})
   952  }
   953  
   954  func TestControllerSyncUnhealthyGameServers(t *testing.T) {
   955  	t.Parallel()
   956  
   957  	gsSet := defaultFixture()
   958  
   959  	gs1 := gsSet.GameServer()
   960  	gs1.ObjectMeta.Name = "test-1"
   961  	gs1.Status = agonesv1.GameServerStatus{State: agonesv1.GameServerStateUnhealthy}
   962  
   963  	gs2 := gsSet.GameServer()
   964  	gs2.ObjectMeta.Name = "test-2"
   965  	gs2.Status = agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}
   966  
   967  	gs3 := gsSet.GameServer()
   968  	gs3.ObjectMeta.Name = "test-3"
   969  	now := metav1.Now()
   970  	gs3.ObjectMeta.DeletionTimestamp = &now
   971  	gs3.Status = agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}
   972  
   973  	t.Run("valid case", func(t *testing.T) {
   974  		var updatedCount int
   975  		c, m := newFakeController()
   976  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   977  			ua := action.(k8stesting.UpdateAction)
   978  			gs := ua.GetObject().(*agonesv1.GameServer)
   979  
   980  			assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown)
   981  
   982  			updatedCount++
   983  			return true, nil, nil
   984  		})
   985  
   986  		ctx, cancel := agtesting.StartInformers(m)
   987  		defer cancel()
   988  
   989  		err := c.deleteGameServers(ctx, gsSet, []*agonesv1.GameServer{gs1, gs2, gs3})
   990  		assert.Nil(t, err)
   991  
   992  		assert.Equal(t, 3, updatedCount, "Updates should have occurred")
   993  	})
   994  
   995  	t.Run("error on update step", func(t *testing.T) {
   996  		c, m := newFakeController()
   997  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
   998  			ua := action.(k8stesting.UpdateAction)
   999  			gs := ua.GetObject().(*agonesv1.GameServer)
  1000  
  1001  			assert.Equal(t, gs.Status.State, agonesv1.GameServerStateShutdown)
  1002  
  1003  			return true, nil, errors.New("update-err")
  1004  		})
  1005  
  1006  		ctx, cancel := agtesting.StartInformers(m)
  1007  		defer cancel()
  1008  
  1009  		err := c.deleteGameServers(ctx, gsSet, []*agonesv1.GameServer{gs1, gs2, gs3})
  1010  		require.Error(t, err)
  1011  		assert.Contains(t, err.Error(), "error updating gameserver")
  1012  	})
  1013  }
  1014  
  1015  func TestSyncMoreGameServers(t *testing.T) {
  1016  	t.Parallel()
  1017  	gsSet := defaultFixture()
  1018  
  1019  	t.Run("valid case", func(t *testing.T) {
  1020  
  1021  		c, m := newFakeController()
  1022  		expected := 10
  1023  		count := 0
  1024  
  1025  		m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1026  			ca := action.(k8stesting.CreateAction)
  1027  			gs := ca.GetObject().(*agonesv1.GameServer)
  1028  
  1029  			assert.True(t, metav1.IsControlledBy(gs, gsSet))
  1030  			count++
  1031  
  1032  			return true, gs, nil
  1033  		})
  1034  
  1035  		ctx, cancel := agtesting.StartInformers(m)
  1036  		defer cancel()
  1037  
  1038  		err := c.addMoreGameServers(ctx, gsSet, expected)
  1039  		assert.Nil(t, err)
  1040  		assert.Equal(t, expected, count)
  1041  		agtesting.AssertEventContains(t, m.FakeRecorder.Events, "SuccessfulCreate")
  1042  	})
  1043  
  1044  	t.Run("error on create step", func(t *testing.T) {
  1045  		gsSet := defaultFixture()
  1046  		c, m := newFakeController()
  1047  		expected := 10
  1048  		m.AgonesClient.AddReactor("create", "gameservers", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1049  			ca := action.(k8stesting.CreateAction)
  1050  			gs := ca.GetObject().(*agonesv1.GameServer)
  1051  
  1052  			assert.True(t, metav1.IsControlledBy(gs, gsSet))
  1053  
  1054  			return true, gs, errors.New("create-err")
  1055  		})
  1056  
  1057  		ctx, cancel := agtesting.StartInformers(m)
  1058  		defer cancel()
  1059  
  1060  		err := c.addMoreGameServers(ctx, gsSet, expected)
  1061  		require.Error(t, err)
  1062  		assert.Equal(t, "error creating gameserver for gameserverset test: create-err", err.Error())
  1063  	})
  1064  }
  1065  
  1066  func TestControllerSyncGameServerSetStatus(t *testing.T) {
  1067  	t.Parallel()
  1068  
  1069  	t.Run("all ready list", func(t *testing.T) {
  1070  		gsSet := defaultFixture()
  1071  		c, m := newFakeController()
  1072  
  1073  		updated := false
  1074  		m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1075  			updated = true
  1076  			ua := action.(k8stesting.UpdateAction)
  1077  			gsSet := ua.GetObject().(*agonesv1.GameServerSet)
  1078  
  1079  			assert.Equal(t, int32(1), gsSet.Status.Replicas)
  1080  			assert.Equal(t, int32(1), gsSet.Status.ReadyReplicas)
  1081  			assert.Equal(t, int32(0), gsSet.Status.AllocatedReplicas)
  1082  
  1083  			return true, nil, nil
  1084  		})
  1085  
  1086  		list := []*agonesv1.GameServer{{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}}}
  1087  		err := c.syncGameServerSetStatus(context.Background(), gsSet, list)
  1088  		assert.Nil(t, err)
  1089  		assert.True(t, updated)
  1090  	})
  1091  
  1092  	t.Run("only some ready list", func(t *testing.T) {
  1093  		gsSet := defaultFixture()
  1094  		c, m := newFakeController()
  1095  
  1096  		updated := false
  1097  		m.AgonesClient.AddReactor("update", "gameserversets", func(action k8stesting.Action) (bool, runtime.Object, error) {
  1098  			updated = true
  1099  			ua := action.(k8stesting.UpdateAction)
  1100  			gsSet := ua.GetObject().(*agonesv1.GameServerSet)
  1101  
  1102  			assert.Equal(t, int32(8), gsSet.Status.Replicas)
  1103  			assert.Equal(t, int32(1), gsSet.Status.ReadyReplicas)
  1104  			assert.Equal(t, int32(2), gsSet.Status.AllocatedReplicas)
  1105  
  1106  			return true, nil, nil
  1107  		})
  1108  
  1109  		list := []*agonesv1.GameServer{
  1110  			{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}},
  1111  			{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateStarting}},
  1112  			{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateUnhealthy}},
  1113  			{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStatePortAllocation}},
  1114  			{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateError}},
  1115  			{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}},
  1116  			{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateAllocated}},
  1117  			{Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateAllocated}},
  1118  		}
  1119  		err := c.syncGameServerSetStatus(context.Background(), gsSet, list)
  1120  		assert.Nil(t, err)
  1121  		assert.True(t, updated)
  1122  	})
  1123  }
  1124  
  1125  func TestControllerUpdateValidationHandler(t *testing.T) {
  1126  	t.Parallel()
  1127  
  1128  	ext := newFakeExtensions()
  1129  	gvk := metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("GameServerSet"))
  1130  	fixture := &agonesv1.GameServerSet{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
  1131  		Spec: agonesv1.GameServerSetSpec{Replicas: 5},
  1132  	}
  1133  	raw, err := json.Marshal(fixture)
  1134  	require.NoError(t, err)
  1135  
  1136  	t.Run("valid gameserverset update", func(t *testing.T) {
  1137  		newGSS := fixture.DeepCopy()
  1138  		newGSS.Spec.Replicas = 10
  1139  		newRaw, err := json.Marshal(newGSS)
  1140  		require.NoError(t, err)
  1141  
  1142  		review := admissionv1.AdmissionReview{
  1143  			Request: &admissionv1.AdmissionRequest{
  1144  				Kind:      gvk,
  1145  				Operation: admissionv1.Create,
  1146  				Object: runtime.RawExtension{
  1147  					Raw: newRaw,
  1148  				},
  1149  				OldObject: runtime.RawExtension{
  1150  					Raw: raw,
  1151  				},
  1152  			},
  1153  			Response: &admissionv1.AdmissionResponse{Allowed: true},
  1154  		}
  1155  
  1156  		result, err := ext.updateValidationHandler(review)
  1157  		require.NoError(t, err)
  1158  		if !assert.True(t, result.Response.Allowed) {
  1159  			// show the reason of the failure
  1160  			require.NotNil(t, result.Response.Result)
  1161  			require.NotNil(t, result.Response.Result.Details)
  1162  			require.NotEmpty(t, result.Response.Result.Details.Causes)
  1163  		}
  1164  	})
  1165  
  1166  	t.Run("new object is nil, err excpected", func(t *testing.T) {
  1167  		review := admissionv1.AdmissionReview{
  1168  			Request: &admissionv1.AdmissionRequest{
  1169  				Kind:      gvk,
  1170  				Operation: admissionv1.Create,
  1171  				Object: runtime.RawExtension{
  1172  					Raw: nil,
  1173  				},
  1174  				OldObject: runtime.RawExtension{
  1175  					Raw: raw,
  1176  				},
  1177  			},
  1178  			Response: &admissionv1.AdmissionResponse{Allowed: true},
  1179  		}
  1180  
  1181  		_, err := ext.updateValidationHandler(review)
  1182  		require.Error(t, err)
  1183  		assert.Equal(t, "error unmarshalling new GameServerSet json: : unexpected end of JSON input", err.Error())
  1184  	})
  1185  
  1186  	t.Run("old object is nil, err excpected", func(t *testing.T) {
  1187  		newGSS := fixture.DeepCopy()
  1188  		newGSS.Spec.Replicas = 10
  1189  		newRaw, err := json.Marshal(newGSS)
  1190  		require.NoError(t, err)
  1191  
  1192  		review := admissionv1.AdmissionReview{
  1193  			Request: &admissionv1.AdmissionRequest{
  1194  				Kind:      gvk,
  1195  				Operation: admissionv1.Create,
  1196  				Object: runtime.RawExtension{
  1197  					Raw: newRaw,
  1198  				},
  1199  				OldObject: runtime.RawExtension{
  1200  					Raw: nil,
  1201  				},
  1202  			},
  1203  			Response: &admissionv1.AdmissionResponse{Allowed: true},
  1204  		}
  1205  
  1206  		_, err = ext.updateValidationHandler(review)
  1207  		require.Error(t, err)
  1208  		assert.Equal(t, "error unmarshalling old GameServerSet json: : unexpected end of JSON input", err.Error())
  1209  	})
  1210  
  1211  	t.Run("invalid gameserverset update", func(t *testing.T) {
  1212  		newGSS := fixture.DeepCopy()
  1213  		newGSS.Spec.Template = agonesv1.GameServerTemplateSpec{
  1214  			Spec: agonesv1.GameServerSpec{
  1215  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Static}},
  1216  			},
  1217  		}
  1218  		newRaw, err := json.Marshal(newGSS)
  1219  		require.NoError(t, err)
  1220  
  1221  		assert.NotEqual(t, string(raw), string(newRaw))
  1222  
  1223  		review := admissionv1.AdmissionReview{
  1224  			Request: &admissionv1.AdmissionRequest{
  1225  				Kind:      gvk,
  1226  				Operation: admissionv1.Create,
  1227  				Object: runtime.RawExtension{
  1228  					Raw: newRaw,
  1229  				},
  1230  				OldObject: runtime.RawExtension{
  1231  					Raw: raw,
  1232  				},
  1233  			},
  1234  			Response: &admissionv1.AdmissionResponse{Allowed: true},
  1235  		}
  1236  
  1237  		result, err := ext.updateValidationHandler(review)
  1238  		require.NoError(t, err)
  1239  		require.NotNil(t, result.Response)
  1240  		require.NotNil(t, result.Response.Result)
  1241  		require.NotNil(t, result.Response.Result.Details)
  1242  		assert.False(t, result.Response.Allowed)
  1243  		assert.NotEmpty(t, result.Response.Result.Details.Causes)
  1244  		assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status)
  1245  		assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason)
  1246  		assert.Contains(t, result.Response.Result.Message, "GameServerSet.agones.dev \"\" is invalid")
  1247  	})
  1248  }
  1249  
  1250  func TestCreationValidationHandler(t *testing.T) {
  1251  	t.Parallel()
  1252  
  1253  	ext := newFakeExtensions()
  1254  
  1255  	gvk := metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("GameServerSet"))
  1256  	fixture := &agonesv1.GameServerSet{ObjectMeta: metav1.ObjectMeta{Name: "c1", Namespace: "default"},
  1257  		Spec: agonesv1.GameServerSetSpec{
  1258  			Replicas: 5,
  1259  			Template: agonesv1.GameServerTemplateSpec{
  1260  				Spec: agonesv1.GameServerSpec{Container: "test",
  1261  					Template: corev1.PodTemplateSpec{
  1262  						Spec: corev1.PodSpec{
  1263  							Containers: []corev1.Container{{Name: "c1"}},
  1264  						},
  1265  					},
  1266  				},
  1267  			},
  1268  		},
  1269  	}
  1270  	raw, err := json.Marshal(fixture)
  1271  	require.NoError(t, err)
  1272  
  1273  	t.Run("valid gameserverset create", func(t *testing.T) {
  1274  		newGSS := fixture.DeepCopy()
  1275  		newGSS.Spec.Replicas = 10
  1276  		newRaw, err := json.Marshal(newGSS)
  1277  		require.NoError(t, err)
  1278  
  1279  		review := admissionv1.AdmissionReview{
  1280  			Request: &admissionv1.AdmissionRequest{
  1281  				Kind:      gvk,
  1282  				Operation: admissionv1.Create,
  1283  				Object: runtime.RawExtension{
  1284  					Raw: newRaw,
  1285  				},
  1286  			},
  1287  			Response: &admissionv1.AdmissionResponse{Allowed: true},
  1288  		}
  1289  
  1290  		result, err := ext.creationValidationHandler(review)
  1291  		require.NoError(t, err)
  1292  		if !assert.True(t, result.Response.Allowed) {
  1293  			// show the reason of the failure
  1294  			require.NotNil(t, result.Response.Result)
  1295  			require.NotNil(t, result.Response.Result.Details)
  1296  			require.NotEmpty(t, result.Response.Result.Details.Causes)
  1297  		}
  1298  	})
  1299  
  1300  	t.Run("object is nil, err excpected", func(t *testing.T) {
  1301  		review := admissionv1.AdmissionReview{
  1302  			Request: &admissionv1.AdmissionRequest{
  1303  				Kind:      gvk,
  1304  				Operation: admissionv1.Create,
  1305  				Object: runtime.RawExtension{
  1306  					Raw: nil,
  1307  				},
  1308  			},
  1309  			Response: &admissionv1.AdmissionResponse{Allowed: true},
  1310  		}
  1311  
  1312  		_, err := ext.creationValidationHandler(review)
  1313  		require.Error(t, err)
  1314  		assert.Equal(t, "error unmarshalling GameServerSet json after schema validation: : unexpected end of JSON input", err.Error())
  1315  	})
  1316  
  1317  	t.Run("invalid gameserverset create", func(t *testing.T) {
  1318  		newGSS := fixture.DeepCopy()
  1319  		newGSS.Spec.Template = agonesv1.GameServerTemplateSpec{
  1320  			Spec: agonesv1.GameServerSpec{
  1321  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Static}},
  1322  			},
  1323  		}
  1324  		newRaw, err := json.Marshal(newGSS)
  1325  		require.NoError(t, err)
  1326  
  1327  		assert.NotEqual(t, string(raw), string(newRaw))
  1328  
  1329  		review := admissionv1.AdmissionReview{
  1330  			Request: &admissionv1.AdmissionRequest{
  1331  				Kind:      gvk,
  1332  				Operation: admissionv1.Create,
  1333  				Object: runtime.RawExtension{
  1334  					Raw: newRaw,
  1335  				},
  1336  			},
  1337  			Response: &admissionv1.AdmissionResponse{Allowed: true},
  1338  		}
  1339  
  1340  		result, err := ext.creationValidationHandler(review)
  1341  		require.NoError(t, err)
  1342  		require.NotNil(t, result.Response)
  1343  		require.NotNil(t, result.Response.Result)
  1344  		require.NotNil(t, result.Response.Result.Details)
  1345  		assert.False(t, result.Response.Allowed)
  1346  		assert.NotEmpty(t, result.Response.Result.Details.Causes)
  1347  		assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status)
  1348  		assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason)
  1349  		assert.Contains(t, result.Response.Result.Message, "GameServerSet.agones.dev \"\" is invalid")
  1350  	})
  1351  }
  1352  
  1353  // defaultFixture creates the default GameServerSet fixture
  1354  func defaultFixture() *agonesv1.GameServerSet {
  1355  	gsSet := &agonesv1.GameServerSet{
  1356  		ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "test", UID: "1234"},
  1357  		Spec: agonesv1.GameServerSetSpec{
  1358  			Replicas:   10,
  1359  			Scheduling: apis.Packed,
  1360  			Template:   agonesv1.GameServerTemplateSpec{},
  1361  		},
  1362  	}
  1363  	return gsSet
  1364  }
  1365  
  1366  // createGameServers create an array of GameServers from the GameServerSet
  1367  func createGameServers(gsSet *agonesv1.GameServerSet, size int) []agonesv1.GameServer {
  1368  	var list []agonesv1.GameServer
  1369  	for i := 0; i < size; i++ {
  1370  		gs := gsSet.GameServer()
  1371  		gs.Name = gs.GenerateName + strconv.Itoa(i)
  1372  		gs.Status = agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady}
  1373  		list = append(list, *gs)
  1374  	}
  1375  	return list
  1376  }
  1377  
  1378  // newFakeController returns a controller, backed by the fake Clientset
  1379  func newFakeController() (*Controller, agtesting.Mocks) {
  1380  	m := agtesting.NewMocks()
  1381  	counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory)
  1382  	c := NewController(healthcheck.NewHandler(), counter, m.KubeClient, m.ExtClient, m.AgonesClient, m.AgonesInformerFactory, 16, 64, 64, 64, 5000)
  1383  	c.recorder = m.FakeRecorder
  1384  	return c, m
  1385  }
  1386  
  1387  // newFakeExtensions returns an extensions struct
  1388  func newFakeExtensions() *Extensions {
  1389  	return NewExtensions(generic.New(), webhooks.NewWebHook(http.NewServeMux()))
  1390  }