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