agones.dev/agones@v1.54.0/pkg/cloudproduct/gke/gke_test.go (about) 1 // Copyright 2022 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 package gke 15 16 import ( 17 "fmt" 18 "testing" 19 20 "agones.dev/agones/pkg/apis" 21 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 22 "agones.dev/agones/pkg/util/runtime" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/validation/field" 28 ) 29 30 func TestSyncPodPortsToGameServer(t *testing.T) { 31 assignmentAnnotation := map[string]string{hostPortAssignmentAnnotation: `{"min":7000,"max":8000,"portsAssigned":{"7001":7737,"7002":7738}}`} 32 badAnnotation := map[string]string{hostPortAssignmentAnnotation: `good luck parsing this as JSON`} 33 for name, tc := range map[string]struct { 34 gs *agonesv1.GameServer 35 pod *corev1.Pod 36 wantGS *agonesv1.GameServer 37 wantErr bool 38 }{ 39 "no ports => no change": { 40 gs: &agonesv1.GameServer{}, 41 pod: testPod(nil), 42 wantGS: &agonesv1.GameServer{}, 43 }, 44 "no annotation => no change": { 45 gs: testGameServer([]int32{7777}, nil), 46 pod: testPod(nil), 47 wantGS: testGameServer([]int32{7777}, nil), 48 }, 49 "annotation => ports mapped": { 50 gs: testGameServer([]int32{7002, 7001, 7002}, nil), 51 pod: testPod(assignmentAnnotation), 52 wantGS: testGameServer([]int32{7738, 7737, 7738}, nil), 53 }, 54 "annotation, but ports already assigned => ports mapped": { 55 gs: testGameServer([]int32{7001, 7002}, []int32{7001, 7002}), 56 pod: testPod(assignmentAnnotation), 57 wantGS: testGameServer([]int32{7001, 7002}, []int32{7001, 7002}), 58 }, 59 "bad annotation": { 60 gs: testGameServer([]int32{7002, 7001, 7002}, nil), 61 pod: testPod(badAnnotation), 62 wantErr: true, 63 }, 64 } { 65 t.Run(name, func(t *testing.T) { 66 oldPod := tc.pod.DeepCopy() 67 err := (&gkeAutopilot{}).SyncPodPortsToGameServer(tc.gs, tc.pod) 68 if tc.wantErr { 69 assert.NotNil(t, err) 70 return 71 } 72 if assert.NoError(t, err) { 73 require.Equal(t, tc.wantGS, tc.gs) 74 require.Equal(t, oldPod, tc.pod) 75 } 76 }) 77 } 78 } 79 80 func TestValidateGameServer(t *testing.T) { 81 for name, tc := range map[string]struct { 82 edPods bool 83 ports []agonesv1.GameServerPort 84 scheduling apis.SchedulingStrategy 85 safeToEvict agonesv1.EvictionSafe 86 want field.ErrorList 87 portPolicyNoneFlag string 88 }{ 89 "no ports => validated": {scheduling: apis.Packed}, 90 "good ports => validated": { 91 ports: []agonesv1.GameServerPort{ 92 { 93 Name: "some-tcpudp", 94 PortPolicy: agonesv1.Dynamic, 95 Range: agonesv1.DefaultPortRange, 96 ContainerPort: 4321, 97 Protocol: agonesv1.ProtocolTCPUDP, 98 }, 99 { 100 Name: "awesome-udp", 101 PortPolicy: agonesv1.Dynamic, 102 Range: agonesv1.DefaultPortRange, 103 ContainerPort: 1234, 104 Protocol: corev1.ProtocolUDP, 105 }, 106 { 107 Name: "awesome-tcp", 108 PortPolicy: agonesv1.Dynamic, 109 Range: agonesv1.DefaultPortRange, 110 ContainerPort: 1234, 111 Protocol: corev1.ProtocolTCP, 112 }, 113 { 114 Name: "none-udp", 115 PortPolicy: agonesv1.None, 116 ContainerPort: 1234, 117 Protocol: corev1.ProtocolUDP, 118 }, 119 { 120 Name: "passthrough-udp", 121 PortPolicy: agonesv1.Passthrough, 122 Range: agonesv1.DefaultPortRange, 123 ContainerPort: 1234, 124 Protocol: corev1.ProtocolUDP, 125 }, 126 { 127 Name: "passthrough-tcp", 128 PortPolicy: agonesv1.Passthrough, 129 Range: agonesv1.DefaultPortRange, 130 ContainerPort: 1234, 131 Protocol: corev1.ProtocolTCP, 132 }, 133 }, 134 safeToEvict: agonesv1.EvictionSafeAlways, 135 scheduling: apis.Packed, 136 }, 137 "bad port range => fails validation": { 138 ports: []agonesv1.GameServerPort{ 139 { 140 Name: "best-tcpudp", 141 PortPolicy: agonesv1.Dynamic, 142 Range: agonesv1.DefaultPortRange, 143 ContainerPort: 4321, 144 Protocol: agonesv1.ProtocolTCPUDP, 145 }, 146 { 147 Name: "bad-range", 148 PortPolicy: agonesv1.Dynamic, 149 Range: "game", 150 ContainerPort: 1234, 151 Protocol: corev1.ProtocolUDP, 152 }, 153 { 154 Name: "another-bad-range", 155 PortPolicy: agonesv1.Dynamic, 156 Range: "game", 157 ContainerPort: 1234, 158 Protocol: corev1.ProtocolUDP, 159 }, 160 { 161 Name: "passthrough-udp-bad-range", 162 PortPolicy: agonesv1.Passthrough, 163 Range: "passthrough", 164 ContainerPort: 1234, 165 Protocol: corev1.ProtocolUDP, 166 }, 167 { 168 Name: "passthrough-tcp-bad-range", 169 PortPolicy: agonesv1.Passthrough, 170 Range: "games", 171 ContainerPort: 1234, 172 Protocol: corev1.ProtocolTCP, 173 }, 174 }, 175 safeToEvict: agonesv1.EvictionSafeAlways, 176 scheduling: apis.Packed, 177 want: field.ErrorList{ 178 field.Invalid(field.NewPath("spec", "ports").Index(1).Child("range"), "game", "range must not be used on GKE Autopilot"), 179 field.Invalid(field.NewPath("spec", "ports").Index(2).Child("range"), "game", "range must not be used on GKE Autopilot"), 180 field.Invalid(field.NewPath("spec", "ports").Index(3).Child("range"), "passthrough", "range must not be used on GKE Autopilot"), 181 field.Invalid(field.NewPath("spec", "ports").Index(4).Child("range"), "games", "range must not be used on GKE Autopilot"), 182 }, 183 }, 184 "bad policy (no feature gates) => fails validation": { 185 ports: []agonesv1.GameServerPort{ 186 { 187 Name: "best-tcpudp", 188 PortPolicy: agonesv1.Dynamic, 189 Range: agonesv1.DefaultPortRange, 190 ContainerPort: 4321, 191 Protocol: agonesv1.ProtocolTCPUDP, 192 }, 193 { 194 Name: "bad-udp", 195 PortPolicy: agonesv1.Static, 196 Range: agonesv1.DefaultPortRange, 197 ContainerPort: 1234, 198 Protocol: corev1.ProtocolUDP, 199 }, 200 { 201 Name: "another-bad-udp", 202 PortPolicy: agonesv1.Static, 203 Range: agonesv1.DefaultPortRange, 204 ContainerPort: 1234, 205 Protocol: corev1.ProtocolUDP, 206 }, 207 }, 208 safeToEvict: agonesv1.EvictionSafeOnUpgrade, 209 scheduling: apis.Distributed, 210 want: field.ErrorList{ 211 field.Invalid(field.NewPath("spec", "scheduling"), "Distributed", "scheduling strategy must be Packed on GKE Autopilot"), 212 field.Invalid(field.NewPath("spec", "ports").Index(1).Child("portPolicy"), agonesv1.Static, "portPolicy must be Dynamic, Passthrough, or None on GKE Autopilot"), 213 field.Invalid(field.NewPath("spec", "ports").Index(2).Child("portPolicy"), agonesv1.Static, "portPolicy must be Dynamic, Passthrough, or None on GKE Autopilot"), 214 field.Invalid(field.NewPath("spec", "eviction", "safe"), "OnUpgrade", "eviction.safe OnUpgrade not supported on GKE Autopilot"), 215 }, 216 }, 217 "bad policy (GKEAutopilotExtendedDurationPods enabled) => fails validation but OnUpgrade works": { 218 edPods: true, 219 ports: []agonesv1.GameServerPort{ 220 { 221 Name: "best-tcpudp", 222 PortPolicy: agonesv1.Dynamic, 223 Range: agonesv1.DefaultPortRange, 224 ContainerPort: 4321, 225 Protocol: agonesv1.ProtocolTCPUDP, 226 }, 227 { 228 Name: "bad-udp", 229 PortPolicy: agonesv1.Static, 230 Range: agonesv1.DefaultPortRange, 231 ContainerPort: 1234, 232 Protocol: corev1.ProtocolUDP, 233 }, 234 { 235 Name: "another-bad-udp", 236 PortPolicy: agonesv1.Static, 237 Range: agonesv1.DefaultPortRange, 238 ContainerPort: 1234, 239 Protocol: corev1.ProtocolUDP, 240 }, 241 { 242 Name: "passthrough-udp", 243 PortPolicy: agonesv1.Passthrough, 244 Range: agonesv1.DefaultPortRange, 245 ContainerPort: 1234, 246 Protocol: corev1.ProtocolUDP, 247 }, 248 }, 249 safeToEvict: agonesv1.EvictionSafeOnUpgrade, 250 scheduling: apis.Distributed, 251 want: field.ErrorList{ 252 field.Invalid(field.NewPath("spec", "scheduling"), "Distributed", "scheduling strategy must be Packed on GKE Autopilot"), 253 field.Invalid(field.NewPath("spec", "ports").Index(1).Child("portPolicy"), agonesv1.Static, "portPolicy must be Dynamic, Passthrough, or None on GKE Autopilot"), 254 field.Invalid(field.NewPath("spec", "ports").Index(2).Child("portPolicy"), agonesv1.Static, "portPolicy must be Dynamic, Passthrough, or None on GKE Autopilot"), 255 }, 256 }, 257 "port policy none with feature disabled => fails validation": { 258 portPolicyNoneFlag: "false", 259 ports: []agonesv1.GameServerPort{ 260 { 261 Name: "none-gate-turned-off", 262 PortPolicy: agonesv1.None, 263 Range: agonesv1.DefaultPortRange, 264 ContainerPort: 1234, 265 Protocol: corev1.ProtocolUDP, 266 }, 267 }, 268 scheduling: apis.Packed, 269 want: field.ErrorList{ 270 field.Invalid(field.NewPath("spec", "ports").Index(0).Child("portPolicy"), agonesv1.None, "PortPolicy 'None' is not enabled"), 271 }, 272 }, 273 } { 274 t.Run(name, func(t *testing.T) { 275 // PortPolicy None is behind a feature flag 276 runtime.FeatureTestMutex.Lock() 277 defer runtime.FeatureTestMutex.Unlock() 278 if tc.portPolicyNoneFlag == "" { 279 tc.portPolicyNoneFlag = "true" 280 } 281 require.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=%s", runtime.FeaturePortPolicyNone, tc.portPolicyNoneFlag))) 282 causes := (&gkeAutopilot{useExtendedDurationPods: tc.edPods}).ValidateGameServerSpec(&agonesv1.GameServerSpec{ 283 Ports: tc.ports, 284 Scheduling: tc.scheduling, 285 Eviction: &agonesv1.Eviction{Safe: tc.safeToEvict}, 286 }, field.NewPath("spec")) 287 require.Equal(t, tc.want, causes) 288 }) 289 } 290 } 291 292 func TestPodSeccompUnconfined(t *testing.T) { 293 for name, tc := range map[string]struct { 294 podSpec *corev1.PodSpec 295 wantPodSpec *corev1.PodSpec 296 }{ 297 "no context defined": { 298 podSpec: &corev1.PodSpec{}, 299 wantPodSpec: &corev1.PodSpec{ 300 SecurityContext: &corev1.PodSecurityContext{ 301 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeUnconfined}, 302 }, 303 }, 304 }, 305 "security context set, no seccomp set": { 306 podSpec: &corev1.PodSpec{SecurityContext: &corev1.PodSecurityContext{}}, 307 wantPodSpec: &corev1.PodSpec{ 308 SecurityContext: &corev1.PodSecurityContext{ 309 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeUnconfined}, 310 }, 311 }, 312 }, 313 "seccomp already set": { 314 podSpec: &corev1.PodSpec{ 315 SecurityContext: &corev1.PodSecurityContext{ 316 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, 317 }, 318 }, 319 wantPodSpec: &corev1.PodSpec{ 320 SecurityContext: &corev1.PodSecurityContext{ 321 SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, 322 }, 323 }, 324 }, 325 } { 326 t.Run(name, func(t *testing.T) { 327 podSpec := tc.podSpec.DeepCopy() 328 podSpecSeccompUnconfined(podSpec) 329 assert.Equal(t, tc.wantPodSpec, podSpec) 330 }) 331 } 332 } 333 334 func TestSetPassthroughLabel(t *testing.T) { 335 for name, tc := range map[string]struct { 336 pod *corev1.Pod 337 wantPod *corev1.Pod 338 ports []agonesv1.GameServerPort 339 features string 340 }{ 341 "gameserver with with Passthrough port policy adds label to pod": { 342 features: "", 343 344 pod: &corev1.Pod{ 345 ObjectMeta: metav1.ObjectMeta{ 346 Annotations: map[string]string{}, 347 Labels: map[string]string{}, 348 }, 349 }, 350 ports: []agonesv1.GameServerPort{ 351 { 352 Name: "awesome-udp", 353 PortPolicy: agonesv1.Passthrough, 354 ContainerPort: 1234, 355 Protocol: corev1.ProtocolUDP, 356 }, 357 }, 358 wantPod: &corev1.Pod{ 359 ObjectMeta: metav1.ObjectMeta{ 360 Annotations: map[string]string{}, 361 Labels: map[string]string{ 362 agonesv1.GameServerPortPolicyPodLabel: "autopilot-passthrough", 363 }, 364 }, 365 }, 366 }, 367 "gameserver with Static port policy does not add label to pod": { 368 features: "", 369 370 pod: &corev1.Pod{ 371 ObjectMeta: metav1.ObjectMeta{ 372 Annotations: map[string]string{}, 373 Labels: map[string]string{}, 374 }, 375 }, 376 ports: []agonesv1.GameServerPort{ 377 { 378 Name: "awesome-udp", 379 PortPolicy: agonesv1.Static, 380 ContainerPort: 1234, 381 Protocol: corev1.ProtocolUDP, 382 }, 383 }, 384 wantPod: &corev1.Pod{ 385 ObjectMeta: metav1.ObjectMeta{ 386 Annotations: map[string]string{}, 387 Labels: map[string]string{}, 388 }, 389 }, 390 }, 391 } { 392 t.Run(name, func(t *testing.T) { 393 runtime.FeatureTestMutex.Lock() 394 defer runtime.FeatureTestMutex.Unlock() 395 require.NoError(t, runtime.ParseFeatures(tc.features)) 396 gs := (&autopilotPortAllocator{minPort: 7000, maxPort: 8000}).Allocate(&agonesv1.GameServer{Spec: agonesv1.GameServerSpec{Ports: tc.ports}}) 397 pod := tc.pod.DeepCopy() 398 setPassthroughLabel(&gs.Spec, pod) 399 assert.Equal(t, tc.wantPod, pod) 400 }) 401 } 402 } 403 404 func TestSetEvictionNoExtended(t *testing.T) { 405 emptyPodAnd := func(f func(*corev1.Pod)) *corev1.Pod { 406 pod := &corev1.Pod{ 407 ObjectMeta: metav1.ObjectMeta{ 408 Annotations: map[string]string{}, 409 Labels: map[string]string{}, 410 }, 411 } 412 f(pod) 413 return pod 414 } 415 for desc, tc := range map[string]struct { 416 eviction *agonesv1.Eviction 417 pod *corev1.Pod 418 wantPod *corev1.Pod 419 wantErr bool 420 }{ 421 "eviction: safe: Always, no incoming labels/annotations": { 422 eviction: &agonesv1.Eviction{Safe: agonesv1.EvictionSafeAlways}, 423 pod: emptyPodAnd(func(*corev1.Pod) {}), 424 wantPod: emptyPodAnd(func(pod *corev1.Pod) { 425 pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = agonesv1.True 426 }), 427 }, 428 "eviction: safe: Never, no incoming labels/annotations": { 429 eviction: &agonesv1.Eviction{Safe: agonesv1.EvictionSafeNever}, 430 pod: emptyPodAnd(func(*corev1.Pod) {}), 431 wantPod: emptyPodAnd(func(pod *corev1.Pod) { 432 pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = agonesv1.False 433 }), 434 }, 435 "eviction: safe: OnUpgrade => error": { 436 eviction: &agonesv1.Eviction{Safe: agonesv1.EvictionSafeOnUpgrade}, 437 pod: emptyPodAnd(func(*corev1.Pod) {}), 438 wantErr: true, 439 }, 440 "eviction: safe: Always, incoming labels/annotations": { 441 eviction: &agonesv1.Eviction{Safe: agonesv1.EvictionSafeAlways}, 442 pod: emptyPodAnd(func(pod *corev1.Pod) { 443 pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = "just don't touch, ok?" 444 pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = "seriously, leave it" 445 }), 446 wantPod: emptyPodAnd(func(pod *corev1.Pod) { 447 pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = "just don't touch, ok?" 448 pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = "seriously, leave it" 449 }), 450 }, 451 "eviction: safe: Never, incoming labels/annotations": { 452 eviction: &agonesv1.Eviction{Safe: agonesv1.EvictionSafeNever}, 453 pod: emptyPodAnd(func(pod *corev1.Pod) { 454 pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = "a passthrough" 455 pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = "or is it passthru?" 456 }), 457 wantPod: emptyPodAnd(func(pod *corev1.Pod) { 458 pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = "a passthrough" 459 pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = "or is it passthru?" 460 }), 461 }, 462 "eviction: safe: Never, but safe-to-evict pod annotation set to false": { 463 eviction: &agonesv1.Eviction{Safe: agonesv1.EvictionSafeNever}, 464 pod: emptyPodAnd(func(pod *corev1.Pod) { 465 pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = agonesv1.False 466 }), 467 wantPod: emptyPodAnd(func(pod *corev1.Pod) { 468 pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = agonesv1.False 469 }), 470 }, 471 } { 472 t.Run(desc, func(t *testing.T) { 473 err := setEvictionNoExtended(tc.eviction, tc.pod) 474 if tc.wantErr { 475 assert.Error(t, err) 476 return 477 } 478 assert.NoError(t, err) 479 assert.Equal(t, tc.wantPod, tc.pod) 480 }) 481 } 482 } 483 484 func TestAutopilotPortAllocator(t *testing.T) { 485 for name, tc := range map[string]struct { 486 ports []agonesv1.GameServerPort 487 wantPorts []agonesv1.GameServerPort 488 passthroughFlag string 489 wantAnnotation bool 490 }{ 491 "no ports => no change": {passthroughFlag: "false"}, 492 "ports => assigned and annotated": { 493 passthroughFlag: "true", 494 ports: []agonesv1.GameServerPort{ 495 { 496 Name: "some-tcpudp", 497 PortPolicy: agonesv1.Dynamic, 498 ContainerPort: 4321, 499 Protocol: agonesv1.ProtocolTCPUDP, 500 }, 501 { 502 Name: "awesome-udp", 503 PortPolicy: agonesv1.Dynamic, 504 ContainerPort: 1234, 505 Protocol: corev1.ProtocolUDP, 506 }, 507 { 508 Name: "awesome-tcp", 509 PortPolicy: agonesv1.Dynamic, 510 ContainerPort: 1234, 511 Protocol: corev1.ProtocolTCP, 512 }, 513 { 514 Name: "another-tcpudp", 515 PortPolicy: agonesv1.Dynamic, 516 ContainerPort: 5678, 517 Protocol: agonesv1.ProtocolTCPUDP, 518 }, 519 { 520 Name: "passthrough-tcp", 521 PortPolicy: agonesv1.Passthrough, 522 Range: agonesv1.DefaultPortRange, 523 ContainerPort: 1234, 524 Protocol: corev1.ProtocolTCP, 525 }, 526 { 527 Name: "passthrough-tcpudp", 528 PortPolicy: agonesv1.Passthrough, 529 ContainerPort: 5678, 530 Protocol: agonesv1.ProtocolTCPUDP, 531 }, 532 }, 533 wantPorts: []agonesv1.GameServerPort{ 534 { 535 Name: "some-tcpudp-tcp", 536 PortPolicy: agonesv1.Dynamic, 537 ContainerPort: 4321, 538 HostPort: 1, 539 Protocol: corev1.ProtocolTCP, 540 }, 541 { 542 Name: "some-tcpudp-udp", 543 PortPolicy: agonesv1.Dynamic, 544 ContainerPort: 4321, 545 HostPort: 1, 546 Protocol: corev1.ProtocolUDP, 547 }, 548 { 549 Name: "awesome-udp", 550 PortPolicy: agonesv1.Dynamic, 551 ContainerPort: 1234, 552 HostPort: 2, 553 Protocol: corev1.ProtocolUDP, 554 }, 555 { 556 Name: "awesome-tcp", 557 PortPolicy: agonesv1.Dynamic, 558 ContainerPort: 1234, 559 HostPort: 3, 560 Protocol: corev1.ProtocolTCP, 561 }, 562 { 563 Name: "another-tcpudp-tcp", 564 PortPolicy: agonesv1.Dynamic, 565 ContainerPort: 5678, 566 HostPort: 4, 567 Protocol: corev1.ProtocolTCP, 568 }, 569 { 570 Name: "another-tcpudp-udp", 571 PortPolicy: agonesv1.Dynamic, 572 ContainerPort: 5678, 573 HostPort: 4, 574 Protocol: corev1.ProtocolUDP, 575 }, 576 { 577 Name: "passthrough-tcp", 578 PortPolicy: agonesv1.Passthrough, 579 Range: agonesv1.DefaultPortRange, 580 ContainerPort: 1234, 581 HostPort: 5, 582 Protocol: corev1.ProtocolTCP, 583 }, 584 { 585 Name: "passthrough-tcpudp-tcp", 586 PortPolicy: agonesv1.Passthrough, 587 ContainerPort: 5678, 588 HostPort: 6, 589 Protocol: corev1.ProtocolTCP, 590 }, 591 { 592 Name: "passthrough-tcpudp-udp", 593 PortPolicy: agonesv1.Passthrough, 594 ContainerPort: 5678, 595 HostPort: 6, 596 Protocol: corev1.ProtocolUDP, 597 }, 598 }, 599 wantAnnotation: true, 600 }, 601 "bad policy => no change (should be rejected by webhooks previously)": { 602 passthroughFlag: "false", 603 ports: []agonesv1.GameServerPort{ 604 { 605 Name: "awesome-udp", 606 PortPolicy: agonesv1.Static, 607 ContainerPort: 1234, 608 Protocol: corev1.ProtocolUDP, 609 }, 610 { 611 Name: "awesome-none-udp", 612 PortPolicy: agonesv1.None, 613 ContainerPort: 1234, 614 Protocol: corev1.ProtocolUDP, 615 }, 616 { 617 Name: "passthrough-tcp", 618 PortPolicy: agonesv1.Passthrough, 619 ContainerPort: 1234, 620 Protocol: corev1.ProtocolTCP, 621 }, 622 }, 623 wantPorts: []agonesv1.GameServerPort{ 624 { 625 Name: "awesome-udp", 626 PortPolicy: agonesv1.Static, 627 ContainerPort: 1234, 628 Protocol: corev1.ProtocolUDP, 629 }, 630 { 631 Name: "awesome-none-udp", 632 PortPolicy: agonesv1.None, 633 ContainerPort: 1234, 634 Protocol: corev1.ProtocolUDP, 635 }, 636 { 637 Name: "passthrough-tcp", 638 PortPolicy: agonesv1.Passthrough, 639 ContainerPort: 1234, 640 Protocol: corev1.ProtocolTCP, 641 }, 642 }, 643 }, 644 } { 645 t.Run(name, func(t *testing.T) { 646 // PortPolicy None is behind a feature flag 647 runtime.FeatureTestMutex.Lock() 648 defer runtime.FeatureTestMutex.Unlock() 649 gs := (&autopilotPortAllocator{minPort: 8000, maxPort: 9000}).Allocate(&agonesv1.GameServer{Spec: agonesv1.GameServerSpec{Ports: tc.ports}}) 650 wantGS := &agonesv1.GameServer{Spec: agonesv1.GameServerSpec{Ports: tc.wantPorts}} 651 if tc.wantAnnotation { 652 wantGS.Spec.Template = corev1.PodTemplateSpec{ 653 ObjectMeta: metav1.ObjectMeta{ 654 Annotations: map[string]string{"autopilot.gke.io/host-port-assignment": `{"min":8000,"max":9000}`}, 655 }, 656 } 657 } 658 require.Equal(t, wantGS, gs) 659 }) 660 } 661 } 662 663 func testPod(annotations map[string]string) *corev1.Pod { 664 return &corev1.Pod{ 665 ObjectMeta: metav1.ObjectMeta{ 666 Name: "best-game-server", 667 Namespace: "best-game", 668 Annotations: annotations, 669 }, 670 TypeMeta: metav1.TypeMeta{Kind: "Pod"}, 671 } 672 } 673 674 func testGameServer(portSpecIn []int32, portStatusIn []int32) *agonesv1.GameServer { 675 var portSpec []agonesv1.GameServerPort 676 for _, port := range portSpecIn { 677 portSpec = append(portSpec, agonesv1.GameServerPort{HostPort: port}) 678 } 679 var portStatus []agonesv1.GameServerStatusPort 680 for _, port := range portStatusIn { 681 portStatus = append(portStatus, agonesv1.GameServerStatusPort{Port: port}) 682 } 683 return &agonesv1.GameServer{ 684 ObjectMeta: metav1.ObjectMeta{ 685 Name: "best-game-server", 686 Namespace: "best-game", 687 }, 688 Spec: agonesv1.GameServerSpec{ 689 Ports: portSpec, 690 }, 691 Status: agonesv1.GameServerStatus{ 692 Ports: portStatus, 693 }, 694 } 695 }