Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for internal game server ports #1576

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,20 @@ properties:
portPolicy:
title: the port policy that will be applied to the game server
description: |
portPolicy has three options:
portPolicy has four options:
- "Dynamic" (default) the system allocates a random free hostPort for the gameserver, for game clients to connect to
- "Static", user defines the hostPort that the game client will connect to. Then onus is on the user to ensure that the
port is available. When static is the policy specified, `hostPort` is required to be populated
- "Passthrough" dynamically sets the `containerPort` to the same value as the dynamically selected hostPort.
This will mean that users will need to lookup what port has been opened through the server side SDK.
- "Internal" means that the system won't allocate a hostPort for this port. The onus is on the user to find a way
to connect players to that port, whether it be through a custom proxy or otherwise.
type: string
enum:
- Dynamic
- Static
- Passthrough
- Internal
protocol:
title: Protocol being used. Defaults to UDP. TCP is the only other option
type: string
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/agones/v1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
// Block of const Error messages
const (
ErrContainerRequired = "Container is required when using multiple containers in the pod template"
ErrHostPort = "HostPort cannot be specified with a Dynamic or Passthrough PortPolicy"
ErrHostPort = "HostPort cannot be specified with a Dynamic, Internal or Passthrough PortPolicy"
ErrPortPolicyStatic = "PortPolicy must be Static"
ErrContainerPortRequired = "ContainerPort must be defined for Dynamic and Static PortPolicies"
ErrContainerPortPassthrough = "ContainerPort cannot be specified with Passthrough PortPolicy"
Expand Down
7 changes: 5 additions & 2 deletions pkg/apis/agones/v1/gameserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ const (
// Passthrough dynamically sets the `containerPort` to the same value as the dynamically selected hostPort.
// This will mean that users will need to lookup what port has been opened through the server side SDK.
Passthrough PortPolicy = "Passthrough"
// Internal PortPolicy means that the system will never allocate a hostPort to the container, expecting users
// to use their own internal load balancer/proxy solution to direct players to the container
Internal PortPolicy = "Internal"

// SdkServerLogLevelInfo will cause the SDK server to output all messages except for debug messages.
SdkServerLogLevelInfo SdkServerLogLevel = "Info"
Expand Down Expand Up @@ -424,7 +427,7 @@ func (gss *GameServerSpec) Validate(devAddress string) ([]metav1.StatusCause, bo

// no host port when using dynamic PortPolicy
for _, p := range gss.Ports {
if p.PortPolicy == Dynamic || p.PortPolicy == Static {
if p.PortPolicy == Dynamic || p.PortPolicy == Static || p.PortPolicy == Internal { //nolint: gocyclo
if p.ContainerPort <= 0 {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Expand All @@ -442,7 +445,7 @@ func (gss *GameServerSpec) Validate(devAddress string) ([]metav1.StatusCause, bo
})
}

if p.HostPort > 0 && (p.PortPolicy == Dynamic || p.PortPolicy == Passthrough) {
if p.HostPort > 0 && (p.PortPolicy == Dynamic || p.PortPolicy == Passthrough || p.PortPolicy == Internal) { //nolint: gocyclo
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Field: fmt.Sprintf("%s.hostPort", p.Name),
Expand Down
3 changes: 3 additions & 0 deletions pkg/gameservers/portallocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ func (pa *PortAllocator) Allocate(gs *agonesv1.GameServer) *agonesv1.GameServer
// Also the return gives an escape from the double loop
findOpenPorts := func(amount int) []pn {
var ports []pn
if amount == 0 {
return ports
}
for _, n := range pa.portAllocations {
for p, taken := range n {
if !taken {
Expand Down
9 changes: 9 additions & 0 deletions pkg/gameservers/portallocator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ func TestPortAllocatorAllocate(t *testing.T) {
assert.Equal(t, gsCopy.Spec.Ports[0].HostPort, gsCopy.Spec.Ports[0].ContainerPort)
assert.Nil(t, err)
assert.Equal(t, 11, countTotalAllocatedPorts(pa))

// single port, internal
gsCopy = fixture.DeepCopy()
gsCopy.Spec.Ports[0] = agonesv1.GameServerPort{Name: "internal", PortPolicy: agonesv1.Internal}
assert.Len(t, gsCopy.Spec.Ports, 1)
pa.Allocate(gsCopy)
assert.Empty(t, gsCopy.Spec.Ports[0].HostPort)
assert.Nil(t, err)
assert.Equal(t, 11, countTotalAllocatedPorts(pa))
})

t.Run("ports are all allocated", func(t *testing.T) {
Expand Down
5 changes: 4 additions & 1 deletion site/content/en/docs/Reference/gameserver.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,14 @@ spec:
ports:
# name is a descriptive name for the port
- name: default
# portPolicy has three options:
# portPolicy has four options:
# - "Dynamic" (default) the system allocates a free hostPort for the gameserver, for game clients to connect to
# - "Static", user defines the hostPort that the game client will connect to. Then onus is on the user to ensure that the
# port is available. When static is the policy specified, `hostPort` is required to be populated
# - "Passthrough" dynamically sets the `containerPort` to the same value as the dynamically selected hostPort.
# This will mean that users will need to lookup what port has been opened through the server side SDK.
# - "Internal" means that the system won't allocate a hostPort for this port. The onus is on the user to find a way
# to connect players to that port, whether it be through a custom proxy or otherwise.
portPolicy: Static
# (Alpha) the name of the container to open the port on. Defaults to the game server container if omitted or empty.
container: simple-udp
Expand Down Expand Up @@ -201,6 +203,7 @@ The `spec` field is the actual GameServer specification and it is composed as fo
- `Dynamic` (default) the system allocates a random free hostPort for the gameserver, for game clients to connect to.
- `Static`, user defines the hostPort that the game client will connect to. Then onus is on the user to ensure that the port is available. When static is the policy specified, `hostPort` is required to be populated.
- `Passthrough` dynamically sets the `containerPort` to the same value as the dynamically selected hostPort. This will mean that users will need to lookup what port to open through the server side SDK before starting communications.
- `Internal` means that the system wont allocate a hostPort for this port. The onus is on the user to find a way to connect players to that port, whether it be through a custom proxy or otherwise.
- `container` (Alpha) the name of the container to open the port on. Defaults to the game server container if omitted or empty.
- `containerPort` the port that is being opened on the game server process, this is a required field for `Dynamic` and `Static` port policies, and should not be included in <code>Passthrough</code> configuration.
- `protocol` the protocol being used. Defaults to UDP. TCP is the only other option.
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/gameserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,27 @@ func TestGameServerPassthroughPort(t *testing.T) {
assert.Equal(t, "ACK: Hello World !\n", reply)
}

func TestGameServerInternalPort(t *testing.T) {
t.Parallel()
gs := framework.DefaultGameServer(defaultNs)
gs.Spec.Ports[0] = agonesv1.GameServerPort{PortPolicy: agonesv1.Internal, ContainerPort: 999}
gs.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{Name: "INTERNAL", Value: "TRUE"}}

_, valid := gs.Validate()
assert.True(t, valid)

readyGs, err := framework.CreateGameServerAndWaitUntilReady(defaultNs, gs)
if !assert.NoError(t, err) {
assert.FailNow(t, "Could not get a GameServer ready")
}

port := readyGs.Spec.Ports[0]
assert.Equal(t, agonesv1.Internal, port.PortPolicy)
assert.NotEmpty(t, port.HostPort)

// TODO How to test this? Try pinging server externally and check for fail?
}

// TestGameServerResourceValidation - check that we are not able to use
// invalid PodTemplate for GameServer Spec with wrong Resource Requests and Limits
func TestGameServerResourceValidation(t *testing.T) {
Expand Down