agones.dev/agones@v1.53.0/test/e2e/allocator_test.go (about)

     1  // Copyright 2019 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  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"testing"
    25  	"time"
    26  
    27  	pb "agones.dev/agones/pkg/allocation/go"
    28  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    29  	multiclusterv1 "agones.dev/agones/pkg/apis/multicluster/v1"
    30  	"agones.dev/agones/pkg/util/runtime"
    31  	helper "agones.dev/agones/test/e2e/allochelper"
    32  	e2e "agones.dev/agones/test/e2e/framework"
    33  	"github.com/sirupsen/logrus"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  	"google.golang.org/grpc"
    37  	"google.golang.org/grpc/codes"
    38  	"google.golang.org/grpc/status"
    39  	"google.golang.org/protobuf/encoding/protojson"
    40  	"google.golang.org/protobuf/types/known/wrapperspb"
    41  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    42  	"k8s.io/apimachinery/pkg/util/uuid"
    43  	"k8s.io/apimachinery/pkg/util/wait"
    44  )
    45  
    46  const (
    47  	agonesSystemNamespace          = "agones-system"
    48  	allocatorServiceName           = "agones-allocator"
    49  	allocatorTLSName               = "allocator-tls"
    50  	tlsCrtTag                      = "tls.crt"
    51  	tlsKeyTag                      = "tls.key"
    52  	allocatorReqURLFmt             = "%s:%d"
    53  	allocatorClientSecretName      = "allocator-client.default"
    54  	allocatorClientSecretNamespace = "default"
    55  )
    56  
    57  func TestAllocatorWithDeprecatedRequired(t *testing.T) {
    58  	ctx := context.Background()
    59  
    60  	ip, port := helper.GetAllocatorEndpoint(ctx, t, framework)
    61  	requestURL := fmt.Sprintf(allocatorReqURLFmt, ip, port)
    62  	tlsCA := helper.RefreshAllocatorTLSCerts(ctx, t, ip, framework)
    63  
    64  	var flt *agonesv1.Fleet
    65  	var err error
    66  	if runtime.FeatureEnabled(runtime.FeaturePlayerAllocationFilter) {
    67  		flt, err = helper.CreateFleetWithOpts(ctx, framework.Namespace, framework, func(f *agonesv1.Fleet) {
    68  			f.Spec.Template.Spec.Players = &agonesv1.PlayersSpec{
    69  				InitialCapacity: 10,
    70  			}
    71  		})
    72  	} else {
    73  		flt, err = helper.CreateFleet(ctx, framework.Namespace, framework)
    74  	}
    75  	require.NoError(t, err)
    76  	defer framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Delete(ctx, flt.Name, metav1.DeleteOptions{}) // nolint: errcheck
    77  
    78  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
    79  	request := &pb.AllocationRequest{
    80  		Namespace:                    framework.Namespace,
    81  		RequiredGameServerSelector:   &pb.GameServerSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}},
    82  		PreferredGameServerSelectors: []*pb.GameServerSelector{{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}},
    83  		Scheduling:                   pb.AllocationRequest_Packed,
    84  		Metadata:                     &pb.MetaPatch{Labels: map[string]string{"gslabel": "allocatedbytest"}},
    85  	}
    86  
    87  	var response *pb.AllocationResponse
    88  	// wait for the allocation system to come online
    89  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
    90  		// create the grpc client each time, as we may end up looking at an old cert
    91  		dialOpts, err := helper.CreateRemoteClusterDialOptions(ctx, allocatorClientSecretNamespace, allocatorClientSecretName, tlsCA, framework)
    92  		if err != nil {
    93  			return false, err
    94  		}
    95  
    96  		conn, err := grpc.NewClient(requestURL, dialOpts...)
    97  		if err != nil {
    98  			logrus.WithError(err).Info("failing grpc.NewClient")
    99  			return false, nil
   100  		}
   101  		defer conn.Close() // nolint: errcheck
   102  
   103  		grpcClient := pb.NewAllocationServiceClient(conn)
   104  		response, err = grpcClient.Allocate(context.Background(), request)
   105  		if err != nil {
   106  			logrus.WithError(err).Info("failing Allocate request")
   107  			return false, nil
   108  		}
   109  		helper.ValidateAllocatorResponse(t, response)
   110  
   111  		// let's do a re-allocation
   112  		if runtime.FeatureEnabled(runtime.FeaturePlayerAllocationFilter) {
   113  			// nolint:staticcheck
   114  			request.PreferredGameServerSelectors[0].GameServerState = pb.GameServerSelector_ALLOCATED
   115  			allocatedResponse, err := grpcClient.Allocate(context.Background(), request)
   116  			require.NoError(t, err)
   117  			require.Equal(t, response.GameServerName, allocatedResponse.GameServerName)
   118  			helper.ValidateAllocatorResponse(t, allocatedResponse)
   119  
   120  			// do a capacity based allocation
   121  			logrus.Info("testing capacity allocation filter")
   122  			// nolint:staticcheck
   123  			request.PreferredGameServerSelectors[0].Players = &pb.PlayerSelector{
   124  				MinAvailable: 5,
   125  				MaxAvailable: 10,
   126  			}
   127  			allocatedResponse, err = grpcClient.Allocate(context.Background(), request)
   128  			require.NoError(t, err)
   129  			require.Equal(t, response.GameServerName, allocatedResponse.GameServerName)
   130  			helper.ValidateAllocatorResponse(t, allocatedResponse)
   131  
   132  			// do a capacity based allocation that should fail
   133  			// nolint:staticcheck
   134  			request.PreferredGameServerSelectors = nil
   135  			// nolint:staticcheck
   136  			request.RequiredGameServerSelector.GameServerState = pb.GameServerSelector_ALLOCATED
   137  			// nolint:staticcheck
   138  			request.RequiredGameServerSelector.Players = &pb.PlayerSelector{MinAvailable: 99, MaxAvailable: 200}
   139  
   140  			allocatedResponse, err = grpcClient.Allocate(context.Background(), request)
   141  			assert.Nil(t, allocatedResponse)
   142  			status, ok := status.FromError(err)
   143  			require.True(t, ok)
   144  			assert.Equal(t, codes.ResourceExhausted, status.Code())
   145  		}
   146  
   147  		return true, nil
   148  	})
   149  
   150  	require.NoError(t, err)
   151  }
   152  
   153  func TestAllocatorWithSelectors(t *testing.T) {
   154  	ctx := context.Background()
   155  
   156  	ip, port := helper.GetAllocatorEndpoint(ctx, t, framework)
   157  	requestURL := fmt.Sprintf(allocatorReqURLFmt, ip, port)
   158  	tlsCA := helper.RefreshAllocatorTLSCerts(ctx, t, ip, framework)
   159  
   160  	var flt *agonesv1.Fleet
   161  	var err error
   162  	if runtime.FeatureEnabled(runtime.FeaturePlayerAllocationFilter) {
   163  		flt, err = helper.CreateFleetWithOpts(ctx, framework.Namespace, framework, func(f *agonesv1.Fleet) {
   164  			f.Spec.Template.Spec.Players = &agonesv1.PlayersSpec{
   165  				InitialCapacity: 10,
   166  			}
   167  		})
   168  	} else {
   169  		flt, err = helper.CreateFleet(ctx, framework.Namespace, framework)
   170  	}
   171  	assert.NoError(t, err)
   172  	defer framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Delete(ctx, flt.Name, metav1.DeleteOptions{}) // nolint: errcheck
   173  
   174  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   175  	request := &pb.AllocationRequest{
   176  		Namespace:           framework.Namespace,
   177  		GameServerSelectors: []*pb.GameServerSelector{{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}},
   178  		Scheduling:          pb.AllocationRequest_Packed,
   179  		Metadata:            &pb.MetaPatch{Labels: map[string]string{"gslabel": "allocatedbytest", "blue-frog.fred_thing": "test.dog_fred-blue"}},
   180  	}
   181  
   182  	var response *pb.AllocationResponse
   183  	// wait for the allocation system to come online
   184  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
   185  		// create the grpc client each time, as we may end up looking at an old cert
   186  		dialOpts, err := helper.CreateRemoteClusterDialOptions(ctx, allocatorClientSecretNamespace, allocatorClientSecretName, tlsCA, framework)
   187  		if err != nil {
   188  			return false, err
   189  		}
   190  
   191  		conn, err := grpc.NewClient(requestURL, dialOpts...)
   192  		if err != nil {
   193  			logrus.WithError(err).Info("failing grpc.NewClient")
   194  			return false, nil
   195  		}
   196  		defer conn.Close() // nolint: errcheck
   197  
   198  		grpcClient := pb.NewAllocationServiceClient(conn)
   199  		response, err = grpcClient.Allocate(context.Background(), request)
   200  		if err != nil {
   201  			logrus.WithError(err).Info("failing Allocate request")
   202  			return false, nil
   203  		}
   204  		helper.ValidateAllocatorResponse(t, response)
   205  
   206  		// let's do a re-allocation
   207  		if runtime.FeatureEnabled(runtime.FeaturePlayerAllocationFilter) {
   208  			request.GameServerSelectors[0].GameServerState = pb.GameServerSelector_ALLOCATED
   209  			allocatedResponse, err := grpcClient.Allocate(context.Background(), request)
   210  			require.NoError(t, err)
   211  			require.Equal(t, response.GameServerName, allocatedResponse.GameServerName)
   212  			helper.ValidateAllocatorResponse(t, allocatedResponse)
   213  			assert.Equal(t, flt.ObjectMeta.Name, allocatedResponse.Metadata.Labels[agonesv1.FleetNameLabel])
   214  
   215  			// do a capacity based allocation
   216  			logrus.Info("testing capacity allocation filter")
   217  			request.GameServerSelectors[0].Players = &pb.PlayerSelector{
   218  				MinAvailable: 5,
   219  				MaxAvailable: 10,
   220  			}
   221  			allocatedResponse, err = grpcClient.Allocate(context.Background(), request)
   222  			require.NoError(t, err)
   223  			require.Equal(t, response.GameServerName, allocatedResponse.GameServerName)
   224  			helper.ValidateAllocatorResponse(t, allocatedResponse)
   225  
   226  			// do a capacity based allocation that should fail
   227  			request.GameServerSelectors[0].GameServerState = pb.GameServerSelector_ALLOCATED
   228  			request.GameServerSelectors[0].Players = &pb.PlayerSelector{MinAvailable: 99, MaxAvailable: 200}
   229  
   230  			allocatedResponse, err = grpcClient.Allocate(context.Background(), request)
   231  			assert.Nil(t, allocatedResponse)
   232  			status, ok := status.FromError(err)
   233  			require.True(t, ok)
   234  			assert.Equal(t, codes.ResourceExhausted, status.Code())
   235  		}
   236  
   237  		return true, nil
   238  	})
   239  
   240  	assert.NoError(t, err)
   241  }
   242  
   243  func TestRestAllocatorWithDeprecatedRequired(t *testing.T) {
   244  	ctx := context.Background()
   245  
   246  	ip, port := helper.GetAllocatorEndpoint(ctx, t, framework)
   247  	requestURL := fmt.Sprintf(allocatorReqURLFmt, ip, port)
   248  	tlsCA := helper.RefreshAllocatorTLSCerts(ctx, t, ip, framework)
   249  
   250  	flt, err := helper.CreateFleet(ctx, framework.Namespace, framework)
   251  	if !assert.Nil(t, err) {
   252  		return
   253  	}
   254  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   255  	defer framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Delete(ctx, flt.Name, metav1.DeleteOptions{}) // nolint: errcheck
   256  
   257  	request := &pb.AllocationRequest{
   258  		Namespace:                    framework.Namespace,
   259  		RequiredGameServerSelector:   &pb.GameServerSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}},
   260  		PreferredGameServerSelectors: []*pb.GameServerSelector{{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}},
   261  		Scheduling:                   pb.AllocationRequest_Packed,
   262  		Metadata:                     &pb.MetaPatch{Labels: map[string]string{"gslabel": "allocatedbytest"}},
   263  	}
   264  	tlsCfg, err := helper.GetTLSConfig(ctx, allocatorClientSecretNamespace, allocatorClientSecretName, tlsCA, framework)
   265  	if !assert.Nil(t, err) {
   266  		return
   267  	}
   268  	client := &http.Client{
   269  		Transport: &http.Transport{
   270  			TLSClientConfig: tlsCfg,
   271  		},
   272  	}
   273  	jsonRes, err := json.Marshal(request)
   274  	if !assert.Nil(t, err) {
   275  		return
   276  	}
   277  	req, err := http.NewRequest("POST", "https://"+requestURL+"/gameserverallocation", bytes.NewBuffer(jsonRes))
   278  	if !assert.Nil(t, err) {
   279  		logrus.WithError(err).Info("failed to create rest request")
   280  		return
   281  	}
   282  
   283  	// wait for the allocation system to come online
   284  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(_ context.Context) (bool, error) {
   285  		resp, err := client.Do(req)
   286  		if err != nil {
   287  			logrus.WithError(err).Info("failed Allocate rest request")
   288  			return false, nil
   289  		}
   290  		body, err := io.ReadAll(resp.Body)
   291  		if err != nil {
   292  			logrus.WithError(err).Info("failed to read Allocate response body")
   293  			return false, nil
   294  		}
   295  		defer resp.Body.Close() // nolint: errcheck
   296  		var response pb.AllocationResponse
   297  		err = json.Unmarshal(body, &response)
   298  		if err != nil {
   299  			logrus.WithError(err).Info("failed to unmarshal Allocate response")
   300  			return false, nil
   301  		}
   302  		helper.ValidateAllocatorResponse(t, &response)
   303  		return true, nil
   304  	})
   305  
   306  	assert.NoError(t, err)
   307  }
   308  
   309  func TestAllocatorWithCountersAndLists(t *testing.T) {
   310  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
   311  		t.Skip("FeatureCountsAndLists is not enabled")
   312  		return
   313  	}
   314  	ctx := context.Background()
   315  
   316  	ip, port := helper.GetAllocatorEndpoint(ctx, t, framework)
   317  	requestURL := fmt.Sprintf(allocatorReqURLFmt, ip, port)
   318  	tlsCA := helper.RefreshAllocatorTLSCerts(ctx, t, ip, framework)
   319  
   320  	var flt *agonesv1.Fleet
   321  	var err error
   322  	flt, err = helper.CreateFleetWithOpts(ctx, framework.Namespace, framework, func(f *agonesv1.Fleet) {
   323  		f.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
   324  			"players": {
   325  				Capacity: 10,
   326  			},
   327  		}
   328  		f.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
   329  			"rooms": {
   330  				Capacity: 10,
   331  			},
   332  		}
   333  	})
   334  	assert.NoError(t, err)
   335  	defer framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Delete(ctx, flt.Name, metav1.DeleteOptions{}) // nolint: errcheck
   336  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   337  
   338  	request := &pb.AllocationRequest{
   339  		Namespace: framework.Namespace,
   340  		GameServerSelectors: []*pb.GameServerSelector{{
   341  			MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name},
   342  			Counters: map[string]*pb.CounterSelector{
   343  				"players": {
   344  					MinAvailable: 1,
   345  				},
   346  			},
   347  			Lists: map[string]*pb.ListSelector{
   348  				"rooms": {
   349  					MinAvailable: 1,
   350  				},
   351  			},
   352  		}},
   353  		Counters: map[string]*pb.CounterAction{
   354  			"players": {
   355  				Action: wrapperspb.String(agonesv1.GameServerPriorityIncrement),
   356  				Amount: wrapperspb.Int64(1),
   357  			},
   358  		},
   359  		Lists: map[string]*pb.ListAction{
   360  			"rooms": {
   361  				AddValues:    []string{"1"},
   362  				DeleteValues: []string{"2"}, // This action is ignored. (Value does not exist.)
   363  			},
   364  		},
   365  	}
   366  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
   367  		dialOpts, err := helper.CreateRemoteClusterDialOptions(ctx, allocatorClientSecretNamespace, allocatorClientSecretName, tlsCA, framework)
   368  		if err != nil {
   369  			return false, err
   370  		}
   371  		conn, err := grpc.NewClient(requestURL, dialOpts...)
   372  		if err != nil {
   373  			logrus.WithError(err).Info("failing grpc.NewClient")
   374  			return false, nil
   375  		}
   376  		defer conn.Close() // nolint: errcheck
   377  
   378  		grpcClient := pb.NewAllocationServiceClient(conn)
   379  		response, err := grpcClient.Allocate(context.Background(), request)
   380  		if err != nil {
   381  			return false, nil
   382  		}
   383  		assert.Contains(t, response.GetCounters(), "players")
   384  		assert.Equal(t, int64(10), response.GetCounters()["players"].Capacity.GetValue())
   385  		assert.Equal(t, int64(1), response.GetCounters()["players"].Count.GetValue())
   386  		assert.Contains(t, response.GetLists(), "rooms")
   387  		assert.Equal(t, int64(10), response.GetLists()["rooms"].Capacity.GetValue())
   388  		assert.EqualValues(t, request.Lists["rooms"].AddValues, response.GetLists()["rooms"].Values)
   389  		assert.NotEqualValues(t, request.Lists["rooms"].DeleteValues, response.GetLists()["rooms"].Values)
   390  		return true, nil
   391  	})
   392  	require.NoError(t, err)
   393  }
   394  
   395  func TestRestAllocatorWithCountersAndLists(t *testing.T) {
   396  	if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) {
   397  		t.Skip("FeatureCountsAndLists is not enabled")
   398  		return
   399  	}
   400  	ctx := context.Background()
   401  
   402  	ip, port := helper.GetAllocatorEndpoint(ctx, t, framework)
   403  	requestURL := fmt.Sprintf(allocatorReqURLFmt, ip, port)
   404  	tlsCA := helper.RefreshAllocatorTLSCerts(ctx, t, ip, framework)
   405  
   406  	var flt *agonesv1.Fleet
   407  	var err error
   408  	flt, err = helper.CreateFleetWithOpts(ctx, framework.Namespace, framework, func(f *agonesv1.Fleet) {
   409  		f.Spec.Template.Spec.Counters = map[string]agonesv1.CounterStatus{
   410  			"players": {
   411  				Capacity: 10,
   412  			},
   413  		}
   414  		f.Spec.Template.Spec.Lists = map[string]agonesv1.ListStatus{
   415  			"rooms": {
   416  				Values:   []string{"one", "two", "three"},
   417  				Capacity: 10,
   418  			},
   419  		}
   420  	})
   421  	assert.NoError(t, err)
   422  	defer framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Delete(ctx, flt.Name, metav1.DeleteOptions{}) // nolint: errcheck
   423  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   424  
   425  	request := &pb.AllocationRequest{
   426  		Namespace: framework.Namespace,
   427  		GameServerSelectors: []*pb.GameServerSelector{{
   428  			MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name},
   429  			Counters: map[string]*pb.CounterSelector{
   430  				"players": {
   431  					MinAvailable: 1,
   432  				},
   433  			},
   434  			Lists: map[string]*pb.ListSelector{
   435  				"rooms": {
   436  					MinAvailable: 1,
   437  				},
   438  			},
   439  		}},
   440  		Counters: map[string]*pb.CounterAction{
   441  			"players": {
   442  				Action: wrapperspb.String(agonesv1.GameServerPriorityIncrement),
   443  				Amount: wrapperspb.Int64(1),
   444  			},
   445  		},
   446  		Lists: map[string]*pb.ListAction{
   447  			"rooms": {
   448  				AddValues:    []string{"1"},
   449  				DeleteValues: []string{"three", "one"},
   450  			},
   451  		},
   452  	}
   453  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
   454  		tlsCfg, err := helper.GetTLSConfig(ctx, allocatorClientSecretNamespace, allocatorClientSecretName, tlsCA, framework)
   455  		if !assert.Nil(t, err) {
   456  			return false, err
   457  		}
   458  		client := &http.Client{
   459  			Transport: &http.Transport{
   460  				TLSClientConfig: tlsCfg,
   461  			},
   462  		}
   463  		jsonRes, err := protojson.Marshal(request)
   464  		if !assert.Nil(t, err) {
   465  			return false, nil
   466  		}
   467  		req, err := http.NewRequest("POST", "https://"+requestURL+"/gameserverallocation", bytes.NewBuffer(jsonRes))
   468  		if !assert.Nil(t, err) {
   469  			return false, nil
   470  		}
   471  		resp, err := client.Do(req)
   472  		if err != nil {
   473  			return false, nil
   474  		}
   475  		body, err := io.ReadAll(resp.Body)
   476  		if err != nil {
   477  			return false, nil
   478  		}
   479  		defer resp.Body.Close() // nolint: errcheck
   480  		if resp.StatusCode != http.StatusOK {
   481  			return false, nil
   482  		}
   483  		var response pb.AllocationResponse
   484  		err = protojson.Unmarshal(body, &response)
   485  		if err != nil {
   486  			return false, nil
   487  		}
   488  		assert.Contains(t, response.GetCounters(), "players")
   489  		assert.Equal(t, int64(10), response.GetCounters()["players"].Capacity.GetValue())
   490  		assert.Equal(t, int64(1), response.GetCounters()["players"].Count.GetValue())
   491  		assert.Contains(t, response.GetLists(), "rooms")
   492  		assert.Equal(t, int64(10), response.GetLists()["rooms"].Capacity.GetValue())
   493  		assert.Contains(t, response.GetLists()["rooms"].Values, request.Lists["rooms"].AddValues[0])
   494  		assert.NotContains(t, response.GetLists()["rooms"].Values, request.Lists["rooms"].DeleteValues[0])
   495  		assert.NotContains(t, response.GetLists()["rooms"].Values, request.Lists["rooms"].DeleteValues[1])
   496  		return true, nil
   497  	})
   498  	require.NoError(t, err)
   499  }
   500  
   501  func TestRestAllocatorWithSelectors(t *testing.T) {
   502  	ctx := context.Background()
   503  
   504  	ip, port := helper.GetAllocatorEndpoint(ctx, t, framework)
   505  	requestURL := fmt.Sprintf(allocatorReqURLFmt, ip, port)
   506  	tlsCA := helper.RefreshAllocatorTLSCerts(ctx, t, ip, framework)
   507  
   508  	flt, err := helper.CreateFleet(ctx, framework.Namespace, framework)
   509  	require.NoError(t, err)
   510  	defer framework.AgonesClient.AgonesV1().Fleets(framework.Namespace).Delete(ctx, flt.Name, metav1.DeleteOptions{}) // nolint: errcheck
   511  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   512  
   513  	request := &pb.AllocationRequest{
   514  		Namespace:           framework.Namespace,
   515  		GameServerSelectors: []*pb.GameServerSelector{{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}},
   516  		Scheduling:          pb.AllocationRequest_Packed,
   517  		Metadata:            &pb.MetaPatch{Labels: map[string]string{"gslabel": "allocatedbytest", "blue-frog.fred_thing": "test.dog_fred-blue"}},
   518  	}
   519  	tlsCfg, err := helper.GetTLSConfig(ctx, allocatorClientSecretNamespace, allocatorClientSecretName, tlsCA, framework)
   520  	if !assert.Nil(t, err) {
   521  		return
   522  	}
   523  	client := &http.Client{
   524  		Transport: &http.Transport{
   525  			TLSClientConfig: tlsCfg,
   526  		},
   527  	}
   528  	jsonRes, err := json.Marshal(request)
   529  	if !assert.Nil(t, err) {
   530  		return
   531  	}
   532  	req, err := http.NewRequest("POST", "https://"+requestURL+"/gameserverallocation", bytes.NewBuffer(jsonRes))
   533  	if !assert.Nil(t, err) {
   534  		logrus.WithError(err).Info("failed to create rest request")
   535  		return
   536  	}
   537  
   538  	// wait for the allocation system to come online
   539  	var response pb.AllocationResponse
   540  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(_ context.Context) (bool, error) {
   541  		resp, err := client.Do(req)
   542  		if err != nil {
   543  			logrus.WithError(err).Info("failed Allocate rest request")
   544  			return false, nil
   545  		}
   546  		body, err := io.ReadAll(resp.Body)
   547  		if err != nil {
   548  			logrus.WithError(err).Info("failed to read Allocate response body")
   549  			return false, nil
   550  		}
   551  		defer resp.Body.Close() // nolint: errcheck
   552  		err = json.Unmarshal(body, &response)
   553  		if err != nil {
   554  			logrus.WithError(err).Info("failed to unmarshal Allocate response")
   555  			return false, nil
   556  		}
   557  		helper.ValidateAllocatorResponse(t, &response)
   558  		return true, nil
   559  	})
   560  	require.NoError(t, err)
   561  
   562  	gs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, response.GameServerName, metav1.GetOptions{})
   563  	require.NoError(t, err)
   564  	assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State)
   565  	assert.Equal(t, "allocatedbytest", gs.ObjectMeta.Labels["gslabel"])
   566  	assert.Equal(t, "test.dog_fred-blue", gs.ObjectMeta.Labels["blue-frog.fred_thing"])
   567  }
   568  
   569  // Tests multi-cluster allocation by reusing the same cluster but across namespace.
   570  // Multi-cluster is represented as two namespaces A and B in the same cluster.
   571  // Namespace A received the allocation request, but because namespace B has the highest priority, A will forward the request to B.
   572  func TestAllocatorCrossNamespace(t *testing.T) {
   573  	ctx := context.Background()
   574  
   575  	ip, port := helper.GetAllocatorEndpoint(ctx, t, framework)
   576  	requestURL := fmt.Sprintf(allocatorReqURLFmt, ip, port)
   577  	tlsCA := helper.RefreshAllocatorTLSCerts(ctx, t, ip, framework)
   578  
   579  	// Create namespaces A and B
   580  	namespaceA := framework.Namespace // let's reuse an existing one
   581  	helper.CopyDefaultAllocatorClientSecret(ctx, t, namespaceA, framework)
   582  
   583  	namespaceB := fmt.Sprintf("allocator-b-%s", uuid.NewUUID())
   584  	err := framework.CreateNamespace(namespaceB)
   585  	if !assert.Nil(t, err) {
   586  		return
   587  	}
   588  	defer func() {
   589  		if derr := framework.DeleteNamespace(namespaceB); derr != nil {
   590  			t.Error(derr)
   591  		}
   592  	}()
   593  
   594  	policyName := fmt.Sprintf("a-to-b-%s", uuid.NewUUID())
   595  	p := &multiclusterv1.GameServerAllocationPolicy{
   596  		ObjectMeta: metav1.ObjectMeta{
   597  			Name:      policyName,
   598  			Namespace: namespaceA,
   599  		},
   600  		Spec: multiclusterv1.GameServerAllocationPolicySpec{
   601  			Priority: 1,
   602  			Weight:   1,
   603  			ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   604  				SecretName:          allocatorClientSecretName,
   605  				Namespace:           namespaceB,
   606  				AllocationEndpoints: []string{ip},
   607  				ServerCA:            tlsCA,
   608  			},
   609  		},
   610  	}
   611  	helper.CreateAllocationPolicy(ctx, t, framework, p)
   612  
   613  	// Create a fleet in namespace B. Allocation should not happen in A according to policy
   614  	flt, err := helper.CreateFleet(ctx, namespaceB, framework)
   615  	if !assert.Nil(t, err) {
   616  		return
   617  	}
   618  	framework.AssertFleetCondition(t, flt, e2e.FleetReadyCount(flt.Spec.Replicas))
   619  	defer framework.AgonesClient.AgonesV1().Fleets(namespaceB).Delete(ctx, flt.Name, metav1.DeleteOptions{}) // nolint: errcheck
   620  
   621  	request := &pb.AllocationRequest{
   622  		Namespace: namespaceA,
   623  		// Enable multi-cluster setting
   624  		MultiClusterSetting: &pb.MultiClusterSetting{Enabled: true},
   625  		GameServerSelectors: []*pb.GameServerSelector{{MatchLabels: map[string]string{agonesv1.FleetNameLabel: flt.ObjectMeta.Name}}},
   626  	}
   627  
   628  	// wait for the allocation system to come online
   629  	err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
   630  		// create the grpc client each time, as we may end up looking at an old cert
   631  		dialOpts, err := helper.CreateRemoteClusterDialOptions(ctx, namespaceA, allocatorClientSecretName, tlsCA, framework)
   632  		if err != nil {
   633  			return false, err
   634  		}
   635  
   636  		conn, err := grpc.NewClient(requestURL, dialOpts...)
   637  		if err != nil {
   638  			logrus.WithError(err).Info("failing grpc.NewClient")
   639  			return false, nil
   640  		}
   641  		defer conn.Close() // nolint: errcheck
   642  
   643  		grpcClient := pb.NewAllocationServiceClient(conn)
   644  		response, err := grpcClient.Allocate(context.Background(), request)
   645  		if err != nil {
   646  			logrus.WithError(err).Info("failing Allocate request")
   647  			return false, nil
   648  		}
   649  		helper.ValidateAllocatorResponse(t, response)
   650  		return true, nil
   651  	})
   652  
   653  	assert.NoError(t, err)
   654  }