agones.dev/agones@v1.54.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  		// Autopilot can take a little while due to autoscaling, be a little liberal.
   201  		// Keeping it under 10m so we don't get stack track dumps at 10m as unit tests can't be extended past 10m.
   202  		framework.WaitForState = 8 * time.Minute
   203  	}
   204  
   205  	logrus.WithField("gameServerImage", framework.GameServerImage).
   206  		WithField("pullSecret", framework.PullSecret).
   207  		WithField("stressTestLevel", framework.StressTestLevel).
   208  		WithField("perfOutputDir", framework.PerfOutputDir).
   209  		WithField("version", framework.Version).
   210  		WithField("namespace", framework.Namespace).
   211  		WithField("cloudProduct", framework.CloudProduct).
   212  		WithField("featureGates", runtime.EncodeFeatures()).
   213  		Info("Starting e2e test(s)")
   214  
   215  	return framework, nil
   216  }
   217  
   218  // CreateGameServerAndWaitUntilReady Creates a GameServer and wait for its state to become ready.
   219  func (f *Framework) CreateGameServerAndWaitUntilReady(t *testing.T, ns string, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   220  	t.Helper()
   221  	log := TestLogger(t)
   222  	newGs, err := f.AgonesClient.AgonesV1().GameServers(ns).Create(context.Background(), gs, metav1.CreateOptions{})
   223  	if err != nil {
   224  		return nil, fmt.Errorf("creating %v GameServer instances failed (%v): %v", gs.Spec, gs.Name, err)
   225  	}
   226  
   227  	log.WithField("gs", newGs.ObjectMeta.Name).Info("GameServer created, waiting for Ready")
   228  
   229  	readyGs, err := f.WaitForGameServerState(t, newGs, agonesv1.GameServerStateReady, f.WaitForState)
   230  
   231  	if err != nil {
   232  		return readyGs, fmt.Errorf("waiting for %v GameServer instance readiness timed out (%v): %v",
   233  			gs.Spec, gs.Name, err)
   234  	}
   235  
   236  	expectedPortCount := len(gs.Spec.Ports)
   237  	if expectedPortCount > 0 {
   238  		for _, port := range gs.Spec.Ports {
   239  			if port.Protocol == agonesv1.ProtocolTCPUDP {
   240  				expectedPortCount++
   241  			}
   242  		}
   243  	}
   244  
   245  	if len(readyGs.Status.Ports) != expectedPortCount {
   246  		return readyGs, fmt.Errorf("ready GameServer instance has %d port(s), want %d", len(readyGs.Status.Ports), expectedPortCount)
   247  	}
   248  
   249  	logrus.WithField("gs", newGs.ObjectMeta.Name).Info("GameServer Ready")
   250  
   251  	return readyGs, nil
   252  }
   253  
   254  // WaitForGameServerState Waits untils the gameserver reach a given state before the timeout expires (with a default logger)
   255  func (f *Framework) WaitForGameServerState(t *testing.T, gs *agonesv1.GameServer, state agonesv1.GameServerState,
   256  	timeout time.Duration) (*agonesv1.GameServer, error) {
   257  	t.Helper()
   258  	log := TestLogger(t)
   259  
   260  	var checkGs *agonesv1.GameServer
   261  
   262  	err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeout, true, func(_ context.Context) (bool, error) {
   263  		var err error
   264  		checkGs, err = f.AgonesClient.AgonesV1().GameServers(gs.Namespace).Get(context.Background(), gs.Name, metav1.GetOptions{})
   265  
   266  		if err != nil {
   267  			log.WithError(err).Warn("error retrieving GameServer")
   268  			return false, nil
   269  		}
   270  
   271  		checkState := checkGs.Status.State
   272  		if checkState == state {
   273  			log.WithField("gs", checkGs.ObjectMeta.Name).
   274  				WithField("currentState", checkState).
   275  				WithField("awaitingState", state).Info("GameServer states match")
   276  			return true, nil
   277  		}
   278  		if agonesv1.TerminalGameServerStates[checkState] {
   279  			log.WithField("gs", checkGs.ObjectMeta.Name).
   280  				WithField("currentState", checkState).
   281  				WithField("awaitingState", state).Error("GameServer reached terminal state")
   282  			return false, errors.Errorf("GameServer reached terminal state %s", checkState)
   283  		}
   284  		log.WithField("gs", checkGs.ObjectMeta.Name).
   285  			WithField("currentState", checkState).
   286  			WithField("awaitingState", state).Info("Waiting for states to match")
   287  
   288  		return false, nil
   289  	})
   290  
   291  	return checkGs, errors.Wrapf(err, "waiting for GameServer %v/%v to be %v",
   292  		gs.Namespace, gs.Name, state)
   293  }
   294  
   295  // CycleAllocations repeatedly Allocates a GameServer in the Fleet (if one is available), once every specified period.
   296  // Each Allocated GameServer gets deleted allocDuration after it was Allocated.
   297  // GameServers will continue to be Allocated until a message is passed to the done channel.
   298  func (f *Framework) CycleAllocations(ctx context.Context, t *testing.T, flt *agonesv1.Fleet, period time.Duration, allocDuration time.Duration) {
   299  	err := wait.PollUntilContextCancel(ctx, period, true, func(_ context.Context) (bool, error) {
   300  		gsa := GetAllocation(flt)
   301  		gsa, err := f.AgonesClient.AllocationV1().GameServerAllocations(flt.Namespace).Create(context.Background(), gsa, metav1.CreateOptions{})
   302  		if err != nil || gsa.Status.State != allocationv1.GameServerAllocationAllocated {
   303  			// Ignore error. Could be that the buffer was empty, will try again next cycle.
   304  			return false, nil
   305  		}
   306  
   307  		// Deallocate after allocDuration.
   308  		go func(gsa *allocationv1.GameServerAllocation) {
   309  			time.Sleep(allocDuration)
   310  			err := f.AgonesClient.AgonesV1().GameServers(gsa.Namespace).Delete(context.Background(), gsa.Status.GameServerName, metav1.DeleteOptions{})
   311  			require.NoError(t, err)
   312  		}(gsa)
   313  
   314  		return false, nil
   315  	})
   316  	// Ignore wait timeout error, will always be returned when the context is cancelled at the end of the test.
   317  	if !wait.Interrupted(err) {
   318  		require.NoError(t, err)
   319  	}
   320  }
   321  
   322  // ScaleFleet will scale a Fleet with retries to a specified replica size.
   323  func (f *Framework) ScaleFleet(t *testing.T, log *logrus.Entry, flt *agonesv1.Fleet, replicas int32) {
   324  	fleets := f.AgonesClient.AgonesV1().Fleets(f.Namespace)
   325  	ctx := context.Background()
   326  
   327  	require.Eventuallyf(t, func() bool {
   328  		flt, err := fleets.Get(ctx, flt.ObjectMeta.Name, metav1.GetOptions{})
   329  		if err != nil {
   330  			log.WithError(err).Info("Could not get Fleet")
   331  			return false
   332  		}
   333  
   334  		fltCopy := flt.DeepCopy()
   335  		fltCopy.Spec.Replicas = replicas
   336  		_, err = fleets.Update(ctx, fltCopy, metav1.UpdateOptions{})
   337  		if err != nil {
   338  			log.WithError(err).Info("Could not scale Fleet")
   339  			return false
   340  		}
   341  
   342  		return true
   343  	}, 5*time.Minute, time.Second, "Could not scale Fleet %s", flt.ObjectMeta.Name)
   344  }
   345  
   346  // 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.
   347  func (f *Framework) AssertFleetCondition(t *testing.T, flt *agonesv1.Fleet, condition func(*logrus.Entry, *agonesv1.Fleet) bool) {
   348  	err := f.WaitForFleetCondition(t, flt, condition)
   349  	require.NoError(t, err, "error waiting for fleet condition on fleet: %v", flt.Name)
   350  }
   351  
   352  // 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.
   353  func (f *Framework) WaitForFleetCondition(t *testing.T, flt *agonesv1.Fleet, condition func(*logrus.Entry, *agonesv1.Fleet) bool) error {
   354  	log := TestLogger(t).WithField("fleet", flt.Name)
   355  	log.Info("waiting for fleet condition")
   356  	err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, f.WaitForState, true, func(_ context.Context) (bool, error) {
   357  		fleet, err := f.AgonesClient.AgonesV1().Fleets(flt.ObjectMeta.Namespace).Get(context.Background(), flt.ObjectMeta.Name, metav1.GetOptions{})
   358  		if err != nil {
   359  			return true, err
   360  		}
   361  
   362  		return condition(log, fleet), nil
   363  	})
   364  	if err != nil {
   365  		// save this to be returned later.
   366  		resultErr := err
   367  		log.WithField("fleetStatus", fmt.Sprintf("%+v", flt.Status)).WithError(err).
   368  			Info("error waiting for fleet condition, dumping Fleet and Gameserver data")
   369  
   370  		f.LogEvents(t, log, flt.ObjectMeta.Namespace, flt)
   371  
   372  		gsList, err := f.ListGameServersFromFleet(flt)
   373  		require.NoError(t, err)
   374  
   375  		for i := range gsList {
   376  			gs := gsList[i]
   377  			log = log.WithField("gs", gs.ObjectMeta.Name)
   378  			log.WithField("status", fmt.Sprintf("%+v", gs.Status)).Info("GameServer state dump:")
   379  			f.LogEvents(t, log, gs.ObjectMeta.Namespace, &gs)
   380  		}
   381  
   382  		return resultErr
   383  	}
   384  	return nil
   385  }
   386  
   387  // 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.
   388  // nolint: dupl
   389  func (f *Framework) WaitForFleetAutoScalerCondition(t *testing.T, fas *autoscaling.FleetAutoscaler, condition func(log *logrus.Entry, fas *autoscaling.FleetAutoscaler) bool) {
   390  	log := TestLogger(t).WithField("fleetautoscaler", fas.Name)
   391  	log.Info("waiting for fleetautoscaler condition")
   392  	err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 2*time.Minute, true, func(_ context.Context) (bool, error) {
   393  		fleetautoscaler, err := f.AgonesClient.AutoscalingV1().FleetAutoscalers(fas.ObjectMeta.Namespace).Get(context.Background(), fas.ObjectMeta.Name, metav1.GetOptions{})
   394  		if err != nil {
   395  			return true, err
   396  		}
   397  
   398  		return condition(log, fleetautoscaler), nil
   399  	})
   400  	require.NoError(t, err, "error waiting for fleetautoscaler condition on fleetautoscaler %v", fas.Name)
   401  }
   402  
   403  // ListGameServersFromFleet lists GameServers from a particular fleet
   404  func (f *Framework) ListGameServersFromFleet(flt *agonesv1.Fleet) ([]agonesv1.GameServer, error) {
   405  	var results []agonesv1.GameServer
   406  
   407  	opts := metav1.ListOptions{LabelSelector: labels.Set{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}.String()}
   408  	gsSetList, err := f.AgonesClient.AgonesV1().GameServerSets(flt.ObjectMeta.Namespace).List(context.Background(), opts)
   409  	if err != nil {
   410  		return results, err
   411  	}
   412  
   413  	for i := range gsSetList.Items {
   414  		gsSet := &gsSetList.Items[i]
   415  		opts := metav1.ListOptions{LabelSelector: labels.Set{agonesv1.GameServerSetGameServerLabel: gsSet.ObjectMeta.Name}.String()}
   416  		gsList, err := f.AgonesClient.AgonesV1().GameServers(flt.ObjectMeta.Namespace).List(context.Background(), opts)
   417  		if err != nil {
   418  			return results, err
   419  		}
   420  
   421  		results = append(results, gsList.Items...)
   422  	}
   423  
   424  	return results, nil
   425  }
   426  
   427  // FleetReadyCount returns the ready count in a fleet
   428  func FleetReadyCount(amount int32) func(*logrus.Entry, *agonesv1.Fleet) bool {
   429  	return func(log *logrus.Entry, fleet *agonesv1.Fleet) bool {
   430  		log.WithField("fleetStatus", fmt.Sprintf("%+v", fleet.Status)).WithField("fleet", fleet.ObjectMeta.Name).WithField("expected", amount).Info("Checking Fleet Ready replicas")
   431  		return fleet.Status.ReadyReplicas == amount
   432  	}
   433  }
   434  
   435  // WaitForFleetGameServersCondition waits for all GameServers for a given fleet to match
   436  // a condition specified by a callback.
   437  func (f *Framework) WaitForFleetGameServersCondition(flt *agonesv1.Fleet,
   438  	cond func(server *agonesv1.GameServer) bool) error {
   439  	return f.WaitForFleetGameServerListCondition(flt,
   440  		func(servers []agonesv1.GameServer) bool {
   441  			for i := range servers {
   442  				gs := &servers[i]
   443  				if !cond(gs) {
   444  					return false
   445  				}
   446  			}
   447  			return true
   448  		})
   449  }
   450  
   451  // WaitForFleetGameServerListCondition waits for the list of GameServers to match a condition
   452  // specified by a callback and the size of GameServers to match fleet's Spec.Replicas.
   453  func (f *Framework) WaitForFleetGameServerListCondition(flt *agonesv1.Fleet,
   454  	cond func(servers []agonesv1.GameServer) bool) error {
   455  	return wait.PollUntilContextTimeout(context.Background(), 2*time.Second, f.WaitForState, true, func(_ context.Context) (done bool, err error) {
   456  		gsList, err := f.ListGameServersFromFleet(flt)
   457  		if err != nil {
   458  			return false, err
   459  		}
   460  		if int32(len(gsList)) != flt.Spec.Replicas {
   461  			return false, nil
   462  		}
   463  		return cond(gsList), nil
   464  	})
   465  }
   466  
   467  // NewStatsCollector returns new instance of statistics collector,
   468  // which can be used to emit performance statistics for load tests and stress tests.
   469  func (f *Framework) NewStatsCollector(name, version string) *StatsCollector {
   470  	if f.StressTestLevel > 0 {
   471  		name = fmt.Sprintf("stress_%v_%v", f.StressTestLevel, name)
   472  	}
   473  	return &StatsCollector{name: name, outputDir: f.PerfOutputDir, version: version}
   474  }
   475  
   476  // CleanUp Delete all Agones resources in a given namespace.
   477  func (f *Framework) CleanUp(ns string) error {
   478  	logrus.Info("Cleaning up now.")
   479  	defer logrus.Info("Finished cleanup.")
   480  	agonesV1 := f.AgonesClient.AgonesV1()
   481  	deleteOptions := metav1.DeleteOptions{}
   482  	listOptions := metav1.ListOptions{}
   483  
   484  	// find and delete pods created by tests and labeled with our special label
   485  	pods := f.KubeClient.CoreV1().Pods(ns)
   486  	ctx := context.Background()
   487  	podList, err := pods.List(ctx, metav1.ListOptions{
   488  		LabelSelector: AutoCleanupLabelKey + "=" + AutoCleanupLabelValue,
   489  	})
   490  	if err != nil {
   491  		return err
   492  	}
   493  
   494  	for i := range podList.Items {
   495  		p := &podList.Items[i]
   496  		if err := pods.Delete(ctx, p.ObjectMeta.Name, deleteOptions); err != nil {
   497  			return err
   498  		}
   499  	}
   500  
   501  	err = agonesV1.Fleets(ns).DeleteCollection(ctx, deleteOptions, listOptions)
   502  	if err != nil {
   503  		return err
   504  	}
   505  
   506  	err = f.AgonesClient.AutoscalingV1().FleetAutoscalers(ns).DeleteCollection(ctx, deleteOptions, listOptions)
   507  	if err != nil {
   508  		return err
   509  	}
   510  
   511  	return agonesV1.GameServers(ns).
   512  		DeleteCollection(ctx, deleteOptions, listOptions)
   513  }
   514  
   515  // CreateAndApplyAllocation creates and applies an Allocation to a Fleet
   516  func (f *Framework) CreateAndApplyAllocation(t *testing.T, flt *agonesv1.Fleet) *allocationv1.GameServerAllocation {
   517  	gsa := GetAllocation(flt)
   518  	gsa, err := f.AgonesClient.AllocationV1().GameServerAllocations(flt.ObjectMeta.Namespace).Create(context.Background(), gsa, metav1.CreateOptions{})
   519  	require.NoError(t, err)
   520  	require.Equal(t, string(allocationv1.GameServerAllocationAllocated), string(gsa.Status.State))
   521  	return gsa
   522  }
   523  
   524  // SendGameServerUDP sends a message to a gameserver and returns its reply
   525  // finds the first udp port from the spec to send the message to,
   526  // returns error if no Ports were allocated
   527  func (f *Framework) SendGameServerUDP(t *testing.T, gs *agonesv1.GameServer, msg string) (string, error) {
   528  	if len(gs.Status.Ports) == 0 {
   529  		return "", errors.New("Empty Ports array")
   530  	}
   531  
   532  	// use first udp port
   533  	for _, p := range gs.Spec.Ports {
   534  		if p.Protocol == corev1.ProtocolUDP {
   535  			return f.SendGameServerUDPToPort(t, gs, p.Name, msg)
   536  		}
   537  	}
   538  	return "", errors.New("No UDP ports")
   539  }
   540  
   541  // SendGameServerUDPToPort sends a message to a gameserver at the named port and returns its reply
   542  // returns error if no Ports were allocated or a port of the specified name doesn't exist
   543  func (f *Framework) SendGameServerUDPToPort(t *testing.T, gs *agonesv1.GameServer, portName string, msg string) (string, error) {
   544  	log := TestLogger(t)
   545  	if len(gs.Status.Ports) == 0 {
   546  		return "", errors.New("Empty Ports array")
   547  	}
   548  	var port agonesv1.GameServerStatusPort
   549  	for _, p := range gs.Status.Ports {
   550  		if p.Name == portName {
   551  			port = p
   552  		}
   553  	}
   554  	address := fmt.Sprintf("%s:%d", gs.Status.Address, port.Port)
   555  	reply, err := f.SendUDP(t, address, msg)
   556  
   557  	if err != nil {
   558  		log.WithField("gs", gs.ObjectMeta.Name).WithField("status", fmt.Sprintf("%+v", gs.Status)).Info("Failed to send UDP packet to GameServer. Dumping Events!")
   559  		f.LogEvents(t, log, gs.ObjectMeta.Namespace, gs)
   560  	}
   561  
   562  	return reply, err
   563  }
   564  
   565  // SendUDP sends a message to an address, and returns its reply if
   566  // it returns one in 10 seconds. Will retry 5 times, in case UDP packets drop.
   567  func (f *Framework) SendUDP(t *testing.T, address, msg string) (string, error) {
   568  	log := TestLogger(t).WithField("address", address)
   569  	b := make([]byte, 1024)
   570  	var n int
   571  	// sometimes we get I/O timeout, so let's do a retry
   572  	err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, time.Minute, true, func(_ context.Context) (bool, error) {
   573  		conn, err := net.Dial("udp", address)
   574  		if err != nil {
   575  			log.WithError(err).Info("could not dial address")
   576  			return false, nil
   577  		}
   578  
   579  		defer func() {
   580  			err = conn.Close()
   581  		}()
   582  
   583  		_, err = conn.Write([]byte(msg))
   584  		if err != nil {
   585  			log.WithError(err).Info("could not write message to address")
   586  			return false, nil
   587  		}
   588  
   589  		err = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
   590  		if err != nil {
   591  			log.WithError(err).Info("Could not set read deadline")
   592  			return false, nil
   593  		}
   594  
   595  		n, err = conn.Read(b)
   596  		if err != nil {
   597  			log.WithError(err).Info("Could not read from address")
   598  		}
   599  
   600  		return err == nil, nil
   601  	})
   602  
   603  	if err != nil {
   604  		return "", errors.Wrap(err, "timed out attempting to send UDP packet to address")
   605  	}
   606  
   607  	return string(b[:n]), nil
   608  }
   609  
   610  // SendGameServerTCP sends a message to a gameserver and returns its reply
   611  // finds the first tcp port from the spec to send the message to,
   612  // returns error if no Ports were allocated
   613  func SendGameServerTCP(gs *agonesv1.GameServer, msg string) (string, error) {
   614  	if len(gs.Status.Ports) == 0 {
   615  		return "", errors.New("Empty Ports array")
   616  	}
   617  
   618  	// use first tcp port
   619  	for _, p := range gs.Spec.Ports {
   620  		if p.Protocol == corev1.ProtocolTCP {
   621  			return SendGameServerTCPToPort(gs, p.Name, msg)
   622  		}
   623  	}
   624  	return "", errors.New("No TCP ports")
   625  }
   626  
   627  // SendGameServerTCPToPort sends a message to a gameserver at the named port and returns its reply
   628  // returns error if no Ports were allocated or a port of the specified name doesn't exist
   629  func SendGameServerTCPToPort(gs *agonesv1.GameServer, portName string, msg string) (string, error) {
   630  	if len(gs.Status.Ports) == 0 {
   631  		return "", errors.New("Empty Ports array")
   632  	}
   633  	var port agonesv1.GameServerStatusPort
   634  	for _, p := range gs.Status.Ports {
   635  		if p.Name == portName {
   636  			port = p
   637  		}
   638  	}
   639  	address := fmt.Sprintf("%s:%d", gs.Status.Address, port.Port)
   640  	return SendTCP(address, msg)
   641  }
   642  
   643  // SendTCP sends a message to an address, and returns its reply if
   644  // it returns one in 30 seconds
   645  func SendTCP(address, msg string) (string, error) {
   646  	conn, err := net.Dial("tcp", address)
   647  	if err != nil {
   648  		return "", err
   649  	}
   650  
   651  	if err := conn.SetReadDeadline(time.Now().Add(30 * time.Second)); err != nil {
   652  		return "", err
   653  	}
   654  
   655  	defer func() {
   656  		if err := conn.Close(); err != nil {
   657  			logrus.Warn("Could not close TCP connection")
   658  		}
   659  	}()
   660  
   661  	// writes to the tcp connection
   662  	_, err = fmt.Fprintln(conn, msg)
   663  	if err != nil {
   664  		return "", err
   665  	}
   666  
   667  	response, err := bufio.NewReader(conn).ReadString('\n')
   668  	if err != nil {
   669  		return "", err
   670  	}
   671  
   672  	return response, nil
   673  }
   674  
   675  // GetAllocation returns a GameServerAllocation that is looking for a Ready
   676  // GameServer from this fleet.
   677  func GetAllocation(f *agonesv1.Fleet) *allocationv1.GameServerAllocation {
   678  	// get an allocation
   679  	return &allocationv1.GameServerAllocation{
   680  		Spec: allocationv1.GameServerAllocationSpec{
   681  			Selectors: []allocationv1.GameServerSelector{
   682  				{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}},
   683  			},
   684  		}}
   685  }
   686  
   687  // CreateNamespace creates a namespace and a service account in the test cluster
   688  func (f *Framework) CreateNamespace(namespace string) error {
   689  	kubeCore := f.KubeClient.CoreV1()
   690  	ns := &corev1.Namespace{
   691  		ObjectMeta: metav1.ObjectMeta{
   692  			Name:   namespace,
   693  			Labels: NamespaceLabel,
   694  		},
   695  	}
   696  
   697  	ctx := context.Background()
   698  
   699  	options := metav1.CreateOptions{}
   700  	if _, err := kubeCore.Namespaces().Create(ctx, ns, options); err != nil {
   701  		return errors.Errorf("creating namespace %s failed: %s", namespace, err.Error())
   702  	}
   703  	logrus.Infof("Namespace %s is created", namespace)
   704  
   705  	saName := "agones-sdk"
   706  	if _, err := kubeCore.ServiceAccounts(namespace).Create(ctx, &corev1.ServiceAccount{
   707  		ObjectMeta: metav1.ObjectMeta{
   708  			Name:      saName,
   709  			Namespace: namespace,
   710  			Labels:    map[string]string{"app": "agones"},
   711  		},
   712  	}, options); err != nil {
   713  		err = errors.Errorf("creating ServiceAccount %s in namespace %s failed: %s", saName, namespace, err.Error())
   714  		derr := f.DeleteNamespace(namespace)
   715  		if derr != nil {
   716  			return errors.Wrap(err, derr.Error())
   717  		}
   718  		return err
   719  	}
   720  	logrus.Infof("ServiceAccount %s/%s is created", namespace, saName)
   721  
   722  	rb := &rbacv1.RoleBinding{
   723  		ObjectMeta: metav1.ObjectMeta{
   724  			Name:      "agones-sdk-access",
   725  			Namespace: namespace,
   726  			Labels:    map[string]string{"app": "agones"},
   727  		},
   728  		RoleRef: rbacv1.RoleRef{
   729  			APIGroup: "rbac.authorization.k8s.io",
   730  			Kind:     "ClusterRole",
   731  			Name:     "agones-sdk",
   732  		},
   733  		Subjects: []rbacv1.Subject{
   734  			{
   735  				Kind:      "ServiceAccount",
   736  				Name:      saName,
   737  				Namespace: namespace,
   738  			},
   739  		},
   740  	}
   741  	if _, err := f.KubeClient.RbacV1().RoleBindings(namespace).Create(ctx, rb, options); err != nil {
   742  		err = errors.Errorf("creating RoleBinding for service account %q in namespace %q failed: %s", saName, namespace, err.Error())
   743  		derr := f.DeleteNamespace(namespace)
   744  		if derr != nil {
   745  			return errors.Wrap(err, derr.Error())
   746  		}
   747  		return err
   748  	}
   749  	logrus.Infof("RoleBinding %s/%s is created", namespace, rb.Name)
   750  
   751  	return nil
   752  }
   753  
   754  // DeleteNamespace deletes a namespace from the test cluster
   755  func (f *Framework) DeleteNamespace(namespace string) error {
   756  	kubeCore := f.KubeClient.CoreV1()
   757  	ctx := context.Background()
   758  
   759  	// Remove finalizers
   760  	pods, err := kubeCore.Pods(namespace).List(ctx, metav1.ListOptions{})
   761  	if err != nil {
   762  		return errors.Errorf("listing pods in namespace %s failed: %s", namespace, err)
   763  	}
   764  	for i := range pods.Items {
   765  		pod := &pods.Items[i]
   766  		if len(pod.Finalizers) > 0 {
   767  			pod.Finalizers = nil
   768  			payload := []patchRemoveNoValue{{
   769  				Op:   "remove",
   770  				Path: "/metadata/finalizers",
   771  			}}
   772  			payloadBytes, _ := json.Marshal(payload)
   773  			if _, err := kubeCore.Pods(namespace).Patch(ctx, pod.Name, types.JSONPatchType, payloadBytes, metav1.PatchOptions{}); err != nil {
   774  				return errors.Wrapf(err, "updating pod %s failed", pod.GetName())
   775  			}
   776  		}
   777  	}
   778  
   779  	if err := kubeCore.Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}); err != nil {
   780  		return errors.Wrapf(err, "deleting namespace %s failed", namespace)
   781  	}
   782  	logrus.Infof("Namespace %s is deleted", namespace)
   783  	return nil
   784  }
   785  
   786  type patchRemoveNoValue struct {
   787  	Op   string `json:"op"`
   788  	Path string `json:"path"`
   789  }
   790  
   791  // DefaultGameServer provides a default GameServer fixture, based on parameters
   792  // passed to the Test Framework.
   793  func (f *Framework) DefaultGameServer(namespace string) *agonesv1.GameServer {
   794  	gs := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{GenerateName: "game-server", Namespace: namespace},
   795  		Spec: agonesv1.GameServerSpec{
   796  			Container: "game-server",
   797  			Ports: []agonesv1.GameServerPort{{
   798  				ContainerPort: 7654,
   799  				Name:          "udp-port",
   800  				PortPolicy:    agonesv1.Dynamic,
   801  				Protocol:      corev1.ProtocolUDP,
   802  			}},
   803  			Template: corev1.PodTemplateSpec{
   804  				Spec: corev1.PodSpec{
   805  					Containers: []corev1.Container{{
   806  						Name:            "game-server",
   807  						Image:           f.GameServerImage,
   808  						ImagePullPolicy: corev1.PullIfNotPresent,
   809  						Resources: corev1.ResourceRequirements{
   810  							Requests: corev1.ResourceList{
   811  								corev1.ResourceCPU:    resource.MustParse("30m"),
   812  								corev1.ResourceMemory: resource.MustParse("32Mi"),
   813  							},
   814  							Limits: corev1.ResourceList{
   815  								corev1.ResourceCPU:    resource.MustParse("30m"),
   816  								corev1.ResourceMemory: resource.MustParse("32Mi"),
   817  							},
   818  						},
   819  					}},
   820  				},
   821  			},
   822  		},
   823  	}
   824  
   825  	if f.PullSecret != "" {
   826  		gs.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{
   827  			Name: f.PullSecret}}
   828  	}
   829  
   830  	return gs
   831  }
   832  
   833  // LogEvents logs all the events for a given Kubernetes objects. Useful for debugging why something
   834  // went wrong.
   835  func (f *Framework) LogEvents(t *testing.T, log *logrus.Entry, namespace string, objOrRef k8sruntime.Object) {
   836  	log.WithField("kind", objOrRef.GetObjectKind().GroupVersionKind().Kind).Info("Dumping Events:")
   837  	events, err := f.KubeClient.CoreV1().Events(namespace).SearchWithContext(context.Background(), scheme.Scheme, objOrRef)
   838  	require.NoError(t, err, "error searching for events")
   839  	for i := range events.Items {
   840  		event := events.Items[i]
   841  		log.WithField("lastTimestamp", event.LastTimestamp).WithField("type", event.Type).WithField("reason", event.Reason).WithField("message", event.Message).Info("Event!")
   842  	}
   843  }
   844  
   845  // LogPodContainers takes a Pod as an argument and attempts to output the current and previous logs from each container
   846  // in that Pod It uses the framework's KubeClient to retrieve the logs and outputs them using the provided logger.
   847  func (f *Framework) LogPodContainers(t *testing.T, pod *corev1.Pod) {
   848  	log := TestLogger(t)
   849  	log.WithField("pod", pod.Name).WithField("namespace", pod.Namespace).Info("Logs for Pod:")
   850  
   851  	// sub-function so defer will fire on each printLogs, rather than at the end.
   852  	printLogs := func(container corev1.Container, previous bool) {
   853  		logOptions := &corev1.PodLogOptions{
   854  			Container: container.Name,
   855  			Follow:    false,
   856  			Previous:  previous,
   857  		}
   858  
   859  		req := f.KubeClient.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, logOptions)
   860  		podLogs, err := req.Stream(context.Background())
   861  		log = log.WithField("options", logOptions)
   862  
   863  		if err != nil {
   864  			log.WithError(err).Warn("Error opening log stream for container")
   865  			return
   866  		}
   867  		defer podLogs.Close() // nolint:errcheck,staticcheck
   868  
   869  		logBytes, err := io.ReadAll(podLogs)
   870  		if err != nil {
   871  			log.WithError(err).WithField("options", logOptions).Warn("Error reading logs for container")
   872  			return
   873  		}
   874  
   875  		log.Info("---Logs for container---")
   876  		lines := strings.Split(string(logBytes), "\n")
   877  		for _, line := range lines {
   878  			if line == "" {
   879  				continue
   880  			}
   881  			log.Info(line)
   882  		}
   883  		log.Info("---End of container logs---")
   884  	}
   885  
   886  	// run through the container list twice, so we group current vs previous logs nicely.
   887  	for _, container := range pod.Spec.Containers {
   888  		printLogs(container, false)
   889  	}
   890  
   891  	for _, container := range pod.Spec.Containers {
   892  		printLogs(container, true)
   893  	}
   894  
   895  }
   896  
   897  // SkipOnCloudProduct skips the test if the e2e was invoked with --cloud-product=<product>.
   898  func (f *Framework) SkipOnCloudProduct(t *testing.T, product, reason string) {
   899  	if f.CloudProduct == product {
   900  		t.Skipf("skipping test on cloud product %s: %s", product, reason)
   901  	}
   902  }
   903  
   904  // TestLogger returns the standard logger for helper functions.
   905  func TestLogger(t *testing.T) *logrus.Entry {
   906  	return logrus.WithField("test", t.Name())
   907  }