agones.dev/agones@v1.53.0/test/e2e/gameserver_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 e2e 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "net" 22 "os" 23 "os/exec" 24 "sort" 25 "strings" 26 "sync" 27 "sync/atomic" 28 "testing" 29 "time" 30 31 "github.com/sirupsen/logrus" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 corev1 "k8s.io/api/core/v1" 35 policyv1 "k8s.io/api/policy/v1" 36 k8serrors "k8s.io/apimachinery/pkg/api/errors" 37 "k8s.io/apimachinery/pkg/api/resource" 38 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 39 "k8s.io/apimachinery/pkg/types" 40 "k8s.io/apimachinery/pkg/util/rand" 41 "k8s.io/apimachinery/pkg/util/wait" 42 43 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 44 allocationv1 "agones.dev/agones/pkg/apis/allocation/v1" 45 agtesting "agones.dev/agones/pkg/testing" 46 "agones.dev/agones/pkg/util/runtime" 47 e2eframework "agones.dev/agones/test/e2e/framework" 48 ) 49 50 const ( 51 fakeIPAddress = "192.1.1.2" 52 ) 53 54 func TestCreateConnect(t *testing.T) { 55 t.Parallel() 56 gs := framework.DefaultGameServer(framework.Namespace) 57 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 58 if err != nil { 59 t.Fatalf("Could not get a GameServer ready: %v", err) 60 } 61 assert.Equal(t, len(readyGs.Status.Ports), 1) 62 assert.NotEmpty(t, readyGs.Status.Ports[0].Port) 63 assert.NotEmpty(t, readyGs.Status.Address) 64 assert.NotEmpty(t, readyGs.Status.Addresses) 65 66 var hasPodIPAddress bool 67 for i, addr := range readyGs.Status.Addresses { 68 if addr.Type == agonesv1.NodePodIP { 69 assert.NotEmpty(t, readyGs.Status.Addresses[i].Address) 70 hasPodIPAddress = true 71 } 72 } 73 assert.True(t, hasPodIPAddress) 74 75 assert.NotEmpty(t, readyGs.Status.NodeName) 76 assert.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 77 78 reply, err := framework.SendGameServerUDP(t, readyGs, "Hello World !") 79 if err != nil { 80 t.Fatalf("Could ping GameServer: %v", err) 81 } 82 83 assert.Equal(t, "ACK: Hello World !\n", reply) 84 } 85 86 func TestHostName(t *testing.T) { 87 t.Parallel() 88 89 pods := framework.KubeClient.CoreV1().Pods(framework.Namespace) 90 91 fixtures := map[string]struct { 92 setup func(gs *agonesv1.GameServer) 93 test func(gs *agonesv1.GameServer, pod *corev1.Pod) 94 }{ 95 "standard hostname": { 96 setup: func(_ *agonesv1.GameServer) {}, 97 test: func(gs *agonesv1.GameServer, pod *corev1.Pod) { 98 assert.Equal(t, gs.ObjectMeta.Name, pod.Spec.Hostname) 99 }, 100 }, 101 "a . in the name": { 102 setup: func(gs *agonesv1.GameServer) { 103 gs.ObjectMeta.GenerateName = "game-server-1.0-" 104 }, 105 test: func(_ *agonesv1.GameServer, pod *corev1.Pod) { 106 expected := "game-server-1-0-" 107 // since it's a generated name, we just check the beginning. 108 assert.Equal(t, expected, pod.Spec.Hostname[:len(expected)]) 109 }, 110 }, 111 // generated name will automatically truncate to 63 chars. 112 "generated with > 63 chars": { 113 setup: func(gs *agonesv1.GameServer) { 114 gs.ObjectMeta.GenerateName = "game-server-" + strings.Repeat("n", 100) 115 }, 116 test: func(gs *agonesv1.GameServer, pod *corev1.Pod) { 117 assert.Equal(t, gs.ObjectMeta.Name, pod.Spec.Hostname) 118 }, 119 }, 120 // Note: no need to test for a gs.ObjectMeta.Name > 63 chars, as it will be rejected as invalid 121 } 122 123 for k, v := range fixtures { 124 t.Run(k, func(t *testing.T) { 125 gs := framework.DefaultGameServer(framework.Namespace) 126 gs.Spec.Template.Spec.Subdomain = "default" 127 v.setup(gs) 128 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 129 require.NoError(t, err) 130 pod, err := pods.Get(context.Background(), readyGs.ObjectMeta.Name, metav1.GetOptions{}) 131 require.NoError(t, err) 132 v.test(readyGs, pod) 133 }) 134 } 135 } 136 137 // nolint:dupl 138 func TestSDKSetLabel(t *testing.T) { 139 t.Parallel() 140 ctx := context.Background() 141 gs := framework.DefaultGameServer(framework.Namespace) 142 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 143 if err != nil { 144 t.Fatalf("Could not get a GameServer ready: %v", err) 145 } 146 147 assert.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 148 reply, err := framework.SendGameServerUDP(t, readyGs, "LABEL") 149 if err != nil { 150 t.Fatalf("Could ping GameServer: %v", err) 151 } 152 153 assert.Equal(t, "ACK: LABEL\n", reply) 154 155 // the label is set in a queue, so it may take a moment 156 err = wait.PollUntilContextTimeout(ctx, time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) { 157 gs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 158 if err != nil { 159 return true, err 160 } 161 return gs.ObjectMeta.Labels != nil, nil 162 }) 163 164 if assert.NoError(t, err) { 165 assert.NotEmpty(t, gs.ObjectMeta.Labels["agones.dev/sdk-timestamp"]) 166 } 167 } 168 169 func TestHealthCheckDisable(t *testing.T) { 170 t.Parallel() 171 ctx := context.Background() 172 gs := framework.DefaultGameServer(framework.Namespace) 173 gs.Spec.Health = agonesv1.Health{ 174 Disabled: true, 175 FailureThreshold: 1, 176 InitialDelaySeconds: 1, 177 PeriodSeconds: 1, 178 } 179 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 180 if err != nil { 181 t.Fatalf("Could not get a GameServer ready: %v", err) 182 } 183 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 184 185 _, err = framework.SendGameServerUDP(t, readyGs, "UNHEALTHY") 186 if err != nil { 187 t.Fatalf("Could not ping GameServer: %v", err) 188 } 189 190 time.Sleep(10 * time.Second) 191 192 gs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 193 if err != nil { 194 assert.FailNow(t, "gameserver get failed", err.Error()) 195 } 196 197 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 198 } 199 200 // nolint:dupl 201 func TestSDKSetAnnotation(t *testing.T) { 202 t.Parallel() 203 ctx := context.Background() 204 gs := framework.DefaultGameServer(framework.Namespace) 205 annotation := "agones.dev/sdk-timestamp" 206 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 207 if err != nil { 208 t.Fatalf("Could not get a GameServer ready: %v", err) 209 } 210 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 211 212 assert.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 213 reply, err := framework.SendGameServerUDP(t, readyGs, "ANNOTATION") 214 if err != nil { 215 t.Fatalf("Could ping GameServer: %v", err) 216 } 217 218 assert.Equal(t, "ACK: ANNOTATION\n", reply) 219 220 // the label is set in a queue, so it may take a moment 221 err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 222 gs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 223 if err != nil { 224 return true, err 225 } 226 227 _, ok := gs.ObjectMeta.Annotations[annotation] 228 return ok, nil 229 }) 230 231 logrus.WithField("annotations", gs.ObjectMeta.Annotations).Info("annotation information") 232 233 if !assert.Nil(t, err) { 234 assert.FailNow(t, "error waiting on annotation to be set") 235 } 236 assert.NotEmpty(t, gs.ObjectMeta.Annotations[annotation]) 237 assert.NotEmpty(t, gs.ObjectMeta.Annotations[agonesv1.VersionAnnotation]) 238 } 239 240 func TestUnhealthyGameServerAfterHealthCheckFail(t *testing.T) { 241 t.Parallel() 242 gs := framework.DefaultGameServer(framework.Namespace) 243 gs.Spec.Health.FailureThreshold = 1 244 245 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 246 if err != nil { 247 assert.FailNow(t, "Failed to create a gameserver", err.Error()) 248 } 249 250 reply, err := framework.SendGameServerUDP(t, gs, "UNHEALTHY") 251 if err != nil { 252 assert.FailNow(t, "Failed to send a message to a gameserver", err.Error()) 253 } 254 assert.Equal(t, "ACK: UNHEALTHY\n", reply) 255 256 _, err = framework.WaitForGameServerState(t, gs, agonesv1.GameServerStateUnhealthy, time.Minute) 257 assert.NoError(t, err) 258 } 259 260 func TestUnhealthyGameServersWithoutFreePorts(t *testing.T) { 261 framework.SkipOnCloudProduct(t, "gke-autopilot", "does not support Static PortPolicy") 262 if runtime.FeatureEnabled(runtime.FeatureSidecarContainers) { 263 t.SkipNow() 264 } 265 266 t.Parallel() 267 ctx := context.Background() 268 nodes, err := framework.KubeClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) 269 if err != nil { 270 assert.FailNow(t, "Failed to list nodes", err.Error()) 271 } 272 assert.True(t, len(nodes.Items) > 0) 273 274 template := framework.DefaultGameServer(framework.Namespace) 275 // choose port out of the minport/maxport range 276 // also rand it, just in case there are still previous static GameServers floating around. 277 template.Spec.Ports[0].HostPort = int32(rand.IntnRange(4000, 5000)) 278 template.Spec.Ports[0].PortPolicy = agonesv1.Static 279 280 gameServers := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 281 // one successful static port GameServer 282 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, template.DeepCopy()) 283 require.NoError(t, err) 284 285 // now let's create the same one, but this time, require it be on the same node. 286 newGs := template.DeepCopy() 287 288 if newGs.Spec.Template.Spec.NodeSelector == nil { 289 newGs.Spec.Template.Spec.NodeSelector = map[string]string{} 290 } 291 newGs.Spec.Template.Spec.NodeSelector["kubernetes.io/hostname"] = gs.Status.NodeName 292 newGs, err = gameServers.Create(ctx, newGs, metav1.CreateOptions{}) 293 require.NoError(t, err) 294 295 _, err = framework.WaitForGameServerState(t, newGs, agonesv1.GameServerStateUnhealthy, 5*time.Minute) 296 assert.NoError(t, err) 297 } 298 299 func TestGameServerUnhealthyAfterDeletingPod(t *testing.T) { 300 t.Parallel() 301 ctx := context.Background() 302 gs := framework.DefaultGameServer(framework.Namespace) 303 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 304 if err != nil { 305 t.Fatalf("Could not get a GameServer ready: %v", err) 306 } 307 308 logrus.WithField("gsKey", readyGs.ObjectMeta.Name).Info("GameServer Ready") 309 310 gsClient := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 311 podClient := framework.KubeClient.CoreV1().Pods(framework.Namespace) 312 313 defer gsClient.Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 314 315 pod, err := podClient.Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 316 require.NoError(t, err) 317 require.True(t, metav1.IsControlledBy(pod, readyGs)) 318 319 err = podClient.Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}) 320 require.NoError(t, err) 321 322 _, err = framework.WaitForGameServerState(t, readyGs, agonesv1.GameServerStateUnhealthy, 3*time.Minute) 323 require.NoError(t, err) 324 } 325 326 func TestGameServerRestartBeforeReadyCrash(t *testing.T) { 327 if runtime.FeatureEnabled(runtime.FeatureSidecarContainers) { 328 t.SkipNow() 329 } 330 331 t.Parallel() 332 ctx := context.Background() 333 logger := e2eframework.TestLogger(t) 334 335 gs := framework.DefaultGameServer(framework.Namespace) 336 // give some buffer with gameservers crashing and coming back 337 gs.Spec.Health.PeriodSeconds = 60 * 60 338 gs.Spec.Template.Spec.Containers[0].Env = append(gs.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{Name: "READY", Value: "FALSE"}) 339 gsClient := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 340 newGs, err := gsClient.Create(ctx, gs, metav1.CreateOptions{}) 341 if err != nil { 342 assert.Fail(t, "could not create the gameserver", err.Error()) 343 } 344 defer gsClient.Delete(ctx, newGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 345 346 logger.Info("Waiting for us to have an address to send things to") 347 newGs, err = framework.WaitForGameServerState(t, newGs, agonesv1.GameServerStateScheduled, framework.WaitForState) 348 if err != nil { 349 assert.FailNow(t, "Failed schedule a pod", err.Error()) 350 } 351 352 logger.WithField("gs", newGs.ObjectMeta.Name).Info("GameServer created") 353 354 address := fmt.Sprintf("%s:%d", newGs.Status.Address, newGs.Status.Ports[0].Port) 355 logger.WithField("address", address).Info("Dialing UDP message to address") 356 357 messageAndWait := func(gs *agonesv1.GameServer, msg string, check func(gs *agonesv1.GameServer, pod *corev1.Pod) bool) error { 358 return wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, 3*time.Minute, true, func(ctx context.Context) (bool, error) { 359 gs, err := gsClient.Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 360 if err != nil { 361 logger.WithError(err).Warn("could not get gameserver") 362 return true, err 363 } 364 pod, err := framework.KubeClient.CoreV1().Pods(framework.Namespace).Get(ctx, newGs.ObjectMeta.Name, metav1.GetOptions{}) 365 if err != nil { 366 logger.WithError(err).Warn("could not get pod for gameserver") 367 return true, err 368 } 369 370 if check(gs, pod) { 371 return true, nil 372 } 373 374 // create a connection each time, as weird stuff happens if the receiver isn't up and running. 375 conn, err := net.Dial("udp", address) 376 if err != nil { 377 logger.WithError(err).Warn("could not create connection") 378 return true, err 379 } 380 defer conn.Close() // nolint: errcheck 381 // doing this last, so that there is a short delay between the msg being sent, and the check. 382 logger.WithField("gs", gs.ObjectMeta.Name).WithField("msg", msg). 383 WithField("state", gs.Status.State).Info("sending message") 384 if _, err = conn.Write([]byte(msg)); err != nil { 385 logger.WithError(err).WithField("gs", gs.ObjectMeta.Name). 386 WithField("state", gs.Status.State).Info("error sending packet") 387 } 388 return false, nil 389 }) 390 } 391 392 logger.Info("crashing, and waiting to see restart") 393 err = messageAndWait(newGs, "CRASH", func(_ *agonesv1.GameServer, pod *corev1.Pod) bool { 394 for _, c := range pod.Status.ContainerStatuses { 395 if c.Name == newGs.Spec.Container && c.RestartCount > 0 { 396 logger.Info("successfully crashed. Moving on!") 397 return true 398 } 399 } 400 return false 401 }) 402 assert.NoError(t, err) 403 404 // check that the GameServer is not in an unhealthy state. If it does happen, it should happen pretty quick. 405 // We wait an extra 5s to close the kubelet race in #2445. 406 newGs, err = framework.WaitForGameServerState(t, newGs, agonesv1.GameServerStateUnhealthy, 10*time.Second) 407 // should be an error, as the state should not occur 408 if !assert.Error(t, err) { 409 assert.FailNow(t, "GameServer should not be Unhealthy") 410 } 411 assert.Contains(t, err.Error(), "waiting for GameServer") 412 413 // ping READY until it doesn't fail anymore - since it may take a while 414 // for this to come back up -- or we could get a delayed CRASH, so we have to 415 // wait for the process to restart again to fire the SDK.Ready() 416 logger.Info("marking GameServer as ready") 417 err = messageAndWait(newGs, "READY", func(gs *agonesv1.GameServer, _ *corev1.Pod) bool { 418 if gs.Status.State == agonesv1.GameServerStateReady { 419 logger.Info("ready! Moving On!") 420 return true 421 } 422 return false 423 }) 424 assert.NoError(t, err) 425 426 // now crash, should be unhealthy, since it's after being Ready 427 logger.Info("crashing again, should be unhealthy") 428 // retry on crash, as with the restarts, sometimes Go takes a moment to send this through. 429 err = messageAndWait(newGs, "CRASH", func(gs *agonesv1.GameServer, _ *corev1.Pod) bool { 430 logger.WithField("gs", gs.ObjectMeta.Name).WithField("state", gs.Status.State). 431 Info("checking final crash state") 432 if gs.Status.State == agonesv1.GameServerStateUnhealthy { 433 logger.Info("Unhealthy! We are done!") 434 return true 435 } 436 return false 437 }) 438 assert.NoError(t, err) 439 } 440 441 func TestGameServerUnhealthyAfterReadyCrash(t *testing.T) { 442 t.Parallel() 443 ctx := context.Background() 444 log := e2eframework.TestLogger(t) 445 446 gs := framework.DefaultGameServer(framework.Namespace) 447 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 448 if err != nil { 449 t.Fatalf("Could not get a GameServer ready: %v", err) 450 } 451 452 log.WithField("gs", readyGs.ObjectMeta.Name).Info("GameServer created") 453 454 gsClient := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 455 defer gsClient.Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 456 457 address := fmt.Sprintf("%s:%d", readyGs.Status.Address, readyGs.Status.Ports[0].Port) 458 459 // keep crashing, until we move to Unhealthy. Solves potential issues with controller Informer cache 460 // race conditions in which it has yet to see a GameServer is Ready before the crash. 461 var stop int32 462 defer func() { 463 atomic.StoreInt32(&stop, 1) 464 }() 465 go func() { 466 for { 467 if atomic.LoadInt32(&stop) > 0 { 468 log.Info("UDP Crash stop signal received. Stopping.") 469 return 470 } 471 var writeErr error 472 func() { 473 conn, err := net.Dial("udp", address) 474 assert.NoError(t, err) 475 defer conn.Close() // nolint: errcheck 476 _, writeErr = conn.Write([]byte("CRASH")) 477 }() 478 if writeErr != nil { 479 log.WithError(err).Warn("error sending udp packet. Stopping.") 480 return 481 } 482 log.WithField("address", address).Info("sent UDP packet") 483 time.Sleep(5 * time.Second) 484 } 485 }() 486 _, err = framework.WaitForGameServerState(t, readyGs, agonesv1.GameServerStateUnhealthy, 3*time.Minute) 487 assert.NoError(t, err) 488 } 489 490 func TestGameServerPodCompletedAfterCleanExit(t *testing.T) { 491 if !runtime.FeatureEnabled(runtime.FeatureSidecarContainers) { 492 t.SkipNow() 493 } 494 495 t.Parallel() 496 ctx, cancel := context.WithCancel(context.Background()) 497 defer cancel() 498 log := e2eframework.TestLogger(t) 499 500 gs := framework.DefaultGameServer(framework.Namespace) 501 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 502 if err != nil { 503 t.Fatalf("Could not get a GameServer ready: %v", err) 504 } 505 506 address := fmt.Sprintf("%s:%d", readyGs.Status.Address, readyGs.Status.Ports[0].Port) 507 conn, err := net.Dial("udp", address) 508 mtx := &sync.Mutex{} 509 require.NoError(t, err) 510 defer func() { 511 mtx.Lock() 512 defer mtx.Unlock() 513 if conn != nil { 514 err = conn.Close() 515 if err != nil { 516 log.WithError(err).Warn("error closing udp connection") 517 } 518 } 519 }() 520 521 go func() { 522 for { 523 select { 524 case <-ctx.Done(): 525 return 526 default: 527 mtx.Lock() 528 log.Info("Sending CRASH 0") 529 _, err := conn.Write([]byte("CRASH 0")) 530 mtx.Unlock() 531 if err != nil { 532 log.WithError(err).Warn("error sending udp packet. Stopping.") 533 return 534 } 535 } 536 time.Sleep(5 * time.Second) 537 } 538 }() 539 540 err = wait.PollUntilContextTimeout(ctx, 3*time.Second, 3*time.Minute, true, func(ctx context.Context) (bool, error) { 541 gs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 542 log.WithField("gs", readyGs.ObjectMeta.Name).WithField("state", gs.Status.State).WithError(err).Info("checking if GameServer exists") 543 if k8serrors.IsNotFound(err) { 544 return true, nil 545 } 546 return false, err 547 }) 548 require.NoError(t, err) 549 } 550 551 func TestDevelopmentGameServerLifecycle(t *testing.T) { 552 t.Parallel() 553 ctx := context.Background() 554 555 labels := map[string]string{"development": "true"} 556 gs := &agonesv1.GameServer{ 557 ObjectMeta: metav1.ObjectMeta{ 558 GenerateName: "udp-server", 559 Namespace: framework.Namespace, 560 Annotations: map[string]string{agonesv1.DevAddressAnnotation: fakeIPAddress}, 561 Labels: labels, 562 }, 563 Spec: agonesv1.GameServerSpec{ 564 Container: "udp-server", 565 Ports: []agonesv1.GameServerPort{{ 566 ContainerPort: 7654, 567 HostPort: 7654, 568 Name: "gameport", 569 PortPolicy: agonesv1.Static, 570 Protocol: corev1.ProtocolUDP, 571 }}, 572 Template: corev1.PodTemplateSpec{ 573 Spec: corev1.PodSpec{ 574 Containers: []corev1.Container{{ 575 Name: "placebo", 576 Image: "this is ignored", 577 }}, 578 }, 579 }, 580 }, 581 } 582 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs.DeepCopy()) 583 if err != nil { 584 t.Fatalf("Could not get a GameServer ready: %v", err) 585 } 586 require.Equal(t, agonesv1.GameServerStateReady, readyGs.Status.State) 587 588 // Set dev GS into RequestReady and confirm it goes back to Ready. 589 gsCopy := readyGs.DeepCopy() 590 gsCopy.Status.State = agonesv1.GameServerStateRequestReady 591 reqReadyGs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{}) 592 require.NoError(t, err) 593 require.Equal(t, agonesv1.GameServerStateRequestReady, reqReadyGs.Status.State) 594 595 readyGs, err = framework.WaitForGameServerState(t, reqReadyGs, agonesv1.GameServerStateReady, framework.WaitForState) 596 if err != nil { 597 t.Fatalf("Could not get a GameServer ready from request ready: %v", err) 598 } 599 require.Equal(t, agonesv1.GameServerStateReady, readyGs.Status.State) 600 601 // confirm delete works, because if the finalisers don't get removed, this won't work. 602 err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) 603 require.NoError(t, err) 604 605 err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 606 _, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 607 if k8serrors.IsNotFound(err) { 608 return true, nil 609 } 610 611 return false, err 612 }) 613 require.NoError(t, err) 614 615 // let's make sure we can allocate a dev gameserver 616 readyGs, err = framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs.DeepCopy()) 617 require.NoError(t, err) 618 619 gsa := &allocationv1.GameServerAllocation{ 620 Spec: allocationv1.GameServerAllocationSpec{ 621 Selectors: []allocationv1.GameServerSelector{{ 622 LabelSelector: metav1.LabelSelector{MatchLabels: labels}, 623 }}, 624 }, 625 } 626 gsa, err = framework.AgonesClient.AllocationV1().GameServerAllocations(framework.Namespace).Create(ctx, gsa, metav1.CreateOptions{}) 627 require.NoError(t, err) 628 629 require.Equal(t, readyGs.ObjectMeta.Name, gsa.Status.GameServerName) 630 631 _, err = framework.WaitForGameServerState(t, readyGs, agonesv1.GameServerStateAllocated, time.Minute) 632 require.NoError(t, err) 633 634 // Also confirm that delete works for Allocated state, it should be fine but let's be sure. 635 err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) 636 require.NoError(t, err) 637 638 err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 639 _, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 640 if k8serrors.IsNotFound(err) { 641 return true, nil 642 } 643 644 return false, err 645 }) 646 require.NoError(t, err) 647 } 648 649 func TestGameServerSelfAllocate(t *testing.T) { 650 t.Parallel() 651 ctx := context.Background() 652 gs := framework.DefaultGameServer(framework.Namespace) 653 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 654 if err != nil { 655 t.Fatalf("Could not get a GameServer ready: %v", err) 656 } 657 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 658 659 assert.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 660 reply, err := framework.SendGameServerUDP(t, readyGs, "ALLOCATE") 661 if err != nil { 662 t.Fatalf("Could not message GameServer: %v", err) 663 } 664 665 assert.Equal(t, "ACK: ALLOCATE\n", reply) 666 gs, err = framework.WaitForGameServerState(t, readyGs, agonesv1.GameServerStateAllocated, time.Minute) 667 if assert.NoError(t, err) { 668 assert.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State) 669 } 670 } 671 672 func TestGameServerReadyAllocateReady(t *testing.T) { 673 t.Parallel() 674 ctx := context.Background() 675 logger := e2eframework.TestLogger(t) 676 677 gs := framework.DefaultGameServer(framework.Namespace) 678 679 logger.Info("Moving to Ready") 680 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 681 require.NoError(t, err, "Could not get a GameServer ready") 682 logger = logger.WithField("gs", readyGs.ObjectMeta.Name) 683 684 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 685 686 require.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 687 688 logger.Info("Moving to Allocated") 689 reply, err := framework.SendGameServerUDP(t, readyGs, "ALLOCATE") 690 require.NoError(t, err, "Could not message GameServer") 691 692 require.Equal(t, "ACK: ALLOCATE\n", reply) 693 gs, err = framework.WaitForGameServerState(t, readyGs, agonesv1.GameServerStateAllocated, time.Minute) 694 require.NoError(t, err) 695 require.Equal(t, agonesv1.GameServerStateAllocated, gs.Status.State) 696 697 logger.Info("Moving to Ready again") 698 reply, err = framework.SendGameServerUDP(t, readyGs, "READY") 699 require.NoError(t, err, "Could not message GameServer") 700 require.Equal(t, "ACK: READY\n", reply) 701 gs, err = framework.WaitForGameServerState(t, gs, agonesv1.GameServerStateReady, time.Minute) 702 require.NoError(t, err) 703 require.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 704 } 705 706 func TestGameServerWithPortsMappedToMultipleContainers(t *testing.T) { 707 t.Parallel() 708 ctx := context.Background() 709 710 firstContainerName := "udp-server" 711 secondContainerName := "second-udp-server" 712 firstPort := "gameport" 713 secondPort := "second-gameport" 714 gs := &agonesv1.GameServer{ 715 ObjectMeta: metav1.ObjectMeta{GenerateName: "udp-server", Namespace: framework.Namespace}, 716 Spec: agonesv1.GameServerSpec{ 717 Container: firstContainerName, 718 Ports: []agonesv1.GameServerPort{{ 719 ContainerPort: 7654, 720 Name: firstPort, 721 PortPolicy: agonesv1.Dynamic, 722 Protocol: corev1.ProtocolUDP, 723 }, { 724 ContainerPort: 5000, 725 Name: secondPort, 726 PortPolicy: agonesv1.Dynamic, 727 Protocol: corev1.ProtocolUDP, 728 Container: &secondContainerName, 729 }}, 730 Template: corev1.PodTemplateSpec{ 731 Spec: corev1.PodSpec{ 732 Containers: []corev1.Container{ 733 { 734 Name: firstContainerName, 735 Image: framework.GameServerImage, 736 ImagePullPolicy: corev1.PullIfNotPresent, 737 }, 738 { 739 Name: secondContainerName, 740 Image: framework.GameServerImage, 741 ImagePullPolicy: corev1.PullIfNotPresent, 742 Args: []string{"-port", "5000"}, 743 }, 744 }, 745 }, 746 }, 747 }, 748 } 749 750 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 751 if err != nil { 752 t.Fatalf("Could not get a GameServer ready: %v", err) 753 } 754 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 755 assert.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 756 757 interval := 2 * time.Second 758 timeOut := 60 * time.Second 759 760 expectedMsg1 := "Ping 1" 761 errPoll := wait.PollUntilContextTimeout(context.Background(), interval, timeOut, true, func(_ context.Context) (done bool, err error) { 762 res, err := framework.SendGameServerUDPToPort(t, readyGs, firstPort, expectedMsg1) 763 if err != nil { 764 t.Logf("Could not message GameServer on %s: %v. Will try again...", firstPort, err) 765 } 766 return err == nil && strings.Contains(res, expectedMsg1), nil 767 }) 768 if errPoll != nil { 769 assert.FailNow(t, errPoll.Error(), "expected no errors after polling a port: %s", firstPort) 770 } 771 772 expectedMsg2 := "Ping 2" 773 errPoll = wait.PollUntilContextTimeout(context.Background(), interval, timeOut, true, func(_ context.Context) (done bool, err error) { 774 res, err := framework.SendGameServerUDPToPort(t, readyGs, secondPort, expectedMsg2) 775 if err != nil { 776 t.Logf("Could not message GameServer on %s: %v. Will try again...", secondPort, err) 777 } 778 return err == nil && strings.Contains(res, expectedMsg2), nil 779 }) 780 781 assert.NoError(t, errPoll, "expected no errors after polling a port: %s", secondPort) 782 } 783 784 func TestGameServerReserve(t *testing.T) { 785 t.Parallel() 786 ctx := context.Background() 787 788 // We are deliberately not trying to test the transition between Reserved -> Ready. 789 // 790 // We have found that trying to catch the GameServer in the Reserved state can be flaky, 791 // as we can't control the speed in which the Kubernetes API is going to reply to request, 792 // and we could sometimes miss when the GameServer is in the Reserved State before it goes to Ready. 793 // 794 // Therefore we are going to test for concrete states that we don't need to catch while 795 // in a transitive state. 796 797 gs := framework.DefaultGameServer(framework.Namespace) 798 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 799 if err != nil { 800 assert.FailNow(t, "Could not get a GameServer ready", err.Error()) 801 } 802 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, gs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 803 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateReady) 804 805 reply, err := framework.SendGameServerUDP(t, gs, "RESERVE 0") 806 if err != nil { 807 assert.FailNow(t, "Could not message GameServer", err.Error()) 808 } 809 assert.Equal(t, "ACK: RESERVE 0\n", reply) 810 811 gs, err = framework.WaitForGameServerState(t, gs, agonesv1.GameServerStateReserved, 3*time.Minute) 812 if err != nil { 813 assert.FailNow(t, "Time out on waiting for gs in a Reserved state", err.Error()) 814 } 815 816 reply, err = framework.SendGameServerUDP(t, gs, "ALLOCATE") 817 if err != nil { 818 assert.FailNow(t, "Could not message GameServer", err.Error()) 819 } 820 assert.Equal(t, "ACK: ALLOCATE\n", reply) 821 822 // put it in a totally different state, just to reset things. 823 gs, err = framework.WaitForGameServerState(t, gs, agonesv1.GameServerStateAllocated, 3*time.Minute) 824 if err != nil { 825 assert.FailNow(t, "Time out on waiting for gs in an Allocated state", err.Error()) 826 } 827 828 reply, err = framework.SendGameServerUDP(t, gs, "RESERVE 5s") 829 if err != nil { 830 assert.FailNow(t, "Could not message GameServer", err.Error()) 831 } 832 assert.Equal(t, "ACK: RESERVE 5s\n", reply) 833 834 // sleep, since we're going to wait for the Ready response. 835 time.Sleep(5 * time.Second) 836 _, err = framework.WaitForGameServerState(t, gs, agonesv1.GameServerStateReady, 3*time.Minute) 837 assert.NoError(t, err) 838 } 839 840 func TestGameServerShutdown(t *testing.T) { 841 t.Parallel() 842 ctx := context.Background() 843 gs := framework.DefaultGameServer(framework.Namespace) 844 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 845 if err != nil { 846 t.Fatalf("Could not get a GameServer ready: %v", err) 847 } 848 assert.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 849 850 reply, err := framework.SendGameServerUDP(t, readyGs, "EXIT") 851 if err != nil { 852 t.Fatalf("Could not message GameServer: %v", err) 853 } 854 855 assert.Equal(t, "ACK: EXIT\n", reply) 856 857 err = wait.PollUntilContextTimeout(ctx, time.Second, 3*time.Minute, true, func(ctx context.Context) (bool, error) { 858 gs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 859 860 if k8serrors.IsNotFound(err) { 861 return true, nil 862 } 863 864 return false, err 865 }) 866 867 assert.NoError(t, err) 868 } 869 870 // TestGameServerEvicted test that if Gameserver would be evicted than it becomes Unhealthy 871 // Ephemeral Storage limit set to 0Mi 872 func TestGameServerEvicted(t *testing.T) { 873 t.Parallel() 874 log := e2eframework.TestLogger(t) 875 876 ctx := context.Background() 877 gs := framework.DefaultGameServer(framework.Namespace) 878 newGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 879 require.NoError(t, err) 880 log.WithField("name", newGs.ObjectMeta.Name).Info("GameServer created, waiting for being Evicted and Unhealthy") 881 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, newGs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 882 883 pods := framework.KubeClient.CoreV1().Pods(framework.Namespace) 884 pod, err := pods.Get(ctx, newGs.ObjectMeta.Name, metav1.GetOptions{}) 885 require.NoError(t, err) 886 887 eviction := &policyv1.Eviction{ 888 ObjectMeta: metav1.ObjectMeta{ 889 Name: pod.Name, 890 Namespace: pod.Namespace, 891 }, 892 } 893 go func() { 894 time.Sleep(3 * time.Second) // just make sure it comes in later 895 log.WithField("name", eviction.ObjectMeta.Name).Info("Evicting pod!") 896 err := pods.EvictV1(context.Background(), eviction) 897 require.NoError(t, err) 898 }() 899 900 _, err = framework.WaitForGameServerState(t, newGs, agonesv1.GameServerStateUnhealthy, 10*time.Minute) 901 require.NoError(t, err, fmt.Sprintf("waiting for [%v] GameServer Unhealthy state timed out (%v)", gs.Status.State, gs.Name)) 902 } 903 904 func TestGameServerPassthroughPort(t *testing.T) { 905 framework.SkipOnCloudProduct(t, "gke-autopilot", "does not support Passthrough PortPolicy") 906 t.Parallel() 907 gs := framework.DefaultGameServer(framework.Namespace) 908 gs.Spec.Ports[0] = agonesv1.GameServerPort{PortPolicy: agonesv1.Passthrough} 909 gs.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{Name: "PASSTHROUGH", Value: "TRUE"}} 910 // gate 911 errs := gs.Validate(agtesting.FakeAPIHooks{}) 912 assert.Len(t, errs, 0) 913 914 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 915 if err != nil { 916 assert.FailNow(t, "Could not get a GameServer ready", err.Error()) 917 } 918 919 port := readyGs.Spec.Ports[0] 920 assert.Equal(t, agonesv1.Passthrough, port.PortPolicy) 921 assert.NotEmpty(t, port.HostPort) 922 assert.Equal(t, port.HostPort, port.ContainerPort) 923 924 reply, err := framework.SendGameServerUDP(t, readyGs, "Hello World !") 925 if err != nil { 926 t.Fatalf("Could ping GameServer: %v", err) 927 } 928 929 assert.Equal(t, "ACK: Hello World !\n", reply) 930 } 931 932 func TestGameServerPortPolicyNone(t *testing.T) { 933 if !runtime.FeatureEnabled(runtime.FeaturePortPolicyNone) { 934 t.SkipNow() 935 } 936 937 t.Parallel() 938 939 gs := framework.DefaultGameServer(framework.Namespace) 940 gs.Spec.Ports[0] = agonesv1.GameServerPort{PortPolicy: agonesv1.None, ContainerPort: 7777} 941 // gate 942 errs := gs.Validate(agtesting.FakeAPIHooks{}) 943 assert.Len(t, errs, 0) 944 945 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 946 if err != nil { 947 assert.FailNow(t, "Could not get a GameServer ready", err.Error()) 948 } 949 950 // To test sending UDP traffic directly to a pod when no hostPort is set, a product like Calico which uses BGP is needed 951 // so this only tests that the port is set correctly. 952 port := readyGs.Spec.Ports[0] 953 assert.Equal(t, agonesv1.None, port.PortPolicy) 954 assert.Empty(t, port.HostPort) 955 assert.EqualValues(t, 7777, port.ContainerPort) 956 } 957 958 func TestGameServerTcpProtocol(t *testing.T) { 959 t.Parallel() 960 gs := framework.DefaultGameServer(framework.Namespace) 961 962 gs.Spec.Ports[0].Protocol = corev1.ProtocolTCP 963 gs.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{Name: "TCP", Value: "TRUE"}} 964 965 errs := gs.Validate(agtesting.FakeAPIHooks{}) 966 require.Len(t, errs, 0) 967 968 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 969 require.NoError(t, err) 970 971 replyTCP, err := e2eframework.SendGameServerTCP(readyGs, "Hello World !") 972 require.NoError(t, err) 973 assert.Equal(t, "ACK TCP: Hello World !\n", replyTCP) 974 } 975 976 func TestGameServerTcpUdpProtocol(t *testing.T) { 977 t.Parallel() 978 gs := framework.DefaultGameServer(framework.Namespace) 979 980 gs.Spec.Ports[0].Protocol = agonesv1.ProtocolTCPUDP 981 gs.Spec.Ports[0].Name = "gameserver" 982 gs.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{Name: "TCP", Value: "TRUE"}} 983 984 errs := gs.Validate(agtesting.FakeAPIHooks{}) 985 require.Len(t, errs, 0) 986 987 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 988 if err != nil { 989 assert.FailNow(t, "Could not get a GameServer ready", err.Error()) 990 } 991 992 tcpPort := readyGs.Spec.Ports[0] 993 assert.Equal(t, corev1.ProtocolTCP, tcpPort.Protocol) 994 assert.NotEmpty(t, tcpPort.HostPort) 995 assert.Equal(t, "gameserver-tcp", tcpPort.Name) 996 997 udpPort := readyGs.Spec.Ports[1] 998 assert.Equal(t, corev1.ProtocolUDP, udpPort.Protocol) 999 assert.NotEmpty(t, udpPort.HostPort) 1000 assert.Equal(t, "gameserver-udp", udpPort.Name) 1001 1002 assert.Equal(t, tcpPort.HostPort, udpPort.HostPort) 1003 1004 logrus.WithField("name", readyGs.ObjectMeta.Name).Info("GameServer created, sending UDP ping") 1005 1006 replyUDP, err := framework.SendGameServerUDPToPort(t, readyGs, udpPort.Name, "Hello World !") 1007 require.NoError(t, err) 1008 if err != nil { 1009 t.Fatalf("Could not ping UDP GameServer: %v", err) 1010 } 1011 1012 assert.Equal(t, "ACK: Hello World !\n", replyUDP) 1013 1014 logrus.WithField("name", readyGs.ObjectMeta.Name).Info("UDP ping passed, sending TCP ping") 1015 1016 replyTCP, err := e2eframework.SendGameServerTCPToPort(readyGs, tcpPort.Name, "Hello World !") 1017 if err != nil { 1018 t.Fatalf("Could not ping TCP GameServer: %v", err) 1019 } 1020 1021 assert.Equal(t, "ACK TCP: Hello World !\n", replyTCP) 1022 } 1023 1024 // TestGameServerStaticTcpUdpProtocol checks UDP and TCP connection for TCPUDP Protocol of Static Portpolicy 1025 func TestGameServerStaticTcpUdpProtocol(t *testing.T) { 1026 framework.SkipOnCloudProduct(t, "gke-autopilot", "does not support Static PortPolicy") 1027 t.Parallel() 1028 gs := framework.DefaultGameServer(framework.Namespace) 1029 gs.Spec.Ports[0].PortPolicy = agonesv1.Static 1030 gs.Spec.Ports[0].Protocol = agonesv1.ProtocolTCPUDP 1031 gs.Spec.Ports[0].HostPort = 7000 1032 gs.Spec.Ports[0].Name = "gameserver" 1033 gs.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{Name: "TCP", Value: "TRUE"}} 1034 1035 errs := gs.Validate(agtesting.FakeAPIHooks{}) 1036 require.Len(t, errs, 0) 1037 1038 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1039 require.NoError(t, err) 1040 1041 tcpPort := readyGs.Spec.Ports[0] 1042 assert.Equal(t, corev1.ProtocolTCP, tcpPort.Protocol) 1043 assert.NotEmpty(t, tcpPort.HostPort) 1044 assert.Equal(t, "gameserver-tcp", tcpPort.Name) 1045 1046 udpPort := readyGs.Spec.Ports[1] 1047 assert.Equal(t, corev1.ProtocolUDP, udpPort.Protocol) 1048 assert.NotEmpty(t, udpPort.HostPort) 1049 assert.Equal(t, "gameserver-udp", udpPort.Name) 1050 1051 assert.Equal(t, tcpPort.HostPort, udpPort.HostPort) 1052 1053 logrus.WithField("name", readyGs.ObjectMeta.Name).Info("GameServer created, sending UDP ping") 1054 1055 replyUDP, err := framework.SendGameServerUDPToPort(t, readyGs, udpPort.Name, "Hello World !") 1056 require.NoError(t, err) 1057 if err != nil { 1058 t.Fatalf("Could not ping UDP GameServer: %v", err) 1059 } 1060 1061 assert.Equal(t, "ACK: Hello World !\n", replyUDP) 1062 1063 logrus.WithField("name", readyGs.ObjectMeta.Name).Info("UDP ping passed, sending TCP ping") 1064 1065 replyTCP, err := e2eframework.SendGameServerTCPToPort(readyGs, tcpPort.Name, "Hello World !") 1066 if err != nil { 1067 t.Fatalf("Could not ping TCP GameServer: %v", err) 1068 } 1069 1070 assert.Equal(t, "ACK TCP: Hello World !\n", replyTCP) 1071 } 1072 1073 // TestGameServerStaticTcpProtocol checks TCP connection for TCP Protocol of Static Portpolicy 1074 func TestGameServerStaticTcpProtocol(t *testing.T) { 1075 framework.SkipOnCloudProduct(t, "gke-autopilot", "does not support Static PortPolicy") 1076 t.Parallel() 1077 gs := framework.DefaultGameServer(framework.Namespace) 1078 1079 gs.Spec.Ports[0].PortPolicy = agonesv1.Static 1080 gs.Spec.Ports[0].Protocol = corev1.ProtocolTCP 1081 gs.Spec.Ports[0].HostPort = 7000 1082 gs.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{Name: "TCP", Value: "TRUE"}} 1083 1084 errs := gs.Validate(agtesting.FakeAPIHooks{}) 1085 require.Len(t, errs, 0) 1086 1087 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1088 require.NoError(t, err) 1089 1090 logrus.WithField("name", readyGs.ObjectMeta.Name).Info("sending TCP ping") 1091 1092 replyTCP, err := e2eframework.SendGameServerTCP(readyGs, "Hello World !") 1093 require.NoError(t, err) 1094 assert.Equal(t, "ACK TCP: Hello World !\n", replyTCP) 1095 1096 logrus.WithField("name", readyGs.ObjectMeta.Name).Info("TCP ping Passed") 1097 } 1098 1099 // TestGameServerStaticUdpProtocol checks default(UDP) connection of Static Portpolicy 1100 func TestGameServerStaticUdpProtocol(t *testing.T) { 1101 framework.SkipOnCloudProduct(t, "gke-autopilot", "does not support Static PortPolicy") 1102 t.Parallel() 1103 gs := framework.DefaultGameServer(framework.Namespace) 1104 1105 gs.Spec.Ports[0].PortPolicy = agonesv1.Static 1106 gs.Spec.Ports[0].HostPort = 7000 1107 1108 errs := gs.Validate(agtesting.FakeAPIHooks{}) 1109 require.Len(t, errs, 0) 1110 1111 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1112 require.NoError(t, err) 1113 1114 logrus.WithField("name", readyGs.ObjectMeta.Name).Info("sending UDP ping") 1115 1116 replyTCP, err := framework.SendGameServerUDP(t, readyGs, "Default UDP connection check") 1117 require.NoError(t, err) 1118 assert.Equal(t, "ACK: Default UDP connection check\n", replyTCP) 1119 1120 logrus.WithField("name", readyGs.ObjectMeta.Name).Info("UDP ping Passed") 1121 } 1122 1123 func TestGameServerWithoutPort(t *testing.T) { 1124 t.Parallel() 1125 gs := framework.DefaultGameServer(framework.Namespace) 1126 gs.Spec.Ports = nil 1127 1128 errs := gs.Validate(agtesting.FakeAPIHooks{}) 1129 assert.Len(t, errs, 0) 1130 1131 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1132 1133 require.NoError(t, err, "Could not get a GameServer ready") 1134 assert.Empty(t, readyGs.Spec.Ports) 1135 } 1136 1137 // TestGameServerResourceValidation - check that we are not able to use 1138 // invalid PodTemplate for GameServer Spec with wrong Resource Requests and Limits 1139 func TestGameServerResourceValidation(t *testing.T) { 1140 t.Parallel() 1141 ctx := context.Background() 1142 gs := framework.DefaultGameServer(framework.Namespace) 1143 mi64 := resource.MustParse("64Mi") 1144 gs.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] = mi64 1145 gs.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = resource.MustParse("128Mi") 1146 1147 errs := gs.Validate(agtesting.FakeAPIHooks{}) 1148 assert.False(t, len(errs) == 0) 1149 1150 gsClient := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 1151 1152 _, err := gsClient.Create(ctx, gs.DeepCopy(), metav1.CreateOptions{}) 1153 assert.NotNil(t, err) 1154 statusErr, ok := err.(*k8serrors.StatusError) 1155 assert.True(t, ok) 1156 assert.Len(t, statusErr.Status().Details.Causes, 1) 1157 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type) 1158 assert.Equal(t, "spec.template.spec.containers[0].resources.requests", statusErr.Status().Details.Causes[0].Field) 1159 1160 gs.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceCPU] = resource.MustParse("-50m") 1161 _, err = gsClient.Create(ctx, gs.DeepCopy(), metav1.CreateOptions{}) 1162 assert.NotNil(t, err) 1163 statusErr, ok = err.(*k8serrors.StatusError) 1164 assert.True(t, ok) 1165 assert.Len(t, statusErr.Status().Details.Causes, 2) 1166 sort.Slice(statusErr.Status().Details.Causes, func(i, j int) bool { 1167 return statusErr.Status().Details.Causes[i].Field > statusErr.Status().Details.Causes[j].Field 1168 }) 1169 assert.Equal(t, metav1.CauseTypeFieldValueInvalid, statusErr.Status().Details.Causes[0].Type) 1170 assert.Equal(t, "spec.template.spec.containers[0].resources.requests[cpu]", statusErr.Status().Details.Causes[0].Field) 1171 1172 // test that values are still being set correctly 1173 m50 := resource.MustParse("50m") 1174 gs.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] = mi64 1175 gs.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = mi64 1176 gs.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceCPU] = m50 1177 gs.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = m50 1178 1179 // confirm we have a valid GameServer before running the test 1180 errs = gs.Validate(agtesting.FakeAPIHooks{}) 1181 require.Len(t, errs, 0) 1182 1183 gsCopy, err := gsClient.Create(ctx, gs.DeepCopy(), metav1.CreateOptions{}) 1184 require.NoError(t, err) 1185 assert.Equal(t, mi64, gsCopy.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory]) 1186 assert.Equal(t, mi64, gsCopy.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory]) 1187 assert.Equal(t, m50, gsCopy.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceCPU]) 1188 assert.Equal(t, m50, gsCopy.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU]) 1189 } 1190 1191 func TestGameServerPodTemplateSpecFailSchemaValidation(t *testing.T) { 1192 t.Parallel() 1193 1194 // The Kubernetes dynamic client skips schema validation (for reasons I'm not sure of), so the 1195 // best way I could determine to test schema validation is via calling kubectl directly. 1196 // The schema's from Kubernetes don't include anything like `pattern:` or `enum:` which would 1197 // potentially make this easier to test. 1198 1199 gsYaml := ` 1200 apiVersion: "agones.dev/v1" 1201 kind: GameServer 1202 metadata: 1203 name: "invalid-gameserver" 1204 spec: 1205 ports: 1206 - name: default 1207 portPolicy: Dynamic 1208 containerPort: 7654 1209 template: 1210 spec: 1211 affinity: 1212 nodeAffinity: 1213 preferredDuringSchedulingIgnoredDuringExecution: ERROR 1214 containers: 1215 - name: simple-game-server 1216 image: us-docker.pkg.dev/agones-images/examples/simple-game-server:0.39 1217 ` 1218 err := os.WriteFile("/tmp/invalid.yaml", []byte(gsYaml), 0o644) 1219 require.NoError(t, err) 1220 1221 cmd := exec.Command("kubectl", "apply", "-f", "/tmp/invalid.yaml") 1222 var stdout bytes.Buffer 1223 var stderr bytes.Buffer 1224 cmd.Stdout = &stdout 1225 cmd.Stderr = &stderr 1226 err = cmd.Run() 1227 logrus.WithField("stdout", stdout.String()).WithField("stderr", stderr.String()).WithError(err).Info("Ran command!") 1228 require.Error(t, err) 1229 assert.Contains(t, stderr.String(), "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution") 1230 } 1231 1232 func TestGameServerSetPlayerCapacity(t *testing.T) { 1233 if !runtime.FeatureEnabled(runtime.FeaturePlayerTracking) { 1234 t.SkipNow() 1235 } 1236 t.Parallel() 1237 ctx := context.Background() 1238 1239 t.Run("no initial capacity set", func(t *testing.T) { 1240 gs := framework.DefaultGameServer(framework.Namespace) 1241 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1242 if err != nil { 1243 t.Fatalf("Could not get a GameServer ready: %v", err) 1244 } 1245 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateReady) 1246 assert.Equal(t, int64(0), gs.Status.Players.Capacity) 1247 1248 reply, err := framework.SendGameServerUDP(t, gs, "PLAYER_CAPACITY") 1249 if err != nil { 1250 t.Fatalf("Could not message GameServer: %v", err) 1251 } 1252 assert.Equal(t, "0\n", reply) 1253 1254 reply, err = framework.SendGameServerUDP(t, gs, "PLAYER_CAPACITY 20") 1255 if err != nil { 1256 t.Fatalf("Could not message GameServer: %v", err) 1257 } 1258 assert.Equal(t, "ACK: PLAYER_CAPACITY 20\n", reply) 1259 1260 reply, err = framework.SendGameServerUDP(t, gs, "PLAYER_CAPACITY") 1261 if err != nil { 1262 t.Fatalf("Could not message GameServer: %v", err) 1263 } 1264 assert.Equal(t, "20\n", reply) 1265 1266 err = wait.PollUntilContextTimeout(ctx, time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 1267 gs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1268 if err != nil { 1269 return false, err 1270 } 1271 return gs.Status.Players.Capacity == 20, nil 1272 }) 1273 assert.NoError(t, err) 1274 }) 1275 1276 t.Run("initial capacity set", func(t *testing.T) { 1277 gs := framework.DefaultGameServer(framework.Namespace) 1278 gs.Spec.Players = &agonesv1.PlayersSpec{InitialCapacity: 10} 1279 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1280 if err != nil { 1281 t.Fatalf("Could not get a GameServer ready: %v", err) 1282 } 1283 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateReady) 1284 assert.Equal(t, int64(10), gs.Status.Players.Capacity) 1285 1286 reply, err := framework.SendGameServerUDP(t, gs, "PLAYER_CAPACITY") 1287 if err != nil { 1288 t.Fatalf("Could not message GameServer: %v", err) 1289 } 1290 assert.Equal(t, "10\n", reply) 1291 1292 reply, err = framework.SendGameServerUDP(t, gs, "PLAYER_CAPACITY 20") 1293 if err != nil { 1294 t.Fatalf("Could not message GameServer: %v", err) 1295 } 1296 assert.Equal(t, "ACK: PLAYER_CAPACITY 20\n", reply) 1297 1298 reply, err = framework.SendGameServerUDP(t, gs, "PLAYER_CAPACITY") 1299 if err != nil { 1300 t.Fatalf("Could not message GameServer: %v", err) 1301 } 1302 assert.Equal(t, "20\n", reply) 1303 1304 err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 1305 gs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1306 if err != nil { 1307 return false, err 1308 } 1309 return gs.Status.Players.Capacity == 20, nil 1310 }) 1311 assert.NoError(t, err) 1312 1313 time.Sleep(30 * time.Second) 1314 }) 1315 } 1316 1317 func TestPlayerConnectWithCapacityZero(t *testing.T) { 1318 if !runtime.FeatureEnabled(runtime.FeaturePlayerTracking) { 1319 t.SkipNow() 1320 } 1321 t.Parallel() 1322 1323 gs := framework.DefaultGameServer(framework.Namespace) 1324 playerCount := int64(0) 1325 gs.Spec.Players = &agonesv1.PlayersSpec{InitialCapacity: playerCount} 1326 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1327 require.NoError(t, err) 1328 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateReady) 1329 assert.Equal(t, playerCount, gs.Status.Players.Capacity) 1330 1331 // add a player 1332 msg := "PLAYER_CONNECT 1" 1333 logrus.WithField("msg", msg).Info("Sending Player Connect") 1334 _, err = framework.SendGameServerUDP(t, gs, msg) 1335 // expected error from the log.Fatalf("could not connect player: %v", err) 1336 if assert.Error(t, err) { 1337 _, err := framework.WaitForGameServerState(t, gs, agonesv1.GameServerStateUnhealthy, time.Minute) 1338 assert.NoError(t, err) 1339 } 1340 } 1341 1342 func TestPlayerConnectAndDisconnect(t *testing.T) { 1343 if !runtime.FeatureEnabled(runtime.FeaturePlayerTracking) { 1344 t.SkipNow() 1345 } 1346 t.Parallel() 1347 ctx := context.Background() 1348 1349 gs := framework.DefaultGameServer(framework.Namespace) 1350 playerCount := int64(3) 1351 gs.Spec.Players = &agonesv1.PlayersSpec{InitialCapacity: playerCount} 1352 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1353 if err != nil { 1354 t.Fatalf("Could not get a GameServer ready: %v", err) 1355 } 1356 assert.Equal(t, gs.Status.State, agonesv1.GameServerStateReady) 1357 assert.Equal(t, playerCount, gs.Status.Players.Capacity) 1358 1359 // add three players in quick succession 1360 for i := int64(1); i <= playerCount; i++ { 1361 msg := "PLAYER_CONNECT " + fmt.Sprintf("%d", i) 1362 logrus.WithField("msg", msg).Info("Sending Player Connect") 1363 reply, err := framework.SendGameServerUDP(t, gs, msg) 1364 if err != nil { 1365 t.Fatalf("Could not message GameServer: %v", err) 1366 } 1367 assert.Equal(t, fmt.Sprintf("ACK: %s\n", msg), reply) 1368 } 1369 1370 // deliberately do this before polling, to test the SDK returning the correct 1371 // results before it is committed to the GameServer resource. 1372 reply, err := framework.SendGameServerUDP(t, gs, "PLAYER_CONNECTED 1") 1373 if err != nil { 1374 t.Fatalf("Could not message GameServer: %v", err) 1375 } 1376 assert.Equal(t, "true\n", reply) 1377 1378 reply, err = framework.SendGameServerUDP(t, gs, "GET_PLAYERS") 1379 if err != nil { 1380 t.Fatalf("Could not message GameServer: %v", err) 1381 } 1382 assert.ElementsMatch(t, []string{"1", "2", "3"}, strings.Split(strings.TrimSpace(reply), ",")) 1383 1384 reply, err = framework.SendGameServerUDP(t, gs, "PLAYER_COUNT") 1385 if err != nil { 1386 t.Fatalf("Could not message GameServer: %v", err) 1387 } 1388 assert.Equal(t, "3\n", reply) 1389 1390 err = wait.PollUntilContextTimeout(ctx, time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 1391 gs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1392 if err != nil { 1393 return false, err 1394 } 1395 return gs.Status.Players.Count == playerCount, nil 1396 }) 1397 assert.NoError(t, err) 1398 assert.ElementsMatch(t, []string{"1", "2", "3"}, gs.Status.Players.IDs) 1399 1400 // let's disconnect player 2 1401 logrus.Info("Disconnect Player 2") 1402 reply, err = framework.SendGameServerUDP(t, gs, "PLAYER_DISCONNECT 2") 1403 if err != nil { 1404 t.Fatalf("Could not message GameServer: %v", err) 1405 } 1406 assert.Equal(t, "ACK: PLAYER_DISCONNECT 2\n", reply) 1407 1408 reply, err = framework.SendGameServerUDP(t, gs, "PLAYER_CONNECTED 2") 1409 if err != nil { 1410 t.Fatalf("Could not message GameServer: %v", err) 1411 } 1412 assert.Equal(t, "false\n", reply) 1413 1414 reply, err = framework.SendGameServerUDP(t, gs, "GET_PLAYERS") 1415 if err != nil { 1416 t.Fatalf("Could not message GameServer: %v", err) 1417 } 1418 assert.ElementsMatch(t, []string{"1", "3"}, strings.Split(strings.TrimSpace(reply), ",")) 1419 1420 reply, err = framework.SendGameServerUDP(t, gs, "PLAYER_COUNT") 1421 if err != nil { 1422 t.Fatalf("Could not message GameServer: %v", err) 1423 } 1424 assert.Equal(t, "2\n", reply) 1425 1426 err = wait.PollUntilContextTimeout(ctx, time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 1427 gs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1428 if err != nil { 1429 return false, err 1430 } 1431 return gs.Status.Players.Count == 2, nil 1432 }) 1433 assert.NoError(t, err) 1434 assert.ElementsMatch(t, []string{"1", "3"}, gs.Status.Players.IDs) 1435 } 1436 1437 func TestCounters(t *testing.T) { 1438 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 1439 t.SkipNow() 1440 } 1441 t.Parallel() 1442 ctx := context.Background() 1443 gs := framework.DefaultGameServer(framework.Namespace) 1444 1445 gs.Spec.Counters = make(map[string]agonesv1.CounterStatus) 1446 gs.Spec.Counters["games"] = agonesv1.CounterStatus{ 1447 Count: 1, 1448 Capacity: 50, 1449 } 1450 gs.Spec.Counters["foo"] = agonesv1.CounterStatus{ 1451 Count: 10, 1452 Capacity: 100, 1453 } 1454 gs.Spec.Counters["bar"] = agonesv1.CounterStatus{ 1455 Count: 10, 1456 Capacity: 10, 1457 } 1458 gs.Spec.Counters["baz"] = agonesv1.CounterStatus{ 1459 Count: 1000, 1460 Capacity: 1000, 1461 } 1462 gs.Spec.Counters["qux"] = agonesv1.CounterStatus{ 1463 Count: 42, 1464 Capacity: 50, 1465 } 1466 1467 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1468 require.NoError(t, err) 1469 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, gs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 1470 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 1471 1472 testCases := map[string]struct { 1473 msg string 1474 want string 1475 counterName string 1476 wantCount string 1477 wantCapacity string 1478 }{ 1479 "GetCounterCount": { 1480 msg: "GET_COUNTER_COUNT games", 1481 want: "COUNTER: 1\n", 1482 }, 1483 "GetCounterCount Counter Does Not Exist": { 1484 msg: "GET_COUNTER_COUNT fame", 1485 want: "ERROR: -1\n", 1486 }, 1487 "IncrementCounter": { 1488 msg: "INCREMENT_COUNTER foo 10", 1489 want: "SUCCESS\n", 1490 counterName: "foo", 1491 wantCount: "COUNTER: 20\n", 1492 }, 1493 "IncrementCounter Past Capacity": { 1494 msg: "INCREMENT_COUNTER games 50", 1495 want: "ERROR: could not increment Counter games by amount 50: rpc error: code = Unknown desc = out of range. Count must be within range [0,Capacity]. Found Count: 51, Capacity: 50\n", 1496 counterName: "games", 1497 wantCount: "COUNTER: 1\n", 1498 }, 1499 "IncrementCounter Negative": { 1500 msg: "INCREMENT_COUNTER games -1", 1501 want: "ERROR: amount must be a positive int64, found -1\n", 1502 counterName: "games", 1503 wantCount: "COUNTER: 1\n", 1504 }, 1505 "IncrementCounter Counter Does Not Exist": { 1506 msg: "INCREMENT_COUNTER same 1", 1507 want: "ERROR: could not increment Counter same by amount 1: rpc error: code = Unknown desc = counter not found: same\n", 1508 }, 1509 "DecrementCounter": { 1510 msg: "DECREMENT_COUNTER bar 10", 1511 want: "SUCCESS\n", 1512 counterName: "bar", 1513 wantCount: "COUNTER: 0\n", 1514 }, 1515 "DecrementCounter Past Capacity": { 1516 msg: "DECREMENT_COUNTER games 2", 1517 want: "ERROR: could not decrement Counter games by amount 2: rpc error: code = Unknown desc = out of range. Count must be within range [0,Capacity]. Found Count: -1, Capacity: 50\n", 1518 counterName: "games", 1519 wantCount: "COUNTER: 1\n", 1520 }, 1521 "DecrementCounter Negative": { 1522 msg: "DECREMENT_COUNTER games -1", 1523 want: "ERROR: amount must be a positive int64, found -1\n", 1524 counterName: "games", 1525 wantCount: "COUNTER: 1\n", 1526 }, 1527 "DecrementCounter Counter Does Not Exist": { 1528 msg: "DECREMENT_COUNTER lame 1", 1529 want: "ERROR: could not decrement Counter lame by amount 1: rpc error: code = Unknown desc = counter not found: lame\n", 1530 }, 1531 "SetCounterCount": { 1532 msg: "SET_COUNTER_COUNT baz 0", 1533 want: "SUCCESS\n", 1534 counterName: "baz", 1535 wantCount: "COUNTER: 0\n", 1536 }, 1537 "SetCounterCount Past Capacity": { 1538 msg: "SET_COUNTER_COUNT games 51", 1539 want: "ERROR: could not set Counter games count to amount 51: rpc error: code = Unknown desc = out of range. Count must be within range [0,Capacity]. Found Count: 51, Capacity: 50\n", 1540 counterName: "games", 1541 wantCount: "COUNTER: 1\n", 1542 }, 1543 "SetCounterCount Past Zero": { 1544 msg: "SET_COUNTER_COUNT games -1", 1545 want: "ERROR: could not set Counter games count to amount -1: rpc error: code = Unknown desc = out of range. Count must be within range [0,Capacity]. Found Count: -1, Capacity: 50\n", 1546 counterName: "games", 1547 wantCount: "COUNTER: 1\n", 1548 }, 1549 "GetCounterCapacity": { 1550 msg: "GET_COUNTER_CAPACITY games", 1551 want: "CAPACITY: 50\n", 1552 }, 1553 "GetCounterCapacity Counter Does Not Exist": { 1554 msg: "GET_COUNTER_CAPACITY dame", 1555 want: "ERROR: -1\n", 1556 }, 1557 "SetCounterCapacity": { 1558 msg: "SET_COUNTER_CAPACITY qux 0", 1559 want: "SUCCESS\n", 1560 counterName: "qux", 1561 wantCapacity: "CAPACITY: 0\n", 1562 }, 1563 "SetCounterCapacity Past Zero": { 1564 msg: "SET_COUNTER_CAPACITY games -42", 1565 want: "ERROR: could not set Counter games capacity to amount -42: rpc error: code = Unknown desc = out of range. Capacity must be greater than or equal to 0. Found Capacity: -42\n", 1566 counterName: "games", 1567 wantCount: "COUNTER: 1\n", 1568 }, 1569 } 1570 // nolint:dupl // Linter errors on lines are duplicate of TestLists 1571 for name, testCase := range testCases { 1572 t.Run(name, func(t *testing.T) { 1573 logrus.WithField("msg", testCase.msg).Info(name) 1574 reply, err := framework.SendGameServerUDP(t, gs, testCase.msg) 1575 require.NoError(t, err) 1576 assert.Equal(t, testCase.want, reply) 1577 1578 if testCase.wantCount != "" { 1579 msg := "GET_COUNTER_COUNT " + testCase.counterName 1580 logrus.WithField("msg", msg).Info("Sending GetCounterCount") 1581 reply, err = framework.SendGameServerUDP(t, gs, msg) 1582 require.NoError(t, err) 1583 assert.Equal(t, testCase.wantCount, reply) 1584 } 1585 1586 if testCase.wantCapacity != "" { 1587 msg := "GET_COUNTER_CAPACITY " + testCase.counterName 1588 logrus.WithField("msg", msg).Info("Sending GetCounterCapacity") 1589 reply, err = framework.SendGameServerUDP(t, gs, msg) 1590 require.NoError(t, err) 1591 assert.Equal(t, testCase.wantCapacity, reply) 1592 } 1593 }) 1594 } 1595 } 1596 1597 func TestLists(t *testing.T) { 1598 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 1599 t.SkipNow() 1600 } 1601 t.Parallel() 1602 ctx := context.Background() 1603 gs := framework.DefaultGameServer(framework.Namespace) 1604 1605 gs.Spec.Lists = make(map[string]agonesv1.ListStatus) 1606 gs.Spec.Lists["games"] = agonesv1.ListStatus{ 1607 Values: []string{"game1", "game2"}, 1608 Capacity: 50, 1609 } 1610 gs.Spec.Lists["foo"] = agonesv1.ListStatus{ 1611 Values: []string{}, 1612 Capacity: 1, 1613 } 1614 gs.Spec.Lists["bar"] = agonesv1.ListStatus{ 1615 Values: []string{"bar1", "bar2"}, 1616 Capacity: 10, 1617 } 1618 gs.Spec.Lists["baz"] = agonesv1.ListStatus{ 1619 Values: []string{"baz1"}, 1620 Capacity: 1, 1621 } 1622 gs.Spec.Lists["qux"] = agonesv1.ListStatus{ 1623 Values: []string{"qux1", "qux2", "qux3", "qux4"}, 1624 Capacity: 5, 1625 } 1626 1627 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1628 require.NoError(t, err) 1629 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, gs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 1630 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 1631 1632 testCases := map[string]struct { 1633 msg string 1634 want string 1635 listName string 1636 wantLength string 1637 wantCapacity string 1638 }{ 1639 "GetListCapacity": { 1640 msg: "GET_LIST_CAPACITY games", 1641 want: "CAPACITY: 50\n", 1642 }, 1643 "SetListCapacity": { 1644 msg: "SET_LIST_CAPACITY foo 1000", 1645 want: "SUCCESS\n", 1646 listName: "foo", 1647 wantCapacity: "CAPACITY: 1000\n", 1648 }, 1649 "SetListCapacity past 1000": { 1650 msg: "SET_LIST_CAPACITY games 1001", 1651 want: "ERROR: could not set List games capacity to amount 1001: rpc error: code = Unknown desc = out of range. Capacity must be within range [0,1000]. Found Capacity: 1001\n", 1652 listName: "games", 1653 wantCapacity: "CAPACITY: 50\n", 1654 }, 1655 "SetListCapacity negative": { 1656 msg: "SET_LIST_CAPACITY games -1", 1657 want: "ERROR: could not set List games capacity to amount -1: rpc error: code = Unknown desc = out of range. Capacity must be within range [0,1000]. Found Capacity: -1\n", 1658 listName: "games", 1659 wantCapacity: "CAPACITY: 50\n", 1660 }, 1661 "ListContains": { 1662 msg: "LIST_CONTAINS games game2", 1663 want: "FOUND: true\n", 1664 }, 1665 "ListContains false": { 1666 msg: "LIST_CONTAINS games game0", 1667 want: "FOUND: false\n", 1668 }, 1669 "GetListLength": { 1670 msg: "GET_LIST_LENGTH games", 1671 want: "LENGTH: 2\n", 1672 }, 1673 "GetListValues": { 1674 msg: "GET_LIST_VALUES games", 1675 want: "VALUES: game1,game2\n", 1676 }, 1677 "GetListValues empty": { 1678 msg: "GET_LIST_VALUES foo", 1679 want: "VALUES: <none>\n", 1680 }, 1681 "AppendListValue": { 1682 msg: "APPEND_LIST_VALUE bar bar3", 1683 want: "SUCCESS\n", 1684 listName: "bar", 1685 wantLength: "LENGTH: 3\n", 1686 }, 1687 "AppendListValue past capacity": { 1688 msg: "APPEND_LIST_VALUE baz baz2", 1689 want: "ERROR: could not get List baz: rpc error: code = Unknown desc = out of range. No available capacity. Current Capacity: 1, List Size: 1\n", 1690 listName: "baz", 1691 wantLength: "LENGTH: 1\n", 1692 }, 1693 "DeleteListValue": { 1694 msg: "DELETE_LIST_VALUE qux qux3", 1695 want: "SUCCESS\n", 1696 listName: "qux", 1697 wantLength: "LENGTH: 3\n", 1698 }, 1699 "DeleteListValue value does not exist": { 1700 msg: "DELETE_LIST_VALUE games game4", 1701 want: "ERROR: could not get List games: rpc error: code = Unknown desc = not found: value game4 not in list games\n", 1702 listName: "games", 1703 wantLength: "LENGTH: 2\n", 1704 }, 1705 } 1706 // nolint:dupl // Linter errors on lines are duplicate of TestCounters 1707 for name, testCase := range testCases { 1708 t.Run(name, func(t *testing.T) { 1709 logrus.WithField("msg", testCase.msg).Info(name) 1710 reply, err := framework.SendGameServerUDP(t, gs, testCase.msg) 1711 require.NoError(t, err) 1712 assert.Equal(t, testCase.want, reply) 1713 1714 if testCase.wantLength != "" { 1715 msg := "GET_LIST_LENGTH " + testCase.listName 1716 logrus.WithField("msg", msg).Info("Sending GetListLength") 1717 reply, err = framework.SendGameServerUDP(t, gs, msg) 1718 require.NoError(t, err) 1719 assert.Equal(t, testCase.wantLength, reply) 1720 } 1721 1722 if testCase.wantCapacity != "" { 1723 msg := "GET_LIST_CAPACITY " + testCase.listName 1724 logrus.WithField("msg", msg).Info("Sending GetListCapacity") 1725 reply, err = framework.SendGameServerUDP(t, gs, msg) 1726 require.NoError(t, err) 1727 assert.Equal(t, testCase.wantCapacity, reply) 1728 } 1729 }) 1730 } 1731 } 1732 1733 func TestSideCarCommunicatesWhileTerminating(t *testing.T) { 1734 t.Parallel() 1735 ctx := context.Background() 1736 gs := framework.DefaultGameServer(framework.Namespace) 1737 1738 minute := int64(60) 1739 gs.Spec.Template.Spec.Containers[0].Args = append(gs.Spec.Template.Spec.Containers[0].Args, "--gracefulTerminationDelaySec", "60") 1740 gs.Spec.Template.Spec.TerminationGracePeriodSeconds = &minute 1741 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1742 require.NoError(t, err) 1743 require.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 1744 1745 // delete the GameServer 1746 gameServers := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 1747 err = gameServers.Delete(context.Background(), readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) 1748 require.NoError(t, err, "Could not delete GameServer") 1749 1750 // wait for the deletion timestamp to be set 1751 err = wait.PollUntilContextTimeout(ctx, time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { 1752 gs, err := gameServers.Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 1753 if err != nil { 1754 return false, err 1755 } 1756 return gs.DeletionTimestamp != nil, nil 1757 }) 1758 require.NoError(t, err, "Could not get a GameServer with deletion timestamp") 1759 1760 // send a "GAMESERVER" message, and confirm it works 1761 reply, err := framework.SendGameServerUDP(t, readyGs, "GAMESERVER") 1762 require.NoError(t, err) 1763 require.Equal(t, fmt.Sprintf("NAME: %s\n", readyGs.ObjectMeta.Name), reply) 1764 } 1765 1766 func TestGracefulShutdown(t *testing.T) { 1767 // with the new sidecar pattern, there's no need for waiting on the Shutdown state. 1768 if runtime.FeatureEnabled(runtime.FeatureSidecarContainers) { 1769 t.SkipNow() 1770 } 1771 1772 t.Parallel() 1773 1774 log := e2eframework.TestLogger(t) 1775 ctx := context.Background() 1776 gs := framework.DefaultGameServer(framework.Namespace) 1777 var minute int64 = 60 1778 gs.Spec.Template.Spec.TerminationGracePeriodSeconds = &minute 1779 readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1780 if err != nil { 1781 t.Fatalf("Could not get a GameServer ready: %v", err) 1782 } 1783 assert.Equal(t, readyGs.Status.State, agonesv1.GameServerStateReady) 1784 gameservers := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace) 1785 err = gameservers.Delete(ctx, readyGs.ObjectMeta.Name, metav1.DeleteOptions{}) 1786 require.NoError(t, err) 1787 log.Info("Deleted GameServer, waiting 20 seconds...") 1788 time.Sleep(20 * time.Second) 1789 log.WithField("gs", gs).Info("Checking GameServer") 1790 gs, err = gameservers.Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 1791 require.NoError(t, err) 1792 assert.Equal(t, readyGs.ObjectMeta.Name, gs.ObjectMeta.Name) 1793 1794 // move it to shutdown 1795 gsCopy := gs.DeepCopy() 1796 gsCopy.Status.State = agonesv1.GameServerStateShutdown 1797 _, err = gameservers.Update(ctx, gsCopy, metav1.UpdateOptions{}) 1798 require.NoError(t, err) 1799 1800 start := time.Now() 1801 require.Eventually(t, func() bool { 1802 _, err := gameservers.Get(ctx, readyGs.ObjectMeta.Name, metav1.GetOptions{}) 1803 log.WithError(err).Info("checking GameServer") 1804 if err == nil { 1805 return false 1806 } 1807 return k8serrors.IsNotFound(err) 1808 }, 40*time.Second, time.Second) 1809 1810 diff := int(time.Since(start).Seconds()) 1811 log.WithField("diff", diff).Info("Time difference") 1812 require.Less(t, diff, 40) 1813 } 1814 1815 func TestGameServerSlowStart(t *testing.T) { 1816 t.Parallel() 1817 1818 // Inject an additional game server sidecar that forces a delayed start 1819 // to the main game server container following the pattern at 1820 // https://medium.com/@marko.luksa/delaying-application-start-until-sidecar-is-ready-2ec2d21a7b74 1821 gs := framework.DefaultGameServer(framework.Namespace) 1822 gs.Spec.Template.Spec.Containers = append( 1823 []corev1.Container{{ 1824 Name: "delay-game-server-start", 1825 Image: "alpine:latest", 1826 ImagePullPolicy: corev1.PullIfNotPresent, 1827 Command: []string{"sleep", "3600"}, 1828 Lifecycle: &corev1.Lifecycle{ 1829 PostStart: &corev1.LifecycleHandler{ 1830 Exec: &corev1.ExecAction{ 1831 Command: []string{"sleep", "60"}, 1832 }, 1833 }, 1834 }, 1835 Resources: corev1.ResourceRequirements{ 1836 Requests: corev1.ResourceList{ 1837 corev1.ResourceCPU: resource.MustParse("30m"), 1838 corev1.ResourceMemory: resource.MustParse("64Mi"), 1839 }, 1840 Limits: corev1.ResourceList{ 1841 corev1.ResourceCPU: resource.MustParse("30m"), 1842 corev1.ResourceMemory: resource.MustParse("64Mi"), 1843 }, 1844 }, 1845 }}, 1846 gs.Spec.Template.Spec.Containers...) 1847 1848 // Validate that a game server whose primary container starts slowly (a full minute 1849 // after the SDK starts) is capable of reaching Ready. Here we force the condition 1850 // with a lifecycle hook, but it imitates a slow image pull, or other container 1851 // start delays. 1852 _, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1853 assert.NoError(t, err) 1854 } 1855 1856 func TestGameServerPatch(t *testing.T) { 1857 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 1858 t.SkipNow() 1859 } 1860 t.Parallel() 1861 ctx := context.Background() 1862 1863 gs := framework.DefaultGameServer(framework.Namespace) 1864 gs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs) 1865 require.NoError(t, err) 1866 defer framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Delete(ctx, gs.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint: errcheck 1867 assert.Equal(t, agonesv1.GameServerStateReady, gs.Status.State) 1868 1869 // Create a gameserver to patch against 1870 gsCopy := gs.DeepCopy() 1871 gsCopy.ObjectMeta.Labels = map[string]string{"foo": "foo-value"} 1872 1873 patch, err := gs.Patch(gsCopy) 1874 require.NoError(t, err) 1875 patchString := string(patch) 1876 require.Contains(t, patchString, fmt.Sprintf("{\"op\":\"test\",\"path\":\"/metadata/resourceVersion\",\"value\":%q}", gs.ObjectMeta.ResourceVersion)) 1877 require.Contains(t, patchString, "{\"op\":\"add\",\"path\":\"/metadata/labels\",\"value\":{\"foo\":\"foo-value\"}}") 1878 1879 // Confirm patch is applied correctly 1880 patchedGs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Patch(ctx, gs.GetObjectMeta().GetName(), types.JSONPatchType, patch, metav1.PatchOptions{}) 1881 require.NoError(t, err) 1882 require.Equal(t, patchedGs.ObjectMeta.Labels, map[string]string{"foo": "foo-value"}) 1883 require.NotEqual(t, patchedGs.ObjectMeta.ResourceVersion, gs.ObjectMeta.ResourceVersion) 1884 1885 // Confirm a patch applied to an old version of a game server is not applied 1886 gsCopy.ObjectMeta.Labels = map[string]string{"bar": "bar-value"} 1887 patch, err = gs.Patch(gsCopy) 1888 require.NoError(t, err) 1889 1890 _, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Patch(ctx, gs.GetObjectMeta().GetName(), types.JSONPatchType, patch, metav1.PatchOptions{}) 1891 require.Error(t, err) 1892 1893 getGs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1894 require.NoError(t, err) 1895 require.Equal(t, getGs.ObjectMeta.Labels, map[string]string{"foo": "foo-value"}) 1896 require.Equal(t, getGs.ObjectMeta.ResourceVersion, patchedGs.ObjectMeta.ResourceVersion) 1897 1898 // Confirm patch goes through with the most up-to-date game server 1899 gsCopy = patchedGs.DeepCopy() 1900 gsCopy.ObjectMeta.Labels = map[string]string{"bar": "bar-value"} 1901 patch, err = patchedGs.Patch(gsCopy) 1902 require.NoError(t, err) 1903 1904 rePatchedGs, err := framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Patch(ctx, gs.GetObjectMeta().GetName(), types.JSONPatchType, patch, metav1.PatchOptions{}) 1905 require.NoError(t, err) 1906 require.Equal(t, rePatchedGs.ObjectMeta.Labels, map[string]string{"bar": "bar-value"}) 1907 1908 getGs, err = framework.AgonesClient.AgonesV1().GameServers(framework.Namespace).Get(ctx, gs.ObjectMeta.Name, metav1.GetOptions{}) 1909 require.NoError(t, err) 1910 require.Equal(t, getGs.ObjectMeta.Labels, map[string]string{"bar": "bar-value"}) 1911 require.Equal(t, getGs.ObjectMeta.ResourceVersion, rePatchedGs.ObjectMeta.ResourceVersion) 1912 }