agones.dev/agones@v1.54.0/pkg/gameservers/pernodecounter_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 gameservers
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    22  	agtesting "agones.dev/agones/pkg/testing"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/watch"
    29  	k8stesting "k8s.io/client-go/testing"
    30  )
    31  
    32  const (
    33  	defaultNs = "default"
    34  	name1     = "node1"
    35  	name2     = "node2"
    36  )
    37  
    38  func TestPerNodeCounterGameServerEvents(t *testing.T) {
    39  	t.Parallel()
    40  
    41  	pnc, m := newFakePerNodeCounter()
    42  
    43  	fakeWatch := watch.NewFake()
    44  	m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil))
    45  
    46  	_, cancel := agtesting.StartInformers(m)
    47  	defer cancel()
    48  
    49  	assert.Empty(t, pnc.Counts())
    50  
    51  	gs := &agonesv1.GameServer{
    52  		ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, UID: "uid-gs1", ResourceVersion: "1"},
    53  		Status: agonesv1.GameServerStatus{
    54  			State: agonesv1.GameServerStateStarting, NodeName: name1,
    55  		},
    56  	}
    57  
    58  	fakeWatch.Add(gs.DeepCopy())
    59  	require.Eventuallyf(t, func() bool {
    60  		return len(pnc.Counts()) == 0
    61  	}, 5*time.Second, time.Second, "Should be empty, instead has %v elements", len(pnc.Counts()))
    62  
    63  	gs.Status.State = agonesv1.GameServerStateReady
    64  	gs.ObjectMeta.ResourceVersion = "2"
    65  	fakeWatch.Add(gs.DeepCopy())
    66  
    67  	var counts map[string]NodeCount
    68  	require.Eventuallyf(t, func() bool {
    69  		counts = pnc.Counts()
    70  		return len(counts) == 1
    71  	}, 5*time.Second, time.Second, "len should be 1, instead: %v", len(counts))
    72  	assert.Equal(t, int64(1), counts[name1].Ready)
    73  	assert.Equal(t, int64(0), counts[name1].Allocated)
    74  
    75  	gs.Status.State = agonesv1.GameServerStateAllocated
    76  	gs.ObjectMeta.ResourceVersion = "3"
    77  	fakeWatch.Add(gs.DeepCopy())
    78  
    79  	require.Eventuallyf(t, func() bool {
    80  		counts = pnc.Counts()
    81  		return len(counts) == 1 && int64(0) == counts[name1].Ready
    82  	}, 5*time.Second, time.Second, "Ready should be 0, but is instead", counts[name1].Ready)
    83  	assert.Equal(t, int64(1), counts[name1].Allocated)
    84  
    85  	gs.Status.State = agonesv1.GameServerStateShutdown
    86  	gs.ObjectMeta.ResourceVersion = "4"
    87  	fakeWatch.Add(gs.DeepCopy())
    88  	require.Eventuallyf(t, func() bool {
    89  		counts = pnc.Counts()
    90  		return len(counts) == 1 && int64(0) == counts[name1].Allocated
    91  	}, 5*time.Second, time.Second, "Allocated should be 0, but is instead", counts[name1].Allocated)
    92  	assert.Equal(t, int64(0), counts[name1].Ready)
    93  
    94  	gs.ObjectMeta.Name = "gs2"
    95  	gs.ObjectMeta.UID = "uid-gs2"
    96  	gs.ObjectMeta.ResourceVersion = "1"
    97  	gs.Status.State = agonesv1.GameServerStateReady
    98  	gs.Status.NodeName = name2
    99  
   100  	fakeWatch.Add(gs.DeepCopy())
   101  	require.Eventuallyf(t, func() bool {
   102  		counts = pnc.Counts()
   103  		return len(counts) == 2
   104  	}, 5*time.Second, time.Second, "len should be 2, instead: %v", len(counts))
   105  	assert.Equal(t, int64(0), counts[name1].Ready)
   106  	assert.Equal(t, int64(0), counts[name1].Allocated)
   107  	assert.Equal(t, int64(1), counts[name2].Ready)
   108  	assert.Equal(t, int64(0), counts[name2].Allocated)
   109  
   110  	gs.ObjectMeta.Name = "gs3"
   111  	gs.ObjectMeta.UID = "uid-gs3"
   112  	gs.ObjectMeta.ResourceVersion = "1"
   113  	// not likely, but to test the flow
   114  	gs.Status.State = agonesv1.GameServerStateAllocated
   115  	gs.Status.NodeName = name2
   116  
   117  	fakeWatch.Add(gs.DeepCopy())
   118  	require.Eventuallyf(t, func() bool {
   119  		counts = pnc.Counts()
   120  		return len(counts) == 2 && int64(1) == counts[name2].Allocated
   121  	}, 5*time.Second, time.Second, "Allocated should be 1, but is instead", counts[name2].Allocated)
   122  	assert.Equal(t, int64(0), counts[name1].Ready)
   123  	assert.Equal(t, int64(0), counts[name1].Allocated)
   124  	assert.Equal(t, int64(1), counts[name2].Ready)
   125  }
   126  
   127  func TestPerNodeCounterNodeEvents(t *testing.T) {
   128  	t.Parallel()
   129  
   130  	pnc, m := newFakePerNodeCounter()
   131  
   132  	gsWatch := watch.NewFake()
   133  	nodeWatch := watch.NewFake()
   134  	m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(gsWatch, nil))
   135  	m.KubeClient.AddWatchReactor("nodes", k8stesting.DefaultWatchReactor(nodeWatch, nil))
   136  
   137  	_, cancel := agtesting.StartInformers(m)
   138  	defer cancel()
   139  
   140  	require.Empty(t, pnc.Counts())
   141  
   142  	gs := &agonesv1.GameServer{
   143  		ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, UID: "uid-gs1", ResourceVersion: "1"},
   144  		Status: agonesv1.GameServerStatus{
   145  			State: agonesv1.GameServerStateReady, NodeName: name1}}
   146  	node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Namespace: defaultNs, Name: name1}}
   147  
   148  	gsWatch.Add(gs.DeepCopy())
   149  	nodeWatch.Add(node.DeepCopy())
   150  	require.Eventuallyf(t, func() bool {
   151  		return len(pnc.Counts()) == 1
   152  	}, 5*time.Second, time.Second, "Should be 1 element, not %v", len(pnc.Counts()))
   153  
   154  	nodeWatch.Delete(node.DeepCopy())
   155  	require.Eventually(t, func() bool {
   156  		return len(pnc.Counts()) == 0
   157  	}, 5*time.Second, time.Second, "pnc.Counts() should be empty, but is instead has %v element", len(pnc.Counts()))
   158  }
   159  
   160  func TestPerNodeCounterRun(t *testing.T) {
   161  	t.Parallel()
   162  	pnc, m := newFakePerNodeCounter()
   163  
   164  	gs1 := &agonesv1.GameServer{
   165  		ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, UID: "uid-gs1", ResourceVersion: "1"},
   166  		Status: agonesv1.GameServerStatus{
   167  			State: agonesv1.GameServerStateReady, NodeName: name1}}
   168  
   169  	gs2 := gs1.DeepCopy()
   170  	gs2.ObjectMeta.Name = "gs2"
   171  	gs2.ObjectMeta.UID = "uid-gs2"
   172  	gs2.ObjectMeta.ResourceVersion = "1"
   173  	gs2.Status.State = agonesv1.GameServerStateAllocated
   174  
   175  	gs3 := gs1.DeepCopy()
   176  	gs3.ObjectMeta.Name = "gs3"
   177  	gs3.ObjectMeta.UID = "uid-gs3"
   178  	gs3.ObjectMeta.ResourceVersion = "1"
   179  	gs3.Status.State = agonesv1.GameServerStateStarting
   180  	gs3.Status.NodeName = name2
   181  
   182  	gs4 := gs1.DeepCopy()
   183  	gs4.ObjectMeta.Name = "gs4"
   184  	gs4.ObjectMeta.UID = "uid-gs4"
   185  	gs4.ObjectMeta.ResourceVersion = "1"
   186  	gs4.Status.State = agonesv1.GameServerStateAllocated
   187  
   188  	m.AgonesClient.AddReactor("list", "gameservers", func(_ k8stesting.Action) (bool, runtime.Object, error) {
   189  		return true, &agonesv1.GameServerList{Items: []agonesv1.GameServer{*gs1, *gs2, *gs3, *gs4}}, nil
   190  	})
   191  	ctx, cancel := agtesting.StartInformers(m, pnc.gameServerSynced)
   192  	defer cancel()
   193  
   194  	err := pnc.Run(ctx, 0)
   195  	require.NoError(t, err)
   196  
   197  	counts := pnc.Counts()
   198  
   199  	require.Len(t, counts, 2)
   200  	assert.Equal(t, int64(1), counts[name1].Ready)
   201  	assert.Equal(t, int64(2), counts[name1].Allocated)
   202  	assert.Equal(t, int64(0), counts[name2].Ready)
   203  	assert.Equal(t, int64(0), counts[name2].Allocated)
   204  }
   205  
   206  func TestPerNodeCounterDuplicateEvents(t *testing.T) {
   207  	t.Parallel()
   208  
   209  	pnc, m := newFakePerNodeCounter()
   210  
   211  	fakeWatch := watch.NewFake()
   212  	m.AgonesClient.AddWatchReactor("gameservers", k8stesting.DefaultWatchReactor(fakeWatch, nil))
   213  
   214  	_, cancel := agtesting.StartInformers(m)
   215  	defer cancel()
   216  
   217  	assert.Empty(t, pnc.Counts())
   218  
   219  	// Create a GameServer in Ready state
   220  	gs := &agonesv1.GameServer{
   221  		ObjectMeta: metav1.ObjectMeta{Name: "gs1", Namespace: defaultNs, UID: "uid-gs1", ResourceVersion: "1"},
   222  		Status: agonesv1.GameServerStatus{
   223  			State: agonesv1.GameServerStateReady, NodeName: name1,
   224  		},
   225  	}
   226  
   227  	// Send the same Add event twice with the same ResourceVersion
   228  	fakeWatch.Add(gs.DeepCopy())
   229  	fakeWatch.Add(gs.DeepCopy())
   230  
   231  	var counts map[string]NodeCount
   232  	require.Eventuallyf(t, func() bool {
   233  		counts = pnc.Counts()
   234  		return len(counts) == 1
   235  	}, 5*time.Second, time.Second, "len should be 1, instead: %v", len(counts))
   236  
   237  	// Should only count once, not twice
   238  	assert.Equal(t, int64(1), counts[name1].Ready)
   239  	assert.Equal(t, int64(0), counts[name1].Allocated)
   240  
   241  	// Send duplicate Update events with the same ResourceVersion
   242  	gs.Status.State = agonesv1.GameServerStateAllocated
   243  	gs.ObjectMeta.ResourceVersion = "2"
   244  
   245  	fakeWatch.Modify(gs.DeepCopy())
   246  	fakeWatch.Modify(gs.DeepCopy())
   247  	fakeWatch.Modify(gs.DeepCopy())
   248  
   249  	require.Eventuallyf(t, func() bool {
   250  		counts = pnc.Counts()
   251  		return len(counts) == 1 && int64(0) == counts[name1].Ready && int64(1) == counts[name1].Allocated
   252  	}, 5*time.Second, time.Second, "Should transition to Allocated once")
   253  
   254  	// Should only count the transition once despite 3 duplicate events
   255  	assert.Equal(t, int64(0), counts[name1].Ready)
   256  	assert.Equal(t, int64(1), counts[name1].Allocated)
   257  
   258  	// Test duplicate events across Add and Modify
   259  	gs2 := &agonesv1.GameServer{
   260  		ObjectMeta: metav1.ObjectMeta{Name: "gs2", Namespace: defaultNs, UID: "uid-gs2", ResourceVersion: "1"},
   261  		Status: agonesv1.GameServerStatus{
   262  			State: agonesv1.GameServerStateReady, NodeName: name2,
   263  		},
   264  	}
   265  
   266  	// Send Add, then send the same event as Modify
   267  	fakeWatch.Add(gs2.DeepCopy())
   268  	fakeWatch.Modify(gs2.DeepCopy())
   269  
   270  	require.Eventuallyf(t, func() bool {
   271  		counts = pnc.Counts()
   272  		return len(counts) == 2
   273  	}, 5*time.Second, time.Second, "len should be 2, instead: %v", len(counts))
   274  
   275  	// Should only count once despite being sent as both Add and Modify
   276  	assert.Equal(t, int64(1), counts[name2].Ready)
   277  	assert.Equal(t, int64(0), counts[name2].Allocated)
   278  }
   279  
   280  // newFakeController returns a controller, backed by the fake Clientset
   281  func newFakePerNodeCounter() (*PerNodeCounter, agtesting.Mocks) {
   282  	m := agtesting.NewMocks()
   283  	c := NewPerNodeCounter(m.KubeInformerFactory, m.AgonesInformerFactory)
   284  	return c, m
   285  }