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 }