agones.dev/agones@v1.53.0/pkg/portallocator/portallocator_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 portallocator
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  	"sync"
    21  	"testing"
    22  	"time"
    23  
    24  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    25  	agtesting "agones.dev/agones/pkg/testing"
    26  	utilruntime "agones.dev/agones/pkg/util/runtime"
    27  	"github.com/sirupsen/logrus"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	corev1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/labels"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/watch"
    36  	k8stesting "k8s.io/client-go/testing"
    37  	"k8s.io/client-go/tools/cache"
    38  )
    39  
    40  var (
    41  	n1 = corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1", UID: "node1"}}
    42  	n2 = corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node2", UID: "node2"}}
    43  	n3 = corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node3", UID: "node3"}}
    44  )
    45  
    46  func TestPortAllocatorAllocate(t *testing.T) {
    47  	t.Parallel()
    48  	utilruntime.FeatureTestMutex.Lock()
    49  	defer utilruntime.FeatureTestMutex.Unlock()
    50  
    51  	err := utilruntime.ParseFeatures(string(utilruntime.FeaturePortRanges) + "=true")
    52  	assert.NoError(t, err)
    53  	defer func() {
    54  		_ = utilruntime.ParseFeatures("")
    55  	}()
    56  
    57  	m := agtesting.NewMocks()
    58  	pr := map[string]PortRange{
    59  		agonesv1.DefaultPortRange: {MinPort: 10, MaxPort: 50},
    60  		"test":                    {MinPort: 51, MaxPort: 100},
    61  	}
    62  	pa := newAllocator(pr, m.KubeInformerFactory, m.AgonesInformerFactory)
    63  	nodeWatch := watch.NewFake()
    64  	m.KubeClient.AddWatchReactor("nodes", k8stesting.DefaultWatchReactor(nodeWatch, nil))
    65  
    66  	ctx, cancel := agtesting.StartInformers(m, pa.allocators[0].nodeSynced, pa.allocators[1].nodeSynced)
    67  	defer cancel()
    68  
    69  	// Make sure the add's don't corrupt the sync
    70  	// (no longer an issue, but leave this here for posterity)
    71  	nodeWatch.Add(&n1)
    72  	nodeWatch.Add(&n2)
    73  	assert.True(t, cache.WaitForCacheSync(ctx.Done(), pa.allocators[0].nodeSynced, pa.allocators[1].nodeSynced))
    74  
    75  	err = pa.allocators[0].syncAll()
    76  	require.NoError(t, err)
    77  	err = pa.allocators[1].syncAll()
    78  	require.NoError(t, err)
    79  
    80  	// single port empty
    81  	fixture := dynamicGameServerFixture()
    82  	fixture.Spec.Ports = append(fixture.Spec.Ports,
    83  		agonesv1.GameServerPort{Name: "passthrough", Range: "test", PortPolicy: agonesv1.Passthrough},
    84  		agonesv1.GameServerPort{Name: "passthrough", Range: "test", PortPolicy: agonesv1.Passthrough},
    85  	)
    86  	gs := pa.Allocate(fixture)
    87  
    88  	assert.NotNil(t, gs)
    89  	assert.GreaterOrEqual(t, gs.Spec.Ports[0].HostPort, int32(10))
    90  	assert.LessOrEqual(t, gs.Spec.Ports[0].HostPort, int32(50))
    91  	assert.GreaterOrEqual(t, gs.Spec.Ports[1].HostPort, int32(51))
    92  	assert.LessOrEqual(t, gs.Spec.Ports[1].HostPort, int32(100))
    93  	assert.GreaterOrEqual(t, gs.Spec.Ports[2].HostPort, int32(51))
    94  	assert.LessOrEqual(t, gs.Spec.Ports[2].HostPort, int32(100))
    95  }
    96  
    97  func TestPortRangeAllocatorAllocate(t *testing.T) {
    98  	t.Parallel()
    99  	fixture := dynamicGameServerFixture()
   100  
   101  	t.Run("test allocated port counts", func(t *testing.T) {
   102  		m := agtesting.NewMocks()
   103  		pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 50, m.KubeInformerFactory, m.AgonesInformerFactory)
   104  		nodeWatch := watch.NewFake()
   105  		m.KubeClient.AddWatchReactor("nodes", k8stesting.DefaultWatchReactor(nodeWatch, nil))
   106  
   107  		ctx, cancel := agtesting.StartInformers(m, pa.nodeSynced)
   108  		defer cancel()
   109  
   110  		// Make sure the add's don't corrupt the sync
   111  		// (no longer an issue, but leave this here for posterity)
   112  		nodeWatch.Add(&n1)
   113  		nodeWatch.Add(&n2)
   114  		assert.True(t, cache.WaitForCacheSync(ctx.Done(), pa.nodeSynced))
   115  
   116  		err := pa.syncAll()
   117  		require.NoError(t, err)
   118  
   119  		// single port dynamic
   120  		gs := pa.Allocate(fixture.DeepCopy())
   121  		require.NotNil(t, gs)
   122  		assert.Equal(t, 1, countTotalAllocatedPorts(pa))
   123  
   124  		gs = pa.Allocate(fixture.DeepCopy())
   125  		require.NotNil(t, gs)
   126  		assert.Equal(t, 2, countTotalAllocatedPorts(pa))
   127  
   128  		// double port, dynamic
   129  		gsCopy := fixture.DeepCopy()
   130  		gsCopy.Spec.Ports = append(gsCopy.Spec.Ports, agonesv1.GameServerPort{Name: "another", ContainerPort: 6666, PortPolicy: agonesv1.Dynamic, Range: agonesv1.DefaultPortRange})
   131  		require.Len(t, gsCopy.Spec.Ports, 2)
   132  		gs = pa.Allocate(gsCopy.DeepCopy())
   133  		require.NotNil(t, gs)
   134  		assert.Equal(t, 4, countTotalAllocatedPorts(pa))
   135  
   136  		// three ports, dynamic
   137  		gsCopy = gsCopy.DeepCopy()
   138  		gsCopy.Spec.Ports = append(gsCopy.Spec.Ports, agonesv1.GameServerPort{Name: "another", ContainerPort: 6666, PortPolicy: agonesv1.Dynamic, Range: agonesv1.DefaultPortRange})
   139  		require.Len(t, gsCopy.Spec.Ports, 3)
   140  
   141  		gs = pa.Allocate(gsCopy)
   142  		require.NotNil(t, gs)
   143  		assert.Equal(t, 7, countTotalAllocatedPorts(pa))
   144  
   145  		// 4 ports, 1 static, rest dynamic
   146  		gsCopy = gsCopy.DeepCopy()
   147  		expected := int32(9999)
   148  		gsCopy.Spec.Ports = append(gsCopy.Spec.Ports, agonesv1.GameServerPort{Name: "another", ContainerPort: 6666, HostPort: expected, PortPolicy: agonesv1.Static, Range: agonesv1.DefaultPortRange})
   149  		require.Len(t, gsCopy.Spec.Ports, 4)
   150  		gs = pa.Allocate(gsCopy)
   151  		require.NotNil(t, gs)
   152  		assert.Equal(t, 10, countTotalAllocatedPorts(pa))
   153  		assert.Equal(t, agonesv1.Static, gsCopy.Spec.Ports[3].PortPolicy)
   154  		assert.Equal(t, expected, gsCopy.Spec.Ports[3].HostPort)
   155  
   156  		// single port, passthrough
   157  		gsCopy = fixture.DeepCopy()
   158  		gsCopy.Spec.Ports[0] = agonesv1.GameServerPort{Name: "passthrough", PortPolicy: agonesv1.Passthrough, Range: agonesv1.DefaultPortRange}
   159  
   160  		gs = pa.Allocate(gsCopy)
   161  		require.NotNil(t, gs)
   162  		assert.NotEmpty(t, gsCopy.Spec.Ports[0].HostPort)
   163  		assert.Equal(t, gsCopy.Spec.Ports[0].HostPort, gsCopy.Spec.Ports[0].ContainerPort)
   164  		assert.Equal(t, 11, countTotalAllocatedPorts(pa))
   165  
   166  		// single port to two ports, tcpudp
   167  		gsCopy = fixture.DeepCopy()
   168  		gsCopy.Spec.Ports[0] = agonesv1.GameServerPort{Name: "gameport", PortPolicy: agonesv1.Dynamic, Protocol: agonesv1.ProtocolTCPUDP, Range: agonesv1.DefaultPortRange}
   169  
   170  		gs = pa.Allocate(gsCopy)
   171  		require.NotNil(t, gs)
   172  		assert.Equal(t, gsCopy.Spec.Ports[0].HostPort, gsCopy.Spec.Ports[1].HostPort)
   173  
   174  		assert.Equal(t, corev1.ProtocolTCP, gsCopy.Spec.Ports[0].Protocol)
   175  		assert.Equal(t, corev1.ProtocolUDP, gsCopy.Spec.Ports[1].Protocol)
   176  		assert.Equal(t, "gameport-tcp", gsCopy.Spec.Ports[0].Name)
   177  		assert.Equal(t, "gameport-udp", gsCopy.Spec.Ports[1].Name)
   178  		assert.Equal(t, 12, countTotalAllocatedPorts(pa))
   179  
   180  		// no port
   181  		gsCopy = fixture.DeepCopy()
   182  		gsCopy.Spec.Ports = nil
   183  		assert.Len(t, gsCopy.Spec.Ports, 0)
   184  		pa.Allocate(gsCopy)
   185  		assert.Nil(t, gsCopy.Spec.Ports)
   186  		assert.Nil(t, err)
   187  		assert.Equal(t, 12, countTotalAllocatedPorts(pa))
   188  	})
   189  
   190  	t.Run("ports are all allocated", func(t *testing.T) {
   191  		m := agtesting.NewMocks()
   192  		pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 20, m.KubeInformerFactory, m.AgonesInformerFactory)
   193  		nodeWatch := watch.NewFake()
   194  		m.KubeClient.AddWatchReactor("nodes", k8stesting.DefaultWatchReactor(nodeWatch, nil))
   195  
   196  		ctx, cancel := agtesting.StartInformers(m, pa.nodeSynced)
   197  		defer cancel()
   198  
   199  		// Make sure the add's don't corrupt the sync
   200  		nodeWatch.Add(&n1)
   201  		nodeWatch.Add(&n2)
   202  		assert.True(t, cache.WaitForCacheSync(ctx.Done(), pa.nodeSynced))
   203  
   204  		err := pa.syncAll()
   205  		require.NoError(t, err)
   206  
   207  		// two nodes
   208  		for x := 0; x < 2; x++ {
   209  			for i := 0; i < 11; i++ {
   210  				var p int32
   211  				gs := pa.Allocate(fixture.DeepCopy())
   212  				require.NotNil(t, gs)
   213  				assert.True(t, 10 <= gs.Spec.Ports[0].HostPort && gs.Spec.Ports[0].HostPort <= 20, "%v is not between 10 and 20", p)
   214  			}
   215  		}
   216  
   217  		assert.Len(t, pa.portAllocations, 2)
   218  		gs := pa.Allocate(fixture.DeepCopy())
   219  		require.NotNil(t, gs)
   220  		assert.NotEmpty(t, gs.Spec.Ports[0].HostPort)
   221  		assert.Len(t, pa.portAllocations, 3)
   222  	})
   223  
   224  	t.Run("ports are all allocated with multiple ports per GameServers", func(t *testing.T) {
   225  		m := agtesting.NewMocks()
   226  		minPort := int32(10)
   227  		maxPort := int32(20)
   228  		pa := newRangeAllocator(agonesv1.DefaultPortRange, minPort, maxPort, m.KubeInformerFactory, m.AgonesInformerFactory)
   229  		nodeWatch := watch.NewFake()
   230  		m.KubeClient.AddWatchReactor("nodes", k8stesting.DefaultWatchReactor(nodeWatch, nil))
   231  
   232  		ctx, cancel := agtesting.StartInformers(m, pa.nodeSynced)
   233  		defer cancel()
   234  
   235  		morePortFixture := fixture.DeepCopy()
   236  		morePortFixture.Spec.Ports = append(morePortFixture.Spec.Ports,
   237  			agonesv1.GameServerPort{Name: "another", ContainerPort: 6666, PortPolicy: agonesv1.Dynamic, Range: agonesv1.DefaultPortRange},
   238  			agonesv1.GameServerPort{Name: "static", ContainerPort: 6666, PortPolicy: agonesv1.Static, Range: agonesv1.DefaultPortRange, HostPort: 9999},
   239  			agonesv1.GameServerPort{Name: "passthrough", PortPolicy: agonesv1.Passthrough, Range: agonesv1.DefaultPortRange})
   240  
   241  		// Make sure the add's don't corrupt the sync
   242  		nodeWatch.Add(&n1)
   243  		nodeWatch.Add(&n2)
   244  		assert.True(t, cache.WaitForCacheSync(ctx.Done(), pa.nodeSynced))
   245  
   246  		err := pa.syncAll()
   247  		require.NoError(t, err)
   248  
   249  		// two nodes
   250  		for x := 0; x < 2; x++ {
   251  			for i := 0; i < 3; i++ {
   252  				gsCopy := morePortFixture.DeepCopy()
   253  				gsCopy.ObjectMeta.UID = types.UID(strconv.Itoa(x) + ":" + strconv.Itoa(i))
   254  				gs := pa.Allocate(gsCopy)
   255  				require.NotNil(t, gs)
   256  
   257  				// Dynamic
   258  				assert.NotEmpty(t, gs.Spec.Ports[0].HostPort)
   259  
   260  				// Passthrough
   261  				passThrough := gs.Spec.Ports[3]
   262  				assert.Equal(t, agonesv1.Passthrough, passThrough.PortPolicy)
   263  				assert.NotEmpty(t, passThrough.HostPort)
   264  				assert.Equal(t, passThrough.HostPort, passThrough.ContainerPort)
   265  
   266  				logrus.WithField("uid", gsCopy.ObjectMeta.UID).WithField("ports", gs.Spec.Ports).Info("Allocated Port")
   267  
   268  				for _, p := range gs.Spec.Ports {
   269  					if p.PortPolicy == agonesv1.Dynamic || p.PortPolicy == agonesv1.Passthrough {
   270  						assert.True(t, 10 <= p.HostPort && p.HostPort <= maxPort, "%v is not between 10 and 20", p)
   271  					}
   272  				}
   273  			}
   274  		}
   275  
   276  		logrus.WithField("Allocated", countTotalAllocatedPorts(pa)).WithField("Total", countTotalPorts(pa)).Info("Ports count")
   277  		assert.Len(t, pa.portAllocations, 2)
   278  		// allocate extra 3 ports
   279  		gs := pa.Allocate(morePortFixture.DeepCopy())
   280  		require.NotNil(t, gs)
   281  		assert.NotEmpty(t, gs.Spec.Ports[0].HostPort)
   282  		assert.Len(t, pa.portAllocations, 2)
   283  		logrus.WithField("Allocated", countTotalAllocatedPorts(pa)).WithField("Total", countTotalPorts(pa)).Info("Ports count")
   284  
   285  		// allocate extra 3 ports
   286  		gs = pa.Allocate(morePortFixture.DeepCopy())
   287  		require.NotNil(t, gs)
   288  		assert.NotEmpty(t, gs.Spec.Ports[0].HostPort)
   289  		assert.Len(t, pa.portAllocations, 3)
   290  		logrus.WithField("Allocated", countTotalAllocatedPorts(pa)).WithField("Total", countTotalPorts(pa)).Info("Ports count")
   291  	})
   292  
   293  	t.Run("ports are unique in a node", func(t *testing.T) {
   294  		fixture := dynamicGameServerFixture()
   295  		m := agtesting.NewMocks()
   296  		pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 20, m.KubeInformerFactory, m.AgonesInformerFactory)
   297  
   298  		m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   299  			nl := &corev1.NodeList{Items: []corev1.Node{n1}}
   300  			return true, nl, nil
   301  		})
   302  		_, cancel := agtesting.StartInformers(m, pa.nodeSynced)
   303  		defer cancel()
   304  
   305  		err := pa.syncAll()
   306  		require.NoError(t, err)
   307  
   308  		var ports []int32
   309  		for i := 10; i <= 20; i++ {
   310  			gs := pa.Allocate(fixture.DeepCopy())
   311  			require.NotNil(t, gs)
   312  			assert.NotContains(t, ports, gs.Spec.Ports[0].HostPort)
   313  			ports = append(ports, gs.Spec.Ports[0].HostPort)
   314  		}
   315  	})
   316  
   317  	t.Run("portPolicy as an empty string", func(t *testing.T) {
   318  		m := agtesting.NewMocks()
   319  		pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 50, m.KubeInformerFactory, m.AgonesInformerFactory)
   320  		nodeWatch := watch.NewFake()
   321  		m.KubeClient.AddWatchReactor("nodes", k8stesting.DefaultWatchReactor(nodeWatch, nil))
   322  
   323  		ctx, cancel := agtesting.StartInformers(m, pa.nodeSynced)
   324  		defer cancel()
   325  
   326  		// Make sure the add's don't corrupt the sync
   327  		// (no longer an issue, but leave this here for posterity)
   328  		nodeWatch.Add(&n1)
   329  		nodeWatch.Add(&n2)
   330  		assert.True(t, cache.WaitForCacheSync(ctx.Done(), pa.nodeSynced))
   331  
   332  		err := pa.syncAll()
   333  		require.NoError(t, err)
   334  
   335  		// single port empty
   336  		fd := fixture.DeepCopy()
   337  		fd.Spec.Ports[0].PortPolicy = ""
   338  		gs := pa.Allocate(fd)
   339  		assert.NotNil(t, gs)
   340  		assert.Equal(t, 0, countTotalAllocatedPorts(pa))
   341  	})
   342  }
   343  
   344  func TestPortRangeAllocatorAllocate_NamedRange(t *testing.T) {
   345  	t.Parallel()
   346  	utilruntime.FeatureTestMutex.Lock()
   347  	defer utilruntime.FeatureTestMutex.Unlock()
   348  
   349  	err := utilruntime.ParseFeatures(string(utilruntime.FeaturePortRanges) + "=true")
   350  	assert.NoError(t, err)
   351  	defer func() {
   352  		_ = utilruntime.ParseFeatures("")
   353  	}()
   354  
   355  	m := agtesting.NewMocks()
   356  	pa := newRangeAllocator("test", 10, 50, m.KubeInformerFactory, m.AgonesInformerFactory)
   357  	nodeWatch := watch.NewFake()
   358  	m.KubeClient.AddWatchReactor("nodes", k8stesting.DefaultWatchReactor(nodeWatch, nil))
   359  
   360  	ctx, cancel := agtesting.StartInformers(m, pa.nodeSynced)
   361  	defer cancel()
   362  
   363  	// Make sure the add's don't corrupt the sync
   364  	// (no longer an issue, but leave this here for posterity)
   365  	nodeWatch.Add(&n1)
   366  	nodeWatch.Add(&n2)
   367  	assert.True(t, cache.WaitForCacheSync(ctx.Done(), pa.nodeSynced))
   368  
   369  	err = pa.syncAll()
   370  	require.NoError(t, err)
   371  
   372  	// single port empty
   373  	fixture := dynamicGameServerFixture()
   374  	fixture.Spec.Ports = append(fixture.Spec.Ports,
   375  		agonesv1.GameServerPort{Name: "passthrough", Range: "test", PortPolicy: agonesv1.Passthrough},
   376  		agonesv1.GameServerPort{Name: "passthrough", Range: "test", PortPolicy: agonesv1.Passthrough},
   377  	)
   378  	gs := pa.Allocate(fixture)
   379  
   380  	assert.NotNil(t, gs)
   381  	assert.Empty(t, gs.Spec.Ports[0].HostPort)
   382  	assert.NotEmpty(t, gs.Spec.Ports[1].HostPort)
   383  	assert.NotEmpty(t, gs.Spec.Ports[2].HostPort)
   384  	assert.Equal(t, 2, countTotalAllocatedPorts(pa))
   385  }
   386  
   387  func TestPortRangeAllocatorMultithreadAllocate(t *testing.T) {
   388  	fixture := dynamicGameServerFixture()
   389  	m := agtesting.NewMocks()
   390  	pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 20, m.KubeInformerFactory, m.AgonesInformerFactory)
   391  
   392  	m.KubeClient.AddReactor("list", "nodes", func(k8stesting.Action) (bool, runtime.Object, error) {
   393  		nl := &corev1.NodeList{Items: []corev1.Node{n1, n2}}
   394  		return true, nl, nil
   395  	})
   396  	_, cancel := agtesting.StartInformers(m, pa.nodeSynced)
   397  	defer cancel()
   398  
   399  	err := pa.syncAll()
   400  	require.NoError(t, err)
   401  
   402  	wg := sync.WaitGroup{}
   403  
   404  	// do this for more than the nodes that are pre-allocated, to make sure
   405  	// it works for dynamic node addition
   406  	for i := 0; i < 5; i++ {
   407  		wg.Add(1)
   408  		go func(i int) {
   409  			for x := 0; x < 10; x++ {
   410  				logrus.WithField("x", x).WithField("i", i).Info("allocating!")
   411  				gs := pa.Allocate(fixture.DeepCopy())
   412  				require.NotNil(t, gs)
   413  				for _, p := range gs.Spec.Ports {
   414  					assert.NotEmpty(t, p.HostPort)
   415  				}
   416  			}
   417  			wg.Done()
   418  		}(i)
   419  	}
   420  
   421  	wg.Wait()
   422  }
   423  
   424  func TestPortRangeAllocatorDeAllocate(t *testing.T) {
   425  	t.Parallel()
   426  
   427  	fixture := dynamicGameServerFixture()
   428  	m := agtesting.NewMocks()
   429  	pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 20, m.KubeInformerFactory, m.AgonesInformerFactory)
   430  	nodes := []corev1.Node{n1, n2, n3}
   431  	m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   432  		nl := &corev1.NodeList{Items: nodes}
   433  		return true, nl, nil
   434  	})
   435  	_, cancel := agtesting.StartInformers(m, pa.nodeSynced)
   436  	defer cancel()
   437  	err := pa.syncAll()
   438  	require.NoError(t, err)
   439  
   440  	// gate
   441  	assert.NotEmpty(t, fixture.Spec.Ports)
   442  
   443  	for i := 0; i <= 100; i++ {
   444  		gs := pa.Allocate(fixture.DeepCopy())
   445  		require.NotNil(t, gs)
   446  
   447  		port := gs.Spec.Ports[0]
   448  		assert.True(t, 10 <= port.HostPort && port.HostPort <= 20)
   449  		assert.Equal(t, 1, countAllocatedPorts(pa, port.HostPort))
   450  		assert.Len(t, pa.gameServerRegistry, 1)
   451  
   452  		// test a non allocated
   453  		nonAllocatedGS := gs.DeepCopy()
   454  		nonAllocatedGS.ObjectMeta.Name = "no"
   455  		nonAllocatedGS.ObjectMeta.UID = "no"
   456  		pa.DeAllocate(nonAllocatedGS)
   457  		assert.Equal(t, 1, countAllocatedPorts(pa, port.HostPort))
   458  		assert.Len(t, pa.gameServerRegistry, 1)
   459  
   460  		pa.DeAllocate(gs)
   461  		assert.Equal(t, 0, countAllocatedPorts(pa, port.HostPort))
   462  		assert.Len(t, pa.gameServerRegistry, 0)
   463  	}
   464  }
   465  
   466  func TestPortRangeAllocatorSyncPortAllocations(t *testing.T) {
   467  	t.Parallel()
   468  
   469  	m := agtesting.NewMocks()
   470  	pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 20, m.KubeInformerFactory, m.AgonesInformerFactory)
   471  
   472  	m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   473  		nl := &corev1.NodeList{Items: []corev1.Node{n1, n2, n3}}
   474  		return true, nl, nil
   475  	})
   476  
   477  	m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   478  		gs1 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs1", UID: "1"},
   479  			Spec: agonesv1.GameServerSpec{
   480  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, HostPort: 10}},
   481  			},
   482  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 10}}, NodeName: n1.ObjectMeta.Name}}
   483  		gs2 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs2", UID: "2"},
   484  			Spec: agonesv1.GameServerSpec{
   485  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, HostPort: 10}},
   486  			},
   487  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 10}}, NodeName: n2.ObjectMeta.Name}}
   488  		gs3 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs3", UID: "3"},
   489  			Spec: agonesv1.GameServerSpec{
   490  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, HostPort: 11}},
   491  			},
   492  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 11}}, NodeName: n3.ObjectMeta.Name}}
   493  		gs4 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs4", UID: "4"},
   494  			Spec: agonesv1.GameServerSpec{
   495  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Passthrough, HostPort: 12}},
   496  			},
   497  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
   498  		gs5 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs5", UID: "5"},
   499  			Spec: agonesv1.GameServerSpec{
   500  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, HostPort: 12}},
   501  			},
   502  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
   503  		gs6 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs6", UID: "6"},
   504  			Spec: agonesv1.GameServerSpec{
   505  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Static, HostPort: 12}},
   506  			},
   507  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateCreating}}
   508  		gs7 := agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs7", UID: "7"},
   509  			Spec: agonesv1.GameServerSpec{
   510  				Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, HostPort: 30}},
   511  			},
   512  			Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 30}}, NodeName: n1.ObjectMeta.Name}}
   513  		gsl := &agonesv1.GameServerList{Items: []agonesv1.GameServer{gs1, gs2, gs3, gs4, gs5, gs6, gs7}}
   514  		return true, gsl, nil
   515  	})
   516  
   517  	_, cancel := agtesting.StartInformers(m, pa.gameServerSynced, pa.nodeSynced)
   518  	defer cancel()
   519  
   520  	err := pa.syncAll()
   521  	require.NoError(t, err)
   522  
   523  	assert.Len(t, pa.portAllocations, 3)
   524  	assert.Len(t, pa.gameServerRegistry, 5)
   525  
   526  	// count the number of allocated ports,
   527  	assert.Equal(t, 2, countAllocatedPorts(pa, 10))
   528  	assert.Equal(t, 1, countAllocatedPorts(pa, 11))
   529  	assert.Equal(t, 2, countAllocatedPorts(pa, 12))
   530  
   531  	count := 0
   532  	for i := int32(10); i <= 20; i++ {
   533  		count += countAllocatedPorts(pa, i)
   534  	}
   535  	assert.Equal(t, 5, count)
   536  }
   537  
   538  func TestPortRangeAllocatorSyncDeleteGameServer(t *testing.T) {
   539  	t.Parallel()
   540  
   541  	m := agtesting.NewMocks()
   542  	gsWatch := watch.NewFake()
   543  	m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
   544  
   545  	gs1 := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs1", UID: "1"},
   546  		Spec: agonesv1.GameServerSpec{
   547  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, HostPort: 10}},
   548  		},
   549  		Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 10}}, NodeName: n1.ObjectMeta.Name}}
   550  	gs2 := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs2", UID: "2"},
   551  		Spec: agonesv1.GameServerSpec{
   552  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, HostPort: 11}},
   553  		},
   554  		Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 11}}, NodeName: n1.ObjectMeta.Name}}
   555  	gs3 := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs3", UID: "3"},
   556  		Spec: agonesv1.GameServerSpec{
   557  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Passthrough, HostPort: 10}},
   558  		},
   559  		Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 10}}, NodeName: n2.ObjectMeta.Name}}
   560  	gs4 := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs4", UID: "4"},
   561  		Spec: agonesv1.GameServerSpec{
   562  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, HostPort: 10}},
   563  		},
   564  		Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 10}}, NodeName: n2.ObjectMeta.Name}}
   565  
   566  	pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 20, m.KubeInformerFactory, m.AgonesInformerFactory)
   567  
   568  	m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   569  		nl := &corev1.NodeList{Items: []corev1.Node{n1, n2, n3}}
   570  		return true, nl, nil
   571  	})
   572  
   573  	_, cancel := agtesting.StartInformers(m, pa.gameServerSynced, pa.nodeSynced)
   574  	defer cancel()
   575  
   576  	gsWatch.Add(gs1.DeepCopy())
   577  	gsWatch.Add(gs2.DeepCopy())
   578  	gsWatch.Add(gs3.DeepCopy())
   579  	require.Eventually(t, func() bool {
   580  		list, err := pa.gameServerLister.GameServers(gs1.ObjectMeta.Namespace).List(labels.Everything())
   581  		assert.NoError(t, err)
   582  		return len(list) == 3
   583  	}, 5*time.Second, time.Second)
   584  
   585  	err := pa.syncAll()
   586  	require.NoError(t, err)
   587  
   588  	// gate
   589  	pa.mutex.RLock() // reading mutable state, so read lock
   590  	assert.Equal(t, 2, countAllocatedPorts(pa, 10))
   591  	assert.Equal(t, 1, countAllocatedPorts(pa, 11))
   592  	pa.mutex.RUnlock()
   593  
   594  	// delete allocated gs
   595  	gsWatch.Delete(gs3.DeepCopy())
   596  	require.Eventually(t, func() bool {
   597  		list, err := pa.gameServerLister.GameServers(gs1.ObjectMeta.Namespace).List(labels.Everything())
   598  		assert.NoError(t, err)
   599  		return len(list) == 2
   600  	}, 5*time.Second, time.Second)
   601  
   602  	pa.mutex.RLock() // reading mutable state, so read lock
   603  	assert.Equal(t, 1, countAllocatedPorts(pa, 10))
   604  	assert.Equal(t, 1, countAllocatedPorts(pa, 11))
   605  	pa.mutex.RUnlock()
   606  
   607  	// delete the currently non allocated server, all should be the same
   608  	// simulated getting an old delete message
   609  	gsWatch.Delete(gs4.DeepCopy())
   610  	require.Never(t, func() bool {
   611  		list, err := pa.gameServerLister.GameServers(gs1.ObjectMeta.Namespace).List(labels.Everything())
   612  		assert.NoError(t, err)
   613  		return len(list) != 2
   614  	}, time.Second, 100*time.Millisecond)
   615  	pa.mutex.RLock() // reading mutable state, so read lock
   616  	assert.Equal(t, 1, countAllocatedPorts(pa, 10))
   617  	assert.Equal(t, 1, countAllocatedPorts(pa, 11))
   618  	pa.mutex.RUnlock()
   619  }
   620  
   621  func TestNodePortAllocation(t *testing.T) {
   622  	t.Parallel()
   623  
   624  	m := agtesting.NewMocks()
   625  	pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 20, m.KubeInformerFactory, m.AgonesInformerFactory)
   626  	nodes := []corev1.Node{n1, n2, n3}
   627  	m.KubeClient.AddReactor("list", "nodes", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   628  		nl := &corev1.NodeList{Items: nodes}
   629  		return true, nl, nil
   630  	})
   631  	result := pa.nodePortAllocation([]*corev1.Node{&n1, &n2, &n3})
   632  	require.Len(t, result, 3)
   633  	for _, n := range nodes {
   634  		ports, ok := result[n.ObjectMeta.Name]
   635  		assert.True(t, ok, "Should have a port allocation for %s", n.ObjectMeta.Name)
   636  		assert.Len(t, ports, 11)
   637  		for _, v := range ports {
   638  			assert.False(t, v)
   639  		}
   640  	}
   641  }
   642  
   643  func TestTakePortAllocation(t *testing.T) {
   644  	t.Parallel()
   645  
   646  	fixture := []portAllocation{{1: false, 2: false}, {1: false, 2: false}, {1: false, 3: false}}
   647  	result := setPortAllocation(2, fixture, true)
   648  	assert.True(t, result[0][2])
   649  
   650  	for i, row := range fixture {
   651  		for p, taken := range row {
   652  			if i != 0 && p != 2 {
   653  				assert.False(t, taken, fmt.Sprintf("row %d and port %d should be false", i, p))
   654  			}
   655  		}
   656  	}
   657  }
   658  
   659  func TestPortRangeAllocatorRegisterExistingGameServerPorts(t *testing.T) {
   660  	t.Parallel()
   661  	m := agtesting.NewMocks()
   662  	pa := newRangeAllocator(agonesv1.DefaultPortRange, 10, 13, m.KubeInformerFactory, m.AgonesInformerFactory)
   663  
   664  	gs1 := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs1", UID: "1"},
   665  		Spec: agonesv1.GameServerSpec{
   666  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, Range: agonesv1.DefaultPortRange, HostPort: 10}},
   667  		},
   668  		Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 10}}, NodeName: n1.ObjectMeta.Name}}
   669  
   670  	gs2 := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs2", UID: "2"},
   671  		Spec: agonesv1.GameServerSpec{
   672  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, Range: agonesv1.DefaultPortRange, HostPort: 11}},
   673  		},
   674  		Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 11}}, NodeName: n2.ObjectMeta.Name}}
   675  
   676  	gs3 := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs3", UID: "3"},
   677  		Spec: agonesv1.GameServerSpec{
   678  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Passthrough, Range: agonesv1.DefaultPortRange, HostPort: 12}},
   679  		},
   680  		Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStateReady, Ports: []agonesv1.GameServerStatusPort{{Port: 12}}, NodeName: n1.ObjectMeta.Name}}
   681  
   682  	gs4 := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gs4", UID: "3"},
   683  		Spec: agonesv1.GameServerSpec{
   684  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, Range: agonesv1.DefaultPortRange, HostPort: 13}},
   685  		},
   686  		Status: agonesv1.GameServerStatus{State: agonesv1.GameServerStatePortAllocation, Ports: []agonesv1.GameServerStatusPort{{Port: 13}}}}
   687  
   688  	allocations, nonReadyNodesPorts := pa.registerExistingGameServerPorts([]*agonesv1.GameServer{gs1, gs2, gs3, gs4}, []*corev1.Node{&n1, &n2, &n3}, map[types.UID]bool{})
   689  
   690  	assert.Equal(t, []int32{13}, nonReadyNodesPorts)
   691  	assert.Equal(t, portAllocation{10: true, 11: false, 12: true, 13: false}, allocations[0])
   692  	assert.Equal(t, portAllocation{10: false, 11: true, 12: false, 13: false}, allocations[1])
   693  	assert.Equal(t, portAllocation{10: false, 11: false, 12: false, 13: false}, allocations[2])
   694  }
   695  
   696  func dynamicGameServerFixture() *agonesv1.GameServer {
   697  	return &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default", UID: "1234"},
   698  		Spec: agonesv1.GameServerSpec{
   699  			Ports: []agonesv1.GameServerPort{{PortPolicy: agonesv1.Dynamic, Range: agonesv1.DefaultPortRange, ContainerPort: 7777}},
   700  		},
   701  	}
   702  }
   703  
   704  // countAllocatedPorts counts how many of a given port have been
   705  // allocated across nodes
   706  func countAllocatedPorts(pa *portRangeAllocator, p int32) int {
   707  	count := 0
   708  	for _, node := range pa.portAllocations {
   709  		if node[p] {
   710  			count++
   711  		}
   712  	}
   713  	return count
   714  }
   715  
   716  // countTotalAllocatedPorts counts the total number of allocated ports
   717  func countTotalAllocatedPorts(pa *portRangeAllocator) int {
   718  	count := 0
   719  	for _, node := range pa.portAllocations {
   720  		for _, alloc := range node {
   721  			if alloc {
   722  				count++
   723  			}
   724  		}
   725  	}
   726  	return count
   727  }
   728  
   729  func countTotalPorts(pa *portRangeAllocator) int {
   730  	count := 0
   731  	for _, node := range pa.portAllocations {
   732  		count += len(node)
   733  	}
   734  	return count
   735  }