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

Passthrough autopilot - added ports array case and updated unit tests #3842

Merged
21 changes: 19 additions & 2 deletions pkg/apis/agones/v1/gameserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ const (
// NodePodIP identifies an IP address from a pod.
NodePodIP corev1.NodeAddressType = "PodIP"

// PassthroughPortAssignmentAnnotation is an annotation to keep track of game server container and its Passthrough ports indices
PassthroughPortAssignmentAnnotation = "autopilot.gke.io/container-passthrough-port-assignment"
vicentefb marked this conversation as resolved.
Show resolved Hide resolved

// True is the string "true" to appease the goconst lint.
True = "true"
// False is the string "false" to appease the goconst lint.
Expand Down Expand Up @@ -745,9 +748,10 @@ func (gs *GameServer) Pod(apiHooks APIHooks, sidecars ...corev1.Container) (*cor
}

gs.podObjectMeta(pod)
for _, p := range gs.Spec.Ports {
var hostPort int32

passthroughContainerPortMap := make(map[string][]string)
vicentefb marked this conversation as resolved.
Show resolved Hide resolved
for i, p := range gs.Spec.Ports {
var hostPort int32
if !runtime.FeatureEnabled(runtime.FeaturePortPolicyNone) || p.PortPolicy != None {
hostPort = p.HostPort
}
Expand All @@ -765,7 +769,20 @@ func (gs *GameServer) Pod(apiHooks APIHooks, sidecars ...corev1.Container) (*cor
if err != nil {
return nil, err
}
if runtime.FeatureEnabled(runtime.FeatureAutopilotPassthroughPort) && p.PortPolicy == Passthrough {
passthroughContainerPortMap[*p.Container] = append(passthroughContainerPortMap[*p.Container], fmt.Sprint(i))
}
}

if len(passthroughContainerPortMap) != 0 {
var containerToPassthroughMapJSON []byte
vicentefb marked this conversation as resolved.
Show resolved Hide resolved
containerToPassthroughMapJSON, err := json.Marshal(passthroughContainerPortMap)
if err != nil {
return nil, err
}
pod.ObjectMeta.Annotations[PassthroughPortAssignmentAnnotation] = string(containerToPassthroughMapJSON)
}

// Put the sidecars at the start of the list of containers so that the kubelet starts them first.
containers := make([]corev1.Container, 0, len(sidecars)+len(pod.Spec.Containers))
containers = append(containers, sidecars...)
Expand Down
42 changes: 42 additions & 0 deletions pkg/apis/agones/v1/gameserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package v1

import (
"encoding/json"
"fmt"
"strings"
"testing"
Expand Down Expand Up @@ -1497,6 +1498,47 @@ func TestGameServerDisableServiceAccount(t *testing.T) {
assert.Equal(t, "/var/run/secrets/kubernetes.io/serviceaccount", pod.Spec.Containers[0].VolumeMounts[0].MountPath)
}

func TestGameServerPassthroughPortAnnotation(t *testing.T) {
t.Parallel()
runtime.FeatureTestMutex.Lock()
defer runtime.FeatureTestMutex.Unlock()
require.NoError(t, runtime.ParseFeatures(string(runtime.FeatureAutopilotPassthroughPort)+"=true"))
gs := &GameServer{ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "1234"}, Spec: GameServerSpec{
Ports: []GameServerPort{
{Name: "defaultPassthrough", PortPolicy: Passthrough},
{Name: "defaultDynamic", PortPolicy: Dynamic, ContainerPort: 7654},
{Name: "defaultDynamicTwo", PortPolicy: Dynamic, ContainerPort: 7659},
{Name: "defaultPassthroughTwo", PortPolicy: Passthrough},
},
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
vicentefb marked this conversation as resolved.
Show resolved Hide resolved
Containers: []corev1.Container{{Name: "container", Image: "container/image"}},
},
},
}}

passthroughContainerPortMap := make(map[string][]string)
passthroughContainerPortMap["container"] = append(passthroughContainerPortMap["container"], "0", "3")
var containerToPassthroughMapJSON []byte
containerToPassthroughMapJSON, err := json.Marshal(passthroughContainerPortMap)
if err != nil {
assert.NoError(t, err)
}

gs.ApplyDefaults()
pod, err := gs.Pod(fakeAPIHooks{})
assert.NoError(t, err)
assert.Len(t, pod.Spec.Containers, 1)
assert.Empty(t, pod.Spec.Containers[0].VolumeMounts)
assert.Equal(t, pod.ObjectMeta.Annotations[PassthroughPortAssignmentAnnotation], string(containerToPassthroughMapJSON))

err = gs.DisableServiceAccount(pod)
assert.NoError(t, err)
assert.Len(t, pod.Spec.Containers, 1)
assert.Len(t, pod.Spec.Containers[0].VolumeMounts, 1)
assert.Equal(t, "/var/run/secrets/kubernetes.io/serviceaccount", pod.Spec.Containers[0].VolumeMounts[0].MountPath)
}

func TestGameServerCountPorts(t *testing.T) {
fixture := &GameServer{Spec: GameServerSpec{Ports: []GameServerPort{
{PortPolicy: Dynamic},
Expand Down
23 changes: 21 additions & 2 deletions pkg/gameservers/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,27 @@ func (ext *Extensions) creationMutationHandlerPod(review admissionv1.AdmissionRe

ext.baseLogger.WithField("pod.Name", pod.Name).Debug("creationMutationHandlerPod")

// TODO: We need to deal with case of multiple and mixed type ports before enabling the feature gate.
pod.Spec.Containers[1].Ports[0].ContainerPort = pod.Spec.Containers[1].Ports[0].HostPort
annotation, ok := pod.ObjectMeta.Annotations[agonesv1.PassthroughPortAssignmentAnnotation]
if !ok {
return review, nil
vicentefb marked this conversation as resolved.
Show resolved Hide resolved
}

passthroughPortAssignmentMap := make(map[string][]string)
if err := json.Unmarshal([]byte(annotation), &passthroughPortAssignmentMap); err != nil {
return review, errors.Wrapf(err, "could not unmarshal annotation %s (value %q)", passthroughPortAssignmentMap, annotation)
}

for _, p := range pod.Spec.Containers {
vicentefb marked this conversation as resolved.
Show resolved Hide resolved
if containerPassthrough, ok := passthroughPortAssignmentMap[p.Name]; ok {
for i := range containerPassthrough {
i, err := strconv.Atoi(containerPassthrough[i])
if err != nil {
return review, err
}
p.Ports[i].ContainerPort = p.Ports[i].HostPort
vicentefb marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

newPod, err := json.Marshal(pod)
if err != nil {
Expand Down
45 changes: 32 additions & 13 deletions pkg/gameservers/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,8 +590,9 @@ func TestControllerCreationMutationHandlerPod(t *testing.T) {
}

t.Run("valid pod mutation for Passthrough portPolicy, containerPort should be the same as hostPort", func(t *testing.T) {
gameServerHostPort := float64(newPassthroughPortSingleContainerSpec().Containers[1].Ports[0].HostPort)
fixture := &corev1.Pod{Spec: newPassthroughPortSingleContainerSpec()}
gameServerHostPort0 := float64(newPassthroughPortSingleContainerSpec().Spec.Containers[1].Ports[0].HostPort)
gameServerHostPort2 := float64(newPassthroughPortSingleContainerSpec().Spec.Containers[1].Ports[2].HostPort)
fixture := newPassthroughPortSingleContainerSpec()
raw, err := json.Marshal(fixture)
require.NoError(t, err)
review := admissionv1.AdmissionReview{
Expand All @@ -606,7 +607,8 @@ func TestControllerCreationMutationHandlerPod(t *testing.T) {
}
expected := expected{
patches: []jsonpatch.JsonPatchOperation{
{Operation: "replace", Path: "/spec/containers/1/ports/0/containerPort", Value: gameServerHostPort}},
{Operation: "replace", Path: "/spec/containers/1/ports/0/containerPort", Value: gameServerHostPort0},
{Operation: "replace", Path: "/spec/containers/1/ports/2/containerPort", Value: gameServerHostPort2}},
}

result, err := ext.creationMutationHandlerPod(review)
Expand Down Expand Up @@ -2275,15 +2277,32 @@ func newSingleContainerSpec() agonesv1.GameServerSpec {
}
}

func newPassthroughPortSingleContainerSpec() corev1.PodSpec {
return corev1.PodSpec{
Containers: []corev1.Container{
{Name: "agones-gameserver-sidecar",
Image: "container/image",
Env: []corev1.EnvVar{{Name: passthroughPortEnvVar, Value: "TRUE"}}},
{Name: "simple-game-server",
Image: "container2/image",
Ports: []corev1.ContainerPort{{HostPort: 7777, ContainerPort: 555}},
Env: []corev1.EnvVar{{Name: passthroughPortEnvVar, Value: "TRUE"}}}},
// Assume ports 0 and 2 are Passthrough ports for "example-server" container
// The annotation would look like autopilot.gke.io/passthrough-port-assignment: '{"example-server":["0","2"]}'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is out of date (it's {"example-server":[0,2]}, right?)

Copy link
Collaborator Author

@vicentefb vicentefb May 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could still be valid case unless I'm missing something (?) In this method i'm building the PodSpec and the example-server container has 3 ports so technically container ports 0 and 2 are still valid. I'm aware that the HostPort and ContainerPort are not the same value in this piece of code but that's intentional since at this point the webhook hasn't been triggered. Nevertheless, I included another container in this PodSpec and updated the comment. But please lmk!

func newPassthroughPortSingleContainerSpec() corev1.Pod {
passthroughContainerPortMap := make(map[string][]string)
passthroughContainerPortMap["example-server"] = append(passthroughContainerPortMap["example-server"], "0", "2")
var containerToPassthroughMapJSON []byte
containerToPassthroughMapJSON, err := json.Marshal(passthroughContainerPortMap)
vicentefb marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return corev1.Pod{}
}
return corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{agonesv1.PassthroughPortAssignmentAnnotation: string(containerToPassthroughMapJSON)},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
vicentefb marked this conversation as resolved.
Show resolved Hide resolved
{Name: "agones-gameserver-sidecar",
Image: "container/image",
Env: []corev1.EnvVar{{Name: passthroughPortEnvVar, Value: "TRUE"}}},
{Name: "example-server",
Image: "container2/image",
Ports: []corev1.ContainerPort{
{HostPort: 7777, ContainerPort: 5555},
{HostPort: 7776, ContainerPort: 7797},
{HostPort: 7775, ContainerPort: 7793}},
Env: []corev1.EnvVar{{Name: passthroughPortEnvVar, Value: "TRUE"}}}},
},
}
}
Loading