agones.dev/agones@v1.54.0/pkg/gameserversets/allocation_overflow_test.go (about)

     1  // Copyright 2023 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 gameserversets
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"testing"
    21  	"time"
    22  
    23  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    24  	"agones.dev/agones/pkg/gameservers"
    25  	agtesting "agones.dev/agones/pkg/testing"
    26  	"github.com/heptiolabs/healthcheck"
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/watch"
    31  	k8stesting "k8s.io/client-go/testing"
    32  )
    33  
    34  func TestAllocationOverflowControllerWatchGameServers(t *testing.T) {
    35  	t.Parallel()
    36  
    37  	gsSet := defaultFixture()
    38  	gsSet.Status.Replicas = gsSet.Spec.Replicas
    39  	gsSet.Status.ReadyReplicas = gsSet.Spec.Replicas
    40  	c, m := newFakeAllocationOverflowController()
    41  
    42  	received := make(chan string, 10)
    43  	defer close(received)
    44  
    45  	gsSetWatch := watch.NewFake()
    46  	m.AgonesClient.AddWatchReactor("gameserversets", k8stesting.DefaultWatchReactor(gsSetWatch, nil))
    47  
    48  	c.workerqueue.SyncHandler = func(_ context.Context, name string) error {
    49  		received <- name
    50  		return nil
    51  	}
    52  
    53  	ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced)
    54  	defer cancel()
    55  
    56  	go func() {
    57  		err := c.Run(ctx)
    58  		require.NoError(t, err)
    59  	}()
    60  
    61  	change := func() string {
    62  		select {
    63  		case result := <-received:
    64  			return result
    65  		case <-time.After(3 * time.Second):
    66  			require.FailNow(t, "timeout occurred")
    67  		}
    68  		return ""
    69  	}
    70  
    71  	nochange := func() {
    72  		select {
    73  		case <-received:
    74  			assert.Fail(t, "Should be no value")
    75  		case <-time.After(time.Second):
    76  		}
    77  	}
    78  
    79  	gsSetWatch.Add(gsSet.DeepCopy())
    80  	nochange()
    81  
    82  	// update with no allocation overflow
    83  	require.Nil(t, gsSet.Spec.AllocationOverflow)
    84  	gsSet.Spec.Replicas++
    85  	gsSetWatch.Modify(gsSet.DeepCopy())
    86  	nochange()
    87  
    88  	// update with no labels or annotations
    89  	gsSet.Spec.AllocationOverflow = &agonesv1.AllocationOverflow{}
    90  	gsSet.Spec.Replicas++
    91  	gsSetWatch.Modify(gsSet.DeepCopy())
    92  	nochange()
    93  
    94  	// update with allocation <= replicas (and a label)
    95  	gsSet.Spec.AllocationOverflow.Labels = map[string]string{"colour": "green"}
    96  	gsSet.Status.AllocatedReplicas = 2
    97  	gsSetWatch.Modify(gsSet.DeepCopy())
    98  	nochange()
    99  
   100  	// update with allocation > replicas
   101  	gsSet.Status.AllocatedReplicas = 20
   102  	gsSetWatch.Modify(gsSet.DeepCopy())
   103  	require.Equal(t, fmt.Sprintf("%s/%s", gsSet.ObjectMeta.Namespace, gsSet.ObjectMeta.Name), change())
   104  
   105  	// delete
   106  	gsSetWatch.Delete(gsSet.DeepCopy())
   107  	nochange()
   108  }
   109  
   110  func TestAllocationOverflowSyncGameServerSet(t *testing.T) {
   111  	t.Parallel()
   112  
   113  	// setup fictures.
   114  	setup := func(gs func(server *agonesv1.GameServer)) (*agonesv1.GameServerSet, *AllocationOverflowController, agtesting.Mocks) {
   115  		gsSet := defaultFixture()
   116  		gsSet.Status.AllocatedReplicas = 5
   117  		gsSet.Status.Replicas = 3
   118  		gsSet.Spec.Replicas = 3
   119  		gsSet.Spec.AllocationOverflow = &agonesv1.AllocationOverflow{Labels: map[string]string{"colour": "green"}}
   120  		list := createGameServers(gsSet, 5)
   121  		for i := range list {
   122  			list[i].Status.State = agonesv1.GameServerStateAllocated
   123  			gs(&list[i])
   124  		}
   125  
   126  		c, m := newFakeAllocationOverflowController()
   127  		m.AgonesClient.AddReactor("list", "gameserversets", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   128  			return true, &agonesv1.GameServerSetList{Items: []agonesv1.GameServerSet{*gsSet}}, nil
   129  		})
   130  		m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   131  			return true, &agonesv1.GameServerList{Items: list}, nil
   132  		})
   133  		return gsSet, c, m
   134  	}
   135  
   136  	// run the sync process
   137  	run := func(c *AllocationOverflowController, m agtesting.Mocks, gsSet *agonesv1.GameServerSet, update func(action k8stesting.Action) (bool, runtime.Object, error)) func() {
   138  		m.AgonesClient.AddReactor("update", "gameservers", update)
   139  		ctx, cancel := agtesting.StartInformers(m, c.gameServerSetSynced, c.gameServerSynced)
   140  		err := c.syncGameServerSet(ctx, gsSet.ObjectMeta.Namespace+"/"+gsSet.ObjectMeta.Name)
   141  		require.NoError(t, err)
   142  		return cancel
   143  	}
   144  
   145  	t.Run("labels are applied", func(t *testing.T) {
   146  		gsSet, c, m := setup(func(_ *agonesv1.GameServer) {})
   147  		count := 0
   148  		cancel := run(c, m, gsSet, func(action k8stesting.Action) (bool, runtime.Object, error) {
   149  			ua := action.(k8stesting.UpdateAction)
   150  			gs := ua.GetObject().(*agonesv1.GameServer)
   151  			require.Equal(t, gs.Status.State, agonesv1.GameServerStateAllocated)
   152  			require.Equal(t, "green", gs.ObjectMeta.Labels["colour"])
   153  
   154  			count++
   155  			return true, nil, nil
   156  		})
   157  		defer cancel()
   158  		require.Equal(t, 2, count)
   159  	})
   160  
   161  	t.Run("Labels are already set", func(t *testing.T) {
   162  		gsSet, c, m := setup(func(gs *agonesv1.GameServer) {
   163  			gs.ObjectMeta.Labels["colour"] = "green"
   164  		})
   165  		cancel := run(c, m, gsSet, func(_ k8stesting.Action) (bool, runtime.Object, error) {
   166  			require.Fail(t, "should not update")
   167  			return true, nil, nil
   168  		})
   169  		defer cancel()
   170  	})
   171  
   172  	t.Run("one label is set", func(t *testing.T) {
   173  		set := false
   174  		gsSet, c, m := setup(func(gs *agonesv1.GameServer) {
   175  			// just make one as already set
   176  			if !set {
   177  				gs.ObjectMeta.Labels["colour"] = "green"
   178  				set = true
   179  			}
   180  		})
   181  
   182  		count := 0
   183  		cancel := run(c, m, gsSet, func(action k8stesting.Action) (bool, runtime.Object, error) {
   184  			ua := action.(k8stesting.UpdateAction)
   185  			gs := ua.GetObject().(*agonesv1.GameServer)
   186  			require.Equal(t, gs.Status.State, agonesv1.GameServerStateAllocated)
   187  			require.Equal(t, "green", gs.ObjectMeta.Labels["colour"])
   188  
   189  			count++
   190  			return true, nil, nil
   191  		})
   192  		defer cancel()
   193  		require.Equal(t, 1, count)
   194  	})
   195  }
   196  
   197  // newFakeAllocationOverflowController returns a controller, backed by the fake Clientset
   198  func newFakeAllocationOverflowController() (*AllocationOverflowController, agtesting.Mocks) {
   199  	m := agtesting.NewMocks()
   200  	counter := gameservers.NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory)
   201  	c := NewAllocatorOverflowController(healthcheck.NewHandler(), counter, m.AgonesClient, m.AgonesInformerFactory)
   202  	return c, m
   203  }