agones.dev/agones@v1.54.0/pkg/gameserverallocations/controller_test.go (about)

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gameserverallocations
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"strconv"
    25  	"testing"
    26  	"time"
    27  
    28  	pb "agones.dev/agones/pkg/allocation/go"
    29  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    30  	allocationv1 "agones.dev/agones/pkg/apis/allocation/v1"
    31  	multiclusterv1 "agones.dev/agones/pkg/apis/multicluster/v1"
    32  	"agones.dev/agones/pkg/gameservers"
    33  	agtesting "agones.dev/agones/pkg/testing"
    34  	"agones.dev/agones/pkg/util/apiserver"
    35  	"github.com/heptiolabs/healthcheck"
    36  	"github.com/pkg/errors"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  	"google.golang.org/grpc"
    40  	"google.golang.org/grpc/codes"
    41  	"google.golang.org/grpc/status"
    42  	corev1 "k8s.io/api/core/v1"
    43  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    44  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    45  	k8sruntime "k8s.io/apimachinery/pkg/runtime"
    46  	"k8s.io/apimachinery/pkg/util/wait"
    47  	"k8s.io/apimachinery/pkg/watch"
    48  	k8stesting "k8s.io/client-go/testing"
    49  )
    50  
    51  const (
    52  	defaultNs = "default"
    53  	n1        = "node1"
    54  	n2        = "node2"
    55  
    56  	unhealthyEndpoint = "unhealthy_endpoint:443"
    57  )
    58  
    59  func TestControllerAllocator(t *testing.T) {
    60  	t.Parallel()
    61  
    62  	t.Run("successful allocation", func(t *testing.T) {
    63  		f, gsList := defaultFixtures(4)
    64  
    65  		gsaSelectors := &allocationv1.GameServerAllocation{
    66  			Spec: allocationv1.GameServerAllocationSpec{
    67  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}}},
    68  			}}
    69  		gsaRequired := &allocationv1.GameServerAllocation{
    70  			Spec: allocationv1.GameServerAllocationSpec{
    71  				Required: allocationv1.GameServerSelector{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: f.ObjectMeta.Name}}},
    72  			}}
    73  
    74  		c, m := newFakeController()
    75  		gsWatch := watch.NewFake()
    76  		m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
    77  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
    78  			return true, &agonesv1.GameServerList{Items: gsList}, nil
    79  		})
    80  
    81  		updated := map[string]bool{}
    82  		m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
    83  			ua := action.(k8stesting.UpdateAction)
    84  			gs := ua.GetObject().(*agonesv1.GameServer)
    85  
    86  			if _, ok := updated[gs.ObjectMeta.Name]; ok {
    87  				return true, nil, k8serrors.NewConflict(agonesv1.Resource("gameservers"), gs.ObjectMeta.Name, fmt.Errorf("already updated"))
    88  			}
    89  
    90  			updated[gs.ObjectMeta.Name] = true
    91  			gsWatch.Modify(gs)
    92  			return true, gs, nil
    93  		})
    94  
    95  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
    96  		defer cancel()
    97  
    98  		if err := c.Run(ctx, 1); err != nil {
    99  			assert.FailNow(t, err.Error())
   100  		}
   101  		// wait for it to be up and running
   102  		err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) {
   103  			return c.allocator.allocationCache.workerqueue.RunCount() == 1, nil
   104  		})
   105  		assert.NoError(t, err)
   106  
   107  		test := func(gsa *allocationv1.GameServerAllocation, expectedState allocationv1.GameServerAllocationState) {
   108  			buf := bytes.NewBuffer(nil)
   109  			err := json.NewEncoder(buf).Encode(gsa)
   110  			require.NoError(t, err)
   111  			r, err := http.NewRequest(http.MethodPost, "/", buf)
   112  			r.Header.Set("Content-Type", k8sruntime.ContentTypeJSON)
   113  			require.NoError(t, err)
   114  			rec := httptest.NewRecorder()
   115  			err = c.processAllocationRequest(ctx, rec, r, "default")
   116  			require.NoError(t, err)
   117  			require.Equal(t, http.StatusCreated, rec.Code)
   118  			ret := &allocationv1.GameServerAllocation{}
   119  			err = json.Unmarshal(rec.Body.Bytes(), ret)
   120  			require.NoError(t, err)
   121  
   122  			if len(gsa.Spec.Selectors) != 0 {
   123  				require.Equal(t, gsa.Spec.Selectors[0].LabelSelector, ret.Spec.Selectors[0].LabelSelector)
   124  			} else {
   125  				// nolint:staticcheck
   126  				require.Equal(t, gsa.Spec.Required.LabelSelector, ret.Spec.Selectors[0].LabelSelector)
   127  			}
   128  
   129  			require.True(t, expectedState == ret.Status.State, "Failed: %s vs %s", expectedState, ret.Status.State)
   130  		}
   131  
   132  		test(gsaSelectors.DeepCopy(), allocationv1.GameServerAllocationAllocated)
   133  		test(gsaRequired.DeepCopy(), allocationv1.GameServerAllocationAllocated)
   134  		test(gsaSelectors.DeepCopy(), allocationv1.GameServerAllocationAllocated)
   135  		test(gsaRequired.DeepCopy(), allocationv1.GameServerAllocationAllocated)
   136  		test(gsaSelectors.DeepCopy(), allocationv1.GameServerAllocationUnAllocated)
   137  		test(gsaRequired.DeepCopy(), allocationv1.GameServerAllocationUnAllocated)
   138  	})
   139  
   140  	t.Run("method not allowed", func(t *testing.T) {
   141  		c, _ := newFakeController()
   142  		r, err := http.NewRequest(http.MethodGet, "/", nil)
   143  		rec := httptest.NewRecorder()
   144  		assert.NoError(t, err)
   145  
   146  		err = c.processAllocationRequest(context.Background(), rec, r, "default")
   147  		assert.NoError(t, err)
   148  
   149  		assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
   150  	})
   151  
   152  	t.Run("invalid gameserverallocation", func(t *testing.T) {
   153  		c, _ := newFakeController()
   154  		gsa := &allocationv1.GameServerAllocation{
   155  			Spec: allocationv1.GameServerAllocationSpec{
   156  				Scheduling: "wrong",
   157  			}}
   158  		buf := bytes.NewBuffer(nil)
   159  		err := json.NewEncoder(buf).Encode(gsa)
   160  		assert.NoError(t, err)
   161  		r, err := http.NewRequest(http.MethodPost, "/", buf)
   162  		r.Header.Set("Content-Type", k8sruntime.ContentTypeJSON)
   163  		assert.NoError(t, err)
   164  		rec := httptest.NewRecorder()
   165  		err = c.processAllocationRequest(context.Background(), rec, r, "default")
   166  		assert.NoError(t, err)
   167  
   168  		assert.Equal(t, http.StatusUnprocessableEntity, rec.Code)
   169  
   170  		s := &metav1.Status{}
   171  		err = json.NewDecoder(rec.Body).Decode(s)
   172  		assert.NoError(t, err)
   173  
   174  		assert.Equal(t, metav1.StatusReasonInvalid, s.Reason)
   175  	})
   176  }
   177  
   178  func TestAllocationApiResource(t *testing.T) {
   179  	t.Parallel()
   180  
   181  	c, m := newFakeController()
   182  	c.registerAPIResource(context.Background())
   183  
   184  	ts := httptest.NewServer(m.Mux)
   185  	defer ts.Close()
   186  
   187  	client := ts.Client()
   188  
   189  	resp, err := client.Get(ts.URL + "/apis/" + allocationv1.SchemeGroupVersion.String())
   190  	if !assert.Nil(t, err) {
   191  		assert.FailNow(t, err.Error())
   192  	}
   193  	defer resp.Body.Close() // nolint: errcheck
   194  
   195  	list := &metav1.APIResourceList{}
   196  	err = json.NewDecoder(resp.Body).Decode(list)
   197  	assert.Nil(t, err)
   198  
   199  	if assert.Len(t, list.APIResources, 1) {
   200  		assert.Equal(t, "gameserverallocation", list.APIResources[0].SingularName)
   201  	}
   202  }
   203  
   204  func TestMultiClusterAllocationFromLocal(t *testing.T) {
   205  	t.Parallel()
   206  	t.Run("Handle allocation request locally", func(t *testing.T) {
   207  		c, m := newFakeController()
   208  		fleetName := addReactorForGameServer(&m)
   209  
   210  		m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   211  			return true, &multiclusterv1.GameServerAllocationPolicyList{
   212  				Items: []multiclusterv1.GameServerAllocationPolicy{
   213  					{
   214  						Spec: multiclusterv1.GameServerAllocationPolicySpec{
   215  							Priority: 1,
   216  							Weight:   200,
   217  							ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   218  								ClusterName: "multicluster",
   219  								SecretName:  "localhostsecret",
   220  								Namespace:   defaultNs,
   221  								ServerCA:    []byte("not-used"),
   222  							},
   223  						},
   224  						ObjectMeta: metav1.ObjectMeta{
   225  							Labels:    map[string]string{"cluster": "onprem"},
   226  							Namespace: defaultNs,
   227  						},
   228  					},
   229  				},
   230  			}, nil
   231  		})
   232  
   233  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
   234  		defer cancel()
   235  
   236  		if err := c.Run(ctx, 1); err != nil {
   237  			assert.FailNow(t, err.Error())
   238  		}
   239  		// wait for it to be up and running
   240  		err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) {
   241  			return c.allocator.allocationCache.workerqueue.RunCount() == 1, nil
   242  		})
   243  		assert.NoError(t, err)
   244  
   245  		gsa := &allocationv1.GameServerAllocation{
   246  			ObjectMeta: metav1.ObjectMeta{
   247  				Namespace: defaultNs,
   248  				Name:      "alloc1",
   249  			},
   250  			Spec: allocationv1.GameServerAllocationSpec{
   251  				MultiClusterSetting: allocationv1.MultiClusterSetting{
   252  					Enabled: true,
   253  					PolicySelector: metav1.LabelSelector{
   254  						MatchLabels: map[string]string{
   255  							"cluster": "onprem",
   256  						},
   257  					},
   258  				},
   259  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}},
   260  			},
   261  		}
   262  
   263  		ret, err := executeAllocation(gsa, c)
   264  		require.NoError(t, err)
   265  		assert.Equal(t, gsa.Spec.Selectors[0].LabelSelector, ret.Spec.Selectors[0].LabelSelector)
   266  		assert.Equal(t, gsa.Namespace, ret.Namespace)
   267  		expectedState := allocationv1.GameServerAllocationAllocated
   268  		assert.True(t, expectedState == ret.Status.State, "Failed: %s vs %s", expectedState, ret.Status.State)
   269  	})
   270  
   271  	t.Run("Missing multicluster policy", func(t *testing.T) {
   272  		c, m := newFakeController()
   273  		fleetName := addReactorForGameServer(&m)
   274  
   275  		m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   276  			return true, &multiclusterv1.GameServerAllocationPolicyList{
   277  				Items: []multiclusterv1.GameServerAllocationPolicy{},
   278  			}, nil
   279  		})
   280  
   281  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
   282  		defer cancel()
   283  
   284  		if err := c.Run(ctx, 1); err != nil {
   285  			assert.FailNow(t, err.Error())
   286  		}
   287  		// wait for it to be up and running
   288  		err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) {
   289  			return c.allocator.allocationCache.workerqueue.RunCount() == 1, nil
   290  		})
   291  		assert.NoError(t, err)
   292  
   293  		gsa := &allocationv1.GameServerAllocation{
   294  			ObjectMeta: metav1.ObjectMeta{
   295  				Namespace: defaultNs,
   296  				Name:      "alloc1",
   297  			},
   298  			Spec: allocationv1.GameServerAllocationSpec{
   299  				MultiClusterSetting: allocationv1.MultiClusterSetting{
   300  					Enabled: true,
   301  					PolicySelector: metav1.LabelSelector{
   302  						MatchLabels: map[string]string{
   303  							"cluster": "onprem",
   304  						},
   305  					},
   306  				},
   307  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}},
   308  			},
   309  		}
   310  
   311  		_, err = executeAllocation(gsa, c)
   312  		assert.Error(t, err)
   313  	})
   314  
   315  	t.Run("Could not find a Ready GameServer", func(t *testing.T) {
   316  		c, m := newFakeController()
   317  
   318  		m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   319  			return true, &multiclusterv1.GameServerAllocationPolicyList{
   320  				Items: []multiclusterv1.GameServerAllocationPolicy{
   321  					{
   322  						Spec: multiclusterv1.GameServerAllocationPolicySpec{
   323  							Priority: 1,
   324  							Weight:   200,
   325  							ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   326  								ClusterName: "multicluster",
   327  								SecretName:  "localhostsecret",
   328  								Namespace:   defaultNs,
   329  								ServerCA:    []byte("not-used"),
   330  							},
   331  						},
   332  						ObjectMeta: metav1.ObjectMeta{
   333  							Labels:    map[string]string{"cluster": "onprem"},
   334  							Namespace: defaultNs,
   335  						},
   336  					},
   337  				},
   338  			}, nil
   339  		})
   340  
   341  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
   342  		defer cancel()
   343  
   344  		if err := c.Run(ctx, 1); err != nil {
   345  			assert.FailNow(t, err.Error())
   346  		}
   347  		// wait for it to be up and running
   348  		err := wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (done bool, err error) {
   349  			return c.allocator.allocationCache.workerqueue.RunCount() == 1, nil
   350  		})
   351  		assert.NoError(t, err)
   352  
   353  		gsa := &allocationv1.GameServerAllocation{
   354  			ObjectMeta: metav1.ObjectMeta{
   355  				Namespace: defaultNs,
   356  				Name:      "alloc1",
   357  			},
   358  			Spec: allocationv1.GameServerAllocationSpec{
   359  				MultiClusterSetting: allocationv1.MultiClusterSetting{
   360  					Enabled: true,
   361  					PolicySelector: metav1.LabelSelector{
   362  						MatchLabels: map[string]string{
   363  							"cluster": "onprem",
   364  						},
   365  					},
   366  				},
   367  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: "empty-fleet"}}}},
   368  			},
   369  		}
   370  
   371  		ret, err := executeAllocation(gsa, c)
   372  		assert.NoError(t, err)
   373  		assert.Equal(t, gsa.Spec.Selectors[0].LabelSelector, ret.Spec.Selectors[0].LabelSelector)
   374  		assert.Equal(t, gsa.Namespace, ret.Namespace)
   375  		expectedState := allocationv1.GameServerAllocationUnAllocated
   376  		assert.True(t, expectedState == ret.Status.State, "Failed: %s vs %s", expectedState, ret.Status.State)
   377  	})
   378  }
   379  
   380  func TestMultiClusterAllocationFromRemote(t *testing.T) {
   381  	const clusterName = "remotecluster"
   382  	t.Parallel()
   383  	t.Run("Handle allocation request remotely", func(t *testing.T) {
   384  		c, m := newFakeController()
   385  		fleetName := addReactorForGameServer(&m)
   386  		expectedGSName := "mocked"
   387  		endpoint := "x.x.x.x"
   388  
   389  		// Allocation policy reactor
   390  		secretName := clusterName + "secret"
   391  		targetedNamespace := "tns"
   392  		m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   393  			return true, &multiclusterv1.GameServerAllocationPolicyList{
   394  				Items: []multiclusterv1.GameServerAllocationPolicy{
   395  					{
   396  						Spec: multiclusterv1.GameServerAllocationPolicySpec{
   397  							Priority: 1,
   398  							Weight:   200,
   399  							ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   400  								AllocationEndpoints: []string{endpoint, "non-existing"},
   401  								ClusterName:         clusterName,
   402  								SecretName:          secretName,
   403  								Namespace:           targetedNamespace,
   404  								ServerCA:            clientCert,
   405  							},
   406  						},
   407  						ObjectMeta: metav1.ObjectMeta{
   408  							Namespace: defaultNs,
   409  						},
   410  					},
   411  				},
   412  			}, nil
   413  		})
   414  
   415  		m.KubeClient.AddReactor("list", "secrets",
   416  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   417  				return true, getTestSecret(secretName, nil), nil
   418  			})
   419  
   420  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
   421  		defer cancel()
   422  
   423  		// This call initializes the cache
   424  		err := c.allocator.allocationCache.syncCache()
   425  		assert.Nil(t, err)
   426  
   427  		err = c.allocator.allocationCache.counter.Run(ctx, 0)
   428  		assert.Nil(t, err)
   429  
   430  		gsa := &allocationv1.GameServerAllocation{
   431  			ObjectMeta: metav1.ObjectMeta{
   432  				Namespace: defaultNs,
   433  				Name:      "alloc1",
   434  			},
   435  			Spec: allocationv1.GameServerAllocationSpec{
   436  				MultiClusterSetting: allocationv1.MultiClusterSetting{
   437  					Enabled: true,
   438  				},
   439  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}},
   440  			},
   441  		}
   442  
   443  		c.allocator.remoteAllocationCallback = func(_ context.Context, e string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) {
   444  			assert.Equal(t, endpoint+":443", e)
   445  			serverResponse := pb.AllocationResponse{
   446  				GameServerName: expectedGSName,
   447  			}
   448  			return &serverResponse, nil
   449  		}
   450  
   451  		result, err := executeAllocation(gsa, c)
   452  		if assert.NoError(t, err) {
   453  			assert.Equal(t, expectedGSName, result.Status.GameServerName)
   454  		}
   455  	})
   456  
   457  	t.Run("Remote server returns conflict and then random error", func(t *testing.T) {
   458  		c, m := newFakeController()
   459  		fleetName := addReactorForGameServer(&m)
   460  
   461  		// Mock server to return unallocated and then error
   462  		count := 0
   463  		retry := 0
   464  		endpoint := "z.z.z.z"
   465  
   466  		c.allocator.remoteAllocationCallback = func(_ context.Context, _ string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) {
   467  			if count == 0 {
   468  				serverResponse := pb.AllocationResponse{}
   469  				count++
   470  				return &serverResponse, status.Error(codes.Aborted, "conflict")
   471  			}
   472  
   473  			retry++
   474  			return nil, errors.New("test error message")
   475  		}
   476  
   477  		// Allocation policy reactor
   478  		secretName := clusterName + "secret"
   479  		m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   480  			return true, &multiclusterv1.GameServerAllocationPolicyList{
   481  				Items: []multiclusterv1.GameServerAllocationPolicy{
   482  					{
   483  						Spec: multiclusterv1.GameServerAllocationPolicySpec{
   484  							Priority: 1,
   485  							Weight:   200,
   486  							ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   487  								AllocationEndpoints: []string{endpoint},
   488  								ClusterName:         clusterName,
   489  								SecretName:          secretName,
   490  								ServerCA:            clientCert,
   491  							},
   492  						},
   493  						ObjectMeta: metav1.ObjectMeta{
   494  							Name:      "name1",
   495  							Namespace: defaultNs,
   496  						},
   497  					},
   498  					{
   499  						Spec: multiclusterv1.GameServerAllocationPolicySpec{
   500  							Priority: 2,
   501  							Weight:   200,
   502  							ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   503  								AllocationEndpoints: []string{endpoint},
   504  								ClusterName:         "remotecluster2",
   505  								SecretName:          secretName,
   506  								ServerCA:            clientCert,
   507  							},
   508  						},
   509  						ObjectMeta: metav1.ObjectMeta{
   510  							Name:      "name2",
   511  							Namespace: defaultNs,
   512  						},
   513  					},
   514  				},
   515  			}, nil
   516  		})
   517  
   518  		m.KubeClient.AddReactor("list", "secrets",
   519  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   520  				return true, getTestSecret(secretName, clientCert), nil
   521  			})
   522  
   523  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
   524  		defer cancel()
   525  
   526  		// This call initializes the cache
   527  		err := c.allocator.allocationCache.syncCache()
   528  		assert.Nil(t, err)
   529  
   530  		err = c.allocator.allocationCache.counter.Run(ctx, 0)
   531  		assert.Nil(t, err)
   532  
   533  		gsa := &allocationv1.GameServerAllocation{
   534  			ObjectMeta: metav1.ObjectMeta{
   535  				Namespace: defaultNs,
   536  				Name:      "alloc1",
   537  			},
   538  			Spec: allocationv1.GameServerAllocationSpec{
   539  				MultiClusterSetting: allocationv1.MultiClusterSetting{
   540  					Enabled: true,
   541  				},
   542  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}},
   543  			},
   544  		}
   545  
   546  		_, err = executeAllocation(gsa, c)
   547  		if assert.Error(t, err) {
   548  			assert.Contains(t, err.Error(), "test error message")
   549  		}
   550  		assert.Truef(t, retry > 1, "Retry count %v. Expecting to retry on error.", retry)
   551  	})
   552  
   553  	t.Run("First server fails and second server succeeds", func(t *testing.T) {
   554  		c, m := newFakeController()
   555  		fleetName := addReactorForGameServer(&m)
   556  
   557  		healthyEndpoint := "healthy_endpoint:443"
   558  
   559  		expectedGSName := "mocked"
   560  		c.allocator.remoteAllocationCallback = func(_ context.Context, endpoint string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) {
   561  			if endpoint == unhealthyEndpoint {
   562  				return nil, errors.New("test error message")
   563  			}
   564  
   565  			assert.Equal(t, healthyEndpoint, endpoint)
   566  			serverResponse := pb.AllocationResponse{
   567  				GameServerName: expectedGSName,
   568  			}
   569  			return &serverResponse, nil
   570  		}
   571  
   572  		// Allocation policy reactor
   573  		secretName := clusterName + "secret"
   574  
   575  		m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   576  			return true, &multiclusterv1.GameServerAllocationPolicyList{
   577  				Items: []multiclusterv1.GameServerAllocationPolicy{
   578  					{
   579  						Spec: multiclusterv1.GameServerAllocationPolicySpec{
   580  							Priority: 1,
   581  							Weight:   200,
   582  							ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   583  								AllocationEndpoints: []string{unhealthyEndpoint, healthyEndpoint},
   584  								ClusterName:         clusterName,
   585  								SecretName:          secretName,
   586  								ServerCA:            clientCert,
   587  							},
   588  						},
   589  						ObjectMeta: metav1.ObjectMeta{
   590  							Namespace: defaultNs,
   591  						},
   592  					},
   593  				},
   594  			}, nil
   595  		})
   596  
   597  		m.KubeClient.AddReactor("list", "secrets",
   598  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   599  				return true, getTestSecret(secretName, clientCert), nil
   600  			})
   601  
   602  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
   603  		defer cancel()
   604  
   605  		// This call initializes the cache
   606  		err := c.allocator.allocationCache.syncCache()
   607  		assert.Nil(t, err)
   608  
   609  		err = c.allocator.allocationCache.counter.Run(ctx, 0)
   610  		assert.Nil(t, err)
   611  
   612  		gsa := &allocationv1.GameServerAllocation{
   613  			ObjectMeta: metav1.ObjectMeta{
   614  				Namespace: defaultNs,
   615  				Name:      "alloc1",
   616  			},
   617  			Spec: allocationv1.GameServerAllocationSpec{
   618  				MultiClusterSetting: allocationv1.MultiClusterSetting{
   619  					Enabled: true,
   620  				},
   621  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}},
   622  			},
   623  		}
   624  
   625  		result, err := executeAllocation(gsa, c)
   626  		if assert.NoError(t, err) {
   627  			assert.Equal(t, expectedGSName, result.Status.GameServerName)
   628  		}
   629  	})
   630  	t.Run("No allocations called after total timeout", func(t *testing.T) {
   631  		c, m := newFakeControllerWithTimeout(10*time.Second, 0*time.Second)
   632  		fleetName := addReactorForGameServer(&m)
   633  
   634  		calls := 0
   635  		c.allocator.remoteAllocationCallback = func(_ context.Context, _ string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) {
   636  			calls++
   637  			return nil, errors.New("Error")
   638  		}
   639  
   640  		// Allocation policy reactor
   641  		secretName := clusterName + "secret"
   642  		m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   643  			return true, &multiclusterv1.GameServerAllocationPolicyList{
   644  				Items: []multiclusterv1.GameServerAllocationPolicy{
   645  					{
   646  						Spec: multiclusterv1.GameServerAllocationPolicySpec{
   647  							Priority: 1,
   648  							Weight:   200,
   649  							ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   650  								AllocationEndpoints: []string{unhealthyEndpoint},
   651  								ClusterName:         clusterName,
   652  								SecretName:          secretName,
   653  								ServerCA:            clientCert,
   654  							},
   655  						},
   656  						ObjectMeta: metav1.ObjectMeta{
   657  							Namespace: defaultNs,
   658  						},
   659  					},
   660  				},
   661  			}, nil
   662  		})
   663  
   664  		m.KubeClient.AddReactor("list", "secrets",
   665  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   666  				return true, getTestSecret(secretName, clientCert), nil
   667  			})
   668  
   669  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
   670  		defer cancel()
   671  
   672  		// This call initializes the cache
   673  		err := c.allocator.allocationCache.syncCache()
   674  		assert.Nil(t, err)
   675  
   676  		err = c.allocator.allocationCache.counter.Run(ctx, 0)
   677  		assert.Nil(t, err)
   678  
   679  		gsa := &allocationv1.GameServerAllocation{
   680  			ObjectMeta: metav1.ObjectMeta{
   681  				Namespace: defaultNs,
   682  				Name:      "alloc1",
   683  			},
   684  			Spec: allocationv1.GameServerAllocationSpec{
   685  				MultiClusterSetting: allocationv1.MultiClusterSetting{
   686  					Enabled: true,
   687  				},
   688  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}},
   689  			},
   690  		}
   691  
   692  		_, err = executeAllocation(gsa, c)
   693  		assert.Error(t, err)
   694  		st, ok := status.FromError(err)
   695  		assert.True(t, ok)
   696  		assert.Equal(t, st.Code(), codes.DeadlineExceeded)
   697  		assert.Equal(t, 0, calls)
   698  	})
   699  	t.Run("First allocation fails and second succeeds on the same server", func(t *testing.T) {
   700  		c, m := newFakeController()
   701  		fleetName := addReactorForGameServer(&m)
   702  
   703  		// Mock server to return DeadlineExceeded on the first call and success on subsequent ones
   704  		calls := 0
   705  		c.allocator.remoteAllocationCallback = func(_ context.Context, _ string, _ grpc.DialOption, _ *pb.AllocationRequest) (*pb.AllocationResponse, error) {
   706  			calls++
   707  			if calls == 1 {
   708  				return nil, status.Errorf(codes.DeadlineExceeded, "remote allocation call timeout")
   709  			}
   710  			return &pb.AllocationResponse{}, nil
   711  		}
   712  
   713  		// Allocation policy reactor
   714  		secretName := clusterName + "secret"
   715  		m.AgonesClient.AddReactor("list", "gameserverallocationpolicies", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   716  			return true, &multiclusterv1.GameServerAllocationPolicyList{
   717  				Items: []multiclusterv1.GameServerAllocationPolicy{
   718  					{
   719  						Spec: multiclusterv1.GameServerAllocationPolicySpec{
   720  							Priority: 1,
   721  							Weight:   200,
   722  							ConnectionInfo: multiclusterv1.ClusterConnectionInfo{
   723  								AllocationEndpoints: []string{unhealthyEndpoint},
   724  								ClusterName:         clusterName,
   725  								SecretName:          secretName,
   726  								ServerCA:            clientCert,
   727  							},
   728  						},
   729  						ObjectMeta: metav1.ObjectMeta{
   730  							Namespace: defaultNs,
   731  						},
   732  					},
   733  				},
   734  			}, nil
   735  		})
   736  
   737  		m.KubeClient.AddReactor("list", "secrets",
   738  			func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   739  				return true, getTestSecret(secretName, clientCert), nil
   740  			})
   741  
   742  		ctx, cancel := agtesting.StartInformers(m, c.allocator.allocationPolicySynced, c.allocator.secretSynced, c.allocator.allocationCache.gameServerSynced)
   743  		defer cancel()
   744  
   745  		// This call initializes the cache
   746  		err := c.allocator.allocationCache.syncCache()
   747  		assert.Nil(t, err)
   748  
   749  		err = c.allocator.allocationCache.counter.Run(ctx, 0)
   750  		assert.Nil(t, err)
   751  
   752  		gsa := &allocationv1.GameServerAllocation{
   753  			ObjectMeta: metav1.ObjectMeta{
   754  				Namespace: defaultNs,
   755  				Name:      "alloc1",
   756  			},
   757  			Spec: allocationv1.GameServerAllocationSpec{
   758  				MultiClusterSetting: allocationv1.MultiClusterSetting{
   759  					Enabled: true,
   760  				},
   761  				Selectors: []allocationv1.GameServerSelector{{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{agonesv1.FleetNameLabel: fleetName}}}},
   762  			},
   763  		}
   764  
   765  		_, err = executeAllocation(gsa, c)
   766  		assert.NoError(t, err)
   767  		assert.Equal(t, 2, calls)
   768  	})
   769  }
   770  
   771  func executeAllocation(gsa *allocationv1.GameServerAllocation, c *Extensions) (*allocationv1.GameServerAllocation, error) {
   772  	r, err := createRequest(gsa)
   773  	if err != nil {
   774  		return nil, err
   775  	}
   776  	rec := httptest.NewRecorder()
   777  	if err := c.processAllocationRequest(context.Background(), rec, r, gsa.Namespace); err != nil {
   778  		return nil, err
   779  	}
   780  
   781  	ret := &allocationv1.GameServerAllocation{}
   782  	jsn := rec.Body.Bytes()
   783  	err = json.Unmarshal(jsn, ret)
   784  	return ret, errors.Wrapf(err, "failed to unmarshal allocation response: %s", jsn)
   785  }
   786  
   787  func addReactorForGameServer(m *agtesting.Mocks) string {
   788  	f, gsList := defaultFixtures(3)
   789  	gsWatch := watch.NewFake()
   790  	m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
   791  	m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, k8sruntime.Object, error) {
   792  		return true, &agonesv1.GameServerList{Items: gsList}, nil
   793  	})
   794  	m.AgonesClient.AddReactor("update", "gameservers", func(action k8stesting.Action) (bool, k8sruntime.Object, error) {
   795  		ua := action.(k8stesting.UpdateAction)
   796  		gs := ua.GetObject().(*agonesv1.GameServer)
   797  		gsWatch.Modify(gs)
   798  		return true, gs, nil
   799  	})
   800  	return f.ObjectMeta.Name
   801  }
   802  
   803  func createRequest(gsa *allocationv1.GameServerAllocation) (*http.Request, error) {
   804  	buf := bytes.NewBuffer(nil)
   805  	if err := json.NewEncoder(buf).Encode(gsa); err != nil {
   806  		return nil, err
   807  	}
   808  
   809  	r, err := http.NewRequest(http.MethodPost, "/", buf)
   810  	if err != nil {
   811  		return nil, err
   812  	}
   813  
   814  	r.Header.Set("Content-Type", k8sruntime.ContentTypeJSON)
   815  	r.Header.Set("Accept", k8sruntime.ContentTypeJSON)
   816  
   817  	return r, nil
   818  }
   819  
   820  func defaultFixtures(gsLen int) (*agonesv1.Fleet, []agonesv1.GameServer) {
   821  	f := &agonesv1.Fleet{
   822  		ObjectMeta: metav1.ObjectMeta{
   823  			Name:      "fleet-1",
   824  			Namespace: defaultNs,
   825  			UID:       "1234",
   826  		},
   827  		Spec: agonesv1.FleetSpec{
   828  			Replicas: 5,
   829  			Template: agonesv1.GameServerTemplateSpec{},
   830  		},
   831  	}
   832  	f.ApplyDefaults()
   833  	gsSet := f.GameServerSet()
   834  	gsSet.ObjectMeta.Name = "gsSet1"
   835  	var gsList []agonesv1.GameServer
   836  	for i := 1; i <= gsLen; i++ {
   837  		gs := gsSet.GameServer()
   838  		gs.ObjectMeta.Name = "gs" + strconv.Itoa(i)
   839  		gs.Status.State = agonesv1.GameServerStateReady
   840  		gsList = append(gsList, *gs)
   841  	}
   842  	return f, gsList
   843  }
   844  
   845  // newFakeController returns a controller, backed by the fake Clientset
   846  func newFakeController() (*Extensions, agtesting.Mocks) {
   847  	return newFakeControllerWithTimeout(10*time.Second, 30*time.Second)
   848  }
   849  
   850  // newFakeController returns a controller, backed by the fake Clientset with custom allocation timeouts
   851  func newFakeControllerWithTimeout(remoteAllocationTimeout time.Duration, totalRemoteAllocationTimeout time.Duration) (*Extensions, agtesting.Mocks) {
   852  	m := agtesting.NewMocks()
   853  	m.Mux = http.NewServeMux()
   854  	counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory)
   855  	api := apiserver.NewAPIServer(m.Mux)
   856  	c := NewExtensions(api, healthcheck.NewHandler(), counter, m.KubeClient, m.KubeInformerFactory, m.AgonesClient, m.AgonesInformerFactory, remoteAllocationTimeout, totalRemoteAllocationTimeout, 500*time.Millisecond)
   857  	c.recorder = m.FakeRecorder
   858  	c.allocator.recorder = m.FakeRecorder
   859  	return c, m
   860  }
   861  
   862  func getTestSecret(secretName string, serverCert []byte) *corev1.SecretList {
   863  	return &corev1.SecretList{
   864  		Items: []corev1.Secret{
   865  			{
   866  				Data: map[string][]byte{
   867  					"ca.crt":  serverCert,
   868  					"tls.key": clientKey,
   869  					"tls.crt": clientCert,
   870  				},
   871  				ObjectMeta: metav1.ObjectMeta{
   872  					Name:      secretName,
   873  					Namespace: defaultNs,
   874  				},
   875  			},
   876  		},
   877  	}
   878  }
   879  
   880  var clientCert = []byte(`-----BEGIN CERTIFICATE-----
   881  MIIDuzCCAqOgAwIBAgIUduDWtqpUsp3rZhCEfUrzI05laVIwDQYJKoZIhvcNAQEL
   882  BQAwbTELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9u
   883  ZG9uMRgwFgYDVQQKDA9HbG9iYWwgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFy
   884  dG1lbnQxCjAIBgNVBAMMASowHhcNMTkwNTAyMjIzMDQ3WhcNMjkwNDI5MjIzMDQ3
   885  WjBtMQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQHDAZMb25k
   886  b24xGDAWBgNVBAoMD0dsb2JhbCBTZWN1cml0eTEWMBQGA1UECwwNSVQgRGVwYXJ0
   887  bWVudDEKMAgGA1UEAwwBKjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
   888  AKGDasjadVwe0bXUEQfZCkMEAkzn0qTud3RYytympmaS0c01SWFNZwPRO0rpdIOZ
   889  fyXVXVOAhgmgCR6QuXySmyQIoYl/D6tVhc5r9FyWPIBtzQKCJTX0mZOZwMn22qvo
   890  bfnDnVsZ1Ny3RLZIF3um3xovvePXyg1z7D/NvCogNuYpyUUEITPZX6ss5ods/U78
   891  BxLhKrT8iyu61ZC+ZegbHQqFRngbeb348gE1JwKTslDfe4oH7tZ+bNDZxnGcvh9j
   892  eyagpM0zys4gFfQf/vfD2aEsUJ+GesUQC6uGVoGnTFshFhBsAK6vpIQ4ZQujaJ0r
   893  NKgJ/ccBJFiJXMCR44yWFY0CAwEAAaNTMFEwHQYDVR0OBBYEFEe1gDd8JpzgnvOo
   894  1AEloAXxmxHCMB8GA1UdIwQYMBaAFEe1gDd8JpzgnvOo1AEloAXxmxHCMA8GA1Ud
   895  EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAI5GyuakVgunerCGCSN7Ghsr
   896  ys9vJytbyT+BLmxNBPSXWQwcm3g9yCDdgf0Y3q3Eef7IEZu4I428318iLzhfKln1
   897  ua4fxvmTFKJ65lQKNkc6Y4e3w1t+C2HOl6fOIVT231qsCoM5SAwQQpqAzEUj6kZl
   898  x+3avw9KSlXqR/mCAkePyoKvprxeb6RVDdq92Ug0qzoAHLpvIkuHdlF0dNp6/kO0
   899  1pVL0BqW+6UTimSSvH8F/cMeYKbkhpE1u2c/NtNwsR2jN4M9kl3KHqkynk67PfZv
   900  pwlCqZx4M8FpdfCbOZeRLzClUBdD5qzev0L3RNUx7UJzEIN+4LCBv37DIojNOyA=
   901  -----END CERTIFICATE-----`)
   902  
   903  var clientKey = []byte(`-----BEGIN PRIVATE KEY-----
   904  MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChg2rI2nVcHtG1
   905  1BEH2QpDBAJM59Kk7nd0WMrcpqZmktHNNUlhTWcD0TtK6XSDmX8l1V1TgIYJoAke
   906  kLl8kpskCKGJfw+rVYXOa/RcljyAbc0CgiU19JmTmcDJ9tqr6G35w51bGdTct0S2
   907  SBd7pt8aL73j18oNc+w/zbwqIDbmKclFBCEz2V+rLOaHbP1O/AcS4Sq0/IsrutWQ
   908  vmXoGx0KhUZ4G3m9+PIBNScCk7JQ33uKB+7WfmzQ2cZxnL4fY3smoKTNM8rOIBX0
   909  H/73w9mhLFCfhnrFEAurhlaBp0xbIRYQbACur6SEOGULo2idKzSoCf3HASRYiVzA
   910  keOMlhWNAgMBAAECggEAaRPDjEq8IaOXUdFXByEIERNxn7EOlOjj5FjEGgt9pKwO
   911  PJBXXitqQsyD47fAasGZO/b1EZdDHM32QOFtG4OR1T6cQYTdn90zAVmwj+/aCr/k
   912  qaYcKV8p7yIPkBW+rCq6Kc0++X7zwmilFmYOiQ7GhRXcV3gTZu8tG1FxAoMU1GYA
   913  WoGiu+UsEm0MFIOwV/DOukDaj6j4Q9wD0tqi2MsjrugjDI8/mSx5mlvo3yZHubl0
   914  ChQaWZyUlL2B40mQJc3qsRZzso3sbU762L6G6npQJ19dHgsBfBBs/Q4/DdeqcOb4
   915  Q9OZ8Q3Q5nXQ7359Sh94LvLOoaWecRTBPGaRvGAGLQKBgQDTOZPEaJJI9heUQ0Ar
   916  VvUuyjILv8CG+PV+rGZ7+yMFCUlmM/m9I0IIc3WbmxxiRypBv46zxpczQHwWZRf2
   917  7IUZdyrBXRtNoaXbWh3dSgqa7WuHGUzqmn+98sQDodewCyGon8LG9atyge8vFo/l
   918  N0Y21duYj4NeJod82Y0RAKsuzwKBgQDDwCuvbq0FkugklUr5WLFrYTzWrTYPio5k
   919  ID6Ku57yaZNVRv52FTF3Ac5LoKGCv8iPg+x0SiTmCbW2DF2ohvTuJy1H/unJ4bYG
   920  B9vEVOiScbvrvuQ6iMgfxNUCEEQvmn6+uc+KHVwPixY4j6/q1ZLXLPbjqXYHPYi+
   921  lx9ZG0As4wKBgDj52QAr7Pm9WBLoKREHvc9HP0SoDrjZwu7Odj6POZ0MKj5lWsJI
   922  FnHNIzY8GuXvqFhf4ZBgyzxJ8q7fyh0TI7wAxwmtocXJCsImhtPAOygbTtv8WSEX
   923  V8nXCESqjVGxTvz7S0D716llny0met4rkMcN3NREMf1di0KENGcXtRVFAoGBAKs3
   924  bD5/NNF6RJizCKf+fvjoTVmMmYuQaqmDVpDsOMPZumfNuAa61NA+AR4/OuXtL9Tv
   925  1COHMq0O8yRvvoAIwzWHiOC/Q+g0B41Q1FXu2po05uT1zBSyzTCUbqfmaG2m2ZOj
   926  XLd2pK5nvqDsdTeXZV/WUYCiGb2Ngg0Ki/3ZixF3AoGACwPxxoAWkuD6T++35Vdt
   927  OxAh/qyGMtgfvdBJPfA3u4digTckBDTwYBhrmvC2Vuc4cpb15RYuUT/M+c3gS3P0
   928  q+2uLIuwciETPD7psK76NsQM3ZL/IEaZB3VMxbMMFn/NQRbmntTd/twZ42zieX+R
   929  2VpXYUjoRcuir2oU0wh3Hic=
   930  -----END PRIVATE KEY-----`)