agones.dev/agones@v1.53.0/test/e2e/framework/framework.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 framework is a package helping setting up end-to-end testing across a
    16  // Kubernetes cluster.
    17  package framework
    18  
    19  import (
    20  	"bufio"
    21  	"context"
    22  	"encoding/json"
    23  	"flag"
    24  	"fmt"
    25  	"io"
    26  	"net"
    27  	"os"
    28  	"os/user"
    29  	"path/filepath"
    30  	"strings"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/pkg/errors"
    35  	"github.com/sirupsen/logrus"
    36  	"github.com/spf13/pflag"
    37  	"github.com/spf13/viper"
    38  	"github.com/stretchr/testify/require"
    39  	corev1 "k8s.io/api/core/v1"
    40  	rbacv1 "k8s.io/api/rbac/v1"
    41  	"k8s.io/apimachinery/pkg/api/resource"
    42  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    43  	"k8s.io/apimachinery/pkg/labels"
    44  	k8sruntime "k8s.io/apimachinery/pkg/runtime"
    45  	"k8s.io/apimachinery/pkg/types"
    46  	"k8s.io/apimachinery/pkg/util/wait"
    47  	"k8s.io/client-go/kubernetes"
    48  	"k8s.io/client-go/kubernetes/scheme"
    49  
    50  	// required to use gcloud login see: https://github.com/kubernetes/client-go/issues/242
    51  	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    52  
    53  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    54  	allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
    55  	autoscaling "agones.dev/agones/pkg/apis/autoscaling/v1"
    56  	"agones.dev/agones/pkg/client/clientset/versioned"
    57  	"agones.dev/agones/pkg/util/runtime"
    58  )
    59  
    60  // special labels that can be put on pods to trigger automatic cleanup.
    61  const (
    62  	AutoCleanupLabelKey   = "agones.dev/e2e-test-auto-cleanup"
    63  	AutoCleanupLabelValue = "true"
    64  )
    65  
    66  // NamespaceLabel is the label that is put on all namespaces that are created
    67  // for e2e tests.
    68  var NamespaceLabel = map[string]string{"owner": "e2e-test"}
    69  
    70  // Framework is a testing framework
    71  type Framework struct {
    72  	KubeClient      kubernetes.Interface
    73  	AgonesClient    versioned.Interface
    74  	GameServerImage string
    75  	PullSecret      string
    76  	StressTestLevel int
    77  	PerfOutputDir   string
    78  	Version         string
    79  	Namespace       string
    80  	CloudProduct    string
    81  	WaitForState    time.Duration // default time to wait for state changes, may change based on cloud product.
    82  }
    83  
    84  func newFramework(kubeconfig string, qps float32, burst int) (*Framework, error) {
    85  	logger := runtime.NewLoggerWithSource("framework")
    86  	config, err := runtime.InClusterBuildConfig(logger, kubeconfig)
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "build config from flags failed")
    89  	}
    90  
    91  	if qps > 0 {
    92  		config.QPS = qps
    93  	}
    94  	if burst > 0 {
    95  		config.Burst = burst
    96  	}
    97  
    98  	kubeClient, err := kubernetes.NewForConfig(config)
    99  	if err != nil {
   100  		return nil, errors.Wrap(err, "creating new kube-client failed")
   101  	}
   102  
   103  	agonesClient, err := versioned.NewForConfig(config)
   104  	if err != nil {
   105  		return nil, errors.Wrap(err, "creating new agones-client failed")
   106  	}
   107  
   108  	return &Framework{
   109  		KubeClient:   kubeClient,
   110  		AgonesClient: agonesClient,
   111  	}, nil
   112  }
   113  
   114  const (
   115  	kubeconfigFlag      = "kubeconfig"
   116  	gsimageFlag         = "gameserver-image"
   117  	pullSecretFlag      = "pullsecret"
   118  	stressTestLevelFlag = "stress"
   119  	perfOutputDirFlag   = "perf-output"
   120  	versionFlag         = "version"
   121  	namespaceFlag       = "namespace"
   122  	cloudProductFlag    = "cloud-product"
   123  )
   124  
   125  // ParseTestFlags Parses go test flags separately because pflag package ignores flags with '-test.' prefix
   126  // Related issues:
   127  // https://github.com/spf13/pflag/issues/63
   128  // https://github.com/spf13/pflag/issues/238
   129  func ParseTestFlags() error {
   130  	// if we have a "___" in the arguments path, then this is IntelliJ running the test, so ignore this, as otherwise
   131  	// it breaks.
   132  	if strings.Contains(os.Args[0], "___") {
   133  		logrus.Info("Running test via Intellij. Skipping Test Flag Parsing")
   134  		return nil
   135  	}
   136  
   137  	var testFlags []string
   138  	for _, f := range os.Args[1:] {
   139  		if strings.HasPrefix(f, "-test.") {
   140  			testFlags = append(testFlags, f)
   141  		}
   142  	}
   143  	return flag.CommandLine.Parse(testFlags)
   144  }
   145  
   146  // NewFromFlags sets up the testing framework with the standard command line flags.
   147  func NewFromFlags() (*Framework, error) {
   148  	usr, err := user.Current()
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	viper.SetDefault(kubeconfigFlag, filepath.Join(usr.HomeDir, ".kube", "config"))
   154  	viper.SetDefault(gsimageFlag, "us-docker.pkg.dev/agones-images/examples/simple-game-server:0.39")
   155  	viper.SetDefault(pullSecretFlag, "")
   156  	viper.SetDefault(stressTestLevelFlag, 0)
   157  	viper.SetDefault(perfOutputDirFlag, "")
   158  	viper.SetDefault(versionFlag, "")
   159  	viper.SetDefault(runtime.FeatureGateFlag, "")
   160  	viper.SetDefault(namespaceFlag, "")
   161  	viper.SetDefault(cloudProductFlag, "generic")
   162  
   163  	pflag.String(kubeconfigFlag, viper.GetString(kubeconfigFlag), "kube config path, e.g. $HOME/.kube/config")
   164  	pflag.String(gsimageFlag, viper.GetString(gsimageFlag), "gameserver image to use for those tests")
   165  	pflag.String(pullSecretFlag, viper.GetString(pullSecretFlag), "optional secret to be used for pulling the gameserver and/or Agones SDK sidecar images")
   166  	pflag.Int(stressTestLevelFlag, viper.GetInt(stressTestLevelFlag), "enable stress test at given level 0-100")
   167  	pflag.String(perfOutputDirFlag, viper.GetString(perfOutputDirFlag), "write performance statistics to the specified directory")
   168  	pflag.String(versionFlag, viper.GetString(versionFlag), "agones controller version to be tested, consists of release version plus a short hash of the latest commit")
   169  	pflag.String(namespaceFlag, viper.GetString(namespaceFlag), "namespace is used to isolate test runs to their own namespaces")
   170  	pflag.String(cloudProductFlag, viper.GetString(cloudProductFlag), "cloud product of cluster references by kubeconfig; defaults to 'generic'; options are 'generic', 'gke-autopilot'")
   171  	runtime.FeaturesBindFlags()
   172  	pflag.Parse()
   173  
   174  	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
   175  	runtime.Must(viper.BindEnv(kubeconfigFlag))
   176  	runtime.Must(viper.BindEnv(gsimageFlag))
   177  	runtime.Must(viper.BindEnv(pullSecretFlag))
   178  	runtime.Must(viper.BindEnv(stressTestLevelFlag))
   179  	runtime.Must(viper.BindEnv(perfOutputDirFlag))
   180  	runtime.Must(viper.BindEnv(versionFlag))
   181  	runtime.Must(viper.BindEnv(namespaceFlag))
   182  	runtime.Must(viper.BindEnv(cloudProductFlag))
   183  	runtime.Must(viper.BindPFlags(pflag.CommandLine))
   184  	runtime.Must(runtime.FeaturesBindEnv())
   185  	runtime.Must(runtime.ParseFeaturesFromEnv())
   186  
   187  	framework, err := newFramework(viper.GetString(kubeconfigFlag), 0, 0)
   188  	if err != nil {
   189  		return framework, err
   190  	}
   191  	framework.GameServerImage = viper.GetString(gsimageFlag)
   192  	framework.PullSecret = viper.GetString(pullSecretFlag)
   193  	framework.StressTestLevel = viper.GetInt(stressTestLevelFlag)
   194  	framework.PerfOutputDir = viper.GetString(perfOutputDirFlag)
   195  	framework.Version = viper.GetString(versionFlag)
   196  	framework.Namespace = viper.GetString(namespaceFlag)
   197  	framework.CloudProduct = viper.GetString(cloudProductFlag)
   198  	framework.WaitForState = 5 * time.Minute
   199  	if framework.CloudProduct == "gke-autopilot" {
   200  		framework.WaitForState = 10 * time.Minute // Autopilot can take a little while due to autoscaling, be a little liberal.
   201  	}
   202  
   203  	logrus.WithField("gameServerImage", framework.GameServerImage).
   204  		WithField("pullSecret", framework.PullSecret).
   205  		WithField("stressTestLevel", framework.StressTestLevel).
   206  		WithField("perfOutputDir", framework.PerfOutputDir).
   207  		WithField("version", framework.Version).
   208  		WithField("namespace", framework.Namespace).
   209  		WithField("cloudProduct", framework.CloudProduct).
   210  		WithField("featureGates", runtime.EncodeFeatures()).
   211  		Info("Starting e2e test(s)")
   212  
   213  	return framework, nil
   214  }
   215  
   216  // CreateGameServerAndWaitUntilReady Creates a GameServer and wait for its state to become ready.
   217  func (f *Framework) CreateGameServerAndWaitUntilReady(t *testing.T, ns string, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   218  	t.Helper()
   219  	log := TestLogger(t)
   220  	newGs, err := f.AgonesClient.AgonesV1().GameServers(ns).Create(context.Background(), gs, metav1.CreateOptions{})
   221  	if err != nil {
   222  		return nil, fmt.Errorf("creating %v GameServer instances failed (%v): %v", gs.Spec, gs.Name, err)
   223  	}
   224  
   225  	log.WithField("gs", newGs.ObjectMeta.Name).Info("GameServer created, waiting for Ready")
   226  
   227  	readyGs, err := f.WaitForGameServerState(t, newGs, agonesv1.GameServerStateReady, f.WaitForState)
   228  
   229  	if err != nil {
   230  		return readyGs, fmt.Errorf("waiting for %v GameServer instance readiness timed out (%v): %v",
   231  			gs.Spec, gs.Name, err)
   232  	}
   233  
   234  	expectedPortCount := len(gs.Spec.Ports)
   235  	if expectedPortCount > 0 {
   236  		for _, port := range gs.Spec.Ports {
   237  			if port.Protocol == agonesv1.ProtocolTCPUDP {
   238  				expectedPortCount++
   239  			}
   240  		}
   241  	}
   242  
   243  	if len(readyGs.Status.Ports) != expectedPortCount {
   244  		return readyGs, fmt.Errorf("ready GameServer instance has %d port(s), want %d", len(readyGs.Status.Ports), expectedPortCount)
   245  	}
   246  
   247  	logrus.WithField("gs", newGs.ObjectMeta.Name).Info("GameServer Ready")
   248  
   249  	return readyGs, nil
   250  }
   251  
   252  // WaitForGameServerState Waits untils the gameserver reach a given state before the timeout expires (with a default logger)
   253  func (f *Framework) WaitForGameServerState(t *testing.T, gs *agonesv1.GameServer, state agonesv1.GameServerState,
   254  	timeout time.Duration) (*agonesv1.GameServer, error) {
   255  	t.Helper()
   256  	log := TestLogger(t)
   257  
   258  	var checkGs *agonesv1.GameServer
   259  
   260  	err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeout, true, func(_ context.Context) (bool, error) {
   261  		var err error
   262  		checkGs, err = f.AgonesClient.AgonesV1().GameServers(gs.Namespace).Get(context.Background(), gs.Name, metav1.GetOptions{})
   263  
   264  		if err != nil {
   265  			log.WithError(err).Warn("error retrieving GameServer")
   266  			return false, nil
   267  		}
   268  
   269  		checkState := checkGs.Status.State
   270  		if checkState == state {
   271  			log.WithField("gs", checkGs.ObjectMeta.Name).
   272  				WithField("currentState", checkState).
   273  				WithField("awaitingState", state).Info("GameServer states match")
   274  			return true, nil
   275  		}
   276  		if agonesv1.TerminalGameServerStates[checkState] {
   277  			log.WithField("gs", checkGs.ObjectMeta.Name).
   278  				WithField("currentState", checkState).
   279  				WithField("awaitingState", state).Error("GameServer reached terminal state")
   280  			return false, errors.Errorf("GameServer reached terminal state %s", checkState)
   281  		}
   282  		log.WithField("gs", checkGs.ObjectMeta.Name).
   283  			WithField("currentState", checkState).
   284  			WithField("awaitingState", state).Info("Waiting for states to match")
   285  
   286  		return false, nil
   287  	})
   288  
   289  	return checkGs, errors.Wrapf(err, "waiting for GameServer %v/%v to be %v",
   290  		gs.Namespace, gs.Name, state)
   291  }
   292  
   293  // CycleAllocations repeatedly Allocates a GameServer in the Fleet (if one is available), once every specified period.
   294  // Each Allocated GameServer gets deleted allocDuration after it was Allocated.
   295  // GameServers will continue to be Allocated until a message is passed to the done channel.
   296  func (f *Framework) CycleAllocations(ctx context.Context, t *testing.T, flt *agonesv1.Fleet, period time.Duration, allocDuration time.Duration) {
   297  	err := wait.PollUntilContextCancel(ctx, period, true, func(_ context.Context) (bool, error) {
   298  		gsa := GetAllocation(flt)
   299  		gsa, err := f.AgonesClient.AllocationV1().GameServerAllocations(flt.Namespace).Create(context.Background(), gsa, metav1.CreateOptions{})
   300  		if err != nil || gsa.Status.State != allocationv1.GameServerAllocationAllocated {
   301  			// Ignore error. Could be that the buffer was empty, will try again next cycle.
   302  			return false, nil
   303  		}
   304  
   305  		// Deallocate after allocDuration.
   306  		go func(gsa *allocationv1.GameServerAllocation) {
   307  			time.Sleep(allocDuration)
   308  			err := f.AgonesClient.AgonesV1().GameServers(gsa.Namespace).Delete(context.Background(), gsa.Status.GameServerName, metav1.DeleteOptions{})
   309  			require.NoError(t, err)
   310  		}(gsa)
   311  
   312  		return false, nil
   313  	})
   314  	// Ignore wait timeout error, will always be returned when the context is cancelled at the end of the test.
   315  	if !wait.Interrupted(err) {
   316  		require.NoError(t, err)
   317  	}
   318  }
   319  
   320  // ScaleFleet will scale a Fleet with retries to a specified replica size.
   321  func (f *Framework) ScaleFleet(t *testing.T, log *logrus.Entry, flt *agonesv1.Fleet, replicas int32) {
   322  	fleets := f.AgonesClient.AgonesV1().Fleets(f.Namespace)
   323  	ctx := context.Background()
   324  
   325  	require.Eventuallyf(t, func() bool {
   326  		flt, err := fleets.Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{})
   327  		if err != nil {
   328  			log.WithError(err).Info("Could not get Fleet")
   329  			return false
   330  		}
   331  
   332  		fltCopy := flt.DeepCopy()
   333  		fltCopy.Spec.Replicas = replicas
   334  		_, err = fleets.Update(ctx, fltCopy, metav1.UpdateOptions{})
   335  		if err != nil {
   336  			log.WithError(err).Info("Could not scale Fleet")
   337  			return false
   338  		}
   339  
   340  		return true
   341  	}, 5*time.Minute, time.Second, "Could not scale Fleet %s", flt.ObjectMeta.Name)
   342  }
   343  
   344  // AssertFleetCondition waits for the Fleet to be in a specific condition or fails the test if the condition can't be met in 5 minutes.
   345  func (f *Framework) AssertFleetCondition(t *testing.T, flt *agonesv1.Fleet, condition func(*logrus.Entry, *agonesv1.Fleet) bool) {
   346  	err := f.WaitForFleetCondition(t, flt, condition)
   347  	require.NoError(t, err, "error waiting for fleet condition on fleet: %v", flt.Name)
   348  }
   349  
   350  // WaitForFleetCondition waits for the Fleet to be in a specific condition or returns an error if the condition can't be met in 5 minutes.
   351  func (f *Framework) WaitForFleetCondition(t *testing.T, flt *agonesv1.Fleet, condition func(*logrus.Entry, *agonesv1.Fleet) bool) error {
   352  	log := TestLogger(t).WithField("fleet", flt.Name)
   353  	log.Info("waiting for fleet condition")
   354  	err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, f.WaitForState, true, func(_ context.Context) (bool, error) {
   355  		fleet, err := f.AgonesClient.AgonesV1().Fleets(flt.ObjectMeta.Namespace).Get(context.Background(), flt.ObjectMeta.Name, metav1.GetOptions{})
   356  		if err != nil {
   357  			return true, err
   358  		}
   359  
   360  		return condition(log, fleet), nil
   361  	})
   362  	if err != nil {
   363  		// save this to be returned later.
   364  		resultErr := err
   365  		log.WithField("fleetStatus", fmt.Sprintf("%+v", flt.Status)).WithError(err).
   366  			Info("error waiting for fleet condition, dumping Fleet and Gameserver data")
   367  
   368  		f.LogEvents(t, log, flt.ObjectMeta.Namespace, flt)
   369  
   370  		gsList, err := f.ListGameServersFromFleet(flt)
   371  		require.NoError(t, err)
   372  
   373  		for i := range gsList {
   374  			gs := gsList[i]
   375  			log = log.WithField("gs", gs.ObjectMeta.Name)
   376  			log.WithField("status", fmt.Sprintf("%+v", gs.Status)).Info("GameServer state dump:")
   377  			f.LogEvents(t, log, gs.ObjectMeta.Namespace, &gs)
   378  		}
   379  
   380  		return resultErr
   381  	}
   382  	return nil
   383  }
   384  
   385  // WaitForFleetAutoScalerCondition waits for the FleetAutoscaler to be in a specific condition or fails the test if the condition can't be met in 2 minutes.
   386  // nolint: dupl
   387  func (f *Framework) WaitForFleetAutoScalerCondition(t *testing.T, fas *autoscaling.FleetAutoscaler, condition func(log *logrus.Entry, fas *autoscaling.FleetAutoscaler) bool) {
   388  	log := TestLogger(t).WithField("fleetautoscaler", fas.Name)
   389  	log.Info("waiting for fleetautoscaler condition")
   390  	err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 2*time.Minute, true, func(_ context.Context) (bool, error) {
   391  		fleetautoscaler, err := f.AgonesClient.AutoscalingV1().FleetAutoscalers(fas.ObjectMeta.Namespace).Get(context.Background(), fas.ObjectMeta.Name, metav1.GetOptions{})
   392  		if err != nil {
   393  			return true, err
   394  		}
   395  
   396  		return condition(log, fleetautoscaler), nil
   397  	})
   398  	require.NoError(t, err, "error waiting for fleetautoscaler condition on fleetautoscaler %v", fas.Name)
   399  }
   400  
   401  // ListGameServersFromFleet lists GameServers from a particular fleet
   402  func (f *Framework) ListGameServersFromFleet(flt *agonesv1.Fleet) ([]agonesv1.GameServer, error) {
   403  	var results []agonesv1.GameServer
   404  
   405  	opts := metav1.ListOptions{LabelSelector: labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}.String()}
   406  	gsSetList, err := f.AgonesClient.AgonesV1().GameServerSets(flt.ObjectMeta.Namespace).List(context.Background(), opts)
   407  	if err != nil {
   408  		return results, err
   409  	}
   410  
   411  	for i := range gsSetList.Items {
   412  		gsSet := &gsSetList.Items[i]
   413  		opts := metav1.ListOptions{LabelSelector: labels.Set{agonesv1.GameServerSetGameServerLabel: gsSet.ObjectMeta.Name}.String()}
   414  		gsList, err := f.AgonesClient.AgonesV1().GameServers(flt.ObjectMeta.Namespace).List(context.Background(), opts)
   415  		if err != nil {
   416  			return results, err
   417  		}
   418  
   419  		results = append(results, gsList.Items...)
   420  	}
   421  
   422  	return results, nil
   423  }
   424  
   425  // FleetReadyCount returns the ready count in a fleet
   426  func FleetReadyCount(amount int32) func(*logrus.Entry, *agonesv1.Fleet) bool {
   427  	return func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
   428  		log.WithField("fleetStatus", fmt.Sprintf("%+v", fleet.Status)).WithField("fleet", fleet.ObjectMeta.Name).WithField("expected", amount).Info("Checking Fleet Ready replicas")
   429  		return fleet.Status.ReadyReplicas == amount
   430  	}
   431  }
   432  
   433  // WaitForFleetGameServersCondition waits for all GameServers for a given fleet to match
   434  // a condition specified by a callback.
   435  func (f *Framework) WaitForFleetGameServersCondition(flt *agonesv1.Fleet,
   436  	cond func(server *agonesv1.GameServer) bool) error {
   437  	return f.WaitForFleetGameServerListCondition(flt,
   438  		func(servers []agonesv1.GameServer) bool {
   439  			for i := range servers {
   440  				gs := &servers[i]
   441  				if !cond(gs) {
   442  					return false
   443  				}
   444  			}
   445  			return true
   446  		})
   447  }
   448  
   449  // WaitForFleetGameServerListCondition waits for the list of GameServers to match a condition
   450  // specified by a callback and the size of GameServers to match fleet's Spec.Replicas.
   451  func (f *Framework) WaitForFleetGameServerListCondition(flt *agonesv1.Fleet,
   452  	cond func(servers []agonesv1.GameServer) bool) error {
   453  	return wait.PollUntilContextTimeout(context.Background(), 2*time.Second, f.WaitForState, true, func(_ context.Context) (done bool, err error) {
   454  		gsList, err := f.ListGameServersFromFleet(flt)
   455  		if err != nil {
   456  			return false, err
   457  		}
   458  		if int32(len(gsList)) != flt.Spec.Replicas {
   459  			return false, nil
   460  		}
   461  		return cond(gsList), nil
   462  	})
   463  }
   464  
   465  // NewStatsCollector returns new instance of statistics collector,
   466  // which can be used to emit performance statistics for load tests and stress tests.
   467  func (f *Framework) NewStatsCollector(name, version string) *StatsCollector {
   468  	if f.StressTestLevel > 0 {
   469  		name = fmt.Sprintf("stress_%v_%v", f.StressTestLevel, name)
   470  	}
   471  	return &StatsCollector{name: name, outputDir: f.PerfOutputDir, version: version}
   472  }
   473  
   474  // CleanUp Delete all Agones resources in a given namespace.
   475  func (f *Framework) CleanUp(ns string) error {
   476  	logrus.Info("Cleaning up now.")
   477  	defer logrus.Info("Finished cleanup.")
   478  	agonesV1 := f.AgonesClient.AgonesV1()
   479  	deleteOptions := metav1.DeleteOptions{}
   480  	listOptions := metav1.ListOptions{}
   481  
   482  	// find and delete pods created by tests and labeled with our special label
   483  	pods := f.KubeClient.CoreV1().Pods(ns)
   484  	ctx := context.Background()
   485  	podList, err := pods.List(ctx, metav1.ListOptions{
   486  		LabelSelector: AutoCleanupLabelKey + "=" + AutoCleanupLabelValue,
   487  	})
   488  	if err != nil {
   489  		return err
   490  	}
   491  
   492  	for i := range podList.Items {
   493  		p := &podList.Items[i]
   494  		if err := pods.Delete(ctx, p.ObjectMeta.Name, deleteOptions); err != nil {
   495  			return err
   496  		}
   497  	}
   498  
   499  	err = agonesV1.Fleets(ns).DeleteCollection(ctx, deleteOptions, listOptions)
   500  	if err != nil {
   501  		return err
   502  	}
   503  
   504  	err = f.AgonesClient.AutoscalingV1().FleetAutoscalers(ns).DeleteCollection(ctx, deleteOptions, listOptions)
   505  	if err != nil {
   506  		return err
   507  	}
   508  
   509  	return agonesV1.GameServers(ns).
   510  		DeleteCollection(ctx, deleteOptions, listOptions)
   511  }
   512  
   513  // CreateAndApplyAllocation creates and applies an Allocation to a Fleet
   514  func (f *Framework) CreateAndApplyAllocation(t *testing.T, flt *agonesv1.Fleet) *allocationv1.GameServerAllocation {
   515  	gsa := GetAllocation(flt)
   516  	gsa, err := f.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(context.Background(), gsa, metav1.CreateOptions{})
   517  	require.NoError(t, err)
   518  	require.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(gsa.Status.State))
   519  	return gsa
   520  }
   521  
   522  // SendGameServerUDP sends a message to a gameserver and returns its reply
   523  // finds the first udp port from the spec to send the message to,
   524  // returns error if no Ports were allocated
   525  func (f *Framework) SendGameServerUDP(t *testing.T, gs *agonesv1.GameServer, msg string) (string, error) {
   526  	if len(gs.Status.Ports) == 0 {
   527  		return "", errors.New("Empty Ports array")
   528  	}
   529  
   530  	// use first udp port
   531  	for _, p := range gs.Spec.Ports {
   532  		if p.Protocol == corev1.ProtocolUDP {
   533  			return f.SendGameServerUDPToPort(t, gs, p.Name, msg)
   534  		}
   535  	}
   536  	return "", errors.New("No UDP ports")
   537  }
   538  
   539  // SendGameServerUDPToPort sends a message to a gameserver at the named port and returns its reply
   540  // returns error if no Ports were allocated or a port of the specified name doesn't exist
   541  func (f *Framework) SendGameServerUDPToPort(t *testing.T, gs *agonesv1.GameServer, portName string, msg string) (string, error) {
   542  	log := TestLogger(t)
   543  	if len(gs.Status.Ports) == 0 {
   544  		return "", errors.New("Empty Ports array")
   545  	}
   546  	var port agonesv1.GameServerStatusPort
   547  	for _, p := range gs.Status.Ports {
   548  		if p.Name == portName {
   549  			port = p
   550  		}
   551  	}
   552  	address := fmt.Sprintf("%s:%d", gs.Status.Address, port.Port)
   553  	reply, err := f.SendUDP(t, address, msg)
   554  
   555  	if err != nil {
   556  		log.WithField("gs", gs.ObjectMeta.Name).WithField("status", fmt.Sprintf("%+v", gs.Status)).Info("Failed to send UDP packet to GameServer. Dumping Events!")
   557  		f.LogEvents(t, log, gs.ObjectMeta.Namespace, gs)
   558  	}
   559  
   560  	return reply, err
   561  }
   562  
   563  // SendUDP sends a message to an address, and returns its reply if
   564  // it returns one in 10 seconds. Will retry 5 times, in case UDP packets drop.
   565  func (f *Framework) SendUDP(t *testing.T, address, msg string) (string, error) {
   566  	log := TestLogger(t).WithField("address", address)
   567  	b := make([]byte, 1024)
   568  	var n int
   569  	// sometimes we get I/O timeout, so let's do a retry
   570  	err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, time.Minute, true, func(_ context.Context) (bool, error) {
   571  		conn, err := net.Dial("udp", address)
   572  		if err != nil {
   573  			log.WithError(err).Info("could not dial address")
   574  			return false, nil
   575  		}
   576  
   577  		defer func() {
   578  			err = conn.Close()
   579  		}()
   580  
   581  		_, err = conn.Write([]byte(msg))
   582  		if err != nil {
   583  			log.WithError(err).Info("could not write message to address")
   584  			return false, nil
   585  		}
   586  
   587  		err = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
   588  		if err != nil {
   589  			log.WithError(err).Info("Could not set read deadline")
   590  			return false, nil
   591  		}
   592  
   593  		n, err = conn.Read(b)
   594  		if err != nil {
   595  			log.WithError(err).Info("Could not read from address")
   596  		}
   597  
   598  		return err == nil, nil
   599  	})
   600  
   601  	if err != nil {
   602  		return "", errors.Wrap(err, "timed out attempting to send UDP packet to address")
   603  	}
   604  
   605  	return string(b[:n]), nil
   606  }
   607  
   608  // SendGameServerTCP sends a message to a gameserver and returns its reply
   609  // finds the first tcp port from the spec to send the message to,
   610  // returns error if no Ports were allocated
   611  func SendGameServerTCP(gs *agonesv1.GameServer, msg string) (string, error) {
   612  	if len(gs.Status.Ports) == 0 {
   613  		return "", errors.New("Empty Ports array")
   614  	}
   615  
   616  	// use first tcp port
   617  	for _, p := range gs.Spec.Ports {
   618  		if p.Protocol == corev1.ProtocolTCP {
   619  			return SendGameServerTCPToPort(gs, p.Name, msg)
   620  		}
   621  	}
   622  	return "", errors.New("No TCP ports")
   623  }
   624  
   625  // SendGameServerTCPToPort sends a message to a gameserver at the named port and returns its reply
   626  // returns error if no Ports were allocated or a port of the specified name doesn't exist
   627  func SendGameServerTCPToPort(gs *agonesv1.GameServer, portName string, msg string) (string, error) {
   628  	if len(gs.Status.Ports) == 0 {
   629  		return "", errors.New("Empty Ports array")
   630  	}
   631  	var port agonesv1.GameServerStatusPort
   632  	for _, p := range gs.Status.Ports {
   633  		if p.Name == portName {
   634  			port = p
   635  		}
   636  	}
   637  	address := fmt.Sprintf("%s:%d", gs.Status.Address, port.Port)
   638  	return SendTCP(address, msg)
   639  }
   640  
   641  // SendTCP sends a message to an address, and returns its reply if
   642  // it returns one in 30 seconds
   643  func SendTCP(address, msg string) (string, error) {
   644  	conn, err := net.Dial("tcp", address)
   645  	if err != nil {
   646  		return "", err
   647  	}
   648  
   649  	if err := conn.SetReadDeadline(time.Now().Add(30 * time.Second)); err != nil {
   650  		return "", err
   651  	}
   652  
   653  	defer func() {
   654  		if err := conn.Close(); err != nil {
   655  			logrus.Warn("Could not close TCP connection")
   656  		}
   657  	}()
   658  
   659  	// writes to the tcp connection
   660  	_, err = fmt.Fprintln(conn, msg)
   661  	if err != nil {
   662  		return "", err
   663  	}
   664  
   665  	response, err := bufio.NewReader(conn).ReadString('\n')
   666  	if err != nil {
   667  		return "", err
   668  	}
   669  
   670  	return response, nil
   671  }
   672  
   673  // GetAllocation returns a GameServerAllocation that is looking for a Ready
   674  // GameServer from this fleet.
   675  func GetAllocation(f *agonesv1.Fleet) *allocationv1.GameServerAllocation {
   676  	// get an allocation
   677  	return &allocationv1.GameServerAllocation{
   678  		Spec: allocationv1.GameServerAllocationSpec{
   679  			Selectors: []allocationv1.GameServerSelector{
   680  				{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}},
   681  			},
   682  		}}
   683  }
   684  
   685  // CreateNamespace creates a namespace and a service account in the test cluster
   686  func (f *Framework) CreateNamespace(namespace string) error {
   687  	kubeCore := f.KubeClient.CoreV1()
   688  	ns := &corev1.Namespace{
   689  		ObjectMeta: metav1.ObjectMeta{
   690  			Name:   namespace,
   691  			Labels: NamespaceLabel,
   692  		},
   693  	}
   694  
   695  	ctx := context.Background()
   696  
   697  	options := metav1.CreateOptions{}
   698  	if _, err := kubeCore.Namespaces().Create(ctx, ns, options); err != nil {
   699  		return errors.Errorf("creating namespace %s failed: %s", namespace, err.Error())
   700  	}
   701  	logrus.Infof("Namespace %s is created", namespace)
   702  
   703  	saName := "agones-sdk"
   704  	if _, err := kubeCore.ServiceAccounts(namespace).Create(ctx, &corev1.ServiceAccount{
   705  		ObjectMeta: metav1.ObjectMeta{
   706  			Name:      saName,
   707  			Namespace: namespace,
   708  			Labels:    map[string]string{"app": "agones"},
   709  		},
   710  	}, options); err != nil {
   711  		err = errors.Errorf("creating ServiceAccount %s in namespace %s failed: %s", saName, namespace, err.Error())
   712  		derr := f.DeleteNamespace(namespace)
   713  		if derr != nil {
   714  			return errors.Wrap(err, derr.Error())
   715  		}
   716  		return err
   717  	}
   718  	logrus.Infof("ServiceAccount %s/%s is created", namespace, saName)
   719  
   720  	rb := &rbacv1.RoleBinding{
   721  		ObjectMeta: metav1.ObjectMeta{
   722  			Name:      "agones-sdk-access",
   723  			Namespace: namespace,
   724  			Labels:    map[string]string{"app": "agones"},
   725  		},
   726  		RoleRef: rbacv1.RoleRef{
   727  			APIGroup: "rbac.authorization.k8s.io",
   728  			Kind:     "ClusterRole",
   729  			Name:     "agones-sdk",
   730  		},
   731  		Subjects: []rbacv1.Subject{
   732  			{
   733  				Kind:      "ServiceAccount",
   734  				Name:      saName,
   735  				Namespace: namespace,
   736  			},
   737  		},
   738  	}
   739  	if _, err := f.KubeClient.RbacV1().RoleBindings(namespace).Create(ctx, rb, options); err != nil {
   740  		err = errors.Errorf("creating RoleBinding for service account %q in namespace %q failed: %s", saName, namespace, err.Error())
   741  		derr := f.DeleteNamespace(namespace)
   742  		if derr != nil {
   743  			return errors.Wrap(err, derr.Error())
   744  		}
   745  		return err
   746  	}
   747  	logrus.Infof("RoleBinding %s/%s is created", namespace, rb.Name)
   748  
   749  	return nil
   750  }
   751  
   752  // DeleteNamespace deletes a namespace from the test cluster
   753  func (f *Framework) DeleteNamespace(namespace string) error {
   754  	kubeCore := f.KubeClient.CoreV1()
   755  	ctx := context.Background()
   756  
   757  	// Remove finalizers
   758  	pods, err := kubeCore.Pods(namespace).List(ctx, metav1.ListOptions{})
   759  	if err != nil {
   760  		return errors.Errorf("listing pods in namespace %s failed: %s", namespace, err)
   761  	}
   762  	for i := range pods.Items {
   763  		pod := &pods.Items[i]
   764  		if len(pod.Finalizers) > 0 {
   765  			pod.Finalizers = nil
   766  			payload := []patchRemoveNoValue{{
   767  				Op:   "remove",
   768  				Path: "/metadata/finalizers",
   769  			}}
   770  			payloadBytes, _ := json.Marshal(payload)
   771  			if _, err := kubeCore.Pods(namespace).Patch(ctx, pod.Name, types.JSONPatchType, payloadBytes, metav1.PatchOptions{}); err != nil {
   772  				return errors.Wrapf(err, "updating pod %s failed", pod.GetName())
   773  			}
   774  		}
   775  	}
   776  
   777  	if err := kubeCore.Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}); err != nil {
   778  		return errors.Wrapf(err, "deleting namespace %s failed", namespace)
   779  	}
   780  	logrus.Infof("Namespace %s is deleted", namespace)
   781  	return nil
   782  }
   783  
   784  type patchRemoveNoValue struct {
   785  	Op   string `json:"op"`
   786  	Path string `json:"path"`
   787  }
   788  
   789  // DefaultGameServer provides a default GameServer fixture, based on parameters
   790  // passed to the Test Framework.
   791  func (f *Framework) DefaultGameServer(namespace string) *agonesv1.GameServer {
   792  	gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{GenerateName: "game-server", Namespace: namespace},
   793  		Spec: agonesv1.GameServerSpec{
   794  			Container: "game-server",
   795  			Ports: []agonesv1.GameServerPort{{
   796  				ContainerPort: 7654,
   797  				Name:          "udp-port",
   798  				PortPolicy:    agonesv1.Dynamic,
   799  				Protocol:      corev1.ProtocolUDP,
   800  			}},
   801  			Template: corev1.PodTemplateSpec{
   802  				Spec: corev1.PodSpec{
   803  					Containers: []corev1.Container{{
   804  						Name:            "game-server",
   805  						Image:           f.GameServerImage,
   806  						ImagePullPolicy: corev1.PullIfNotPresent,
   807  						Resources: corev1.ResourceRequirements{
   808  							Requests: corev1.ResourceList{
   809  								corev1.ResourceCPU:    resource.MustParse("30m"),
   810  								corev1.ResourceMemory: resource.MustParse("32Mi"),
   811  							},
   812  							Limits: corev1.ResourceList{
   813  								corev1.ResourceCPU:    resource.MustParse("30m"),
   814  								corev1.ResourceMemory: resource.MustParse("32Mi"),
   815  							},
   816  						},
   817  					}},
   818  				},
   819  			},
   820  		},
   821  	}
   822  
   823  	if f.PullSecret != "" {
   824  		gs.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{
   825  			Name: f.PullSecret}}
   826  	}
   827  
   828  	return gs
   829  }
   830  
   831  // LogEvents logs all the events for a given Kubernetes objects. Useful for debugging why something
   832  // went wrong.
   833  func (f *Framework) LogEvents(t *testing.T, log *logrus.Entry, namespace string, objOrRef k8sruntime.Object) {
   834  	log.WithField("kind", objOrRef.GetObjectKind().GroupVersionKind().Kind).Info("Dumping Events:")
   835  	events, err := f.KubeClient.CoreV1().Events(namespace).Search(scheme.Scheme, objOrRef)
   836  	require.NoError(t, err, "error searching for events")
   837  	for i := range events.Items {
   838  		event := events.Items[i]
   839  		log.WithField("lastTimestamp", event.LastTimestamp).WithField("type", event.Type).WithField("reason", event.Reason).WithField("message", event.Message).Info("Event!")
   840  	}
   841  }
   842  
   843  // LogPodContainers takes a Pod as an argument and attempts to output the current and previous logs from each container
   844  // in that Pod It uses the framework's KubeClient to retrieve the logs and outputs them using the provided logger.
   845  func (f *Framework) LogPodContainers(t *testing.T, pod *corev1.Pod) {
   846  	log := TestLogger(t)
   847  	log.WithField("pod", pod.Name).WithField("namespace", pod.Namespace).Info("Logs for Pod:")
   848  
   849  	// sub-function so defer will fire on each printLogs, rather than at the end.
   850  	printLogs := func(container corev1.Container, previous bool) {
   851  		logOptions := &corev1.PodLogOptions{
   852  			Container: container.Name,
   853  			Follow:    false,
   854  			Previous:  previous,
   855  		}
   856  
   857  		req := f.KubeClient.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, logOptions)
   858  		podLogs, err := req.Stream(context.Background())
   859  		log = log.WithField("options", logOptions)
   860  
   861  		if err != nil {
   862  			log.WithError(err).Warn("Error opening log stream for container")
   863  			return
   864  		}
   865  		defer podLogs.Close() // nolint:errcheck,staticcheck
   866  
   867  		logBytes, err := io.ReadAll(podLogs)
   868  		if err != nil {
   869  			log.WithError(err).WithField("options", logOptions).Warn("Error reading logs for container")
   870  			return
   871  		}
   872  
   873  		log.Info("---Logs for container---")
   874  		lines := strings.Split(string(logBytes), "\n")
   875  		for _, line := range lines {
   876  			if line == "" {
   877  				continue
   878  			}
   879  			log.Info(line)
   880  		}
   881  		log.Info("---End of container logs---")
   882  	}
   883  
   884  	// run through the container list twice, so we group current vs previous logs nicely.
   885  	for _, container := range pod.Spec.Containers {
   886  		printLogs(container, false)
   887  	}
   888  
   889  	for _, container := range pod.Spec.Containers {
   890  		printLogs(container, true)
   891  	}
   892  
   893  }
   894  
   895  // SkipOnCloudProduct skips the test if the e2e was invoked with --cloud-product=<product>.
   896  func (f *Framework) SkipOnCloudProduct(t *testing.T, product, reason string) {
   897  	if f.CloudProduct == product {
   898  		t.Skipf("skipping test on cloud product %s: %s", product, reason)
   899  	}
   900  }
   901  
   902  // TestLogger returns the standard logger for helper functions.
   903  func TestLogger(t *testing.T) *logrus.Entry {
   904  	return logrus.WithField("test", t.Name())
   905  }