agones.dev/agones@v1.53.0/test/e2e/fleet_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 e2e
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sort"
    21  	"strings"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/sirupsen/logrus"
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	appsv1 "k8s.io/api/apps/v1"
    30  	autoscalingv1 "k8s.io/api/autoscaling/v1"
    31  	corev1 "k8s.io/api/core/v1"
    32  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    33  	"k8s.io/apimachinery/pkg/api/resource"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/labels"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/util/intstr"
    38  	"k8s.io/apimachinery/pkg/util/rand"
    39  	"k8s.io/apimachinery/pkg/util/validation"
    40  	"k8s.io/apimachinery/pkg/util/wait"
    41  	"k8s.io/client-go/util/retry"
    42  
    43  	"agones.dev/agones/pkg/apis"
    44  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    45  	allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
    46  	typedagonesv1 "agones.dev/agones/pkg/client/clientset/versioned/typed/agones/v1"
    47  	"agones.dev/agones/pkg/util/runtime"
    48  	e2e "agones.dev/agones/test/e2e/framework"
    49  )
    50  
    51  const (
    52  	key           = "test-state"
    53  	red           = "red"
    54  	green         = "green"
    55  	replicasCount = 3
    56  )
    57  
    58  // TestFleetRequestsLimits reproduces an issue when 1000m and 1 CPU is not equal, but should be equal
    59  // Every fleet should create no more than 2 GameServerSet at once on a simple fleet patch
    60  func TestFleetRequestsLimits(t *testing.T) {
    61  	t.Parallel()
    62  	ctx := context.Background()
    63  
    64  	flt := defaultFleet(framework.Namespace)
    65  	flt.Spec.Template.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = *resource.NewScaledQuantity(1000, -3)
    66  
    67  	client := framework.AgonesClient.AgonesV1()
    68  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
    69  	if assert.NoError(t, err) {
    70  		defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
    71  	}
    72  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
    73  
    74  	newReplicas := int32(5)
    75  	patch := fmt.Sprintf(`[{ "op": "replace", "path": "/spec/template/spec/template/spec/containers/0/resources/requests/cpu", "value": "1000m"},
    76  				{ "op": "replace", "path": "/spec/replicas", "value": %d}]`, newReplicas)
    77  
    78  	_, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Patch(ctx, flt.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{})
    79  	require.NoError(t, err)
    80  
    81  	// In bug scenario fleet was infinitely creating new GSSets (5 at a time), because 1000m CPU was changed to 1 CPU
    82  	// internally - thought as new wrong GSSet in a Fleet Controller
    83  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(newReplicas))
    84  }
    85  
    86  // TestFleetStrategyValidation reproduces an issue when we are trying
    87  // to update a fleet with no strategy in a new one
    88  func TestFleetStrategyValidation(t *testing.T) {
    89  	t.Parallel()
    90  	ctx := context.Background()
    91  
    92  	flt := defaultFleet(framework.Namespace)
    93  
    94  	client := framework.AgonesClient.AgonesV1()
    95  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
    96  	require.NoError(t, err)
    97  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
    98  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
    99  
   100  	flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{})
   101  	assert.NoError(t, err)
   102  	// func to check that we receive an expected error
   103  	verifyErr := func(err error) {
   104  		assert.NotNil(t, err)
   105  		statusErr, ok := err.(*k8serrors.StatusError)
   106  		assert.True(t, ok)
   107  		fmt.Println(statusErr)
   108  		assert.Len(t, statusErr.Status().Details.Causes, 1)
   109  		assert.Equal(t, metav1.CauseTypeFieldValueNotSupported, statusErr.Status().Details.Causes[0].Type)
   110  		assert.Contains(t, statusErr.Status().Details.Causes[0].Message, `supported values: "RollingUpdate", "Recreate"`)
   111  	}
   112  
   113  	// Change DeploymentStrategy Type, set it to empty string, which is forbidden
   114  	fltCopy := flt.DeepCopy()
   115  	fltCopy.Spec.Strategy.Type = appsv1.DeploymentStrategyType("")
   116  	_, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   117  	verifyErr(err)
   118  
   119  	// Try to remove whole DeploymentStrategy in a patch
   120  	patch := `[{ "op": "remove", "path": "/spec/strategy"},
   121  				{ "op": "replace", "path": "/spec/replicas", "value": 3}]`
   122  	_, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Patch(ctx, flt.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{})
   123  	verifyErr(err)
   124  }
   125  
   126  func TestFleetScaleWithDualAllocations(t *testing.T) {
   127  	t.Parallel()
   128  	ctx := context.Background()
   129  
   130  	client := framework.AgonesClient.AgonesV1()
   131  	flt := defaultFleet(framework.Namespace)
   132  	flt.Spec.Replicas = 5
   133  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   134  	require.NoError(t, err)
   135  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   136  
   137  	log := e2e.TestLogger(t).WithField("fleet", flt.Name)
   138  
   139  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   140  	_ = framework.CreateAndApplyAllocation(t, flt)
   141  
   142  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   143  		return fleet.Status.AllocatedReplicas == 1
   144  	})
   145  
   146  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (done bool, err error) {
   147  		flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{})
   148  		if err != nil {
   149  			return false, err
   150  		}
   151  
   152  		fltCopy := flt.DeepCopy()
   153  		fltCopy.Spec.Template.ObjectMeta.Labels = map[string]string{"version": "new"}
   154  		_, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   155  		return true, err
   156  	})
   157  	require.NoError(t, err)
   158  
   159  	selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name})
   160  	require.Eventually(t, func() bool {
   161  		gssList, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx,
   162  			metav1.ListOptions{LabelSelector: selector.String()})
   163  		if err != nil {
   164  			log.WithError(err).Error("Should be able to list")
   165  			return false
   166  		}
   167  
   168  		// wait until there are two of them
   169  		if len(gssList.Items) < 2 {
   170  			return false
   171  		}
   172  
   173  		// sort by timestamp, so we have a consistent order.
   174  		sort.Slice(gssList.Items, func(i, j int) bool {
   175  			return gssList.Items[i].ObjectMeta.CreationTimestamp.Compare(gssList.Items[j].ObjectMeta.CreationTimestamp.Time) == -1
   176  		})
   177  
   178  		// First one will have 1 with one allocated, second one should have 9 ready.
   179  		if gssList.Items[0].Status.AllocatedReplicas != 1 || gssList.Items[1].Status.ReadyReplicas != 4 {
   180  			log.WithField("gss0", fmt.Sprintf("%+v", gssList.Items[0].Status)).WithField("gss1", fmt.Sprintf("%+v", gssList.Items[1].Status)).Info("Checking GameServerSet")
   181  			return false
   182  		}
   183  		return true
   184  	}, 5*time.Minute, time.Second)
   185  
   186  	// Allocate 2 game servers.
   187  	_ = framework.CreateAndApplyAllocation(t, flt)
   188  	_ = framework.CreateAndApplyAllocation(t, flt)
   189  
   190  	// Scale the fleet down to 2 replicas.
   191  	framework.ScaleFleet(t, log, flt, 2)
   192  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   193  		log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking after 2 more allocations, and scaling to 2")
   194  		return fleet.Status.AllocatedReplicas == 3 && fleet.Status.ReadyReplicas == 0
   195  	})
   196  	require.NoError(t, err)
   197  
   198  	// Then scale the fleet back to 10 replicas.
   199  	framework.ScaleFleet(t, log, flt, 5)
   200  	require.NoError(t, err)
   201  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   202  		log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking after scaling back to 5")
   203  		return fleet.Status.AllocatedReplicas == 3 && fleet.Status.ReadyReplicas == 2
   204  	})
   205  }
   206  
   207  func TestFleetScaleUpAllocateEditAndScaleDownToZero(t *testing.T) {
   208  	t.Parallel()
   209  
   210  	ctx := context.Background()
   211  
   212  	client := framework.AgonesClient.AgonesV1()
   213  
   214  	flt := defaultFleet(framework.Namespace)
   215  	flt.Spec.Replicas = 1
   216  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   217  	require.NoError(t, err)
   218  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   219  
   220  	assert.Equal(t, int32(1), flt.Spec.Replicas)
   221  
   222  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   223  
   224  	// Scale up to 5 replicas
   225  	const targetScale = 5
   226  	flt = scaleFleetPatch(ctx, t, flt, targetScale)
   227  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale))
   228  
   229  	// Allocate 1 replica
   230  	gsa := framework.CreateAndApplyAllocation(t, flt)
   231  
   232  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   233  		return fleet.Status.AllocatedReplicas == 1
   234  	})
   235  
   236  	flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{})
   237  	require.NoError(t, err)
   238  
   239  	// Edit Port to 6000
   240  	// Change Port to trigger creating a new GSSet
   241  	fltCopy := flt.DeepCopy()
   242  	fltCopy.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{
   243  		ContainerPort: 6000,
   244  		Name:          "gameport",
   245  		PortPolicy:    agonesv1.Dynamic,
   246  		Protocol:      corev1.ProtocolUDP,
   247  	}}
   248  
   249  	flt, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   250  	require.NoError(t, err)
   251  	assert.Equal(t, int32(6000), flt.Spec.Template.Spec.Ports[0].ContainerPort)
   252  
   253  	// Wait for one more GSSet to be created
   254  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) {
   255  		selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name})
   256  		list, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx,
   257  			metav1.ListOptions{LabelSelector: selector.String()})
   258  		if err != nil {
   259  			return false, err
   260  		}
   261  		ready := false
   262  		// 2 GSSet should be created
   263  		if len(list.Items) == 2 {
   264  			ready = true
   265  		}
   266  		return ready, nil
   267  	})
   268  
   269  	require.NoError(t, err)
   270  
   271  	// RollingUpdate has happened due to changing Port, so waiting the complete of the RollingUpdate
   272  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   273  		return fleet.Status.ReadyReplicas == 4
   274  	})
   275  
   276  	// Scale down to zero
   277  	const scaleDownTarget = 0
   278  	flt = scaleFleetPatch(ctx, t, flt, scaleDownTarget)
   279  	// Expect Replicas = 0, No GSS or GS
   280  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0))
   281  
   282  	// Cleanup the allocated GameServer
   283  	gp := int64(1)
   284  	err = client.GameServers(framework.Namespace).Delete(ctx, gsa.Status.GameServerName, metav1.DeleteOptions{GracePeriodSeconds: &gp})
   285  	require.NoError(t, err)
   286  
   287  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0))
   288  
   289  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   290  		return fleet.Status.AllocatedReplicas == 0
   291  	})
   292  
   293  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   294  		return fleet.Status.Replicas == 0
   295  	})
   296  
   297  }
   298  
   299  func TestFleetScaleUpEditAndScaleDown(t *testing.T) {
   300  	t.Parallel()
   301  
   302  	// Use scaleFleetPatch (true) or scaleFleetSubresource (false)
   303  	fixtures := []bool{true, false}
   304  
   305  	for _, usePatch := range fixtures {
   306  		t.Run("Use fleet Patch "+fmt.Sprint(usePatch), func(t *testing.T) {
   307  			t.Parallel()
   308  			ctx := context.Background()
   309  
   310  			client := framework.AgonesClient.AgonesV1()
   311  
   312  			flt := defaultFleet(framework.Namespace)
   313  			flt.Spec.Replicas = 1
   314  			flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   315  			require.NoError(t, err)
   316  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   317  
   318  			assert.Equal(t, int32(1), flt.Spec.Replicas)
   319  
   320  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   321  
   322  			// scale up
   323  			const targetScale = 3
   324  			if usePatch {
   325  				flt = scaleFleetPatch(ctx, t, flt, targetScale)
   326  				assert.Equal(t, int32(targetScale), flt.Spec.Replicas)
   327  			} else {
   328  				flt = scaleFleetSubresource(ctx, t, flt, targetScale)
   329  			}
   330  
   331  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale))
   332  			gsa := framework.CreateAndApplyAllocation(t, flt)
   333  
   334  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   335  				return fleet.Status.AllocatedReplicas == 1
   336  			})
   337  
   338  			flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{})
   339  			require.NoError(t, err)
   340  
   341  			// Change ContainerPort to trigger creating a new GSSet
   342  			fltCopy := flt.DeepCopy()
   343  			fltCopy.Spec.Template.Spec.Ports[0].ContainerPort++
   344  			flt, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   345  			require.NoError(t, err)
   346  
   347  			// Wait for one more GSSet to be created and ReadyReplicas created in new GSS
   348  			err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
   349  				selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name})
   350  				list, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx,
   351  					metav1.ListOptions{LabelSelector: selector.String()})
   352  				if err != nil {
   353  					return false, err
   354  				}
   355  				ready := false
   356  				if len(list.Items) == 2 {
   357  					for _, v := range list.Items {
   358  						if v.Status.ReadyReplicas > 0 && v.Status.AllocatedReplicas == 0 {
   359  							ready = true
   360  						}
   361  					}
   362  				}
   363  				return ready, nil
   364  			})
   365  
   366  			require.NoError(t, err)
   367  
   368  			// scale down, with allocation
   369  			const scaleDownTarget = 1
   370  			if usePatch {
   371  				flt = scaleFleetPatch(ctx, t, flt, scaleDownTarget)
   372  			} else {
   373  				flt = scaleFleetSubresource(ctx, t, flt, scaleDownTarget)
   374  			}
   375  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0))
   376  
   377  			// delete the allocated GameServer
   378  			gp := int64(1)
   379  			err = client.GameServers(framework.Namespace).Delete(ctx, gsa.Status.GameServerName, metav1.DeleteOptions{GracePeriodSeconds: &gp})
   380  			require.NoError(t, err)
   381  
   382  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(1))
   383  
   384  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   385  				return fleet.Status.AllocatedReplicas == 0
   386  			})
   387  		})
   388  	}
   389  }
   390  
   391  // TestFleetRollingUpdate - test that the limited number of gameservers are created and deleted at a time
   392  // maxUnavailable and maxSurge parameters check.
   393  func TestFleetRollingUpdate(t *testing.T) {
   394  	t.Parallel()
   395  	ctx := context.Background()
   396  	fixtures := []struct {
   397  		// Use scaleFleetPatch (true) or scaleFleetSubresource (false)
   398  		usePatch bool
   399  		maxSurge string
   400  		// If true, create and cycle GS Allocations when triggering a rolling update:
   401  		// - Repeatedly allocate and shutdown GameServers (keeping ~50% of the Fleet in an Allocated state).
   402  		// - Once 50% of the Fleet is Allocated, trigger the rolling update.
   403  		// - Keep allocating/shutting down GameServers, to allocate in both the old and new GSSets.
   404  		// - Verify the rolling update completes.
   405  		// This simulates updating a Fleet that is live/in-use, and reproduces an issue where allocated GameServers
   406  		// causes a rolling update to get stuck and keep the old GameServerSet up.
   407  		cycleAllocations bool
   408  	}{
   409  		{
   410  			usePatch:         true,
   411  			maxSurge:         "25%",
   412  			cycleAllocations: false,
   413  		},
   414  		{
   415  			usePatch:         true,
   416  			maxSurge:         "10%",
   417  			cycleAllocations: false,
   418  		},
   419  		{
   420  			usePatch:         false,
   421  			maxSurge:         "25%",
   422  			cycleAllocations: false,
   423  		},
   424  		{
   425  			usePatch:         false,
   426  			maxSurge:         "10%",
   427  			cycleAllocations: false,
   428  		},
   429  		{
   430  			usePatch:         true,
   431  			maxSurge:         "25%",
   432  			cycleAllocations: true,
   433  		},
   434  	}
   435  	for i := range fixtures {
   436  		fixture := fixtures[i]
   437  		t.Run(fmt.Sprintf("Use fleet Patch %t %s cycle %t", fixture.usePatch, fixture.maxSurge, fixture.cycleAllocations), func(t *testing.T) {
   438  			client := framework.AgonesClient.AgonesV1()
   439  
   440  			flt := defaultFleet(framework.Namespace)
   441  			flt.ApplyDefaults()
   442  			flt.Spec.Replicas = 1
   443  			rollingUpdatePercent := intstr.FromString(fixture.maxSurge)
   444  			flt.Spec.Strategy.RollingUpdate.MaxSurge = &rollingUpdatePercent
   445  			flt.Spec.Strategy.RollingUpdate.MaxUnavailable = &rollingUpdatePercent
   446  			log := e2e.TestLogger(t).WithField("fleet", flt.ObjectMeta.Name)
   447  
   448  			flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   449  			require.NoError(t, err)
   450  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   451  
   452  			assert.Equal(t, int32(1), flt.Spec.Replicas)
   453  			assert.Equal(t, fixture.maxSurge, flt.Spec.Strategy.RollingUpdate.MaxSurge.StrVal)
   454  			assert.Equal(t, fixture.maxSurge, flt.Spec.Strategy.RollingUpdate.MaxUnavailable.StrVal)
   455  
   456  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   457  
   458  			// scale up
   459  			const targetScale = 8
   460  			if fixture.usePatch {
   461  				flt = scaleFleetPatch(ctx, t, flt, targetScale)
   462  				assert.Equal(t, int32(targetScale), flt.Spec.Replicas)
   463  			} else {
   464  				flt = scaleFleetSubresource(ctx, t, flt, targetScale)
   465  			}
   466  
   467  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale))
   468  
   469  			flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{})
   470  			require.NoError(t, err)
   471  
   472  			cycleCtx, cancelCycle := context.WithCancel(ctx)
   473  			defer cancelCycle()
   474  			if fixture.cycleAllocations {
   475  				// Repeatedly cycle allocations to keep ~half of the GameServers Allocated. Repeatedly Allocate and
   476  				// delete such that both the old and new GSSet contain allocated GameServers.
   477  				const halfScale = targetScale / 2
   478  				const period = 3 * time.Second
   479  				go framework.CycleAllocations(cycleCtx, t, flt, period, period*halfScale)
   480  
   481  				// Wait for at least half of the fleet to have be cycled (either Allocated or shutting down)
   482  				// before updating the fleet.
   483  				err = framework.WaitForFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   484  					return fleet.Status.ReadyReplicas < halfScale
   485  				})
   486  			}
   487  
   488  			// Change ContainerPort to trigger creating a new GSSet. Retry in case of a conflict.
   489  			fltName := flt.GetName()
   490  			require.Eventually(t, func() bool {
   491  				flt, err = client.Fleets(framework.Namespace).Get(ctx, fltName, metav1.GetOptions{})
   492  				require.NoError(t, err)
   493  				fltCopy := flt.DeepCopy()
   494  				fltCopy.Spec.Template.Spec.Ports[0].ContainerPort++
   495  				flt, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   496  				if err != nil {
   497  					log.WithError(err).Info("Failed to update Fleet")
   498  				}
   499  				return err == nil
   500  			}, time.Minute, time.Second)
   501  
   502  			selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name})
   503  			// New GSS was created
   504  			err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
   505  				gssList, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx,
   506  					metav1.ListOptions{LabelSelector: selector.String()})
   507  				if err != nil {
   508  					return false, err
   509  				}
   510  				return len(gssList.Items) == 2, nil
   511  			})
   512  			assert.NoError(t, err)
   513  			// Check that total number of gameservers in the system does not exceed the RollingUpdate
   514  			// parameters (creating no more than maxSurge, deleting maxUnavailable servers at a time)
   515  			// Wait for old GSSet to be deleted
   516  			err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
   517  				list, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).List(ctx,
   518  					metav1.ListOptions{LabelSelector: selector.String()})
   519  				if err != nil {
   520  					return false, err
   521  				}
   522  
   523  				maxSurge, err := intstr.GetScaledValueFromIntOrPercent(flt.Spec.Strategy.RollingUpdate.MaxSurge, int(flt.Spec.Replicas), true)
   524  				require.NoError(t, err)
   525  
   526  				roundUp := false
   527  				maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(flt.Spec.Strategy.RollingUpdate.MaxUnavailable, int(flt.Spec.Replicas), roundUp)
   528  
   529  				if maxUnavailable == 0 {
   530  					maxUnavailable = 1
   531  				}
   532  				// This difference is inevitable, also could be seen with Deployments and ReplicaSets
   533  				shift := maxUnavailable
   534  				require.NoError(t, err)
   535  
   536  				// Ignore any GameServers that are shutting down (resulting from Allocation cycling).
   537  				shuttingDown := 0
   538  				for _, gs := range list.Items {
   539  					if gs.IsBeingDeleted() {
   540  						shuttingDown++
   541  					}
   542  				}
   543  				expectedTotal := targetScale + maxSurge + maxUnavailable + shift + shuttingDown
   544  				if len(list.Items) > expectedTotal {
   545  					err = fmt.Errorf("new replicas should be less than target + maxSurge + maxUnavailable + shift + shuttingDown. Replicas: %d, Expected: %d", len(list.Items), expectedTotal)
   546  				}
   547  				if err != nil {
   548  					return false, err
   549  				}
   550  				gssList, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx,
   551  					metav1.ListOptions{LabelSelector: selector.String()})
   552  				if err != nil {
   553  					return false, err
   554  				}
   555  				return len(gssList.Items) == 1, nil
   556  			})
   557  
   558  			assert.NoError(t, err)
   559  
   560  			// Stop cycling Allocations.
   561  			// The AssertFleetConditions below will wait until the Allocation cycling has
   562  			// fully stopped (when all Allocated GameServers are shut down).
   563  			cancelCycle()
   564  
   565  			// scale down, with allocation
   566  			const scaleDownTarget = 1
   567  			if fixture.usePatch {
   568  				flt = scaleFleetPatch(ctx, t, flt, scaleDownTarget)
   569  			} else {
   570  				flt = scaleFleetSubresource(ctx, t, flt, scaleDownTarget)
   571  			}
   572  
   573  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(1))
   574  
   575  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   576  				return fleet.Status.AllocatedReplicas == 0
   577  			})
   578  		})
   579  	}
   580  }
   581  
   582  func TestUpdateFleetReplicaAndSpec(t *testing.T) {
   583  	t.Parallel()
   584  
   585  	client := framework.AgonesClient.AgonesV1()
   586  	ctx := context.Background()
   587  
   588  	flt := defaultFleet(framework.Namespace)
   589  	flt.ApplyDefaults()
   590  
   591  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   592  	require.NoError(t, err)
   593  
   594  	logrus.WithField("fleet", flt).Info("Created Fleet")
   595  
   596  	selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name})
   597  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   598  
   599  	require.Eventuallyf(t, func() bool {
   600  		list, err := client.GameServerSets(framework.Namespace).List(ctx,
   601  			metav1.ListOptions{LabelSelector: selector.String()})
   602  		require.NoError(t, err)
   603  		return len(list.Items) == 1
   604  	}, time.Minute, time.Second, "Wrong number of GameServerSets")
   605  
   606  	// update both replicas and template at the same time
   607  
   608  	flt, err = client.Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.GetName(), metav1.GetOptions{})
   609  	require.NoError(t, err)
   610  	fltCopy := flt.DeepCopy()
   611  	fltCopy.Spec.Replicas = 0
   612  	fltCopy.Spec.Template.Spec.Ports[0].ContainerPort++
   613  	require.NotEqual(t, flt.Spec.Template.Spec.Ports[0].ContainerPort, fltCopy.Spec.Template.Spec.Ports[0].ContainerPort)
   614  
   615  	flt, err = client.Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   616  	require.NoError(t, err)
   617  	require.Empty(t, flt.Spec.Replicas)
   618  
   619  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   620  
   621  	require.Eventuallyf(t, func() bool {
   622  		list, err := client.GameServerSets(framework.Namespace).List(ctx,
   623  			metav1.ListOptions{LabelSelector: selector.String()})
   624  		require.NoError(t, err)
   625  		return len(list.Items) == 1 && list.Items[0].Spec.Replicas == 0
   626  	}, time.Minute, time.Second, "Wrong number of GameServerSets")
   627  }
   628  
   629  func TestScaleFleetUpAndDownWithGameServerAllocation(t *testing.T) {
   630  	t.Parallel()
   631  	ctx := context.Background()
   632  	fixtures := []bool{false, true}
   633  
   634  	for _, usePatch := range fixtures {
   635  		t.Run("Use fleet Patch "+fmt.Sprint(usePatch), func(t *testing.T) {
   636  			t.Parallel()
   637  
   638  			client := framework.AgonesClient.AgonesV1()
   639  
   640  			flt := defaultFleet(framework.Namespace)
   641  			flt.Spec.Replicas = 1
   642  			flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   643  			require.NoError(t, err)
   644  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   645  
   646  			assert.Equal(t, int32(1), flt.Spec.Replicas)
   647  
   648  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   649  
   650  			// scale up
   651  			const targetScale = 3
   652  			if usePatch {
   653  				flt = scaleFleetPatch(ctx, t, flt, targetScale)
   654  				assert.Equal(t, int32(targetScale), flt.Spec.Replicas)
   655  			} else {
   656  				flt = scaleFleetSubresource(ctx, t, flt, targetScale)
   657  			}
   658  
   659  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale))
   660  
   661  			// get an allocation
   662  			gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"},
   663  				Spec: allocationv1.GameServerAllocationSpec{
   664  					Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}},
   665  				}}
   666  
   667  			gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa, metav1.CreateOptions{})
   668  			require.NoError(t, err)
   669  			assert.Equal(t, allocationv1.GameServerAllocationAllocated, gsa.Status.State)
   670  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   671  				return fleet.Status.AllocatedReplicas == 1
   672  			})
   673  
   674  			// scale down, with allocation
   675  			const scaleDownTarget = 1
   676  			if usePatch {
   677  				flt = scaleFleetPatch(ctx, t, flt, scaleDownTarget)
   678  			} else {
   679  				flt = scaleFleetSubresource(ctx, t, flt, scaleDownTarget)
   680  			}
   681  
   682  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0))
   683  
   684  			// delete the allocated GameServer
   685  			gp := int64(1)
   686  			err = client.GameServers(framework.Namespace).Delete(ctx, gsa.Status.GameServerName, metav1.DeleteOptions{GracePeriodSeconds: &gp})
   687  			require.NoError(t, err)
   688  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(1))
   689  
   690  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   691  				return fleet.Status.AllocatedReplicas == 0
   692  			})
   693  		})
   694  	}
   695  }
   696  
   697  func TestFleetUpdates(t *testing.T) {
   698  	t.Parallel()
   699  	ctx := context.Background()
   700  
   701  	fixtures := map[string]func() *agonesv1.Fleet{
   702  		"recreate": func() *agonesv1.Fleet {
   703  			flt := defaultFleet(framework.Namespace)
   704  			flt.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
   705  			return flt
   706  		},
   707  		"rolling": func() *agonesv1.Fleet {
   708  			flt := defaultFleet(framework.Namespace)
   709  			flt.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType
   710  			return flt
   711  		},
   712  	}
   713  
   714  	for k, v := range fixtures {
   715  		t.Run(k, func(t *testing.T) {
   716  			t.Parallel()
   717  			client := framework.AgonesClient.AgonesV1()
   718  			log := e2e.TestLogger(t)
   719  
   720  			flt := v()
   721  			flt.Spec.Template.ObjectMeta.Annotations = map[string]string{key: red}
   722  			flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   723  			require.NoError(t, err)
   724  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   725  
   726  			// gate that we have the keys we expect.
   727  			err = framework.WaitForFleetGameServersCondition(flt, func(gs *agonesv1.GameServer) bool {
   728  				return gs.ObjectMeta.Annotations[key] == red
   729  			})
   730  			require.NoError(t, err)
   731  
   732  			// if the generation has been updated, it's time to try again.
   733  			err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
   734  				flt, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{})
   735  				if err != nil {
   736  					log.WithError(err).WithField("flt", flt.ObjectMeta.Name).Warn("Could not retrieve fleet, trying again")
   737  					return false, err
   738  				}
   739  				fltCopy := flt.DeepCopy()
   740  				fltCopy.Spec.Template.ObjectMeta.Annotations[key] = green
   741  				_, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   742  				if err != nil {
   743  					log.WithError(err).WithField("flt", flt.ObjectMeta.Name).Warn("Could not update fleet, trying again")
   744  					return false, nil
   745  				}
   746  
   747  				return true, nil
   748  			})
   749  			require.NoError(t, err)
   750  
   751  			// let's make sure we're fully Ready
   752  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   753  				return flt.Spec.Replicas == fleet.Status.ReadyReplicas
   754  			})
   755  
   756  			// ...and fully rolled out
   757  			err = framework.WaitForFleetGameServersCondition(flt, func(gs *agonesv1.GameServer) bool {
   758  				return gs.ObjectMeta.Annotations[key] == green
   759  			})
   760  			require.NoError(t, err)
   761  		})
   762  	}
   763  }
   764  
   765  func TestUpdateGameServerConfigurationInFleet(t *testing.T) {
   766  	t.Parallel()
   767  	ctx := context.Background()
   768  
   769  	client := framework.AgonesClient.AgonesV1()
   770  
   771  	gsSpec := framework.DefaultGameServer(framework.Namespace).Spec
   772  	oldPort := int32(7111)
   773  	gsSpec.Ports = []agonesv1.GameServerPort{{
   774  		ContainerPort: oldPort,
   775  		Name:          "gameport",
   776  		PortPolicy:    agonesv1.Dynamic,
   777  		Protocol:      corev1.ProtocolUDP,
   778  	}}
   779  	flt := fleetWithGameServerSpec(&gsSpec, framework.Namespace)
   780  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   781  	require.NoError(t, err)
   782  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   783  
   784  	assert.Equal(t, int32(replicasCount), flt.Spec.Replicas)
   785  
   786  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   787  
   788  	// get an allocation
   789  	gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"},
   790  		Spec: allocationv1.GameServerAllocationSpec{
   791  			Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}},
   792  		}}
   793  
   794  	gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa, metav1.CreateOptions{})
   795  	require.NoError(t, err)
   796  	assert.Equal(t, allocationv1.GameServerAllocationAllocated, gsa.Status.State)
   797  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   798  		return fleet.Status.AllocatedReplicas == 1
   799  	})
   800  
   801  	flt, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.Name, metav1.GetOptions{})
   802  	require.NoError(t, err)
   803  
   804  	// Update the configuration of the gameservers of the fleet, i.e. container port.
   805  	// The changes should only be rolled out to gameservers in ready state, but not the allocated gameserver.
   806  	newPort := int32(7222)
   807  	fltCopy := flt.DeepCopy()
   808  	fltCopy.Spec.Template.Spec.Ports[0].ContainerPort = newPort
   809  
   810  	_, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   811  	require.NoError(t, err)
   812  
   813  	err = framework.WaitForFleetGameServersCondition(flt, func(gs *agonesv1.GameServer) bool {
   814  		containerPort := gs.Spec.Ports[0].ContainerPort
   815  		return (gs.Name == gsa.Status.GameServerName && containerPort == oldPort) ||
   816  			(gs.Name != gsa.Status.GameServerName && containerPort == newPort)
   817  	})
   818  	require.NoError(t, err)
   819  }
   820  
   821  func TestReservedGameServerInFleet(t *testing.T) {
   822  	t.Parallel()
   823  	ctx := context.Background()
   824  
   825  	client := framework.AgonesClient.AgonesV1()
   826  
   827  	flt := defaultFleet(framework.Namespace)
   828  	flt.Spec.Replicas = 3
   829  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   830  	if assert.NoError(t, err) {
   831  		defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   832  	}
   833  
   834  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   835  
   836  	gsList, err := framework.ListGameServersFromFleet(flt)
   837  	assert.NoError(t, err)
   838  
   839  	assert.Len(t, gsList, int(flt.Spec.Replicas))
   840  
   841  	// mark one as reserved
   842  	gsCopy := gsList[0].DeepCopy()
   843  	gsCopy.Status.State = agonesv1.GameServerStateReserved
   844  	_, err = client.GameServers(framework.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
   845  	assert.NoError(t, err)
   846  
   847  	// make sure counts are correct
   848  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   849  		return fleet.Status.ReadyReplicas == 2 && fleet.Status.ReservedReplicas == 1
   850  	})
   851  
   852  	// scale down to 0
   853  	flt = scaleFleetSubresource(ctx, t, flt, 0)
   854  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0))
   855  
   856  	// one should be left behind
   857  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
   858  		result := fleet.Status.ReservedReplicas == 1
   859  		log.WithField("reserved", fleet.Status.ReservedReplicas).WithField("result", result).Info("waiting for 1 reserved replica")
   860  		return result
   861  	})
   862  
   863  	// check against gameservers directly too, just to be extra sure
   864  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(_ context.Context) (done bool, err error) {
   865  		list, err := framework.ListGameServersFromFleet(flt)
   866  		if err != nil {
   867  			return true, err
   868  		}
   869  		l := len(list)
   870  		e := logrus.WithField("len", l)
   871  		if l >= 1 {
   872  			e = e.WithField("state", list[0].Status.State)
   873  		}
   874  		e.Info("waiting for 1 reserved gs")
   875  		return l == 1 && list[0].Status.State == agonesv1.GameServerStateReserved, nil
   876  	})
   877  	assert.NoError(t, err)
   878  }
   879  
   880  // TestFleetGSSpecValidation is built to test Fleet's underlying Gameserver template
   881  // validation. Gameserver Spec contained in a Fleet should be valid to create a fleet.
   882  func TestFleetGSSpecValidation(t *testing.T) {
   883  	t.Parallel()
   884  	ctx := context.Background()
   885  	client := framework.AgonesClient.AgonesV1()
   886  
   887  	// check two Containers in Gameserver Spec Template validation
   888  	flt := defaultFleet(framework.Namespace)
   889  	containerName := "container2"
   890  	flt.Spec.Template.Spec.Template =
   891  		corev1.PodTemplateSpec{
   892  			Spec: corev1.PodSpec{
   893  				Containers: []corev1.Container{{Name: "container", Image: "myImage"}, {Name: containerName, Image: "myImage2"}},
   894  			},
   895  		}
   896  	flt.Spec.Template.Spec.Container = "testing"
   897  	_, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   898  	assert.NotNil(t, err)
   899  	statusErr, ok := err.(*k8serrors.StatusError)
   900  	assert.True(t, ok)
   901  
   902  	assert.Len(t, statusErr.Status().Details.Causes, 2)
   903  	assert.Contains(t, statusErr.Status().Details.Causes[1].Message, "Container must be empty or the name of a container in the pod template")
   904  
   905  	assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type)
   906  	assert.Contains(t, statusErr.Status().Details.Causes[0].Message, "Could not find a container named testing")
   907  
   908  	flt.Spec.Template.Spec.Container = ""
   909  	_, err = client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   910  	assert.NotNil(t, err)
   911  	statusErr, ok = err.(*k8serrors.StatusError)
   912  	assert.True(t, ok)
   913  	if assert.Len(t, statusErr.Status().Details.Causes, 2) {
   914  		assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[1].Type)
   915  		assert.Contains(t, statusErr.Status().Details.Causes[1].Message, "Could not find a container named ")
   916  	}
   917  	assert.Equal(t, metav1.CauseTypeFieldValueRequired, statusErr.Status().Details.Causes[0].Type)
   918  	assert.Contains(t, statusErr.Status().Details.Causes[0].Message, agonesv1.ErrContainerRequired)
   919  
   920  	// use valid name for a container, one of two defined above
   921  	flt.Spec.Template.Spec.Container = containerName
   922  	_, err = client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   923  	require.NoError(t, err)
   924  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   925  
   926  	// check port configuration validation
   927  	fltPort := defaultFleet(framework.Namespace)
   928  
   929  	fltPort.Spec.Template.Spec.Ports = []agonesv1.GameServerPort{{Name: "Dyn", HostPort: 5555, PortPolicy: agonesv1.Dynamic, ContainerPort: 5555}}
   930  
   931  	_, err = client.Fleets(framework.Namespace).Create(ctx, fltPort, metav1.CreateOptions{})
   932  	assert.NotNil(t, err)
   933  	statusErr, ok = err.(*k8serrors.StatusError)
   934  	assert.True(t, ok)
   935  	assert.Len(t, statusErr.Status().Details.Causes, 1)
   936  	assert.Contains(t, statusErr.Status().Details.Causes[0].Message, agonesv1.ErrHostPort)
   937  
   938  	fltPort.Spec.Template.Spec.Ports[0].HostPort = 0 // validation fails above because the HostPort is specified, make it good.
   939  	_, err = client.Fleets(framework.Namespace).Create(ctx, fltPort, metav1.CreateOptions{})
   940  	require.NoError(t, err)
   941  	defer client.Fleets(framework.Namespace).Delete(ctx, fltPort.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   942  }
   943  
   944  // TestFleetNameValidation is built to test Fleet Name length validation,
   945  // Fleet Name should have at most 63 chars.
   946  func TestFleetNameValidation(t *testing.T) {
   947  	t.Parallel()
   948  	ctx := context.Background()
   949  	client := framework.AgonesClient.AgonesV1()
   950  
   951  	flt := defaultFleet(framework.Namespace)
   952  	nameLen := validation.LabelValueMaxLength + 1
   953  	flt.Name = strings.Repeat("f", nameLen)
   954  	_, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   955  	require.NotNil(t, err)
   956  	statusErr := err.(*k8serrors.StatusError)
   957  	assert.True(t, len(statusErr.Status().Details.Causes) > 0)
   958  	assert.Equal(t, metav1.CauseType("FieldValueTooLong"), statusErr.Status().Details.Causes[0].Type)
   959  	goodFlt := defaultFleet(framework.Namespace)
   960  	goodFlt.Name = flt.Name[0 : nameLen-1]
   961  	goodFlt, err = client.Fleets(framework.Namespace).Create(ctx, goodFlt, metav1.CreateOptions{})
   962  	require.NoError(t, err)
   963  	defer client.Fleets(framework.Namespace).Delete(ctx, goodFlt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   964  }
   965  
   966  func assertSuccessOrUpdateConflict(t *testing.T, err error) {
   967  	if !k8serrors.IsConflict(err) {
   968  		// update conflicts are sometimes ok, we simply lost the race.
   969  		require.NoError(t, err)
   970  	}
   971  }
   972  
   973  // TestGameServerAllocationDuringGameServerDeletion is built to specifically
   974  // test for race conditions of allocations when doing scale up/down,
   975  // rolling updates, etc. Failures may not happen ALL the time -- as that is the
   976  // nature of race conditions.
   977  func TestGameServerAllocationDuringGameServerDeletion(t *testing.T) {
   978  	t.Parallel()
   979  	ctx := context.Background()
   980  
   981  	testAllocationRaceCondition := func(t *testing.T, fleet func(string) *agonesv1.Fleet, deltaSleep time.Duration, delta func(t *testing.T, flt *agonesv1.Fleet)) {
   982  		client := framework.AgonesClient.AgonesV1()
   983  
   984  		flt := fleet(framework.Namespace)
   985  		flt.ApplyDefaults()
   986  		size := int32(10)
   987  		flt.Spec.Replicas = size
   988  		flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   989  		require.NoError(t, err)
   990  		defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   991  
   992  		assert.Equal(t, size, flt.Spec.Replicas)
   993  
   994  		framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   995  
   996  		var allocs []string
   997  
   998  		wg := sync.WaitGroup{}
   999  		wg.Add(2)
  1000  		go func() {
  1001  			for {
  1002  				// this gives room for fleet scaling to go down - makes it more likely for the race condition to fire
  1003  				time.Sleep(100 * time.Millisecond)
  1004  				gsa := &allocationv1.GameServerAllocation{ObjectMeta: metav1.ObjectMeta{GenerateName: "allocation-"},
  1005  					Spec: allocationv1.GameServerAllocationSpec{
  1006  						Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}}},
  1007  					}}
  1008  				gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa, metav1.CreateOptions{})
  1009  				if err != nil || gsa.Status.State == allocationv1.GameServerAllocationUnAllocated {
  1010  					logrus.WithError(err).Info("Allocation ended")
  1011  					break
  1012  				}
  1013  				logrus.WithField("gs", gsa.Status.GameServerName).Info("Allocated")
  1014  				allocs = append(allocs, gsa.Status.GameServerName)
  1015  			}
  1016  			wg.Done()
  1017  		}()
  1018  		go func() {
  1019  			// this tends to force the scaling to happen as we are fleet allocating
  1020  			time.Sleep(deltaSleep)
  1021  			// call the function that makes the change to the fleet
  1022  			logrus.Info("Applying delta function")
  1023  			delta(t, flt)
  1024  			wg.Done()
  1025  		}()
  1026  
  1027  		wg.Wait()
  1028  		assert.NotEmpty(t, allocs)
  1029  
  1030  		for _, name := range allocs {
  1031  			gsCheck, err := client.GameServers(framework.Namespace).Get(ctx, name, metav1.GetOptions{})
  1032  			require.NoError(t, err)
  1033  			assert.True(t, gsCheck.ObjectMeta.DeletionTimestamp.IsZero())
  1034  		}
  1035  	}
  1036  
  1037  	t.Run("scale down", func(t *testing.T) {
  1038  		t.Parallel()
  1039  
  1040  		testAllocationRaceCondition(t, defaultFleet, time.Second,
  1041  			func(t *testing.T, flt *agonesv1.Fleet) {
  1042  				const targetScale = int32(0)
  1043  				flt = scaleFleetPatch(ctx, t, flt, targetScale)
  1044  				assert.Equal(t, targetScale, flt.Spec.Replicas)
  1045  			})
  1046  	})
  1047  
  1048  	t.Run("recreate update", func(t *testing.T) {
  1049  		t.Parallel()
  1050  
  1051  		fleet := func(ns string) *agonesv1.Fleet {
  1052  			flt := defaultFleet(ns)
  1053  			flt.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
  1054  			flt.Spec.Template.ObjectMeta.Annotations = map[string]string{key: red}
  1055  
  1056  			return flt
  1057  		}
  1058  
  1059  		testAllocationRaceCondition(t, fleet, time.Second,
  1060  			func(t *testing.T, flt *agonesv1.Fleet) {
  1061  				flt, err := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{})
  1062  				require.NoError(t, err)
  1063  				fltCopy := flt.DeepCopy()
  1064  				fltCopy.Spec.Template.ObjectMeta.Annotations[key] = green
  1065  				_, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
  1066  				assertSuccessOrUpdateConflict(t, err)
  1067  			})
  1068  	})
  1069  
  1070  	t.Run("rolling update", func(t *testing.T) {
  1071  		t.Parallel()
  1072  
  1073  		fleet := func(ns string) *agonesv1.Fleet {
  1074  			flt := defaultFleet(ns)
  1075  			flt.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType
  1076  			flt.Spec.Template.ObjectMeta.Annotations = map[string]string{key: red}
  1077  
  1078  			return flt
  1079  		}
  1080  
  1081  		testAllocationRaceCondition(t, fleet, time.Duration(0),
  1082  			func(t *testing.T, flt *agonesv1.Fleet) {
  1083  				flt, err := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{})
  1084  				require.NoError(t, err)
  1085  				fltCopy := flt.DeepCopy()
  1086  				fltCopy.Spec.Template.ObjectMeta.Annotations[key] = green
  1087  				_, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
  1088  				assertSuccessOrUpdateConflict(t, err)
  1089  			})
  1090  	})
  1091  }
  1092  
  1093  // TestCreateFleetAndUpdateScaleSubresource is built to
  1094  // test scale subresource usage and its ability to change Fleet Replica size.
  1095  // Both scaling up and down.
  1096  func TestCreateFleetAndUpdateScaleSubresource(t *testing.T) {
  1097  	t.Parallel()
  1098  	ctx := context.Background()
  1099  
  1100  	client := framework.AgonesClient.AgonesV1()
  1101  
  1102  	flt := defaultFleet(framework.Namespace)
  1103  	const initialReplicas int32 = 1
  1104  	flt.Spec.Replicas = initialReplicas
  1105  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
  1106  	require.NoError(t, err)
  1107  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1108  	assert.Equal(t, initialReplicas, flt.Spec.Replicas)
  1109  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1110  
  1111  	newReplicas := initialReplicas * 2
  1112  	scaleFleetSubresource(ctx, t, flt, newReplicas)
  1113  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(newReplicas))
  1114  
  1115  	scaleFleetSubresource(ctx, t, flt, initialReplicas)
  1116  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(initialReplicas))
  1117  }
  1118  
  1119  // TestScaleUpAndDownInParallelStressTest creates N fleets, half of which start with replicas=0
  1120  // and the other half with 0 and scales them up/down 3 times in parallel expecting it to reach
  1121  // the desired number of ready replicas each time.
  1122  // This test is also used as a stress test with 'make stress-test-e2e', in which case it creates
  1123  // many more fleets of bigger sizes and runs many more repetitions.
  1124  func TestScaleUpAndDownInParallelStressTest(t *testing.T) {
  1125  	t.Parallel()
  1126  	ctx := context.Background()
  1127  
  1128  	client := framework.AgonesClient.AgonesV1()
  1129  	fleetCount := 2
  1130  	fleetSize := int32(10)
  1131  	defaultReplicas := int32(1)
  1132  	repeatCount := 3
  1133  	deadline := time.Now().Add(1 * time.Minute)
  1134  
  1135  	logrus.WithField("fleetCount", fleetCount).
  1136  		WithField("fleetSize", fleetSize).
  1137  		WithField("repeatCount", repeatCount).
  1138  		WithField("deadline", deadline).
  1139  		Info("starting scale up/down test")
  1140  
  1141  	if framework.StressTestLevel > 0 {
  1142  		fleetSize = 10 * int32(framework.StressTestLevel)
  1143  		repeatCount = 10
  1144  		fleetCount = 10
  1145  		deadline = time.Now().Add(45 * time.Minute)
  1146  	}
  1147  
  1148  	var fleets []*agonesv1.Fleet
  1149  
  1150  	scaleUpStats := framework.NewStatsCollector(fmt.Sprintf("fleet_%v_scale_up", fleetSize), framework.Version)
  1151  	scaleDownStats := framework.NewStatsCollector(fmt.Sprintf("fleet_%v_scale_down", fleetSize), framework.Version)
  1152  
  1153  	defer scaleUpStats.Report()
  1154  	defer scaleDownStats.Report()
  1155  
  1156  	for fleetNumber := 0; fleetNumber < fleetCount; fleetNumber++ {
  1157  		flt := defaultFleet(framework.Namespace)
  1158  		flt.ObjectMeta.GenerateName = fmt.Sprintf("scale-fleet-%v-", fleetNumber)
  1159  		if fleetNumber%2 == 0 {
  1160  			// even-numbered fleets starts at fleetSize and are scaled down to zero and back.
  1161  			flt.Spec.Replicas = fleetSize
  1162  		} else {
  1163  			// odd-numbered fleets starts at default 1 replica and are scaled up to fleetSize and back.
  1164  			flt.Spec.Replicas = defaultReplicas
  1165  		}
  1166  
  1167  		flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
  1168  		require.NoError(t, err)
  1169  		defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint
  1170  		fleets = append(fleets, flt)
  1171  	}
  1172  
  1173  	// wait for initial fleet conditions.
  1174  	for fleetNumber, flt := range fleets {
  1175  		if fleetNumber%2 == 0 {
  1176  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(fleetSize))
  1177  		} else {
  1178  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(defaultReplicas))
  1179  		}
  1180  	}
  1181  	errorsChan := make(chan error)
  1182  	var wg sync.WaitGroup
  1183  	finished := make(chan bool, 1)
  1184  
  1185  	for fleetNumber, flt := range fleets {
  1186  		wg.Add(1)
  1187  		go func(fleetNumber int, flt *agonesv1.Fleet) {
  1188  			defer wg.Done()
  1189  			defer func() {
  1190  				if err := recover(); err != nil {
  1191  					t.Errorf("recovered panic: %v", err)
  1192  				}
  1193  			}()
  1194  
  1195  			if fleetNumber%2 == 0 {
  1196  				duration, err := scaleAndWait(ctx, t, flt, 0)
  1197  				if err != nil {
  1198  					fmt.Println(err)
  1199  					errorsChan <- err
  1200  					return
  1201  				}
  1202  				scaleDownStats.ReportDuration(duration, nil)
  1203  			}
  1204  			for i := 0; i < repeatCount; i++ {
  1205  				if time.Now().After(deadline) {
  1206  					break
  1207  				}
  1208  				duration, err := scaleAndWait(ctx, t, flt, fleetSize)
  1209  				if err != nil {
  1210  					fmt.Println(err)
  1211  					errorsChan <- err
  1212  					return
  1213  				}
  1214  				scaleUpStats.ReportDuration(duration, nil)
  1215  				duration, err = scaleAndWait(ctx, t, flt, 0)
  1216  				if err != nil {
  1217  					fmt.Println(err)
  1218  					errorsChan <- err
  1219  					return
  1220  				}
  1221  				scaleDownStats.ReportDuration(duration, nil)
  1222  			}
  1223  		}(fleetNumber, flt)
  1224  	}
  1225  	go func() {
  1226  		wg.Wait()
  1227  		close(finished)
  1228  	}()
  1229  
  1230  	select {
  1231  	case <-finished:
  1232  	case err := <-errorsChan:
  1233  		t.Fatalf("Error in waiting for a fleet to scale: %s", err)
  1234  	}
  1235  	fmt.Println("We are Done")
  1236  }
  1237  
  1238  // Creates a fleet and one GameServer with Packed scheduling.
  1239  // Scale to two GameServers with Distributed scheduling.
  1240  // The old GameServer has Scheduling set to 5 and the new one has it set to Distributed.
  1241  func TestUpdateFleetScheduling(t *testing.T) {
  1242  	t.Parallel()
  1243  	ctx := context.Background()
  1244  
  1245  	t.Run("Updating Spec.Scheduling on fleet should be updated in GameServer",
  1246  		func(t *testing.T) {
  1247  			framework.SkipOnCloudProduct(t, "gke-autopilot", "Autopilot does not support Distributed scheduling")
  1248  			client := framework.AgonesClient.AgonesV1()
  1249  
  1250  			flt := defaultFleet(framework.Namespace)
  1251  			flt.Spec.Replicas = 1
  1252  			flt.Spec.Scheduling = apis.Packed
  1253  			flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
  1254  
  1255  			require.NoError(t, err)
  1256  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1257  
  1258  			assert.Equal(t, int32(1), flt.Spec.Replicas)
  1259  			assert.Equal(t, apis.Packed, flt.Spec.Scheduling)
  1260  
  1261  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1262  
  1263  			const targetScale = 2
  1264  			flt = schedulingFleetPatch(ctx, t, flt, apis.Distributed, targetScale)
  1265  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(targetScale))
  1266  
  1267  			assert.Equal(t, int32(targetScale), flt.Spec.Replicas)
  1268  			assert.Equal(t, apis.Distributed, flt.Spec.Scheduling)
  1269  
  1270  			err = framework.WaitForFleetGameServerListCondition(flt,
  1271  				func(gsList []agonesv1.GameServer) bool {
  1272  					return countFleetScheduling(gsList, apis.Distributed) == 1 &&
  1273  						countFleetScheduling(gsList, apis.Packed) == 1
  1274  				})
  1275  			require.NoError(t, err)
  1276  		})
  1277  }
  1278  
  1279  // TestFleetWithZeroReplicas ensures that we can always create 0 replica
  1280  // fleets, which is useful!
  1281  func TestFleetWithZeroReplicas(t *testing.T) {
  1282  	t.Parallel()
  1283  	ctx := context.Background()
  1284  	client := framework.AgonesClient.AgonesV1()
  1285  
  1286  	flt := defaultFleet(framework.Namespace)
  1287  	flt.Spec.Replicas = 0
  1288  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
  1289  	assert.NoError(t, err)
  1290  
  1291  	// can't think of a better way to wait for a bit before checking.
  1292  	time.Sleep(time.Second)
  1293  
  1294  	list, err := framework.ListGameServersFromFleet(flt)
  1295  	assert.NoError(t, err)
  1296  	assert.Empty(t, list)
  1297  }
  1298  
  1299  // TestFleetWithLongLabelsAnnotations ensures that we can not create a fleet
  1300  // with label over 64 chars and Annotations key over 64
  1301  func TestFleetWithLongLabelsAnnotations(t *testing.T) {
  1302  	t.Parallel()
  1303  	ctx := context.Background()
  1304  
  1305  	client := framework.AgonesClient.AgonesV1()
  1306  	fleetSize := int32(1)
  1307  	flt := defaultFleet(framework.Namespace)
  1308  	flt.Spec.Replicas = fleetSize
  1309  	normalLengthName := strings.Repeat("f", validation.LabelValueMaxLength)
  1310  	longName := normalLengthName + "f"
  1311  	flt.Spec.Template.ObjectMeta.Labels = make(map[string]string)
  1312  	flt.Spec.Template.ObjectMeta.Labels["label"] = longName
  1313  	_, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
  1314  	assert.Error(t, err)
  1315  	statusErr, ok := err.(*k8serrors.StatusError)
  1316  	assert.True(t, ok)
  1317  	assert.Len(t, statusErr.Status().Details.Causes, 1)
  1318  	assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type)
  1319  	assert.Equal(t, "spec.template.metadata.labels", statusErr.Status().Details.Causes[0].Field)
  1320  
  1321  	// Set Label to normal size and add Annotations with an error
  1322  	flt.Spec.Template.ObjectMeta.Labels["label"] = normalLengthName
  1323  	flt.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
  1324  	flt.Spec.Template.ObjectMeta.Annotations[longName] = normalLengthName
  1325  	_, err = client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
  1326  	assert.Error(t, err)
  1327  	statusErr, ok = err.(*k8serrors.StatusError)
  1328  	assert.True(t, ok)
  1329  	assert.Len(t, statusErr.Status().Details.Causes, 1)
  1330  	assert.Equal(t, "spec.template.metadata.annotations", statusErr.Status().Details.Causes[0].Field)
  1331  	assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type)
  1332  
  1333  	goodFlt := defaultFleet(framework.Namespace)
  1334  	goodFlt.Spec.Template.ObjectMeta.Labels = make(map[string]string)
  1335  	goodFlt.Spec.Template.ObjectMeta.Labels["label"] = normalLengthName
  1336  	goodFlt, err = client.Fleets(framework.Namespace).Create(ctx, goodFlt, metav1.CreateOptions{})
  1337  	require.NoError(t, err)
  1338  	defer client.Fleets(framework.Namespace).Delete(ctx, goodFlt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1339  	err = framework.WaitForFleetCondition(t, goodFlt, e2e.FleetReadyCount(goodFlt.Spec.Replicas))
  1340  	require.NoError(t, err)
  1341  
  1342  	// Verify validation on Update()
  1343  	flt, err = client.Fleets(framework.Namespace).Get(ctx, goodFlt.ObjectMeta.GetName(), metav1.GetOptions{})
  1344  	require.NoError(t, err)
  1345  	goodFlt = flt.DeepCopy()
  1346  	goodFlt.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
  1347  	goodFlt.Spec.Template.ObjectMeta.Annotations[longName] = normalLengthName
  1348  	_, err = client.Fleets(framework.Namespace).Update(ctx, goodFlt, metav1.UpdateOptions{})
  1349  	assert.Error(t, err)
  1350  	statusErr, ok = err.(*k8serrors.StatusError)
  1351  	assert.True(t, ok)
  1352  	require.Len(t, statusErr.Status().Details.Causes, 1)
  1353  	assert.Equal(t, "spec.template.metadata.annotations", statusErr.Status().Details.Causes[0].Field)
  1354  	assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type)
  1355  
  1356  	// Make sure normal annotations path Validation on Update
  1357  	flt, err = client.Fleets(framework.Namespace).Get(ctx, goodFlt.ObjectMeta.GetName(), metav1.GetOptions{})
  1358  	require.NoError(t, err)
  1359  	goodFlt = flt.DeepCopy()
  1360  	goodFlt.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
  1361  	goodFlt.Spec.Template.ObjectMeta.Annotations[normalLengthName] = longName
  1362  	_, err = client.Fleets(framework.Namespace).Update(ctx, goodFlt, metav1.UpdateOptions{})
  1363  	require.NoError(t, err)
  1364  }
  1365  
  1366  // TestFleetRecreateGameServers tests various gameserver shutdown scenarios to ensure
  1367  // that recreation happens as expected
  1368  func TestFleetRecreateGameServers(t *testing.T) {
  1369  	t.Parallel()
  1370  	ctx := context.Background()
  1371  
  1372  	tests := map[string]struct {
  1373  		f func(t *testing.T, list *agonesv1.GameServerList)
  1374  	}{
  1375  		"pod deletion": {f: func(t *testing.T, list *agonesv1.GameServerList) {
  1376  			podClient := framework.KubeClient.CoreV1().Pods(framework.Namespace)
  1377  
  1378  			for _, gs := range list.Items {
  1379  				pod, err := podClient.Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{})
  1380  				assert.NoError(t, err)
  1381  
  1382  				assert.True(t, metav1.IsControlledBy(pod, &gs))
  1383  
  1384  				err = podClient.Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{})
  1385  				assert.NoError(t, err)
  1386  			}
  1387  		}},
  1388  		"gameserver shutdown": {f: func(t *testing.T, list *agonesv1.GameServerList) {
  1389  			for _, gs := range list.Items {
  1390  				var reply string
  1391  				reply, err := framework.SendGameServerUDP(t, &gs, "EXIT")
  1392  				if err != nil {
  1393  					// if we didn't get a response because the GameServer has gone away, then the packet dropped on the return,
  1394  					// but we're in the state we want, so we can ignore that we didn't get a response.
  1395  					_, gsErr := framework.AgonesClient.AgonesV1().GameServers(gs.ObjectMeta.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{})
  1396  					if k8serrors.IsNotFound(gsErr) {
  1397  						continue
  1398  					}
  1399  					t.Fatalf("Could not message GameServer: %v", err)
  1400  				}
  1401  
  1402  				assert.Equal(t, "ACK: EXIT\n", reply)
  1403  			}
  1404  		}},
  1405  		"gameserver unhealthy": {f: func(t *testing.T, list *agonesv1.GameServerList) {
  1406  			for _, gs := range list.Items {
  1407  				var reply string
  1408  				reply, err := framework.SendGameServerUDP(t, &gs, "UNHEALTHY")
  1409  				if err != nil {
  1410  					t.Fatalf("Could not message GameServer: %v", err)
  1411  				}
  1412  
  1413  				assert.Equal(t, "ACK: UNHEALTHY\n", reply)
  1414  			}
  1415  		}},
  1416  	}
  1417  
  1418  	for k, v := range tests {
  1419  		t.Run(k, func(t *testing.T) {
  1420  			t.Parallel()
  1421  			client := framework.AgonesClient.AgonesV1()
  1422  			flt := defaultFleet(framework.Namespace)
  1423  			// add more game servers, to hunt for race conditions
  1424  			flt.Spec.Replicas = 10
  1425  
  1426  			flt, err := client.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
  1427  			require.NoError(t, err)
  1428  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1429  
  1430  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1431  
  1432  			list, err := listGameServers(ctx, flt, client)
  1433  			assert.NoError(t, err)
  1434  			var gameservers []agonesv1.GameServer
  1435  			for _, gs := range list.Items {
  1436  				if gs.Status.State != agonesv1.GameServerStateShutdown {
  1437  					gameservers = append(gameservers, gs)
  1438  				}
  1439  			}
  1440  			assert.Len(t, gameservers, int(flt.Spec.Replicas))
  1441  
  1442  			// apply deletion function
  1443  			logrus.Info("applying deletion function")
  1444  			v.f(t, list)
  1445  
  1446  			for i, gs := range gameservers {
  1447  				err = wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Minute, true, func(ctx context.Context) (done bool, err error) {
  1448  					_, err = client.GameServers(framework.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{})
  1449  
  1450  					if err != nil && k8serrors.IsNotFound(err) {
  1451  						logrus.Infof("gameserver %d/%d not found", i+1, flt.Spec.Replicas)
  1452  						return true, nil
  1453  					}
  1454  
  1455  					return false, err
  1456  				})
  1457  				assert.NoError(t, err)
  1458  			}
  1459  
  1460  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1461  		})
  1462  	}
  1463  }
  1464  
  1465  // TestFleetResourceValidation - check that we are not able to use
  1466  // invalid PodTemplate for GameServer Spec with wrong Resource Requests and Limits
  1467  func TestFleetResourceValidation(t *testing.T) {
  1468  	t.Parallel()
  1469  	ctx := context.Background()
  1470  
  1471  	client := framework.AgonesClient.AgonesV1()
  1472  
  1473  	// check two Containers in Gameserver Spec Template validation
  1474  	flt := defaultFleet(framework.Namespace)
  1475  	containerName := "container2"
  1476  	resources := corev1.ResourceRequirements{
  1477  		Requests: corev1.ResourceList{
  1478  			corev1.ResourceCPU:    resource.MustParse("30m"),
  1479  			corev1.ResourceMemory: resource.MustParse("32Mi"),
  1480  		},
  1481  		Limits: corev1.ResourceList{
  1482  			corev1.ResourceCPU:    resource.MustParse("30m"),
  1483  			corev1.ResourceMemory: resource.MustParse("32Mi"),
  1484  		},
  1485  	}
  1486  	flt.Spec.Template.Spec.Template =
  1487  		corev1.PodTemplateSpec{
  1488  			Spec: corev1.PodSpec{
  1489  				Containers: []corev1.Container{
  1490  					{Name: "container", Image: framework.GameServerImage, Resources: *(resources.DeepCopy())},
  1491  					{Name: containerName, Image: framework.GameServerImage, Resources: *(resources.DeepCopy())},
  1492  				},
  1493  			},
  1494  		}
  1495  	mi128 := resource.MustParse("128Mi")
  1496  	m50 := resource.MustParse("50m")
  1497  
  1498  	flt.Spec.Template.Spec.Container = containerName
  1499  	containers := flt.Spec.Template.Spec.Template.Spec.Containers
  1500  	containers[1].Resources.Limits[corev1.ResourceMemory] = resource.MustParse("64Mi")
  1501  	containers[1].Resources.Requests[corev1.ResourceMemory] = mi128
  1502  
  1503  	_, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1504  	assert.NotNil(t, err)
  1505  	statusErr, ok := err.(*k8serrors.StatusError)
  1506  	assert.True(t, ok)
  1507  	assert.Len(t, statusErr.Status().Details.Causes, 1)
  1508  	assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type)
  1509  	assert.Equal(t, "spec.template.spec.template.spec.containers[1].resources.requests", statusErr.Status().Details.Causes[0].Field)
  1510  
  1511  	containers[0].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("-50m")
  1512  	_, err = client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1513  	assert.NotNil(t, err)
  1514  	statusErr, ok = err.(*k8serrors.StatusError)
  1515  	assert.True(t, ok)
  1516  
  1517  	assert.Len(t, statusErr.Status().Details.Causes, 3)
  1518  	assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type)
  1519  	assert.Equal(t, "spec.template.spec.template.spec.containers[0].resources.limits[cpu]", statusErr.Status().Details.Causes[0].Field)
  1520  	causes := statusErr.Status().Details.Causes
  1521  	assertCausesContainsString(t, causes, `Invalid value: "30m": must be less than or equal to cpu limit of -50m`)
  1522  	assertCausesContainsString(t, causes, `Invalid value: "-50m": must be greater than or equal to 0`)
  1523  	assertCausesContainsString(t, causes, `Invalid value: "128Mi": must be less than or equal to memory limit of 64Mi`)
  1524  
  1525  	containers[1].Resources.Limits[corev1.ResourceMemory] = mi128
  1526  	containers[0].Resources.Limits[corev1.ResourceCPU] = m50
  1527  	flt, err = client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1528  	if assert.NoError(t, err) {
  1529  		defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1530  	}
  1531  
  1532  	containers = flt.Spec.Template.Spec.Template.Spec.Containers
  1533  	assert.Equal(t, mi128, containers[1].Resources.Limits[corev1.ResourceMemory])
  1534  	assert.Equal(t, m50, containers[0].Resources.Limits[corev1.ResourceCPU])
  1535  }
  1536  
  1537  func TestFleetAggregatedPlayerStatus(t *testing.T) {
  1538  	if !runtime.FeatureEnabled(runtime.FeaturePlayerTracking) {
  1539  		t.SkipNow()
  1540  	}
  1541  	t.Parallel()
  1542  	ctx := context.Background()
  1543  	client := framework.AgonesClient.AgonesV1()
  1544  
  1545  	flt := defaultFleet(framework.Namespace)
  1546  	flt.Spec.Template.Spec.Players = &agonesv1.PlayersSpec{
  1547  		InitialCapacity: 10,
  1548  	}
  1549  
  1550  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1551  	assert.NoError(t, err)
  1552  
  1553  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1554  		if fleet.Status.Players == nil {
  1555  			log.WithField("status", fleet.Status).Info("No Players")
  1556  			return false
  1557  		}
  1558  
  1559  		log.WithField("status", fleet.Status).Info("Checking Capacity")
  1560  		return fleet.Status.Players.Capacity == 30
  1561  	})
  1562  
  1563  	list, err := framework.ListGameServersFromFleet(flt)
  1564  	assert.NoError(t, err)
  1565  	// set 3 random capacities, and connect a random number of players
  1566  	totalCapacity := 0
  1567  	totalPlayers := 0
  1568  	for i := range list {
  1569  		// Do this, otherwise scopelint complains about "using a reference for the variable on range scope"
  1570  		gs := &list[i]
  1571  		players := rand.IntnRange(1, 5)
  1572  		capacity := rand.IntnRange(players, 100)
  1573  		totalCapacity += capacity
  1574  
  1575  		msg := fmt.Sprintf("PLAYER_CAPACITY %d", capacity)
  1576  		reply, err := framework.SendGameServerUDP(t, gs, msg)
  1577  		if err != nil {
  1578  			t.Fatalf("Could not message GameServer: %v", err)
  1579  		}
  1580  		assert.Equal(t, fmt.Sprintf("ACK: %s\n", msg), reply)
  1581  
  1582  		totalPlayers += players
  1583  		for i := 1; i <= players; i++ {
  1584  			msg := "PLAYER_CONNECT " + fmt.Sprintf("%d", i)
  1585  			logrus.WithField("msg", msg).WithField("gs", gs.ObjectMeta.Name).Info("Sending Player Connect")
  1586  			// retry on failure. Will stop flakiness of UDP packets being sent/received.
  1587  			err := wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Minute, true, func(_ context.Context) (done bool, err error) {
  1588  				reply, err := framework.SendGameServerUDP(t, gs, msg)
  1589  				if err != nil {
  1590  					logrus.WithError(err).Warn("error with udp packet")
  1591  					return false, nil
  1592  				}
  1593  				assert.Equal(t, fmt.Sprintf("ACK: %s\n", msg), reply)
  1594  				return true, nil
  1595  			})
  1596  			assert.NoError(t, err)
  1597  		}
  1598  	}
  1599  
  1600  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1601  		log.WithField("players", fleet.Status.Players).WithField("totalCapacity", totalCapacity).
  1602  			WithField("totalPlayers", totalPlayers).Info("Checking Capacity")
  1603  		// since UDP packets might fail, we might get an extra player, so we'll check for that.
  1604  		return (fleet.Status.Players.Capacity == int64(totalCapacity)) && (fleet.Status.Players.Count >= int64(totalPlayers))
  1605  	})
  1606  }
  1607  
  1608  func TestFleetAggregatedCounterStatus(t *testing.T) {
  1609  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1610  		t.SkipNow()
  1611  	}
  1612  	t.Parallel()
  1613  	ctx := context.Background()
  1614  	client := framework.AgonesClient.AgonesV1()
  1615  
  1616  	flt := defaultFleet(framework.Namespace)
  1617  	flt.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
  1618  		"games": {
  1619  			Count:    1,
  1620  			Capacity: 10,
  1621  		},
  1622  	}
  1623  
  1624  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1625  	require.NoError(t, err)
  1626  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1627  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1628  
  1629  	// allocate two of them.
  1630  	framework.CreateAndApplyAllocation(t, flt)
  1631  	framework.CreateAndApplyAllocation(t, flt)
  1632  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1633  		return fleet.Status.AllocatedReplicas == 2
  1634  	})
  1635  
  1636  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1637  		counter, ok := fleet.Status.Counters["games"]
  1638  		if !ok {
  1639  			log.WithField("status", fleet.Status).Info("No games Counter")
  1640  			return false
  1641  		}
  1642  
  1643  		log.WithField("status", fleet.Status).Info("Checking Count and Capacity")
  1644  		log.WithField("AggregatedCounterStatus", counter).Debug("AggregatedCounterStatus")
  1645  		return counter.AllocatedCount == 2 && counter.AllocatedCapacity == 20 && counter.Count == 3 && counter.Capacity == 30
  1646  	})
  1647  
  1648  	list, err := framework.ListGameServersFromFleet(flt)
  1649  	assert.NoError(t, err)
  1650  	totalCapacity := 0
  1651  	totalCount := 0
  1652  	allocatedCapacity := 0
  1653  	allocatedCount := 0
  1654  	// set random counts and capacities for each gameserver
  1655  	for _, gs := range list {
  1656  		count := rand.IntnRange(2, 9)
  1657  		capacity := rand.IntnRange(count, 100)
  1658  
  1659  		totalCapacity += capacity
  1660  		msg := fmt.Sprintf("SET_COUNTER_CAPACITY games %d", capacity)
  1661  		reply, err := framework.SendGameServerUDP(t, &gs, msg)
  1662  		require.NoError(t, err)
  1663  		assert.Equal(t, "SUCCESS\n", reply)
  1664  
  1665  		totalCount += count
  1666  		msg = fmt.Sprintf("SET_COUNTER_COUNT games %d", count)
  1667  		reply, err = framework.SendGameServerUDP(t, &gs, msg)
  1668  		require.NoError(t, err)
  1669  		assert.Equal(t, "SUCCESS\n", reply)
  1670  
  1671  		if gs.Status.State == agonesv1.GameServerStateAllocated {
  1672  			allocatedCapacity += capacity
  1673  			allocatedCount += count
  1674  		}
  1675  	}
  1676  
  1677  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1678  		counter, ok := fleet.Status.Counters["games"]
  1679  		if !ok {
  1680  			log.WithField("status", fleet.Status).Info("No games Counter")
  1681  			return false
  1682  		}
  1683  
  1684  		log.WithField("status", fleet.Status).Info("Checking Aggregated Count and Capacity")
  1685  		log.WithField("AggregatedCounterStatus", counter).Debug("AggregatedCounterStatus")
  1686  		return counter.AllocatedCount == int64(allocatedCount) && counter.AllocatedCapacity == int64(allocatedCapacity) &&
  1687  			counter.Count == int64(totalCount) && counter.Capacity == int64(totalCapacity)
  1688  	})
  1689  }
  1690  
  1691  func TestFleetAggregatedListStatus(t *testing.T) {
  1692  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1693  		t.SkipNow()
  1694  	}
  1695  	t.Parallel()
  1696  	ctx := context.Background()
  1697  	client := framework.AgonesClient.AgonesV1()
  1698  
  1699  	flt := defaultFleet(framework.Namespace)
  1700  	flt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
  1701  		"gamers": {
  1702  			Values:   []string{"gamer0", "gamer1"},
  1703  			Capacity: 10,
  1704  		},
  1705  	}
  1706  
  1707  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1708  	require.NoError(t, err)
  1709  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1710  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1711  
  1712  	// allocate two of them.
  1713  	framework.CreateAndApplyAllocation(t, flt)
  1714  	framework.CreateAndApplyAllocation(t, flt)
  1715  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1716  		return fleet.Status.AllocatedReplicas == 2
  1717  	})
  1718  
  1719  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1720  		list, ok := fleet.Status.Lists["gamers"]
  1721  		if !ok {
  1722  			log.WithField("status", fleet.Status).Info("No gamers List")
  1723  			return false
  1724  		}
  1725  
  1726  		log.WithField("status", fleet.Status).Info("Checking Count and Capacity")
  1727  		log.WithField("AggregatedListStatus", list).Debug("AggregatedListStatus")
  1728  		return list.AllocatedCount == 4 && list.AllocatedCapacity == 20 && list.Count == 6 && list.Capacity == 30
  1729  	})
  1730  
  1731  	list, err := framework.ListGameServersFromFleet(flt)
  1732  	assert.NoError(t, err)
  1733  	totalCapacity := 0
  1734  	totalCount := 0
  1735  	allocatedCapacity := 0
  1736  	allocatedCount := 0
  1737  	// set random counts and capacities for each gameserver
  1738  	for _, gs := range list {
  1739  		count := rand.IntnRange(2, 9)
  1740  		capacity := rand.IntnRange(count, 100)
  1741  
  1742  		totalCapacity += capacity
  1743  		msg := fmt.Sprintf("SET_LIST_CAPACITY gamers %d", capacity)
  1744  		reply, err := framework.SendGameServerUDP(t, &gs, msg)
  1745  		require.NoError(t, err)
  1746  		assert.Equal(t, "SUCCESS\n", reply)
  1747  
  1748  		totalCount += count
  1749  		// Each list starts with a count of 2 (Values: []string{"gamer0", "gamer1"})
  1750  		for i := 2; i < count; i++ {
  1751  			msg = fmt.Sprintf("APPEND_LIST_VALUE gamers gamer%d", i)
  1752  			reply, err = framework.SendGameServerUDP(t, &gs, msg)
  1753  			require.NoError(t, err)
  1754  			assert.Equal(t, "SUCCESS\n", reply)
  1755  		}
  1756  
  1757  		if gs.Status.State == agonesv1.GameServerStateAllocated {
  1758  			allocatedCapacity += capacity
  1759  			allocatedCount += count
  1760  		}
  1761  	}
  1762  
  1763  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1764  		list, ok := fleet.Status.Lists["gamers"]
  1765  		if !ok {
  1766  			log.WithField("status", fleet.Status).Info("No gamers List")
  1767  			return false
  1768  		}
  1769  
  1770  		log.WithField("status", fleet.Status).Info("Checking Aggregated Count and Capacity")
  1771  		log.WithField("AggregatedListStatus", list).Debug("AggregatedListStatus")
  1772  		return list.AllocatedCount == int64(allocatedCount) && list.AllocatedCapacity == int64(allocatedCapacity) &&
  1773  			list.Count == int64(totalCount) && list.Capacity == int64(totalCapacity)
  1774  	})
  1775  }
  1776  
  1777  func TestFleetAllocationOverflow(t *testing.T) {
  1778  	t.Parallel()
  1779  	ctx := context.Background()
  1780  	client := framework.AgonesClient.AgonesV1()
  1781  	fleets := client.Fleets(framework.Namespace)
  1782  
  1783  	setup := func() *agonesv1.Fleet {
  1784  		flt := defaultFleet(framework.Namespace)
  1785  		flt.Spec.AllocationOverflow = &agonesv1.AllocationOverflow{Labels: map[string]string{"colour": "green"}, Annotations: map[string]string{"action": "update"}}
  1786  		flt, err := fleets.Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1787  		require.NoError(t, err)
  1788  		framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1789  
  1790  		// allocate two of them.
  1791  		framework.CreateAndApplyAllocation(t, flt)
  1792  		framework.CreateAndApplyAllocation(t, flt)
  1793  		framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1794  			return fleet.Status.AllocatedReplicas == 2
  1795  		})
  1796  
  1797  		flt, err = fleets.Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{})
  1798  		require.NoError(t, err)
  1799  		return flt
  1800  	}
  1801  
  1802  	assertCount := func(t *testing.T, log *logrus.Entry, flt *agonesv1.Fleet, expected int) {
  1803  		require.Eventuallyf(t, func() bool {
  1804  			log.Info("Checking GameServers")
  1805  			list, err := framework.ListGameServersFromFleet(flt)
  1806  			require.NoError(t, err)
  1807  			count := 0
  1808  
  1809  			for _, gs := range list {
  1810  				if gs.ObjectMeta.Labels["colour"] != "green" {
  1811  					log.WithField("gs", gs).Info("Label not set")
  1812  					continue
  1813  				}
  1814  				if gs.ObjectMeta.Annotations["action"] != "update" {
  1815  					log.WithField("gs", gs).Info("Annotation not set")
  1816  					continue
  1817  				}
  1818  				count++
  1819  			}
  1820  
  1821  			return count == expected
  1822  		}, 5*time.Minute, time.Second, "Labels and annotations not set")
  1823  	}
  1824  
  1825  	t.Run("scale down", func(t *testing.T) {
  1826  		log := e2e.TestLogger(t)
  1827  		flt := setup()
  1828  		defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck
  1829  
  1830  		framework.ScaleFleet(t, log, flt, 0)
  1831  
  1832  		// wait for scale down
  1833  		framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1834  			return fleet.Status.AllocatedReplicas == 2 && fleet.Status.ReadyReplicas == 0
  1835  		})
  1836  
  1837  		assertCount(t, log, flt, 2)
  1838  	})
  1839  
  1840  	t.Run("rolling update", func(t *testing.T) {
  1841  		log := e2e.TestLogger(t)
  1842  		flt := setup()
  1843  		defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck
  1844  
  1845  		fltCopy := flt.DeepCopy()
  1846  		if fltCopy.Spec.Template.ObjectMeta.Labels == nil {
  1847  			fltCopy.Spec.Template.ObjectMeta.Labels = map[string]string{}
  1848  		}
  1849  		fltCopy.Spec.Template.ObjectMeta.Labels["version"] = "2.0"
  1850  		flt, err := fleets.Update(ctx, fltCopy, metav1.UpdateOptions{})
  1851  		require.NoError(t, err)
  1852  
  1853  		// wait for rolling update to finish
  1854  		require.Eventuallyf(t, func() bool {
  1855  			list, err := framework.ListGameServersFromFleet(flt)
  1856  			assert.NoError(t, err)
  1857  			for _, gs := range list {
  1858  				log.WithField("gs", gs).Info("checking game server")
  1859  				if gs.Status.State == agonesv1.GameServerStateReady && gs.ObjectMeta.Labels["version"] == "2.0" {
  1860  					return true
  1861  				}
  1862  			}
  1863  
  1864  			return false
  1865  		}, 5*time.Minute, time.Second, "Rolling update did not complete")
  1866  
  1867  		if runtime.FeatureEnabled(runtime.FeatureRollingUpdateFix) {
  1868  			// In the rolling update fix, the old GSS will be scaled to Spec.Replicas=0.
  1869  			assertCount(t, log, flt, 2)
  1870  		} else {
  1871  			assertCount(t, log, flt, 1)
  1872  		}
  1873  	})
  1874  }
  1875  
  1876  func assertCausesContainsString(t *testing.T, causes []metav1.StatusCause, expected string) {
  1877  	strs := make([]string, 0, len(causes))
  1878  	for _, v := range causes {
  1879  		strs = append(strs, v.Message)
  1880  	}
  1881  	assert.Contains(t, strs, expected)
  1882  }
  1883  
  1884  func listGameServers(ctx context.Context, flt *agonesv1.Fleet, getter typedagonesv1.GameServersGetter) (*agonesv1.GameServerList, error) {
  1885  	selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name})
  1886  	return getter.GameServers(framework.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
  1887  }
  1888  
  1889  // Counts the number of gameservers with the specified scheduling strategy in a fleet
  1890  func countFleetScheduling(gsList []agonesv1.GameServer, scheduling apis.SchedulingStrategy) int {
  1891  	count := 0
  1892  	for i := range gsList {
  1893  		gs := &gsList[i]
  1894  		if gs.Spec.Scheduling == scheduling {
  1895  			count++
  1896  		}
  1897  	}
  1898  	return count
  1899  }
  1900  
  1901  // Patches fleet with scheduling and scale values
  1902  func schedulingFleetPatch(ctx context.Context, t *testing.T, f *agonesv1.Fleet, scheduling apis.SchedulingStrategy, scale int32) *agonesv1.Fleet {
  1903  
  1904  	patch := fmt.Sprintf(`[{ "op": "replace", "path": "/spec/scheduling", "value": "%s" },
  1905  	                       { "op": "replace", "path": "/spec/replicas", "value": %d }]`,
  1906  		scheduling, scale)
  1907  
  1908  	logrus.WithField("fleet", f.ObjectMeta.Name).
  1909  		WithField("scheduling", scheduling).
  1910  		WithField("scale", scale).
  1911  		WithField("patch", patch).
  1912  		Info("updating scheduling")
  1913  
  1914  	fltRes, err := framework.AgonesClient.
  1915  		AgonesV1().
  1916  		Fleets(framework.Namespace).
  1917  		Patch(ctx, f.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{})
  1918  
  1919  	require.NoError(t, err)
  1920  	return fltRes
  1921  }
  1922  
  1923  func scaleAndWait(ctx context.Context, t *testing.T, flt *agonesv1.Fleet, fleetSize int32) (duration time.Duration, err error) {
  1924  	t0 := time.Now()
  1925  	scaleFleetSubresource(ctx, t, flt, fleetSize)
  1926  	err = framework.WaitForFleetCondition(t, flt, e2e.FleetReadyCount(fleetSize))
  1927  	duration = time.Since(t0)
  1928  	return
  1929  }
  1930  
  1931  // scaleFleetPatch creates a patch to apply to a Fleet.
  1932  // Easier for testing, as it removes object generational issues.
  1933  func scaleFleetPatch(ctx context.Context, t *testing.T, f *agonesv1.Fleet, scale int32) *agonesv1.Fleet {
  1934  	patch := fmt.Sprintf(`[{ "op": "replace", "path": "/spec/replicas", "value": %d }]`, scale)
  1935  	logrus.WithField("fleet", f.ObjectMeta.Name).WithField("scale", scale).WithField("patch", patch).Info("Scaling fleet")
  1936  
  1937  	fltRes, err := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Patch(ctx, f.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{})
  1938  	require.NoError(t, err)
  1939  	return fltRes
  1940  }
  1941  
  1942  // scaleFleetSubresource uses scale subresource to change Replicas size of the Fleet.
  1943  // Returns the same f as in parameter, just to keep signature in sync with scaleFleetPatch
  1944  func scaleFleetSubresource(ctx context.Context, t *testing.T, f *agonesv1.Fleet, scale int32) *agonesv1.Fleet {
  1945  	logrus.WithField("fleet", f.ObjectMeta.Name).WithField("scale", scale).Info("Scaling fleet")
  1946  
  1947  	err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
  1948  		client := framework.AgonesClient.AgonesV1()
  1949  		// GetScale returns current Scale object with resourceVersion which is opaque object
  1950  		// and it will be used to create new Scale object
  1951  		opts := metav1.GetOptions{}
  1952  		sc, err := client.Fleets(framework.Namespace).GetScale(ctx, f.ObjectMeta.Name, opts)
  1953  		if err != nil {
  1954  			return err
  1955  		}
  1956  
  1957  		sc2 := newScale(f.Name, scale, sc.ObjectMeta.ResourceVersion)
  1958  		_, err = client.Fleets(framework.Namespace).UpdateScale(ctx, f.ObjectMeta.Name, sc2, metav1.UpdateOptions{})
  1959  		return err
  1960  	})
  1961  
  1962  	if err != nil {
  1963  		t.Fatal("could not update the scale subresource")
  1964  	}
  1965  	return f
  1966  }
  1967  
  1968  // defaultFleet returns a default fleet configuration
  1969  func defaultFleet(namespace string) *agonesv1.Fleet {
  1970  	gs := framework.DefaultGameServer(namespace)
  1971  	return fleetWithGameServerSpec(&gs.Spec, namespace)
  1972  }
  1973  
  1974  // defaultEmptyFleet returns a default fleet configuration with no replicas.
  1975  func defaultEmptyFleet(namespace string) *agonesv1.Fleet {
  1976  	gs := framework.DefaultGameServer(namespace)
  1977  	return fleetWithGameServerSpecAndReplicas(&gs.Spec, namespace, 0)
  1978  }
  1979  
  1980  // fleetWithGameServerSpec returns a fleet with specified gameserver spec
  1981  func fleetWithGameServerSpec(gsSpec *agonesv1.GameServerSpec, namespace string) *agonesv1.Fleet {
  1982  	return fleetWithGameServerSpecAndReplicas(gsSpec, namespace, replicasCount)
  1983  }
  1984  
  1985  // fleetWithGameServerSpecAndReplicas returns a fleet with specified gameserver spec and specified replica count
  1986  func fleetWithGameServerSpecAndReplicas(gsSpec *agonesv1.GameServerSpec, namespace string, replicas int32) *agonesv1.Fleet {
  1987  	return &agonesv1.Fleet{
  1988  		ObjectMeta: metav1.ObjectMeta{GenerateName: "simple-fleet-1.0", Namespace: namespace},
  1989  		Spec: agonesv1.FleetSpec{
  1990  			Replicas: replicas,
  1991  			Template: agonesv1.GameServerTemplateSpec{
  1992  				Spec: *gsSpec,
  1993  			},
  1994  		},
  1995  	}
  1996  }
  1997  
  1998  // newScale returns a scale with specified Replicas spec
  1999  func newScale(fleetName string, newReplicas int32, resourceVersion string) *autoscalingv1.Scale {
  2000  	return &autoscalingv1.Scale{
  2001  		ObjectMeta: metav1.ObjectMeta{Name: fleetName, Namespace: framework.Namespace, ResourceVersion: resourceVersion},
  2002  		Spec: autoscalingv1.ScaleSpec{
  2003  			Replicas: newReplicas,
  2004  		},
  2005  	}
  2006  }