agones.dev/agones@v1.53.0/test/e2e/fleetautoscaler_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  	"bytes"
    19  	"context"
    20  	cryptorand "crypto/rand"
    21  	"crypto/rsa"
    22  	"crypto/sha256"
    23  	"crypto/x509"
    24  	"crypto/x509/pkix"
    25  	"encoding/hex"
    26  	"encoding/pem"
    27  	"fmt"
    28  	"math/big"
    29  	"math/rand"
    30  	"os"
    31  	"os/exec"
    32  	"path/filepath"
    33  	"strconv"
    34  	"strings"
    35  	"testing"
    36  	"time"
    37  
    38  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    39  	allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
    40  	autoscalingv1 "agones.dev/agones/pkg/apis/autoscaling/v1"
    41  	"agones.dev/agones/pkg/util/runtime"
    42  	helper "agones.dev/agones/test/e2e/allochelper"
    43  	e2e "agones.dev/agones/test/e2e/framework"
    44  	"github.com/pkg/errors"
    45  	"github.com/sirupsen/logrus"
    46  	"github.com/stretchr/testify/assert"
    47  	"github.com/stretchr/testify/require"
    48  	admregv1 "k8s.io/api/admissionregistration/v1"
    49  	appsv1 "k8s.io/api/apps/v1"
    50  	corev1 "k8s.io/api/core/v1"
    51  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    52  	"k8s.io/apimachinery/pkg/api/resource"
    53  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    54  	"k8s.io/apimachinery/pkg/fields"
    55  	"k8s.io/apimachinery/pkg/labels"
    56  	"k8s.io/apimachinery/pkg/types"
    57  	"k8s.io/apimachinery/pkg/util/intstr"
    58  	"k8s.io/apimachinery/pkg/util/uuid"
    59  	"k8s.io/apimachinery/pkg/util/wait"
    60  )
    61  
    62  var deletePropagationForeground = metav1.DeletePropagationForeground
    63  
    64  var waitForDeletion = metav1.DeleteOptions{
    65  	PropagationPolicy: &deletePropagationForeground,
    66  }
    67  
    68  func TestAutoscalerBasicFunctions(t *testing.T) {
    69  	t.Parallel()
    70  	ctx := context.Background()
    71  
    72  	stable := framework.AgonesClient.AgonesV1()
    73  	fleets := stable.Fleets(framework.Namespace)
    74  	flt, err := fleets.Create(ctx, defaultFleet(framework.Namespace), metav1.CreateOptions{})
    75  	if assert.Nil(t, err) {
    76  		defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
    77  	}
    78  
    79  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
    80  
    81  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
    82  	defaultFas := defaultFleetAutoscaler(flt, framework.Namespace)
    83  	fas, err := fleetautoscalers.Create(ctx, defaultFas, metav1.CreateOptions{})
    84  	require.NoError(t, err)
    85  	defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
    86  
    87  	// the fleet autoscaler should scale the fleet up now up to BufferSize
    88  	bufferSize := int32(fas.Spec.Policy.Buffer.BufferSize.IntValue())
    89  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(bufferSize))
    90  
    91  	// patch the autoscaler to increase MinReplicas and watch the fleet scale up
    92  	fas, err = patchFleetAutoscaler(ctx, fas, intstr.FromInt(int(bufferSize)), bufferSize+2, fas.Spec.Policy.Buffer.MaxReplicas)
    93  	assert.Nil(t, err, "could not patch fleetautoscaler")
    94  
    95  	// min replicas is now higher than buffer size, will scale to that level
    96  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(fas.Spec.Policy.Buffer.MinReplicas))
    97  
    98  	// patch the autoscaler to remove MinReplicas and watch the fleet scale down to bufferSize
    99  	fas, err = patchFleetAutoscaler(ctx, fas, intstr.FromInt(int(bufferSize)), 0, fas.Spec.Policy.Buffer.MaxReplicas)
   100  	assert.Nil(t, err, "could not patch fleetautoscaler")
   101  
   102  	bufferSize = int32(fas.Spec.Policy.Buffer.BufferSize.IntValue())
   103  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(bufferSize))
   104  
   105  	// do an allocation and watch the fleet scale up
   106  	gsa := framework.CreateAndApplyAllocation(t, flt)
   107  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   108  		return fleet.Status.AllocatedReplicas == 1
   109  	})
   110  
   111  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(bufferSize))
   112  
   113  	// patch autoscaler to switch to relative buffer size and check if the fleet adjusts
   114  	_, err = patchFleetAutoscaler(ctx, fas, intstr.FromString("10%"), 1, fas.Spec.Policy.Buffer.MaxReplicas)
   115  	require.NoError(t, err, "could not patch fleetautoscaler")
   116  
   117  	// 10% with only one allocated GS means only one ready server
   118  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(1))
   119  
   120  	// get the Status of the fleetautoscaler
   121  	fas, err = framework.AgonesClient.AutoscalingV1().FleetAutoscalers(fas.ObjectMeta.Namespace).Get(ctx, fas.Name, metav1.GetOptions{})
   122  	require.NoError(t, err, "could not get fleetautoscaler")
   123  	require.True(t, fas.Status.AbleToScale, "Could not get AbleToScale status")
   124  
   125  	// check that we are able to scale
   126  	framework.WaitForFleetAutoScalerCondition(t, fas, func(_ *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
   127  		return !fas.Status.ScalingLimited
   128  	})
   129  
   130  	// patch autoscaler to a maxReplicas count equal to current replicas count
   131  	_, err = patchFleetAutoscaler(ctx, fas, intstr.FromInt(1), 1, 1)
   132  	require.NoError(t, err, "could not patch fleetautoscaler")
   133  
   134  	// check that we are not able to scale
   135  	framework.WaitForFleetAutoScalerCondition(t, fas, func(_ *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
   136  		return fas.Status.ScalingLimited
   137  	})
   138  
   139  	// delete the allocated GameServer and watch the fleet scale down
   140  	gp := int64(1)
   141  	err = stable.GameServers(framework.Namespace).Delete(ctx, gsa.Status.GameServerName, metav1.DeleteOptions{GracePeriodSeconds: &gp})
   142  	require.NoError(t, err)
   143  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   144  		return fleet.Status.AllocatedReplicas == 0 &&
   145  			fleet.Status.ReadyReplicas == 1 &&
   146  			fleet.Status.Replicas == 1
   147  	})
   148  }
   149  
   150  func TestFleetAutoscalerDefaultSyncInterval(t *testing.T) {
   151  	t.Parallel()
   152  	ctx := context.Background()
   153  
   154  	stable := framework.AgonesClient.AgonesV1()
   155  	fleets := stable.Fleets(framework.Namespace)
   156  	flt, err := fleets.Create(ctx, defaultFleet(framework.Namespace), metav1.CreateOptions{})
   157  	if assert.Nil(t, err) {
   158  		defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   159  	}
   160  
   161  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   162  
   163  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
   164  	dummyFleetName := "dummy-fleet"
   165  	defaultFas := &autoscalingv1.FleetAutoscaler{
   166  		ObjectMeta: metav1.ObjectMeta{
   167  			Name:      dummyFleetName + "-autoscaler",
   168  			Namespace: framework.Namespace,
   169  		},
   170  		Spec: autoscalingv1.FleetAutoscalerSpec{
   171  			FleetName: dummyFleetName,
   172  			Policy: autoscalingv1.FleetAutoscalerPolicy{
   173  				Type: autoscalingv1.BufferPolicyType,
   174  				Buffer: &autoscalingv1.BufferPolicy{
   175  					BufferSize:  intstr.FromInt(3),
   176  					MaxReplicas: 10,
   177  				},
   178  			},
   179  		},
   180  	}
   181  	fas, err := fleetautoscalers.Create(ctx, defaultFas, metav1.CreateOptions{})
   182  	if assert.Nil(t, err) {
   183  		defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   184  	} else {
   185  		// if we could not create the autoscaler, their is no point going further
   186  		logrus.Error("Failed creating autoscaler, aborting TestFleetAutoscalerDefaultSyncInterval")
   187  		return
   188  	}
   189  
   190  	defaultSyncIntervalFas := &autoscalingv1.FleetAutoscaler{}
   191  	defaultSyncIntervalFas.ApplyDefaults()
   192  	assert.Equal(t, defaultSyncIntervalFas.Spec.Sync.FixedInterval.Seconds, fas.Spec.Sync.FixedInterval.Seconds)
   193  }
   194  
   195  // TestFleetAutoScalerRollingUpdate - test fleet with RollingUpdate strategy work with
   196  // FleetAutoscaler, verify that number of GameServers does not goes down below RollingUpdate strategy
   197  // defined level on Fleet updates.
   198  func TestFleetAutoScalerRollingUpdate(t *testing.T) {
   199  	t.Parallel()
   200  	ctx := context.Background()
   201  
   202  	stable := framework.AgonesClient.AgonesV1()
   203  	fleets := stable.Fleets(framework.Namespace)
   204  	flt := defaultFleet(framework.Namespace)
   205  	flt.Spec.Replicas = 2
   206  	maxSurge := 1
   207  	rollingUpdateCount := intstr.FromInt(maxSurge)
   208  
   209  	flt.Spec.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{}
   210  	// Set both MaxSurge and MaxUnavaible to 1
   211  	flt.Spec.Strategy.RollingUpdate.MaxSurge = &rollingUpdateCount
   212  	flt.Spec.Strategy.RollingUpdate.MaxUnavailable = &rollingUpdateCount
   213  
   214  	flt, err := fleets.Create(ctx, flt, metav1.CreateOptions{})
   215  	if assert.Nil(t, err) {
   216  		defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   217  	}
   218  
   219  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   220  
   221  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
   222  
   223  	// Create FleetAutoScaler with 7 Buffer and MinReplicas
   224  	targetScale := 7
   225  	fas := defaultFleetAutoscaler(flt, framework.Namespace)
   226  	fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(targetScale)
   227  	fas.Spec.Policy.Buffer.MinReplicas = int32(targetScale)
   228  	fas, err = fleetautoscalers.Create(ctx, fas, metav1.CreateOptions{})
   229  	if assert.Nil(t, err) {
   230  		defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   231  	} else {
   232  		// if we could not create the autoscaler, their is no point going further
   233  		logrus.Error("Failed creating autoscaler, aborting TestAutoscalerBasicFunctions")
   234  		return
   235  	}
   236  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(int32(targetScale)))
   237  
   238  	// get the Status of the fleetautoscaler
   239  	fas, err = framework.AgonesClient.AutoscalingV1().FleetAutoscalers(fas.ObjectMeta.Namespace).Get(ctx, fas.Name, metav1.GetOptions{})
   240  	require.NoError(t, err, "could not get fleetautoscaler")
   241  	assert.True(t, fas.Status.AbleToScale, "Could not get AbleToScale status")
   242  
   243  	// check that we are able to scale
   244  	framework.WaitForFleetAutoScalerCondition(t, fas, func(_ *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
   245  		return !fas.Status.ScalingLimited
   246  	})
   247  
   248  	// Change ContainerPort to trigger creating a new GSSet
   249  	flt, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{})
   250  
   251  	assert.Nil(t, err, "Able to get the Fleet")
   252  	fltCopy := flt.DeepCopy()
   253  	fltCopy.Spec.Template.Spec.Ports[0].ContainerPort++
   254  	logrus.Info("Current fleet replicas count: ", fltCopy.Spec.Replicas)
   255  
   256  	// In ticket #1156 we apply new Replicas size 2, which is smaller than 7
   257  	// And RollingUpdate is broken, scaling immediately from 7 to 2 and then back to 7
   258  	// Uncomment line below to break this test
   259  	// fltCopy.Spec.Replicas = 2
   260  
   261  	flt, err = framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Update(ctx, fltCopy, metav1.UpdateOptions{})
   262  	assert.NoError(t, err)
   263  
   264  	selector := labels.SelectorFromSet(labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name})
   265  	// Wait till new GSS is created
   266  	err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
   267  		gssList, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx,
   268  			metav1.ListOptions{LabelSelector: selector.String()})
   269  		if err != nil {
   270  			return false, err
   271  		}
   272  		return len(gssList.Items) == 2, nil
   273  	})
   274  	assert.NoError(t, err)
   275  
   276  	// Check that total number of gameservers in the system does not goes lower than RollingUpdate
   277  	// parameters (deleting no more than maxUnavailable servers at a time)
   278  	// Wait for old GSSet to be deleted
   279  	err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
   280  		list, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).List(ctx,
   281  			metav1.ListOptions{LabelSelector: selector.String()})
   282  		if err != nil {
   283  			return false, err
   284  		}
   285  
   286  		maxUnavailable, err := intstr.GetValueFromIntOrPercent(flt.Spec.Strategy.RollingUpdate.MaxUnavailable, 100, true)
   287  		assert.Nil(t, err)
   288  		if len(list.Items) < targetScale-maxUnavailable {
   289  			err = errors.New("New replicas should be not less than (target - maxUnavailable)")
   290  		}
   291  		if err != nil {
   292  			return false, err
   293  		}
   294  		gssList, err := framework.AgonesClient.AgonesV1().GameServerSets(framework.Namespace).List(ctx,
   295  			metav1.ListOptions{LabelSelector: selector.String()})
   296  		if err != nil {
   297  			return false, err
   298  		}
   299  		return len(gssList.Items) == 1, nil
   300  	})
   301  
   302  	assert.NoError(t, err)
   303  }
   304  
   305  // TestAutoscalerStressCreate creates many fleetautoscalers with random values
   306  // to check if the creation validation works as expected and if the fleet scales
   307  // to the expected number of replicas (when the creation is valid)
   308  func TestAutoscalerStressCreate(t *testing.T) {
   309  	t.Parallel()
   310  	ctx := context.Background()
   311  	log := e2e.TestLogger(t)
   312  
   313  	alpha1 := framework.AgonesClient.AgonesV1()
   314  	fleets := alpha1.Fleets(framework.Namespace)
   315  	flt, err := fleets.Create(ctx, defaultFleet(framework.Namespace), metav1.CreateOptions{})
   316  	if assert.Nil(t, err) {
   317  		defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   318  	}
   319  
   320  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   321  
   322  	r := rand.New(rand.NewSource(1783))
   323  
   324  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
   325  
   326  	for i := 0; i < 5; i++ {
   327  		fas := defaultFleetAutoscaler(flt, framework.Namespace)
   328  		bufferSize := r.Int31n(5)
   329  		minReplicas := r.Int31n(5)
   330  		maxReplicas := r.Int31n(8)
   331  		fas.Spec.Policy.Buffer.BufferSize = intstr.FromInt(int(bufferSize))
   332  		fas.Spec.Policy.Buffer.MinReplicas = minReplicas
   333  		fas.Spec.Policy.Buffer.MaxReplicas = maxReplicas
   334  
   335  		valid := bufferSize > 0 &&
   336  			fas.Spec.Policy.Buffer.MaxReplicas > 0 &&
   337  			fas.Spec.Policy.Buffer.MaxReplicas >= bufferSize &&
   338  			fas.Spec.Policy.Buffer.MinReplicas <= fas.Spec.Policy.Buffer.MaxReplicas &&
   339  			(fas.Spec.Policy.Buffer.MinReplicas == 0 || fas.Spec.Policy.Buffer.MinReplicas >= bufferSize)
   340  
   341  		log.WithField("buffer", fmt.Sprintf("%#v", fas.Spec.Policy.Buffer)).Info("This is the FAS policy!")
   342  
   343  		// create a closure to have defered delete func called on each loop iteration.
   344  		func() {
   345  			fas, err := fleetautoscalers.Create(ctx, fas, metav1.CreateOptions{})
   346  			if err == nil {
   347  				log.WithField("fas", fas.ObjectMeta.Name).Info("Created!")
   348  				defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   349  				require.True(t, valid,
   350  					fmt.Sprintf("FleetAutoscaler created even if the parameters are NOT valid: %d %d %d",
   351  						bufferSize,
   352  						fas.Spec.Policy.Buffer.MinReplicas,
   353  						fas.Spec.Policy.Buffer.MaxReplicas))
   354  
   355  				expectedReplicas := bufferSize
   356  				if expectedReplicas < fas.Spec.Policy.Buffer.MinReplicas {
   357  					expectedReplicas = fas.Spec.Policy.Buffer.MinReplicas
   358  				}
   359  				if expectedReplicas > fas.Spec.Policy.Buffer.MaxReplicas {
   360  					expectedReplicas = fas.Spec.Policy.Buffer.MaxReplicas
   361  				}
   362  				// the fleet autoscaler should scale the fleet now to expectedReplicas
   363  				framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(expectedReplicas))
   364  			} else {
   365  				require.False(t, valid,
   366  					fmt.Sprintf("FleetAutoscaler NOT created even if the parameters are valid: %d %d %d (%s)",
   367  						bufferSize,
   368  						minReplicas,
   369  						maxReplicas, err))
   370  			}
   371  		}()
   372  	}
   373  }
   374  
   375  // scaleFleet creates a patch to apply to a Fleet.
   376  // easier for testing, as it removes object generational issues.
   377  func patchFleetAutoscaler(ctx context.Context, fas *autoscalingv1.FleetAutoscaler, bufferSize intstr.IntOrString, minReplicas int32, maxReplicas int32) (*autoscalingv1.FleetAutoscaler, error) {
   378  	var bufferSizeFmt string
   379  	if bufferSize.Type == intstr.Int {
   380  		bufferSizeFmt = fmt.Sprintf("%d", bufferSize.IntValue())
   381  	} else {
   382  		bufferSizeFmt = fmt.Sprintf("%q", bufferSize.String())
   383  	}
   384  
   385  	patch := fmt.Sprintf(
   386  		`[{ "op": "replace", "path": "/spec/policy/buffer/bufferSize", "value": %s },`+
   387  			`{ "op": "replace", "path": "/spec/policy/buffer/minReplicas", "value": %d },`+
   388  			`{ "op": "replace", "path": "/spec/policy/buffer/maxReplicas", "value": %d }]`,
   389  		bufferSizeFmt, minReplicas, maxReplicas)
   390  	logrus.
   391  		WithField("fleetautoscaler", fas.ObjectMeta.Name).
   392  		WithField("bufferSize", bufferSize.String()).
   393  		WithField("minReplicas", minReplicas).
   394  		WithField("maxReplicas", maxReplicas).
   395  		WithField("patch", patch).
   396  		Info("Patching fleetautoscaler")
   397  
   398  	fas, err := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace).
   399  		Patch(ctx, fas.ObjectMeta.Name, types.JSONPatchType, []byte(patch), metav1.PatchOptions{})
   400  	logrus.WithField("fleetautoscaler", fas).Info("Patched fleet autoscaler")
   401  	return fas, err
   402  }
   403  
   404  // defaultFleetAutoscaler returns a default fleet autoscaler configuration for a given fleet
   405  func defaultFleetAutoscaler(f *agonesv1.Fleet, namespace string) *autoscalingv1.FleetAutoscaler {
   406  	return &autoscalingv1.FleetAutoscaler{
   407  		ObjectMeta: metav1.ObjectMeta{Name: f.ObjectMeta.Name + "-autoscaler", Namespace: namespace},
   408  		Spec: autoscalingv1.FleetAutoscalerSpec{
   409  			FleetName: f.ObjectMeta.Name,
   410  			Policy: autoscalingv1.FleetAutoscalerPolicy{
   411  				Type: autoscalingv1.BufferPolicyType,
   412  				Buffer: &autoscalingv1.BufferPolicy{
   413  					BufferSize:  intstr.FromInt(3),
   414  					MaxReplicas: 10,
   415  				},
   416  			},
   417  			Sync: &autoscalingv1.FleetAutoscalerSync{
   418  				Type: autoscalingv1.FixedIntervalSyncType,
   419  				FixedInterval: autoscalingv1.FixedIntervalSync{
   420  					Seconds: 30,
   421  				},
   422  			},
   423  		},
   424  	}
   425  }
   426  
   427  // Test fleetautoscaler with webhook policy type
   428  // scaling from Replicas equals to 1 to 2
   429  func TestAutoscalerWebhook(t *testing.T) {
   430  	t.Parallel()
   431  	ctx := context.Background()
   432  	pod, svc := defaultAutoscalerWebhook(framework.Namespace, "false")
   433  	pod, err := framework.KubeClient.CoreV1().Pods(framework.Namespace).Create(ctx, pod, metav1.CreateOptions{})
   434  	require.NoError(t, err)
   435  	defer framework.KubeClient.CoreV1().Pods(framework.Namespace).Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   436  	svc.ObjectMeta.Name = ""
   437  	svc.ObjectMeta.GenerateName = "test-service-"
   438  
   439  	svc, err = framework.KubeClient.CoreV1().Services(framework.Namespace).Create(ctx, svc, metav1.CreateOptions{})
   440  	require.NoError(t, err)
   441  	defer framework.KubeClient.CoreV1().Services(framework.Namespace).Delete(ctx, svc.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   442  
   443  	alpha1 := framework.AgonesClient.AgonesV1()
   444  	fleets := alpha1.Fleets(framework.Namespace)
   445  	flt := defaultFleet(framework.Namespace)
   446  	initialReplicasCount := int32(1)
   447  	flt.Spec.Replicas = initialReplicasCount
   448  	flt, err = fleets.Create(ctx, flt, metav1.CreateOptions{})
   449  	require.NoError(t, err)
   450  	defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   451  
   452  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   453  
   454  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
   455  	fas := defaultFleetAutoscaler(flt, framework.Namespace)
   456  	fas.Spec.Policy.Type = autoscalingv1.WebhookPolicyType
   457  	fas.Spec.Policy.Buffer = nil
   458  	path := "scale" //nolint:goconst
   459  	fas.Spec.Policy.Webhook = &autoscalingv1.URLConfiguration{
   460  		Service: &admregv1.ServiceReference{
   461  			Name:      svc.ObjectMeta.Name,
   462  			Namespace: framework.Namespace,
   463  			Path:      &path,
   464  		},
   465  	}
   466  	fas, err = fleetautoscalers.Create(ctx, fas, metav1.CreateOptions{})
   467  	require.NoError(t, err)
   468  	defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   469  
   470  	framework.CreateAndApplyAllocation(t, flt)
   471  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
   472  		log.WithField("fleetStatus", fmt.Sprintf("%+v", fleet.Status)).WithField("fleet", fleet.ObjectMeta.Name).Info("Awaiting fleet.Status.AllocatedReplicas == 1")
   473  		return fleet.Status.AllocatedReplicas == 1
   474  	})
   475  
   476  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
   477  		log.WithField("fleetStatus", fmt.Sprintf("%+v", fleet.Status)).
   478  			WithField("fleet", fleet.ObjectMeta.Name).
   479  			WithField("initialReplicasCount", initialReplicasCount).
   480  			Info("Awaiting fleet.Status.Replicas > initialReplicasCount")
   481  		return fleet.Status.Replicas > initialReplicasCount
   482  	})
   483  
   484  	// Wait for LastAppliedPolicy to be set to WebhookPolicyType
   485  	framework.WaitForFleetAutoScalerCondition(t, fas, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
   486  		log.WithField("LastAppliedPolicy", fas.Status.LastAppliedPolicy).
   487  			Info("Waiting for LastAppliedPolicy to be set to WebhookPolicyType")
   488  		return fas.Status.LastAppliedPolicy == autoscalingv1.WebhookPolicyType
   489  	})
   490  
   491  	// Cause an error in Webhook config
   492  	// Use wrong service Path
   493  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
   494  		fas, err = fleetautoscalers.Get(ctx, fas.ObjectMeta.Name, metav1.GetOptions{})
   495  		if err != nil {
   496  			return true, err
   497  		}
   498  		newPath := path + "2"
   499  		fas.Spec.Policy.Webhook.Service.Path = &newPath
   500  		labels := map[string]string{"fleetautoscaler": "wrong"}
   501  		fas.ObjectMeta.Labels = labels
   502  		_, err = fleetautoscalers.Update(ctx, fas, metav1.UpdateOptions{})
   503  		if err != nil {
   504  			logrus.WithError(err).Warn("could not update fleet autoscaler")
   505  			return false, nil
   506  		}
   507  
   508  		return true, nil
   509  	})
   510  	require.NoError(t, err)
   511  
   512  	var l *corev1.EventList
   513  	errString := "Error calculating desired fleet size on FleetAutoscaler"
   514  	found := false
   515  
   516  	// Error - net/http: request canceled while waiting for connection (Client.Timeout exceeded
   517  	// while awaiting headers)
   518  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
   519  		events := framework.KubeClient.CoreV1().Events(framework.Namespace)
   520  		l, err = events.List(ctx, metav1.ListOptions{FieldSelector: fields.AndSelectors(fields.OneTermEqualSelector("involvedObject.name", fas.ObjectMeta.Name), fields.OneTermEqualSelector("type", "Warning")).String()})
   521  		if err != nil {
   522  			return false, err
   523  		}
   524  		for _, v := range l.Items {
   525  			if strings.Contains(v.Message, errString) {
   526  				found = true
   527  			}
   528  		}
   529  		return found, nil
   530  	})
   531  	assert.NoError(t, err, "Received unexpected error")
   532  	assert.True(t, found, "Expected error was not received")
   533  }
   534  
   535  func TestFleetAutoscalerTLSWebhook(t *testing.T) {
   536  	t.Parallel()
   537  	ctx := context.Background()
   538  	// we hardcode 'default' namespace here because certificates above are generated to use this one
   539  	defaultNS := "default"
   540  
   541  	// certs
   542  	caPem, _, caCert, caPrivKey, err := generateRootCA()
   543  	require.NoError(t, err)
   544  	clientCertPEM, clientCertPrivKeyPEM, err := generateLocalCert(caCert, caPrivKey)
   545  	require.NoError(t, err)
   546  
   547  	secr := &corev1.Secret{
   548  		ObjectMeta: metav1.ObjectMeta{
   549  			GenerateName: "autoscalersecret-",
   550  		},
   551  		Type: corev1.SecretTypeTLS,
   552  		Data: make(map[string][]byte),
   553  	}
   554  
   555  	secr.Data[corev1.TLSCertKey] = clientCertPEM
   556  	secr.Data[corev1.TLSPrivateKeyKey] = clientCertPrivKeyPEM
   557  
   558  	secrets := framework.KubeClient.CoreV1().Secrets(defaultNS)
   559  	secr, err = secrets.Create(ctx, secr.DeepCopy(), metav1.CreateOptions{})
   560  	if assert.Nil(t, err) {
   561  		defer secrets.Delete(ctx, secr.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   562  	}
   563  
   564  	pod, svc := defaultAutoscalerWebhook(defaultNS, "false")
   565  	pod.Spec.Volumes = make([]corev1.Volume, 1)
   566  	pod.Spec.Volumes[0] = corev1.Volume{
   567  		Name: "secret-volume",
   568  		VolumeSource: corev1.VolumeSource{
   569  			Secret: &corev1.SecretVolumeSource{
   570  				SecretName: secr.ObjectMeta.Name,
   571  			},
   572  		},
   573  	}
   574  	pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{{
   575  		Name:      "secret-volume",
   576  		MountPath: "/home/service/certs",
   577  	}}
   578  	pod, err = framework.KubeClient.CoreV1().Pods(defaultNS).Create(ctx, pod.DeepCopy(), metav1.CreateOptions{})
   579  	if assert.Nil(t, err) {
   580  		defer framework.KubeClient.CoreV1().Pods(defaultNS).Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   581  	} else {
   582  		// if we could not create the webhook, there is no point going further
   583  		assert.FailNow(t, "Failed creating webhook pod, aborting TestTlsWebhook")
   584  	}
   585  
   586  	// since we're using statically-named service, perform a best-effort delete of a previous service
   587  	err = framework.KubeClient.CoreV1().Services(defaultNS).Delete(ctx, svc.ObjectMeta.Name, waitForDeletion)
   588  	if err != nil {
   589  		assert.True(t, k8serrors.IsNotFound(err))
   590  	}
   591  
   592  	// making sure the service is really gone.
   593  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
   594  		_, err := framework.KubeClient.CoreV1().Services(defaultNS).Get(ctx, svc.ObjectMeta.Name, metav1.GetOptions{})
   595  		return k8serrors.IsNotFound(err), nil
   596  	})
   597  	assert.Nil(t, err)
   598  
   599  	svc, err = framework.KubeClient.CoreV1().Services(defaultNS).Create(ctx, svc.DeepCopy(), metav1.CreateOptions{})
   600  	if assert.Nil(t, err) {
   601  		defer framework.KubeClient.CoreV1().Services(defaultNS).Delete(ctx, svc.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   602  	} else {
   603  		// if we could not create the service, there is no point going further
   604  		assert.FailNow(t, "Failed creating service, aborting TestTlsWebhook")
   605  	}
   606  
   607  	alpha1 := framework.AgonesClient.AgonesV1()
   608  	fleets := alpha1.Fleets(defaultNS)
   609  	flt := defaultFleet(defaultNS)
   610  	initialReplicasCount := int32(1)
   611  	flt.Spec.Replicas = initialReplicasCount
   612  	flt, err = fleets.Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
   613  	if assert.Nil(t, err) {
   614  		defer fleets.Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   615  	}
   616  
   617  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   618  
   619  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(defaultNS)
   620  	fas := defaultFleetAutoscaler(flt, defaultNS)
   621  	fas.Spec.Policy.Type = autoscalingv1.WebhookPolicyType
   622  	fas.Spec.Policy.Buffer = nil
   623  	path := "scale"
   624  
   625  	fas.Spec.Policy.Webhook = &autoscalingv1.URLConfiguration{
   626  		Service: &admregv1.ServiceReference{
   627  			Name:      svc.ObjectMeta.Name,
   628  			Namespace: defaultNS,
   629  			Path:      &path,
   630  		},
   631  		CABundle: caPem,
   632  	}
   633  	fas, err = fleetautoscalers.Create(ctx, fas.DeepCopy(), metav1.CreateOptions{})
   634  	if assert.Nil(t, err) {
   635  		defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   636  	} else {
   637  		// if we could not create the autoscaler, their is no point going further
   638  		assert.FailNow(t, "Failed creating autoscaler, aborting TestTlsWebhook")
   639  	}
   640  	framework.CreateAndApplyAllocation(t, flt)
   641  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   642  		return fleet.Status.AllocatedReplicas == 1
   643  	})
   644  
   645  	framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
   646  		return fleet.Status.Replicas > initialReplicasCount
   647  	})
   648  
   649  	// Wait for LastAppliedPolicy to be set to WebhookPolicyType
   650  	framework.WaitForFleetAutoScalerCondition(t, fas, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
   651  		log.WithField("LastAppliedPolicy", fas.Status.LastAppliedPolicy).
   652  			Info("Waiting for LastAppliedPolicy to be set to WebhookPolicyType")
   653  		return fas.Status.LastAppliedPolicy == autoscalingv1.WebhookPolicyType
   654  	})
   655  }
   656  
   657  func TestAutoscalerWebhookWithMetadata(t *testing.T) {
   658  	if !runtime.FeatureEnabled(runtime.FeatureFleetAutoscaleRequestMetaData) {
   659  		t.SkipNow()
   660  	}
   661  	t.Parallel()
   662  
   663  	ctx := context.Background()
   664  	// Create webhook Pod and Service
   665  	pod, svc := defaultAutoscalerWebhook(framework.Namespace, "true")
   666  	pod, err := framework.KubeClient.CoreV1().Pods(framework.Namespace).Create(ctx, pod, metav1.CreateOptions{})
   667  	require.NoError(t, err)
   668  	defer framework.KubeClient.CoreV1().Pods(framework.Namespace).Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   669  	svc.ObjectMeta.Name = ""
   670  	svc.ObjectMeta.GenerateName = "test-service-"
   671  
   672  	svc, err = framework.KubeClient.CoreV1().Services(framework.Namespace).Create(ctx, svc, metav1.CreateOptions{})
   673  	require.NoError(t, err)
   674  	defer framework.KubeClient.CoreV1().Services(framework.Namespace).Delete(ctx, svc.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   675  
   676  	// Create Fleet with metadata annotation
   677  	alpha1 := framework.AgonesClient.AgonesV1()
   678  	flt := defaultFleet(framework.Namespace)
   679  	initialReplicasCount := int32(1)
   680  	fixedReplicas := int32(11)
   681  	flt.Spec.Replicas = initialReplicasCount
   682  	flt.ObjectMeta.Annotations = map[string]string{
   683  		"fixedReplicas": fmt.Sprintf("%d", fixedReplicas),
   684  	}
   685  	flt, err = alpha1.Fleets(framework.Namespace).Create(ctx, flt, metav1.CreateOptions{})
   686  	require.NoError(t, err)
   687  	defer alpha1.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   688  
   689  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(initialReplicasCount))
   690  
   691  	// Create FleetAutoscaler with webhook policy
   692  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
   693  	fas := defaultFleetAutoscaler(flt, framework.Namespace)
   694  	fas.Spec.Policy.Type = autoscalingv1.WebhookPolicyType
   695  	fas.Spec.Policy.Buffer = nil
   696  	path := "scale"
   697  	fas.Spec.Policy.Webhook = &autoscalingv1.URLConfiguration{
   698  		Service: &admregv1.ServiceReference{
   699  			Name:      svc.ObjectMeta.Name,
   700  			Namespace: framework.Namespace,
   701  			Path:      &path,
   702  		},
   703  	}
   704  	fas, err = fleetautoscalers.Create(ctx, fas, metav1.CreateOptions{})
   705  	require.NoError(t, err)
   706  	defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   707  
   708  	// Trigger allocation to cause autoscaler logic to kick in
   709  	framework.CreateAndApplyAllocation(t, flt)
   710  
   711  	// Wait until replicas match the fixedReplicas value
   712  	framework.AssertFleetCondition(t, flt, func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
   713  		log.WithFields(logrus.Fields{
   714  			"fleetStatus":         fmt.Sprintf("%+v", fleet.Status),
   715  			"expectedReplicas":    fixedReplicas,
   716  			"fleet":               fleet.ObjectMeta.Name,
   717  			"fleetAllocatedCount": fleet.Status.AllocatedReplicas,
   718  		}).Info("Waiting for fleet.Status.Replicas == fixedReplicas")
   719  		return fleet.Status.Replicas == fixedReplicas
   720  	})
   721  
   722  	// Wait for LastAppliedPolicy to be set correctly
   723  	framework.WaitForFleetAutoScalerCondition(t, fas, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
   724  		log.WithField("LastAppliedPolicy", fas.Status.LastAppliedPolicy).
   725  			Info("Waiting for LastAppliedPolicy to be set to WebhookPolicyType")
   726  		return fas.Status.LastAppliedPolicy == autoscalingv1.WebhookPolicyType
   727  	})
   728  }
   729  
   730  func defaultAutoscalerWebhook(namespace string, fixedReplicasEnabled string) (*corev1.Pod, *corev1.Service) {
   731  	l := make(map[string]string)
   732  	appName := fmt.Sprintf("autoscaler-webhook-%v", time.Now().UnixNano())
   733  	l["app"] = appName
   734  	l[e2e.AutoCleanupLabelKey] = e2e.AutoCleanupLabelValue
   735  
   736  	pod := &corev1.Pod{
   737  		ObjectMeta: metav1.ObjectMeta{
   738  			GenerateName: "auto-webhook-",
   739  			Namespace:    namespace,
   740  			Labels:       l,
   741  		},
   742  		Spec: corev1.PodSpec{
   743  			Containers: []corev1.Container{
   744  				{
   745  					Name:            "webhook",
   746  					Image:           "us-docker.pkg.dev/agones-images/examples/autoscaler-webhook:0.20",
   747  					ImagePullPolicy: corev1.PullAlways,
   748  					Ports: []corev1.ContainerPort{{
   749  						ContainerPort: 8000,
   750  						Name:          "autoscaler",
   751  					}},
   752  					Env: []corev1.EnvVar{
   753  						{
   754  							Name:  "FIXED_REPLICAS",
   755  							Value: fixedReplicasEnabled,
   756  						},
   757  					},
   758  				},
   759  			},
   760  		},
   761  	}
   762  	m := make(map[string]string)
   763  	m["app"] = appName
   764  	service := &corev1.Service{
   765  		ObjectMeta: metav1.ObjectMeta{
   766  			Name:      "autoscaler-tls-service",
   767  			Namespace: namespace,
   768  		},
   769  		Spec: corev1.ServiceSpec{
   770  			Selector: m,
   771  			Ports: []corev1.ServicePort{{
   772  				Name:       "newport",
   773  				Port:       8000,
   774  				TargetPort: intstr.IntOrString{StrVal: "autoscaler"},
   775  			}},
   776  		},
   777  	}
   778  
   779  	return pod, service
   780  }
   781  
   782  // Instructions: https://agones.dev/site/docs/getting-started/create-webhook-fleetautoscaler/#chapter-2-configuring-https-fleetautoscaler-webhook-with-ca-bundle
   783  // but also, credits/inspiration to https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/aws-sdk-go/awstesting/certificate_utils.go
   784  
   785  func generateRootCA() (
   786  	caPEM, caPrivKeyPEM []byte, caCert *x509.Certificate, caPrivKey *rsa.PrivateKey, err error,
   787  ) {
   788  	caCert = &x509.Certificate{
   789  		SerialNumber: big.NewInt(42),
   790  		Subject: pkix.Name{
   791  			Country:      []string{"US"},
   792  			Organization: []string{"Agones"},
   793  			CommonName:   "Test Root CA",
   794  		},
   795  		NotBefore: time.Now().Add(-time.Minute),
   796  		NotAfter:  time.Now().AddDate(1, 0, 0),
   797  		KeyUsage:  x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
   798  		ExtKeyUsage: []x509.ExtKeyUsage{
   799  			x509.ExtKeyUsageClientAuth,
   800  			x509.ExtKeyUsageServerAuth,
   801  		},
   802  		BasicConstraintsValid: true,
   803  		IsCA:                  true,
   804  	}
   805  
   806  	// Create CA private and public key
   807  	caPrivKey, err = rsa.GenerateKey(cryptorand.Reader, 4096)
   808  	if err != nil {
   809  		return nil, nil, nil, nil, fmt.Errorf("failed generate CA RSA key, %w", err)
   810  	}
   811  
   812  	// Create CA certificate
   813  	caBytes, err := x509.CreateCertificate(cryptorand.Reader, caCert, caCert, &caPrivKey.PublicKey, caPrivKey)
   814  	if err != nil {
   815  		return nil, nil, nil, nil, fmt.Errorf("failed generate CA certificate, %w", err)
   816  	}
   817  
   818  	// PEM encode CA certificate and private key
   819  	var caPEMBuf bytes.Buffer
   820  	err = pem.Encode(&caPEMBuf, &pem.Block{
   821  		Type:  "CERTIFICATE",
   822  		Bytes: caBytes,
   823  	})
   824  	if err != nil {
   825  		return nil, nil, nil, nil, fmt.Errorf("failed to endcode root PEM, %w", err)
   826  	}
   827  
   828  	var caPrivKeyPEMBuf bytes.Buffer
   829  	err = pem.Encode(&caPrivKeyPEMBuf, &pem.Block{
   830  		Type:  "RSA PRIVATE KEY",
   831  		Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
   832  	})
   833  	if err != nil {
   834  		return nil, nil, nil, nil, fmt.Errorf("failed to endcode private root PEM, %w", err)
   835  	}
   836  
   837  	return caPEMBuf.Bytes(), caPrivKeyPEMBuf.Bytes(), caCert, caPrivKey, nil
   838  }
   839  
   840  func generateLocalCert(parentCert *x509.Certificate, parentPrivKey *rsa.PrivateKey) (
   841  	certPEM, certPrivKeyPEM []byte, err error,
   842  ) {
   843  	cert := &x509.Certificate{
   844  		SerialNumber: big.NewInt(42),
   845  		Subject: pkix.Name{
   846  			Country:      []string{"US"},
   847  			Organization: []string{"Agones"},
   848  			CommonName:   "autoscaler-tls-service.default.svc",
   849  		},
   850  		NotBefore: time.Now().Add(-time.Minute),
   851  		NotAfter:  time.Now().AddDate(1, 0, 0),
   852  		ExtKeyUsage: []x509.ExtKeyUsage{
   853  			x509.ExtKeyUsageClientAuth,
   854  			x509.ExtKeyUsageServerAuth,
   855  		},
   856  		KeyUsage: x509.KeyUsageDigitalSignature,
   857  		DNSNames: []string{"autoscaler-tls-service.default.svc"},
   858  	}
   859  
   860  	// Create server private and public key
   861  	certPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096)
   862  	if err != nil {
   863  		return nil, nil, fmt.Errorf("failed to generate server RSA private key, %w", err)
   864  	}
   865  
   866  	// Create server certificate
   867  	certBytes, err := x509.CreateCertificate(cryptorand.Reader, cert, parentCert, &certPrivKey.PublicKey, parentPrivKey)
   868  	if err != nil {
   869  		return nil, nil, fmt.Errorf("failed to generate server certificate, %w", err)
   870  	}
   871  
   872  	// PEM encode certificate and private key
   873  	var certPEMBuf bytes.Buffer
   874  	err = pem.Encode(&certPEMBuf, &pem.Block{
   875  		Type:  "CERTIFICATE",
   876  		Bytes: certBytes,
   877  	})
   878  	if err != nil {
   879  		return nil, nil, fmt.Errorf("failed to endcode certificate pem, %w", err)
   880  	}
   881  
   882  	var certPrivKeyPEMBuf bytes.Buffer
   883  	err = pem.Encode(&certPrivKeyPEMBuf, &pem.Block{
   884  		Type:  "RSA PRIVATE KEY",
   885  		Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
   886  	})
   887  	if err != nil {
   888  		return nil, nil, fmt.Errorf("failed to endcode private pem, %w", err)
   889  	}
   890  
   891  	return certPEMBuf.Bytes(), certPrivKeyPEMBuf.Bytes(), nil
   892  }
   893  
   894  func TestCounterAutoscaler(t *testing.T) {
   895  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
   896  		t.SkipNow()
   897  	}
   898  	t.Parallel()
   899  
   900  	ctx := context.Background()
   901  	client := framework.AgonesClient.AgonesV1()
   902  	log := e2e.TestLogger(t)
   903  
   904  	flt := defaultFleet(framework.Namespace)
   905  	flt.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
   906  		"players": {
   907  			Count:    7,  // AggregateCount 21
   908  			Capacity: 10, // AggregateCapacity 30
   909  		},
   910  		"sessions": {
   911  			Count:    0, // AggregateCount 0
   912  			Capacity: 5, // AggregateCapacity 15
   913  		},
   914  	}
   915  
   916  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
   917  	require.NoError(t, err)
   918  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
   919  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   920  
   921  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
   922  
   923  	counterFas := func(f func(fap *autoscalingv1.FleetAutoscalerPolicy)) *autoscalingv1.FleetAutoscaler {
   924  		fas := autoscalingv1.FleetAutoscaler{
   925  			ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-counter-autoscaler", Namespace: framework.Namespace},
   926  			Spec: autoscalingv1.FleetAutoscalerSpec{
   927  				FleetName: flt.ObjectMeta.Name,
   928  				Policy: autoscalingv1.FleetAutoscalerPolicy{
   929  					Type: autoscalingv1.CounterPolicyType,
   930  				},
   931  				Sync: &autoscalingv1.FleetAutoscalerSync{
   932  					Type: autoscalingv1.FixedIntervalSyncType,
   933  					FixedInterval: autoscalingv1.FixedIntervalSync{
   934  						Seconds: 1,
   935  					},
   936  				},
   937  			},
   938  		}
   939  		f(&fas.Spec.Policy)
   940  		return &fas
   941  	}
   942  
   943  	testCases := map[string]struct {
   944  		fas          *autoscalingv1.FleetAutoscaler
   945  		wantFasErr   bool
   946  		wantReplicas int32
   947  	}{
   948  		"Scale Down Buffer Int": {
   949  			fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
   950  				fap.Counter = &autoscalingv1.CounterPolicy{
   951  					Key:         "players",
   952  					BufferSize:  intstr.FromInt(5), // Buffer refers to the available capacity (AggregateCapacity - AggregateCount)
   953  					MinCapacity: 10,                // Min and MaxCapacity refer to the total capacity aggregated across the fleet, NOT the available capacity
   954  					MaxCapacity: 100,
   955  				}
   956  			}),
   957  			wantFasErr:   false,
   958  			wantReplicas: 2,
   959  		},
   960  		"Scale Up Buffer Int": {
   961  			fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
   962  				fap.Counter = &autoscalingv1.CounterPolicy{
   963  					Key:         "players",
   964  					BufferSize:  intstr.FromInt(25),
   965  					MinCapacity: 25,
   966  					MaxCapacity: 100,
   967  				}
   968  			}),
   969  			wantFasErr:   false,
   970  			wantReplicas: 9,
   971  		},
   972  		"Scale Down to MaxCapacity": {
   973  			fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
   974  				fap.Counter = &autoscalingv1.CounterPolicy{
   975  					Key:         "sessions",
   976  					BufferSize:  intstr.FromInt(5),
   977  					MinCapacity: 0,
   978  					MaxCapacity: 5,
   979  				}
   980  			}),
   981  			wantFasErr:   false,
   982  			wantReplicas: 1,
   983  		},
   984  		"Scale Up to MinCapacity": {
   985  			fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
   986  				fap.Counter = &autoscalingv1.CounterPolicy{
   987  					Key:         "sessions",
   988  					BufferSize:  intstr.FromInt(1),
   989  					MinCapacity: 30,
   990  					MaxCapacity: 100,
   991  				}
   992  			}),
   993  			wantFasErr:   false,
   994  			wantReplicas: 6,
   995  		},
   996  		"Cannot scale up (MaxCapacity)": {
   997  			fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
   998  				fap.Counter = &autoscalingv1.CounterPolicy{
   999  					Key:         "players",
  1000  					BufferSize:  intstr.FromInt(10),
  1001  					MinCapacity: 10,
  1002  					MaxCapacity: 30,
  1003  				}
  1004  			}),
  1005  			wantFasErr:   false,
  1006  			wantReplicas: 3,
  1007  		},
  1008  		"Cannot scale down (MinCapacity)": {
  1009  			fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1010  				fap.Counter = &autoscalingv1.CounterPolicy{
  1011  					Key:         "sessions",
  1012  					BufferSize:  intstr.FromInt(5),
  1013  					MinCapacity: 15,
  1014  					MaxCapacity: 100,
  1015  				}
  1016  			}),
  1017  			wantFasErr:   false,
  1018  			wantReplicas: 3,
  1019  		},
  1020  		"Buffer Greater than MinCapacity invalid FAS": {
  1021  			fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1022  				fap.Counter = &autoscalingv1.CounterPolicy{
  1023  					Key:         "players",
  1024  					BufferSize:  intstr.FromInt(25),
  1025  					MinCapacity: 10,
  1026  					MaxCapacity: 100,
  1027  				}
  1028  			}),
  1029  			wantFasErr: true,
  1030  		},
  1031  	}
  1032  	for name, testCase := range testCases {
  1033  		t.Run(name, func(t *testing.T) {
  1034  
  1035  			fas, err := fleetautoscalers.Create(ctx, testCase.fas, metav1.CreateOptions{})
  1036  			if testCase.wantFasErr {
  1037  				assert.Error(t, err)
  1038  				return
  1039  			}
  1040  			assert.NoError(t, err)
  1041  
  1042  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(testCase.wantReplicas))
  1043  			fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1044  
  1045  			// Return to starting 3 replicas
  1046  			framework.ScaleFleet(t, log, flt, 3)
  1047  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(3))
  1048  		})
  1049  	}
  1050  }
  1051  
  1052  // nolint:dupl  // Linter errors on lines are duplicate of TestListAutoscalerWithNoReplicas
  1053  func TestCounterAutoscalerWithNoReplicas(t *testing.T) {
  1054  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1055  		t.SkipNow()
  1056  	}
  1057  	t.Parallel()
  1058  
  1059  	ctx := context.Background()
  1060  	client := framework.AgonesClient.AgonesV1()
  1061  	log := e2e.TestLogger(t)
  1062  
  1063  	flt := defaultEmptyFleet(framework.Namespace)
  1064  	flt.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
  1065  		"games": {
  1066  			Capacity: 5,
  1067  		},
  1068  	}
  1069  
  1070  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1071  	require.NoError(t, err)
  1072  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1073  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1074  
  1075  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  1076  
  1077  	counterFas := func(f func(fap *autoscalingv1.FleetAutoscalerPolicy)) *autoscalingv1.FleetAutoscaler {
  1078  		fas := autoscalingv1.FleetAutoscaler{
  1079  			ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-counter-autoscaler", Namespace: framework.Namespace},
  1080  			Spec: autoscalingv1.FleetAutoscalerSpec{
  1081  				FleetName: flt.ObjectMeta.Name,
  1082  				Policy: autoscalingv1.FleetAutoscalerPolicy{
  1083  					Type: autoscalingv1.CounterPolicyType,
  1084  				},
  1085  				Sync: &autoscalingv1.FleetAutoscalerSync{
  1086  					Type: autoscalingv1.FixedIntervalSyncType,
  1087  					FixedInterval: autoscalingv1.FixedIntervalSync{
  1088  						Seconds: 1,
  1089  					},
  1090  				},
  1091  			},
  1092  		}
  1093  		f(&fas.Spec.Policy)
  1094  		return &fas
  1095  	}
  1096  	testCases := map[string]struct {
  1097  		fas          *autoscalingv1.FleetAutoscaler
  1098  		wantFasErr   bool
  1099  		wantReplicas int32
  1100  	}{
  1101  		"Scale Up to MinCapacity": {
  1102  			fas: counterFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1103  				fap.Counter = &autoscalingv1.CounterPolicy{
  1104  					Key:         "games",
  1105  					BufferSize:  intstr.FromInt(3),
  1106  					MinCapacity: 16,
  1107  					MaxCapacity: 100,
  1108  				}
  1109  			}),
  1110  			wantFasErr:   false,
  1111  			wantReplicas: 4, // Capacity:20
  1112  		},
  1113  	}
  1114  	for name, testCase := range testCases {
  1115  		t.Run(name, func(t *testing.T) {
  1116  
  1117  			fas, err := fleetautoscalers.Create(ctx, testCase.fas, metav1.CreateOptions{})
  1118  			if testCase.wantFasErr {
  1119  				assert.Error(t, err)
  1120  				return
  1121  			}
  1122  			assert.NoError(t, err)
  1123  
  1124  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(testCase.wantReplicas))
  1125  			fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1126  
  1127  			// Return to starting 0 replicas
  1128  			framework.ScaleFleet(t, log, flt, 0)
  1129  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0))
  1130  		})
  1131  	}
  1132  }
  1133  
  1134  func TestCounterAutoscalerAllocated(t *testing.T) {
  1135  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1136  		t.SkipNow()
  1137  	}
  1138  	t.Parallel()
  1139  
  1140  	ctx := context.Background()
  1141  	client := framework.AgonesClient.AgonesV1()
  1142  	log := e2e.TestLogger(t)
  1143  
  1144  	defaultFlt := defaultFleet(framework.Namespace)
  1145  	defaultFlt.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
  1146  		"players": {
  1147  			Count:    7,  // AggregateCount 21
  1148  			Capacity: 10, // AggregateCapacity 30
  1149  		},
  1150  	}
  1151  
  1152  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  1153  
  1154  	testCases := map[string]struct {
  1155  		fas             autoscalingv1.CounterPolicy
  1156  		wantAllocatedGs int32 // Must be >= 0 && <= 3
  1157  		wantReadyGs     int32
  1158  	}{
  1159  		"Scale Down Buffer Percent": {
  1160  			fas: autoscalingv1.CounterPolicy{
  1161  				Key:         "players",
  1162  				BufferSize:  intstr.FromString("5%"),
  1163  				MinCapacity: 10,
  1164  				MaxCapacity: 100,
  1165  			},
  1166  			wantAllocatedGs: 0,
  1167  			wantReadyGs:     1,
  1168  		},
  1169  		"Scale Up Buffer Percent": {
  1170  			fas: autoscalingv1.CounterPolicy{
  1171  				Key:         "players",
  1172  				BufferSize:  intstr.FromString("40%"),
  1173  				MinCapacity: 10,
  1174  				MaxCapacity: 100,
  1175  			},
  1176  			wantAllocatedGs: 3,
  1177  			wantReadyGs:     2,
  1178  		},
  1179  	}
  1180  	// nolint:dupl  // Linter errors on lines are duplicate of TestListAutoscalerAllocated
  1181  	for name, testCase := range testCases {
  1182  		t.Run(name, func(t *testing.T) {
  1183  			flt, err := client.Fleets(framework.Namespace).Create(ctx, defaultFlt.DeepCopy(), metav1.CreateOptions{})
  1184  			require.NoError(t, err)
  1185  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1186  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1187  
  1188  			gsa := allocationv1.GameServerAllocation{
  1189  				Spec: allocationv1.GameServerAllocationSpec{
  1190  					Selectors: []allocationv1.GameServerSelector{
  1191  						{LabelSelector: metav1.LabelSelector{
  1192  							MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}},
  1193  					}}}
  1194  
  1195  			// Allocate game servers, as Buffer Percent scales up (or down) based on allocated aggregate capacity
  1196  			for i := int32(0); i < testCase.wantAllocatedGs; i++ {
  1197  				_, err := framework.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{})
  1198  				require.NoError(t, err)
  1199  			}
  1200  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1201  				log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking for game server allocations")
  1202  				return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGs
  1203  			})
  1204  
  1205  			counterFas := &autoscalingv1.FleetAutoscaler{
  1206  				ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-counter-autoscaler", Namespace: framework.Namespace},
  1207  				Spec: autoscalingv1.FleetAutoscalerSpec{
  1208  					FleetName: flt.ObjectMeta.Name,
  1209  					Policy: autoscalingv1.FleetAutoscalerPolicy{
  1210  						Type:    autoscalingv1.CounterPolicyType,
  1211  						Counter: &testCase.fas,
  1212  					},
  1213  					Sync: &autoscalingv1.FleetAutoscalerSync{
  1214  						Type: autoscalingv1.FixedIntervalSyncType,
  1215  						FixedInterval: autoscalingv1.FixedIntervalSync{
  1216  							Seconds: 1,
  1217  						},
  1218  					},
  1219  				},
  1220  			}
  1221  
  1222  			fas, err := fleetautoscalers.Create(ctx, counterFas, metav1.CreateOptions{})
  1223  			assert.NoError(t, err)
  1224  			defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1225  
  1226  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1227  				return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGs && fleet.Status.ReadyReplicas == testCase.wantReadyGs
  1228  			})
  1229  		})
  1230  	}
  1231  }
  1232  
  1233  // Related to the issue about the fleet autoscaler policy not namespaced: https://github.com/googleforgames/agones/issues/3954
  1234  func TestCounterAutoscalerAllocatedMultipleNamespaces(t *testing.T) {
  1235  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1236  		t.SkipNow()
  1237  	}
  1238  
  1239  	ctx := context.Background()
  1240  	client := framework.AgonesClient.AgonesV1()
  1241  	log := e2e.TestLogger(t)
  1242  
  1243  	// Create namespaces A and B
  1244  	namespaceA := framework.Namespace // let's reuse an existing one
  1245  	helper.CopyDefaultAllocatorClientSecret(ctx, t, namespaceA, framework)
  1246  
  1247  	namespaceB := fmt.Sprintf("autoscaler-b-%s", uuid.NewUUID())
  1248  	err := framework.CreateNamespace(namespaceB)
  1249  	require.NoError(t, err)
  1250  	defer func() {
  1251  		if derr := framework.DeleteNamespace(namespaceB); derr != nil {
  1252  			t.Error(derr)
  1253  		}
  1254  	}()
  1255  
  1256  	// Init default fleet A and B with same name in namespace A and B
  1257  	defaultFltA := defaultFleet(namespaceA)
  1258  	defaultFltA.ObjectMeta.Name = "simple-fleet"
  1259  	defaultFltA.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
  1260  		"players": {
  1261  			Count:    7,  // AggregateCount 21
  1262  			Capacity: 10, // AggregateCapacity 30
  1263  		},
  1264  	}
  1265  	defaultFltB := defaultFleet(namespaceB)
  1266  	defaultFltB.ObjectMeta.Name = "simple-fleet"
  1267  	defaultFltB.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
  1268  		"players": {
  1269  			Count:    7,  // AggregateCount 21
  1270  			Capacity: 20, // AggregateCapacity 60
  1271  		},
  1272  	}
  1273  
  1274  	fleetautoscalers := framework.AgonesClient.AutoscalingV1()
  1275  
  1276  	testCases := map[string]struct {
  1277  		fasA             autoscalingv1.CounterPolicy
  1278  		fasB             autoscalingv1.CounterPolicy
  1279  		wantAllocatedGsA int32 // Must be >= 0 && <= 3
  1280  		wantReadyGsA     int32
  1281  		wantAllocatedGsB int32 // Must be >= 0 && <= 3
  1282  		wantReadyGsB     int32
  1283  	}{
  1284  		"Scale Down Buffer Percent from different namespaces with same fleet name": {
  1285  			fasA: autoscalingv1.CounterPolicy{
  1286  				Key:         "players",
  1287  				BufferSize:  intstr.FromString("5%"),
  1288  				MinCapacity: 10,
  1289  				MaxCapacity: 100,
  1290  			},
  1291  			fasB: autoscalingv1.CounterPolicy{
  1292  				Key:         "players",
  1293  				BufferSize:  intstr.FromString("5%"),
  1294  				MinCapacity: 10,
  1295  				MaxCapacity: 100,
  1296  			},
  1297  			wantAllocatedGsA: 3,
  1298  			wantReadyGsA:     0,
  1299  			wantAllocatedGsB: 2,
  1300  			wantReadyGsB:     0,
  1301  		},
  1302  	}
  1303  
  1304  	//nolint:dupl  // Linter errors on lines are duplicate of TestListAutoscalerAllocated
  1305  	for name, testCase := range testCases {
  1306  		t.Run(name, func(t *testing.T) {
  1307  			// Create both fleet A and B
  1308  			fltA, err := client.Fleets(namespaceA).Create(ctx, defaultFltA.DeepCopy(), metav1.CreateOptions{})
  1309  			require.NoError(t, err)
  1310  			defer client.Fleets(namespaceA).Delete(ctx, fltA.ObjectMeta.Name, metav1.DeleteOptions{}) //nolint:errcheck
  1311  			framework.AssertFleetCondition(t, fltA, e2e.FleetReadyCount(fltA.Spec.Replicas))
  1312  
  1313  			fltB, err := client.Fleets(namespaceB).Create(ctx, defaultFltB.DeepCopy(), metav1.CreateOptions{})
  1314  			require.NoError(t, err)
  1315  			defer client.Fleets(namespaceB).Delete(ctx, fltB.ObjectMeta.Name, metav1.DeleteOptions{}) //nolint:errcheck
  1316  			framework.AssertFleetCondition(t, fltB, e2e.FleetReadyCount(fltB.Spec.Replicas))
  1317  
  1318  			// Allocate gameservers in A and B
  1319  			gsaA := allocationv1.GameServerAllocation{
  1320  				Spec: allocationv1.GameServerAllocationSpec{
  1321  					Selectors: []allocationv1.GameServerSelector{
  1322  						{LabelSelector: metav1.LabelSelector{
  1323  							MatchLabels: map[string]string{agonesv1.FleetNameLabel: fltA.ObjectMeta.Name}}},
  1324  					}}}
  1325  
  1326  			// Allocate game servers, as Buffer Percent scales up (or down) based on allocated aggregate capacity
  1327  			for i := int32(0); i < testCase.wantAllocatedGsA; i++ {
  1328  				_, err := framework.AgonesClient.AllocationV1().GameServerAllocations(fltA.ObjectMeta.Namespace).Create(ctx, gsaA.DeepCopy(), metav1.CreateOptions{})
  1329  				require.NoError(t, err)
  1330  			}
  1331  			framework.AssertFleetCondition(t, fltA, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1332  				log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking for game server allocations")
  1333  				return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGsA
  1334  			})
  1335  
  1336  			gsaB := allocationv1.GameServerAllocation{
  1337  				Spec: allocationv1.GameServerAllocationSpec{
  1338  					Selectors: []allocationv1.GameServerSelector{
  1339  						{LabelSelector: metav1.LabelSelector{
  1340  							MatchLabels: map[string]string{agonesv1.FleetNameLabel: fltB.ObjectMeta.Name}}},
  1341  					}}}
  1342  
  1343  			// Allocate game servers, as Buffer Percent scales up (or down) based on allocated aggregate capacity
  1344  			for i := int32(0); i < testCase.wantAllocatedGsB; i++ {
  1345  				_, err := framework.AgonesClient.AllocationV1().GameServerAllocations(fltB.ObjectMeta.Namespace).Create(ctx, gsaB.DeepCopy(), metav1.CreateOptions{})
  1346  				require.NoError(t, err)
  1347  			}
  1348  			framework.AssertFleetCondition(t, fltB, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1349  				log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking for game server allocations")
  1350  				return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGsB
  1351  			})
  1352  
  1353  			// Create fleetautoscaler for A and B
  1354  			counterFasA := &autoscalingv1.FleetAutoscaler{
  1355  				ObjectMeta: metav1.ObjectMeta{Name: fltA.ObjectMeta.Name + "-counter-autoscaler", Namespace: namespaceA},
  1356  				Spec: autoscalingv1.FleetAutoscalerSpec{
  1357  					FleetName: fltA.ObjectMeta.Name,
  1358  					Policy: autoscalingv1.FleetAutoscalerPolicy{
  1359  						Type:    autoscalingv1.CounterPolicyType,
  1360  						Counter: &testCase.fasA,
  1361  					},
  1362  					Sync: &autoscalingv1.FleetAutoscalerSync{
  1363  						Type: autoscalingv1.FixedIntervalSyncType,
  1364  						FixedInterval: autoscalingv1.FixedIntervalSync{
  1365  							Seconds: 1,
  1366  						},
  1367  					},
  1368  				},
  1369  			}
  1370  
  1371  			fasA, err := fleetautoscalers.FleetAutoscalers(namespaceA).Create(ctx, counterFasA, metav1.CreateOptions{})
  1372  			assert.NoError(t, err)
  1373  			defer fleetautoscalers.FleetAutoscalers(namespaceA).Delete(ctx, fasA.ObjectMeta.Name, metav1.DeleteOptions{}) //nolint:errcheck
  1374  
  1375  			counterFasB := &autoscalingv1.FleetAutoscaler{
  1376  				ObjectMeta: metav1.ObjectMeta{Name: fltB.ObjectMeta.Name + "-counter-autoscaler", Namespace: namespaceB},
  1377  				Spec: autoscalingv1.FleetAutoscalerSpec{
  1378  					FleetName: fltB.ObjectMeta.Name,
  1379  					Policy: autoscalingv1.FleetAutoscalerPolicy{
  1380  						Type:    autoscalingv1.CounterPolicyType,
  1381  						Counter: &testCase.fasB,
  1382  					},
  1383  					Sync: &autoscalingv1.FleetAutoscalerSync{
  1384  						Type: autoscalingv1.FixedIntervalSyncType,
  1385  						FixedInterval: autoscalingv1.FixedIntervalSync{
  1386  							Seconds: 1,
  1387  						},
  1388  					},
  1389  				},
  1390  			}
  1391  
  1392  			fasB, err := fleetautoscalers.FleetAutoscalers(namespaceB).Create(ctx, counterFasB, metav1.CreateOptions{})
  1393  			assert.NoError(t, err)
  1394  			defer fleetautoscalers.FleetAutoscalers(namespaceB).Delete(ctx, fasB.ObjectMeta.Name, metav1.DeleteOptions{}) //nolint:errcheck
  1395  
  1396  			// Ensure there is no warning / error on creation, ensure the currentReplicas > 0  for A and B
  1397  			framework.WaitForFleetAutoScalerCondition(t, fasA, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
  1398  				log.WithField("fleet autoscaler", fmt.Sprintf("%+v", fas.Status)).Info("Checking for fleet autoscaler")
  1399  				return fas.Status.CurrentReplicas > 0
  1400  			})
  1401  
  1402  			framework.WaitForFleetAutoScalerCondition(t, fasB, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
  1403  				log.WithField("fleet autoscaler", fmt.Sprintf("%+v", fas.Status)).Info("Checking for fleet autoscaler")
  1404  				return fas.Status.CurrentReplicas > 0
  1405  			})
  1406  
  1407  			// Wait for LastAppliedPolicy to be set to CounterPolicyType for fasA
  1408  			framework.WaitForFleetAutoScalerCondition(t, fasA, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
  1409  				log.WithField("LastAppliedPolicy", fas.Status.LastAppliedPolicy).
  1410  					Info("Waiting for LastAppliedPolicy to be set to CounterPolicyType")
  1411  				return fas.Status.LastAppliedPolicy == autoscalingv1.CounterPolicyType
  1412  			})
  1413  
  1414  			// Wait for LastAppliedPolicy to be set to CounterPolicyType for fasB
  1415  			framework.WaitForFleetAutoScalerCondition(t, fasB, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
  1416  				log.WithField("LastAppliedPolicy", fas.Status.LastAppliedPolicy).
  1417  					Info("Waiting for LastAppliedPolicy to be set to CounterPolicyType")
  1418  				return fas.Status.LastAppliedPolicy == autoscalingv1.CounterPolicyType
  1419  			})
  1420  
  1421  			// Ensure the allocated and ready replicas are correct for A and B
  1422  			framework.AssertFleetCondition(t, fltA, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1423  				return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGsA && fleet.Status.ReadyReplicas == testCase.wantReadyGsA
  1424  			})
  1425  			framework.AssertFleetCondition(t, fltB, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1426  				return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGsB && fleet.Status.ReadyReplicas == testCase.wantReadyGsB
  1427  			})
  1428  		})
  1429  	}
  1430  }
  1431  
  1432  func TestListAutoscaler(t *testing.T) {
  1433  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1434  		t.SkipNow()
  1435  	}
  1436  	t.Parallel()
  1437  
  1438  	ctx := context.Background()
  1439  	client := framework.AgonesClient.AgonesV1()
  1440  	log := e2e.TestLogger(t)
  1441  
  1442  	flt := defaultFleet(framework.Namespace)
  1443  	flt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
  1444  		"games": {
  1445  			Values:   []string{"game1", "game2", "game3"}, // AggregateCount 9
  1446  			Capacity: 5,                                   // AggregateCapacity 15
  1447  		},
  1448  	}
  1449  
  1450  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1451  	require.NoError(t, err)
  1452  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1453  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1454  
  1455  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  1456  
  1457  	listFas := func(f func(fap *autoscalingv1.FleetAutoscalerPolicy)) *autoscalingv1.FleetAutoscaler {
  1458  		fas := autoscalingv1.FleetAutoscaler{
  1459  			ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-list-autoscaler", Namespace: framework.Namespace},
  1460  			Spec: autoscalingv1.FleetAutoscalerSpec{
  1461  				FleetName: flt.ObjectMeta.Name,
  1462  				Policy: autoscalingv1.FleetAutoscalerPolicy{
  1463  					Type: autoscalingv1.ListPolicyType,
  1464  				},
  1465  				Sync: &autoscalingv1.FleetAutoscalerSync{
  1466  					Type: autoscalingv1.FixedIntervalSyncType,
  1467  					FixedInterval: autoscalingv1.FixedIntervalSync{
  1468  						Seconds: 1,
  1469  					},
  1470  				},
  1471  			},
  1472  		}
  1473  		f(&fas.Spec.Policy)
  1474  		return &fas
  1475  	}
  1476  	testCases := map[string]struct {
  1477  		fas          *autoscalingv1.FleetAutoscaler
  1478  		wantFasErr   bool
  1479  		wantReplicas int32
  1480  	}{
  1481  		"Scale Down to Minimum 1 Replica": {
  1482  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1483  				fap.List = &autoscalingv1.ListPolicy{
  1484  					Key:         "games",
  1485  					BufferSize:  intstr.FromInt(2),
  1486  					MinCapacity: 0,
  1487  					MaxCapacity: 3,
  1488  				}
  1489  			}),
  1490  			wantFasErr:   false,
  1491  			wantReplicas: 1, // Count:3 Capacity:5
  1492  		},
  1493  		"Scale Down to Buffer": {
  1494  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1495  				fap.List = &autoscalingv1.ListPolicy{
  1496  					Key:         "games",
  1497  					BufferSize:  intstr.FromInt(3),
  1498  					MinCapacity: 0,
  1499  					MaxCapacity: 5,
  1500  				}
  1501  			}),
  1502  			wantFasErr:   false,
  1503  			wantReplicas: 2, // Count:6 Capacity:10
  1504  		},
  1505  		"MinCapacity Must Be Greater Than Zero Percentage Buffer": {
  1506  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1507  				fap.List = &autoscalingv1.ListPolicy{
  1508  					Key:         "games",
  1509  					BufferSize:  intstr.FromString("50%"),
  1510  					MinCapacity: 0,
  1511  					MaxCapacity: 100,
  1512  				}
  1513  			}),
  1514  			wantFasErr:   true,
  1515  			wantReplicas: 3,
  1516  		},
  1517  		"Scale Up to MinCapacity": {
  1518  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1519  				fap.List = &autoscalingv1.ListPolicy{
  1520  					Key:         "games",
  1521  					BufferSize:  intstr.FromInt(3),
  1522  					MinCapacity: 16,
  1523  					MaxCapacity: 100,
  1524  				}
  1525  			}),
  1526  			wantFasErr:   false,
  1527  			wantReplicas: 4, // Count:12 Capacity:20
  1528  		},
  1529  		"Scale Down to MinCapacity": {
  1530  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1531  				fap.List = &autoscalingv1.ListPolicy{
  1532  					Key:         "games",
  1533  					BufferSize:  intstr.FromInt(1),
  1534  					MinCapacity: 10,
  1535  					MaxCapacity: 100,
  1536  				}
  1537  			}),
  1538  			wantFasErr:   false,
  1539  			wantReplicas: 2, // Count:6 Capacity:10
  1540  		},
  1541  		"MinCapacity Less Than Buffer Invalid": {
  1542  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1543  				fap.List = &autoscalingv1.ListPolicy{
  1544  					Key:         "games",
  1545  					BufferSize:  intstr.FromInt(15),
  1546  					MinCapacity: 5,
  1547  					MaxCapacity: 25,
  1548  				}
  1549  			}),
  1550  			wantFasErr:   true,
  1551  			wantReplicas: 3,
  1552  		},
  1553  		"Scale Up to Buffer": {
  1554  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1555  				fap.List = &autoscalingv1.ListPolicy{
  1556  					Key:         "games",
  1557  					BufferSize:  intstr.FromInt(15),
  1558  					MinCapacity: 15,
  1559  					MaxCapacity: 100,
  1560  				}
  1561  			}),
  1562  			wantFasErr:   false,
  1563  			wantReplicas: 8, // Count:24 Capacity:40
  1564  		},
  1565  		"Scale Up to MaxCapacity": {
  1566  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1567  				fap.List = &autoscalingv1.ListPolicy{
  1568  					Key:         "games",
  1569  					BufferSize:  intstr.FromInt(15),
  1570  					MinCapacity: 15,
  1571  					MaxCapacity: 25,
  1572  				}
  1573  			}),
  1574  			wantFasErr:   false,
  1575  			wantReplicas: 5, // Count:15 Capacity:25
  1576  		},
  1577  	}
  1578  	for name, testCase := range testCases {
  1579  		t.Run(name, func(t *testing.T) {
  1580  
  1581  			fas, err := fleetautoscalers.Create(ctx, testCase.fas, metav1.CreateOptions{})
  1582  			if testCase.wantFasErr {
  1583  				assert.Error(t, err)
  1584  				return
  1585  			}
  1586  			assert.NoError(t, err)
  1587  
  1588  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(testCase.wantReplicas))
  1589  			fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1590  
  1591  			// Return to starting 3 replicas
  1592  			framework.ScaleFleet(t, log, flt, 3)
  1593  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(3))
  1594  		})
  1595  	}
  1596  }
  1597  
  1598  // nolint:dupl  // Linter errors on lines are duplicate of TestCounterAutoscalerWithNoReplicas
  1599  func TestListAutoscalerWithNoReplicas(t *testing.T) {
  1600  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1601  		t.SkipNow()
  1602  	}
  1603  	t.Parallel()
  1604  
  1605  	ctx := context.Background()
  1606  	client := framework.AgonesClient.AgonesV1()
  1607  	log := e2e.TestLogger(t)
  1608  
  1609  	flt := defaultEmptyFleet(framework.Namespace)
  1610  	flt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
  1611  		"games": {
  1612  			Capacity: 5,
  1613  		},
  1614  	}
  1615  
  1616  	flt, err := client.Fleets(framework.Namespace).Create(ctx, flt.DeepCopy(), metav1.CreateOptions{})
  1617  	require.NoError(t, err)
  1618  	defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1619  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1620  
  1621  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  1622  
  1623  	listFas := func(f func(fap *autoscalingv1.FleetAutoscalerPolicy)) *autoscalingv1.FleetAutoscaler {
  1624  		fas := autoscalingv1.FleetAutoscaler{
  1625  			ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-list-autoscaler", Namespace: framework.Namespace},
  1626  			Spec: autoscalingv1.FleetAutoscalerSpec{
  1627  				FleetName: flt.ObjectMeta.Name,
  1628  				Policy: autoscalingv1.FleetAutoscalerPolicy{
  1629  					Type: autoscalingv1.ListPolicyType,
  1630  				},
  1631  				Sync: &autoscalingv1.FleetAutoscalerSync{
  1632  					Type: autoscalingv1.FixedIntervalSyncType,
  1633  					FixedInterval: autoscalingv1.FixedIntervalSync{
  1634  						Seconds: 1,
  1635  					},
  1636  				},
  1637  			},
  1638  		}
  1639  		f(&fas.Spec.Policy)
  1640  		return &fas
  1641  	}
  1642  	testCases := map[string]struct {
  1643  		fas          *autoscalingv1.FleetAutoscaler
  1644  		wantFasErr   bool
  1645  		wantReplicas int32
  1646  	}{
  1647  		"Scale Up to MinCapacity": {
  1648  			fas: listFas(func(fap *autoscalingv1.FleetAutoscalerPolicy) {
  1649  				fap.List = &autoscalingv1.ListPolicy{
  1650  					Key:         "games",
  1651  					BufferSize:  intstr.FromInt(3),
  1652  					MinCapacity: 16,
  1653  					MaxCapacity: 100,
  1654  				}
  1655  			}),
  1656  			wantFasErr:   false,
  1657  			wantReplicas: 4, // Capacity:20
  1658  		},
  1659  	}
  1660  	for name, testCase := range testCases {
  1661  		t.Run(name, func(t *testing.T) {
  1662  
  1663  			fas, err := fleetautoscalers.Create(ctx, testCase.fas, metav1.CreateOptions{})
  1664  			if testCase.wantFasErr {
  1665  				assert.Error(t, err)
  1666  				return
  1667  			}
  1668  			assert.NoError(t, err)
  1669  
  1670  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(testCase.wantReplicas))
  1671  			fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1672  
  1673  			// Return to starting 0 replicas
  1674  			framework.ScaleFleet(t, log, flt, 0)
  1675  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(0))
  1676  		})
  1677  	}
  1678  }
  1679  
  1680  func TestListAutoscalerAllocated(t *testing.T) {
  1681  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1682  		t.SkipNow()
  1683  	}
  1684  	t.Parallel()
  1685  
  1686  	ctx := context.Background()
  1687  	client := framework.AgonesClient.AgonesV1()
  1688  	log := e2e.TestLogger(t)
  1689  
  1690  	defaultFlt := defaultFleet(framework.Namespace)
  1691  	defaultFlt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
  1692  		"gamers": {
  1693  			Values:   []string{"gamer5", "gamer6"},
  1694  			Capacity: 6, // AggregateCapacity 18
  1695  		},
  1696  	}
  1697  
  1698  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  1699  
  1700  	testCases := map[string]struct {
  1701  		fas                  autoscalingv1.ListPolicy
  1702  		wantAllocatedGs      int32 // Must be >= 0 && <= 3
  1703  		wantReadyGs          int32
  1704  		wantSecondAllocation int32 // Must be <= wantReadyGs
  1705  		wantSecondReady      int32
  1706  	}{
  1707  		"Scale Down Buffer Percent": {
  1708  			fas: autoscalingv1.ListPolicy{
  1709  				Key:         "gamers",
  1710  				BufferSize:  intstr.FromString("50%"),
  1711  				MinCapacity: 6,
  1712  				MaxCapacity: 60,
  1713  			},
  1714  			wantAllocatedGs: 0,
  1715  			wantReadyGs:     1,
  1716  		},
  1717  		"Scale Up Buffer Percent": {
  1718  			fas: autoscalingv1.ListPolicy{
  1719  				Key:         "gamers",
  1720  				BufferSize:  intstr.FromString("50%"),
  1721  				MinCapacity: 6,
  1722  				MaxCapacity: 60,
  1723  			},
  1724  			wantAllocatedGs: 3,
  1725  			wantReadyGs:     2,
  1726  		},
  1727  		"Scales Down to Number of Game Servers Allocated": {
  1728  			fas: autoscalingv1.ListPolicy{
  1729  				Key:         "gamers",
  1730  				BufferSize:  intstr.FromInt(2),
  1731  				MinCapacity: 6,
  1732  				MaxCapacity: 60,
  1733  			},
  1734  			wantAllocatedGs: 2,
  1735  			wantReadyGs:     0,
  1736  		},
  1737  	}
  1738  	// nolint:dupl  // Linter errors on lines are duplicate of TestCounterAutoscalerAllocated
  1739  	for name, testCase := range testCases {
  1740  		t.Run(name, func(t *testing.T) {
  1741  			flt, err := client.Fleets(framework.Namespace).Create(ctx, defaultFlt.DeepCopy(), metav1.CreateOptions{})
  1742  			require.NoError(t, err)
  1743  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1744  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1745  
  1746  			// Adds 4 gamers to each allocated gameserver, and removes 2 existing gamers.
  1747  			gsa := allocationv1.GameServerAllocation{
  1748  				Spec: allocationv1.GameServerAllocationSpec{
  1749  					Selectors: []allocationv1.GameServerSelector{
  1750  						{LabelSelector: metav1.LabelSelector{
  1751  							MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}},
  1752  					},
  1753  					Lists: map[string]allocationv1.ListAction{
  1754  						"gamers": {
  1755  							AddValues:    []string{"gamer1", "gamer2", "gamer3", "gamer4"},
  1756  							DeleteValues: []string{"gamer5", "gamer6"},
  1757  						}}}}
  1758  
  1759  			// Allocate game servers, as Buffer Percent scales up (or down) based on allocated aggregate capacity
  1760  			for i := int32(0); i < testCase.wantAllocatedGs; i++ {
  1761  				_, err := framework.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(ctx, gsa.DeepCopy(), metav1.CreateOptions{})
  1762  				require.NoError(t, err)
  1763  			}
  1764  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1765  				log.WithField("fleet", fmt.Sprintf("%+v", fleet.Status)).Info("Checking for game server allocations")
  1766  				return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGs
  1767  			})
  1768  
  1769  			listFas := &autoscalingv1.FleetAutoscaler{
  1770  				ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-list-autoscaler", Namespace: framework.Namespace},
  1771  				Spec: autoscalingv1.FleetAutoscalerSpec{
  1772  					FleetName: flt.ObjectMeta.Name,
  1773  					Policy: autoscalingv1.FleetAutoscalerPolicy{
  1774  						Type: autoscalingv1.ListPolicyType,
  1775  						List: &testCase.fas,
  1776  					},
  1777  					Sync: &autoscalingv1.FleetAutoscalerSync{
  1778  						Type: autoscalingv1.FixedIntervalSyncType,
  1779  						FixedInterval: autoscalingv1.FixedIntervalSync{
  1780  							Seconds: 1,
  1781  						},
  1782  					},
  1783  				},
  1784  			}
  1785  
  1786  			fas, err := fleetautoscalers.Create(ctx, listFas, metav1.CreateOptions{})
  1787  			assert.NoError(t, err)
  1788  			defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1789  
  1790  			framework.AssertFleetCondition(t, flt, func(_ *logrus.Entry, fleet *agonesv1.Fleet) bool {
  1791  				return fleet.Status.AllocatedReplicas == testCase.wantAllocatedGs && fleet.Status.ReadyReplicas == testCase.wantReadyGs
  1792  			})
  1793  		})
  1794  	}
  1795  }
  1796  
  1797  func TestListAutoscalerWithSDKMethods(t *testing.T) {
  1798  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
  1799  		t.SkipNow()
  1800  	}
  1801  	t.Parallel()
  1802  
  1803  	ctx := context.Background()
  1804  	client := framework.AgonesClient.AgonesV1()
  1805  
  1806  	defaultFlt := defaultFleet(framework.Namespace)
  1807  	defaultFlt.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
  1808  		"sessions": {
  1809  			Values:   []string{"session1", "session2"}, // AggregateCount 6
  1810  			Capacity: 4,                                // AggregateCapacity 12
  1811  		},
  1812  	}
  1813  
  1814  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  1815  
  1816  	testCases := map[string]struct {
  1817  		fas           autoscalingv1.ListPolicy
  1818  		order         string // Priority order Ascending or Descending for fleet ready replica deletion
  1819  		msg           string // See agones/examples/simple-game-server/README for list of commands
  1820  		startReplicas int32  // After applying autoscaler policy but before sending update message
  1821  		wantReplicas  int32  // After applying autoscaler policy and sending update message
  1822  	}{
  1823  		"Scale Up to Buffer": {
  1824  			fas: autoscalingv1.ListPolicy{
  1825  				Key:         "sessions",
  1826  				BufferSize:  intstr.FromInt(10),
  1827  				MinCapacity: 12,
  1828  				MaxCapacity: 400,
  1829  			},
  1830  			order:         agonesv1.GameServerPriorityDescending,
  1831  			msg:           "APPEND_LIST_VALUE sessions session0",
  1832  			startReplicas: 5,
  1833  			wantReplicas:  6,
  1834  		},
  1835  		"Scale Down to Buffer": {
  1836  			fas: autoscalingv1.ListPolicy{
  1837  				Key:         "sessions",
  1838  				BufferSize:  intstr.FromInt(3),
  1839  				MinCapacity: 3,
  1840  				MaxCapacity: 400,
  1841  			},
  1842  			msg:           "DELETE_LIST_VALUE sessions session1",
  1843  			order:         agonesv1.GameServerPriorityAscending,
  1844  			startReplicas: 2,
  1845  			wantReplicas:  1,
  1846  		},
  1847  	}
  1848  	for name, testCase := range testCases {
  1849  		t.Run(name, func(t *testing.T) {
  1850  			defaultFlt.Spec.Priorities = []agonesv1.Priority{
  1851  				{
  1852  					Type:  agonesv1.GameServerPriorityList,
  1853  					Key:   "sessions",
  1854  					Order: testCase.order,
  1855  				},
  1856  			}
  1857  			flt, err := client.Fleets(framework.Namespace).Create(ctx, defaultFlt.DeepCopy(), metav1.CreateOptions{})
  1858  			require.NoError(t, err)
  1859  			defer client.Fleets(framework.Namespace).Delete(ctx, flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1860  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1861  
  1862  			listFas := &autoscalingv1.FleetAutoscaler{
  1863  				ObjectMeta: metav1.ObjectMeta{Name: flt.ObjectMeta.Name + "-list-autoscaler", Namespace: framework.Namespace},
  1864  				Spec: autoscalingv1.FleetAutoscalerSpec{
  1865  					FleetName: flt.ObjectMeta.Name,
  1866  					Policy: autoscalingv1.FleetAutoscalerPolicy{
  1867  						Type: autoscalingv1.ListPolicyType,
  1868  						List: &testCase.fas,
  1869  					},
  1870  					Sync: &autoscalingv1.FleetAutoscalerSync{
  1871  						Type: autoscalingv1.FixedIntervalSyncType,
  1872  						FixedInterval: autoscalingv1.FixedIntervalSync{
  1873  							Seconds: 1,
  1874  						},
  1875  					},
  1876  				},
  1877  			}
  1878  
  1879  			fas, err := fleetautoscalers.Create(ctx, listFas, metav1.CreateOptions{})
  1880  			assert.NoError(t, err)
  1881  			defer fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1882  
  1883  			// Wait until autoscaler has first re-sized before getting the list of gameservers
  1884  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(testCase.startReplicas))
  1885  
  1886  			gameservers, err := framework.ListGameServersFromFleet(flt)
  1887  			assert.NoError(t, err)
  1888  
  1889  			gs := &gameservers[1]
  1890  			logrus.WithField("command", testCase.msg).WithField("gs", gs.ObjectMeta.Name).Info(name)
  1891  			_, err = framework.SendGameServerUDP(t, gs, testCase.msg)
  1892  			require.NoError(t, err)
  1893  
  1894  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(testCase.wantReplicas))
  1895  		})
  1896  	}
  1897  }
  1898  
  1899  func TestScheduleAutoscaler(t *testing.T) {
  1900  	if !runtime.FeatureEnabled(runtime.FeatureScheduledAutoscaler) {
  1901  		t.SkipNow()
  1902  	}
  1903  	t.Parallel()
  1904  	ctx := context.Background()
  1905  	log := e2e.TestLogger(t)
  1906  
  1907  	stable := framework.AgonesClient.AgonesV1()
  1908  	fleets := stable.Fleets(framework.Namespace)
  1909  	flt, err := fleets.Create(ctx, defaultFleet(framework.Namespace), metav1.CreateOptions{})
  1910  	require.NoError(t, err)
  1911  	defer fleets.Delete(context.Background(), flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1912  
  1913  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1914  
  1915  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  1916  
  1917  	// Active Cron Schedule (e.g. run after 1 * * * *, which is the after the first minute of the hour)
  1918  	scheduleAutoscaler := defaultAutoscalerSchedule(t, flt)
  1919  	scheduleAutoscaler.Spec.Policy.Schedule.ActivePeriod.StartCron = nextCronMinute(time.Now())
  1920  	fas, err := fleetautoscalers.Create(ctx, scheduleAutoscaler, metav1.CreateOptions{})
  1921  	require.NoError(t, err)
  1922  
  1923  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(5))
  1924  	fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1925  
  1926  	// Return to starting 3 replicas
  1927  	framework.ScaleFleet(t, log, flt, 3)
  1928  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(3))
  1929  
  1930  	// Between Active Period Cron Schedule (e.g. run between 1-2 * * * *, which is between the first minute and second minute of the hour)
  1931  	scheduleAutoscaler = defaultAutoscalerSchedule(t, flt)
  1932  	scheduleAutoscaler.Spec.Policy.Schedule.ActivePeriod.StartCron = nextCronMinuteBetween(time.Now())
  1933  	fas, err = fleetautoscalers.Create(ctx, scheduleAutoscaler, metav1.CreateOptions{})
  1934  	require.NoError(t, err)
  1935  
  1936  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(5))
  1937  	fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1938  }
  1939  
  1940  func TestChainAutoscaler(t *testing.T) {
  1941  	if !runtime.FeatureEnabled(runtime.FeatureScheduledAutoscaler) {
  1942  		t.SkipNow()
  1943  	}
  1944  	t.Parallel()
  1945  	ctx := context.Background()
  1946  	log := e2e.TestLogger(t)
  1947  
  1948  	stable := framework.AgonesClient.AgonesV1()
  1949  	fleets := stable.Fleets(framework.Namespace)
  1950  	flt, err := fleets.Create(ctx, defaultFleet(framework.Namespace), metav1.CreateOptions{})
  1951  	if assert.NoError(t, err) {
  1952  		defer fleets.Delete(context.Background(), flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1953  	}
  1954  
  1955  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  1956  
  1957  	fleetautoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  1958  
  1959  	// 1st Schedule Inactive, 2nd Schedule Active - 30 seconds (Fallthrough)
  1960  	chainAutoscaler := defaultAutoscalerChain(t, flt)
  1961  	fas, err := fleetautoscalers.Create(ctx, chainAutoscaler, metav1.CreateOptions{})
  1962  	assert.NoError(t, err)
  1963  
  1964  	// Verify only the second schedule ran
  1965  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(4))
  1966  	expectedChainPolicy := autoscalingv1.FleetAutoscalerPolicyType(fmt.Sprintf("%s:%s:%s", autoscalingv1.ChainPolicyType, "schedule-2", autoscalingv1.SchedulePolicyType))
  1967  	framework.WaitForFleetAutoScalerCondition(t, chainAutoscaler, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
  1968  		log.WithField("LastAppliedPolicy", fas.Status.LastAppliedPolicy).Info("Awaiting application of expected SchedulePolicyType in chain autoscaler")
  1969  		return fas.Status.LastAppliedPolicy == expectedChainPolicy
  1970  	})
  1971  
  1972  	fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  1973  
  1974  	// Return to starting 3 replicas
  1975  	framework.ScaleFleet(t, log, flt, 3)
  1976  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(3))
  1977  
  1978  	// 2 Active Schedules back to back - 1 minute (Fallthrough)
  1979  	chainAutoscaler = defaultAutoscalerChain(t, flt)
  1980  	currentTime := time.Now()
  1981  
  1982  	// First schedule runs for 1 minute
  1983  	chainAutoscaler.Spec.Policy.Chain[0].Schedule.ActivePeriod.StartCron = nextCronMinute(currentTime)
  1984  	chainAutoscaler.Spec.Policy.Chain[0].Schedule.ActivePeriod.Duration = "1m"
  1985  
  1986  	// Second schedule runs 1 minute after the first schedule
  1987  	oneMinute := mustParseDuration(t, "1m")
  1988  	chainAutoscaler.Spec.Policy.Chain[0].Schedule.ActivePeriod.StartCron = nextCronMinute(currentTime.Add(oneMinute))
  1989  	chainAutoscaler.Spec.Policy.Chain[1].Schedule.ActivePeriod.Duration = "5m"
  1990  
  1991  	fas, err = fleetautoscalers.Create(ctx, chainAutoscaler, metav1.CreateOptions{})
  1992  	assert.NoError(t, err)
  1993  
  1994  	// Verify the first schedule has been applied
  1995  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(10))
  1996  	expectedChainPolicy = autoscalingv1.FleetAutoscalerPolicyType(fmt.Sprintf("%s:%s:%s", autoscalingv1.ChainPolicyType, "schedule-1", autoscalingv1.SchedulePolicyType))
  1997  	framework.WaitForFleetAutoScalerCondition(t, chainAutoscaler, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
  1998  		log.WithField("LastAppliedPolicy", fas.Status.LastAppliedPolicy).Info("Awaiting application of expected SchedulePolicyType in chain autoscaler")
  1999  		return fas.Status.LastAppliedPolicy == expectedChainPolicy
  2000  	})
  2001  	// Verify the second schedule has been applied
  2002  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(4))
  2003  	expectedChainPolicy = autoscalingv1.FleetAutoscalerPolicyType(fmt.Sprintf("%s:%s:%s", autoscalingv1.ChainPolicyType, "schedule-2", autoscalingv1.SchedulePolicyType))
  2004  	framework.WaitForFleetAutoScalerCondition(t, chainAutoscaler, func(log *logrus.Entry, fas *autoscalingv1.FleetAutoscaler) bool {
  2005  		log.WithField("LastAppliedPolicy", fas.Status.LastAppliedPolicy).Info("Awaiting application of expected SchedulePolicyType in chain autoscaler")
  2006  		return fas.Status.LastAppliedPolicy == expectedChainPolicy
  2007  	})
  2008  
  2009  	fleetautoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  2010  }
  2011  
  2012  func TestWasmAutoScaler(t *testing.T) {
  2013  	if !runtime.FeatureEnabled(runtime.FeatureWasmAutoscaler) {
  2014  		t.SkipNow()
  2015  	}
  2016  	// Parent test is not marked t.Parallel() because it performs shared setup (nginx pod + service)
  2017  	// that is reused by parallel subtests.
  2018  
  2019  	ctx := context.Background()
  2020  
  2021  	// Shared setup: Get the path to the WASM plugin file and ensure it exists
  2022  	wasmFilePath := filepath.Join("..", "..", "examples", "autoscaler-wasm", "plugin.wasm")
  2023  	_, err := os.Stat(wasmFilePath)
  2024  	require.NoError(t, err, "WASM plugin file does not exist at %s", wasmFilePath)
  2025  
  2026  	// Shared setup: Create a single nginx pod to serve the WASM plugin
  2027  	emptyDirSize := resource.MustParse("50Mi") // 50Mi ~= 50MB
  2028  	var port int32 = 80
  2029  	path := "/plugin.wasm"
  2030  
  2031  	pod := &corev1.Pod{
  2032  		ObjectMeta: metav1.ObjectMeta{
  2033  			GenerateName: "wasm-server-",
  2034  			Namespace:    framework.Namespace,
  2035  			Labels: map[string]string{
  2036  				"app": "wasm-server",
  2037  			},
  2038  		},
  2039  		Spec: corev1.PodSpec{
  2040  			Volumes: []corev1.Volume{
  2041  				{
  2042  					Name: "ephemeral",
  2043  					VolumeSource: corev1.VolumeSource{
  2044  						EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: &emptyDirSize},
  2045  					},
  2046  				},
  2047  			},
  2048  			Containers: []corev1.Container{
  2049  				{
  2050  					Name:  "nginx",
  2051  					Image: "nginx:alpine",
  2052  					Ports: []corev1.ContainerPort{{
  2053  						ContainerPort: port,
  2054  						Name:          "http",
  2055  					}},
  2056  					VolumeMounts: []corev1.VolumeMount{
  2057  						{
  2058  							Name:      "ephemeral",
  2059  							MountPath: "/usr/share/nginx/html",
  2060  						},
  2061  					},
  2062  				},
  2063  			},
  2064  		},
  2065  	}
  2066  	podClient := framework.KubeClient.CoreV1().Pods(framework.Namespace)
  2067  	pod, err = podClient.Create(ctx, pod, metav1.CreateOptions{})
  2068  	require.NoError(t, err)
  2069  	t.Cleanup(func() { _ = podClient.Delete(context.Background(), pod.ObjectMeta.Name, metav1.DeleteOptions{}) })
  2070  
  2071  	// Wait until the Pod is Running
  2072  	require.NoError(t, wait.PollUntilContextTimeout(ctx, time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) {
  2073  		p, err := podClient.Get(ctx, pod.ObjectMeta.Name, metav1.GetOptions{})
  2074  		if err != nil {
  2075  			return false, nil
  2076  		}
  2077  		return p.Status.Phase == corev1.PodRunning, nil
  2078  	}))
  2079  
  2080  	// Copy the WASM plugin file to the nginx container once
  2081  	err = copyFileToContainer(t, framework.Namespace, pod.ObjectMeta.Name, "nginx", wasmFilePath, "/usr/share/nginx/html/plugin.wasm")
  2082  	require.NoError(t, err, "Failed to copy WASM plugin file to container")
  2083  
  2084  	// Compute correct and incorrect SHA256 hashes for the served WASM
  2085  	pluginBytes, err := os.ReadFile(wasmFilePath)
  2086  	require.NoError(t, err)
  2087  	sum := sha256.Sum256(pluginBytes)
  2088  	hashStr := hex.EncodeToString(sum[:])
  2089  	badSum := make([]byte, len(sum))
  2090  	copy(badSum, sum[:])
  2091  	badSum[0] ^= 0xFF
  2092  	badHash := hex.EncodeToString(badSum)
  2093  
  2094  	// Shared setup: Create a service to expose the pod
  2095  	service := &corev1.Service{
  2096  		ObjectMeta: metav1.ObjectMeta{
  2097  			GenerateName: "wasm-server-",
  2098  			Namespace:    framework.Namespace,
  2099  		},
  2100  		Spec: corev1.ServiceSpec{
  2101  			Selector: map[string]string{
  2102  				"app": "wasm-server",
  2103  			},
  2104  			Ports: []corev1.ServicePort{{
  2105  				Port:       port,
  2106  				TargetPort: intstr.FromInt32(port),
  2107  				Protocol:   corev1.ProtocolTCP,
  2108  			}},
  2109  		},
  2110  	}
  2111  	serviceClient := framework.KubeClient.CoreV1().Services(framework.Namespace)
  2112  	service, err = serviceClient.Create(ctx, service, metav1.CreateOptions{})
  2113  	require.NoError(t, err)
  2114  	t.Cleanup(func() {
  2115  		_ = serviceClient.Delete(context.Background(), service.ObjectMeta.Name, metav1.DeleteOptions{})
  2116  	})
  2117  
  2118  	testCases := map[string]struct {
  2119  		bufferSizeConfig     *int
  2120  		functionNameOverride *string
  2121  		hash                 *string
  2122  		expectedFleetSize    int32
  2123  	}{
  2124  		"No buffer_size config": {
  2125  			bufferSizeConfig:  nil,
  2126  			expectedFleetSize: 5,
  2127  		},
  2128  		"buffer_size config of 4": {
  2129  			bufferSizeConfig:  func() *int { i := 4; return &i }(),
  2130  			expectedFleetSize: 4,
  2131  		},
  2132  		"overwrite function to scaleNone": {
  2133  			bufferSizeConfig:     nil,
  2134  			functionNameOverride: func() *string { s := "scaleNone"; return &s }(),
  2135  			expectedFleetSize:    3,
  2136  		},
  2137  		"Correct hash set; scales as expected": {
  2138  			bufferSizeConfig:  nil,
  2139  			hash:              func() *string { s := hashStr; return &s }(),
  2140  			expectedFleetSize: 5,
  2141  		},
  2142  		"Incorrect hash set; no scaling occurs": {
  2143  			bufferSizeConfig:  nil,
  2144  			hash:              func() *string { s := badHash; return &s }(),
  2145  			expectedFleetSize: 3,
  2146  		},
  2147  	}
  2148  
  2149  	for name, tc := range testCases {
  2150  		t.Run(name, func(t *testing.T) {
  2151  			t.Parallel()
  2152  
  2153  			ctx := context.Background()
  2154  
  2155  			// Create fleet
  2156  			fleets := framework.AgonesClient.AgonesV1().Fleets(framework.Namespace)
  2157  			flt, err := fleets.Create(ctx, defaultFleet(framework.Namespace), metav1.CreateOptions{})
  2158  			require.NoError(t, err)
  2159  			defer fleets.Delete(context.Background(), flt.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint:errcheck
  2160  
  2161  			// Create WASM FleetAutoscaler
  2162  			fleetAutoscalers := framework.AgonesClient.AutoscalingV1().FleetAutoscalers(framework.Namespace)
  2163  
  2164  			wasmAutoscaler := &autoscalingv1.FleetAutoscaler{
  2165  				ObjectMeta: metav1.ObjectMeta{
  2166  					Name:      flt.ObjectMeta.Name + "-wasm-autoscaler",
  2167  					Namespace: framework.Namespace,
  2168  				},
  2169  				Spec: autoscalingv1.FleetAutoscalerSpec{
  2170  					FleetName: flt.ObjectMeta.Name,
  2171  					Policy: autoscalingv1.FleetAutoscalerPolicy{
  2172  						Type: autoscalingv1.WasmPolicyType,
  2173  						Wasm: &autoscalingv1.WasmPolicy{
  2174  							Function: "scale",
  2175  							Config:   make(map[string]string),
  2176  							From: autoscalingv1.WasmFrom{
  2177  								URL: &autoscalingv1.URLConfiguration{
  2178  									Service: &admregv1.ServiceReference{
  2179  										Namespace: framework.Namespace,
  2180  										Name:      service.ObjectMeta.Name,
  2181  										Path:      &path,
  2182  										Port:      &port,
  2183  									},
  2184  								},
  2185  							},
  2186  						},
  2187  					},
  2188  					Sync: &autoscalingv1.FleetAutoscalerSync{
  2189  						Type: autoscalingv1.FixedIntervalSyncType,
  2190  						FixedInterval: autoscalingv1.FixedIntervalSync{
  2191  							Seconds: 1,
  2192  						},
  2193  					},
  2194  				},
  2195  			}
  2196  
  2197  			// Overwrite the function name if provided in the test case
  2198  			if tc.functionNameOverride != nil {
  2199  				wasmAutoscaler.Spec.Policy.Wasm.Function = *tc.functionNameOverride
  2200  			}
  2201  
  2202  			// Set buffer_size config if provided
  2203  			if tc.bufferSizeConfig != nil {
  2204  				wasmAutoscaler.Spec.Policy.Wasm.Config["buffer_size"] = strconv.Itoa(*tc.bufferSizeConfig)
  2205  			}
  2206  
  2207  			// Set hash if provided
  2208  			if tc.hash != nil {
  2209  				wasmAutoscaler.Spec.Policy.Wasm.Hash = *tc.hash
  2210  			}
  2211  
  2212  			// make sure the fleet has the correct number of replicas
  2213  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
  2214  
  2215  			// Create the FleetAutoscaler
  2216  			fas, err := fleetAutoscalers.Create(ctx, wasmAutoscaler, metav1.CreateOptions{})
  2217  			require.NoError(t, err)
  2218  			defer func() { _ = fleetAutoscalers.Delete(ctx, fas.ObjectMeta.Name, metav1.DeleteOptions{}) }()
  2219  
  2220  			framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(tc.expectedFleetSize))
  2221  		})
  2222  	}
  2223  }
  2224  
  2225  // defaultAutoscalerSchedule returns a default scheduled autoscaler for testing.
  2226  func defaultAutoscalerSchedule(t *testing.T, f *agonesv1.Fleet) *autoscalingv1.FleetAutoscaler {
  2227  	return &autoscalingv1.FleetAutoscaler{
  2228  		ObjectMeta: metav1.ObjectMeta{
  2229  			Name:      f.ObjectMeta.Name + "-scheduled-autoscaler",
  2230  			Namespace: framework.Namespace,
  2231  		},
  2232  		Spec: autoscalingv1.FleetAutoscalerSpec{
  2233  			FleetName: f.ObjectMeta.Name,
  2234  			Policy: autoscalingv1.FleetAutoscalerPolicy{
  2235  				Type: autoscalingv1.SchedulePolicyType,
  2236  				Schedule: &autoscalingv1.SchedulePolicy{
  2237  					Between: autoscalingv1.Between{
  2238  						Start: currentTimePlusDuration(t, "1s"),
  2239  						End:   currentTimePlusDuration(t, "1m"),
  2240  					},
  2241  					ActivePeriod: autoscalingv1.ActivePeriod{
  2242  						Timezone:  "UTC",
  2243  						StartCron: "* * * * *",
  2244  						Duration:  "",
  2245  					},
  2246  					Policy: autoscalingv1.FleetAutoscalerPolicy{
  2247  						Type: autoscalingv1.BufferPolicyType,
  2248  						Buffer: &autoscalingv1.BufferPolicy{
  2249  							BufferSize:  intstr.FromInt(5),
  2250  							MinReplicas: 5,
  2251  							MaxReplicas: 12,
  2252  						},
  2253  					},
  2254  				},
  2255  			},
  2256  			Sync: &autoscalingv1.FleetAutoscalerSync{
  2257  				Type: autoscalingv1.FixedIntervalSyncType,
  2258  				FixedInterval: autoscalingv1.FixedIntervalSync{
  2259  					Seconds: 5,
  2260  				},
  2261  			},
  2262  		},
  2263  	}
  2264  }
  2265  
  2266  // defaultAutoscalerChain returns a default chain autoscaler for testing.
  2267  func defaultAutoscalerChain(t *testing.T, f *agonesv1.Fleet) *autoscalingv1.FleetAutoscaler {
  2268  	return &autoscalingv1.FleetAutoscaler{
  2269  		ObjectMeta: metav1.ObjectMeta{
  2270  			Name:      f.ObjectMeta.Name + "-chain-autoscaler",
  2271  			Namespace: framework.Namespace,
  2272  		},
  2273  		Spec: autoscalingv1.FleetAutoscalerSpec{
  2274  			FleetName: f.ObjectMeta.Name,
  2275  			Policy: autoscalingv1.FleetAutoscalerPolicy{
  2276  				Type: autoscalingv1.ChainPolicyType,
  2277  				Chain: autoscalingv1.ChainPolicy{
  2278  					{
  2279  						ID: "schedule-1",
  2280  						FleetAutoscalerPolicy: autoscalingv1.FleetAutoscalerPolicy{
  2281  							Type: autoscalingv1.SchedulePolicyType,
  2282  							Schedule: &autoscalingv1.SchedulePolicy{
  2283  								Between: autoscalingv1.Between{
  2284  									Start: currentTimePlusDuration(t, "1s"),
  2285  									End:   currentTimePlusDuration(t, "2m"),
  2286  								},
  2287  								ActivePeriod: autoscalingv1.ActivePeriod{
  2288  									Timezone:  "",
  2289  									StartCron: inactiveCronSchedule(time.Now()),
  2290  									Duration:  "1m",
  2291  								},
  2292  								Policy: autoscalingv1.FleetAutoscalerPolicy{
  2293  									Type: autoscalingv1.BufferPolicyType,
  2294  									Buffer: &autoscalingv1.BufferPolicy{
  2295  										BufferSize:  intstr.FromInt(10),
  2296  										MinReplicas: 10,
  2297  										MaxReplicas: 20,
  2298  									},
  2299  								},
  2300  							},
  2301  						},
  2302  					},
  2303  					{
  2304  						ID: "schedule-2",
  2305  						FleetAutoscalerPolicy: autoscalingv1.FleetAutoscalerPolicy{
  2306  							Type: autoscalingv1.SchedulePolicyType,
  2307  							Schedule: &autoscalingv1.SchedulePolicy{
  2308  								Between: autoscalingv1.Between{
  2309  									Start: currentTimePlusDuration(t, "1s"),
  2310  									End:   currentTimePlusDuration(t, "5m"),
  2311  								},
  2312  								ActivePeriod: autoscalingv1.ActivePeriod{
  2313  									Timezone:  "",
  2314  									StartCron: nextCronMinute(time.Now()),
  2315  									Duration:  "",
  2316  								},
  2317  								Policy: autoscalingv1.FleetAutoscalerPolicy{
  2318  									Type: autoscalingv1.BufferPolicyType,
  2319  									Buffer: &autoscalingv1.BufferPolicy{
  2320  										BufferSize:  intstr.FromInt(4),
  2321  										MinReplicas: 3,
  2322  										MaxReplicas: 7,
  2323  									},
  2324  								},
  2325  							},
  2326  						},
  2327  					},
  2328  				},
  2329  			},
  2330  			Sync: &autoscalingv1.FleetAutoscalerSync{
  2331  				Type: autoscalingv1.FixedIntervalSyncType,
  2332  				FixedInterval: autoscalingv1.FixedIntervalSync{
  2333  					Seconds: 5,
  2334  				},
  2335  			},
  2336  		},
  2337  	}
  2338  }
  2339  
  2340  // inactiveCronSchedule returns the time 3 minutes ago
  2341  // e.g. if the current time is 12:00, this method will return "57 * * * *"
  2342  // meaning 3 minutes before 12:00
  2343  func inactiveCronSchedule(currentTime time.Time) string {
  2344  	prevMinute := currentTime.Add(time.Minute * -3).Minute()
  2345  	return fmt.Sprintf("%d * * * *", prevMinute)
  2346  }
  2347  
  2348  // nextCronMinute returns the very next minute in
  2349  // e.g. if the current time is 12:00, this method will return "1 * * * *"
  2350  // meaning after 12:01
  2351  func nextCronMinute(currentTime time.Time) string {
  2352  	nextMinute := currentTime.Add(time.Minute).Minute()
  2353  	return fmt.Sprintf("%d * * * *", nextMinute)
  2354  }
  2355  
  2356  // nextCronMinuteBetween returns the minute between the very next minute
  2357  // e.g. if the current time is 12:00, this method will return "1-2 * * * *"
  2358  // meaning between 12:01 - 12:02
  2359  // if the current minute if "59" since 59-0 is invalid, we'll return "0-1 * * * *" and wait for a bit longer on e2e tests.
  2360  func nextCronMinuteBetween(currentTime time.Time) string {
  2361  	nextMinute := currentTime.Add(time.Minute).Minute()
  2362  	if nextMinute == 59 {
  2363  		return "0-1 * * * *"
  2364  	}
  2365  
  2366  	secondMinute := currentTime.Add(2 * time.Minute).Minute()
  2367  	return fmt.Sprintf("%d-%d * * * *", nextMinute, secondMinute)
  2368  }
  2369  
  2370  // Parse a duration string and return a duration struct
  2371  func mustParseDuration(t *testing.T, duration string) time.Duration {
  2372  	d, err := time.ParseDuration(duration)
  2373  	assert.Nil(t, err)
  2374  	return d
  2375  }
  2376  
  2377  // Parse a time string and return a metav1.Time
  2378  func currentTimePlusDuration(t *testing.T, duration string) metav1.Time {
  2379  	d := mustParseDuration(t, duration)
  2380  	currentTimePlusDuration := time.Now().Add(d)
  2381  	return metav1.NewTime(currentTimePlusDuration)
  2382  }
  2383  
  2384  // copyFileToContainer copies a file from the local filesystem to a container in a pod
  2385  // Needs kubectl to be on the file path.
  2386  // May want to replace this with a more robust solution using the Kubernetes client-go library at some point, but since all e2e tests use kubectl, this is a quick solution.
  2387  func copyFileToContainer(t *testing.T, namespace, podName, containerName, srcPath, destPath string) error {
  2388  	cmd := exec.Command("kubectl", "cp", srcPath, fmt.Sprintf("%s/%s:%s", namespace, podName, destPath), "-c", containerName)
  2389  	output, err := cmd.CombinedOutput()
  2390  	if err != nil {
  2391  		t.Logf("kubectl cp failed: %s", string(output))
  2392  		return fmt.Errorf("failed to copy file to container: %w", err)
  2393  	}
  2394  	t.Logf("Successfully copied %s to %s/%s:%s", srcPath, namespace, podName, destPath)
  2395  	return nil
  2396  }