agones.dev/agones@v1.54.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 }