Skip to content

Commit

Permalink
feat: differentiated updates to GameServers
Browse files Browse the repository at this point in the history
Signed-off-by: ChrisLiu <[email protected]>
  • Loading branch information
chrisliu1995 committed Dec 25, 2023
1 parent 250bd86 commit f75df8e
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 1 deletion.
12 changes: 12 additions & 0 deletions apis/v1alpha1/gameserver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ type GameServerSpec struct {
UpdatePriority *intstr.IntOrString `json:"updatePriority,omitempty"`
DeletionPriority *intstr.IntOrString `json:"deletionPriority,omitempty"`
NetworkDisabled bool `json:"networkDisabled,omitempty"`
// Containers can be used to make the corresponding GameServer container fields
// different from the fields defined by GameServerTemplate in GameServerSetSpec.
Containers []GameServerContainer `json:"containers,omitempty"`
}

type GameServerContainer struct {
// Name indicates the name of the container to update.
Name string `json:"name"`
// Image indicates the image of the container to update.
Image string `json:"image,omitempty"`
// Resources indicates the resources of the container to update.
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}

type GameServerState string
Expand Down
23 changes: 23 additions & 0 deletions apis/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions config/crd/bases/game.kruise.io_gameservers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,50 @@ spec:
spec:
description: GameServerSpec defines the desired state of GameServer
properties:
containers:
description: Containers can be used to make the corresponding GameServer
container fields different from the fields defined by GameServerTemplate
in GameServerSetSpec.
items:
properties:
image:
description: Image indicates the image of the container to update.
type: string
name:
description: Name indicates the name of the container to update.
type: string
resources:
description: Resources indicates the resources of the container
to update.
properties:
limits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'Limits describes the maximum amount of compute
resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
requests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'Requests describes the minimum amount of compute
resources required. If Requests is omitted for a container,
it defaults to Limits if that is explicitly specified,
otherwise to an implementation-defined value. More info:
https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
type: object
required:
- name
type: object
type: array
deletionPriority:
anyOf:
- type: integer
Expand Down
47 changes: 47 additions & 0 deletions config/crd/bases/game.kruise.io_gameserversets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,53 @@ spec:
serviceQualityAction:
items:
properties:
containers:
description: Containers can be used to make the corresponding
GameServer container fields different from the fields
defined by GameServerTemplate in GameServerSetSpec.
items:
properties:
image:
description: Image indicates the image of the container
to update.
type: string
name:
description: Name indicates the name of the container
to update.
type: string
resources:
description: Resources indicates the resources of
the container to update.
properties:
limits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'Limits describes the maximum amount
of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
requests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'Requests describes the minimum
amount of compute resources required. If Requests
is omitted for a container, it defaults to
Limits if that is explicitly specified, otherwise
to an implementation-defined value. More info:
https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
type: object
required:
- name
type: object
type: array
deletionPriority:
anyOf:
- type: integer
Expand Down
75 changes: 74 additions & 1 deletion pkg/webhook/mutating_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ import (
"context"
"encoding/json"
"fmt"
gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
"github.com/openkruise/kruise-game/cloudprovider/errors"
"github.com/openkruise/kruise-game/cloudprovider/manager"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"k8s.io/klog/v2"
"net/http"
Expand Down Expand Up @@ -56,11 +60,20 @@ func (pmh *PodMutatingHandler) Handle(ctx context.Context, req admission.Request
return admission.Errored(http.StatusInternalServerError, err)
}

if req.Operation == admissionv1.Create {
pod, err = patchContainers(pmh.Client, pod, ctx)
if err != nil {
msg := fmt.Sprintf("Pod %s/%s patchContainers failed, because of %s", pod.Namespace, pod.Name, err.Error())
return admission.Denied(msg)
}

Check warning on line 68 in pkg/webhook/mutating_pod.go

View check run for this annotation

Codecov / codecov/patch

pkg/webhook/mutating_pod.go#L63-L68

Added lines #L63 - L68 were not covered by tests
}

// get the plugin according to pod
plugin, ok := pmh.CloudProviderManager.FindAvailablePlugins(pod)
if !ok {
msg := fmt.Sprintf("Pod %s/%s has no available plugin", pod.Namespace, pod.Name)
return admission.Allowed(msg)
klog.Infof(msg)
return getAdmissionResponse(req, patchResult{pod: pod, err: nil})

Check warning on line 76 in pkg/webhook/mutating_pod.go

View check run for this annotation

Codecov / codecov/patch

pkg/webhook/mutating_pod.go#L75-L76

Added lines #L75 - L76 were not covered by tests
}

// define context with timeout
Expand Down Expand Up @@ -136,3 +149,63 @@ func NewPodMutatingHandler(client client.Client, decoder *admission.Decoder, cpm
eventRecorder: recorder,
}
}

func patchContainers(client client.Client, pod *corev1.Pod, ctx context.Context) (*corev1.Pod, error) {
if _, ok := pod.GetLabels()[gameKruiseV1alpha1.GameServerOwnerGssKey]; !ok {
return pod, nil
}
gs := &gameKruiseV1alpha1.GameServer{}
err := client.Get(ctx, types.NamespacedName{
Namespace: pod.GetNamespace(),
Name: pod.GetName(),
}, gs)
if err != nil {
if k8serrors.IsNotFound(err) {
return pod, nil
}
return pod, err

Check warning on line 166 in pkg/webhook/mutating_pod.go

View check run for this annotation

Codecov / codecov/patch

pkg/webhook/mutating_pod.go#L163-L166

Added lines #L163 - L166 were not covered by tests
}
if gs.Spec.Containers != nil {
var containers []corev1.Container
for _, podContainer := range pod.Spec.Containers {
container := podContainer
for _, gsContainer := range gs.Spec.Containers {
if gsContainer.Name == podContainer.Name {
// patch Image
if gsContainer.Image != podContainer.Image {
container.Image = gsContainer.Image
}

// patch Resources
if limitCPU, ok := gsContainer.Resources.Limits[corev1.ResourceCPU]; ok {
if container.Resources.Limits == nil {
container.Resources.Limits = make(map[corev1.ResourceName]resource.Quantity)
}
container.Resources.Limits[corev1.ResourceCPU] = limitCPU
}
if limitMemory, ok := gsContainer.Resources.Limits[corev1.ResourceMemory]; ok {
if container.Resources.Limits == nil {
container.Resources.Limits = make(map[corev1.ResourceName]resource.Quantity)
}
container.Resources.Limits[corev1.ResourceMemory] = limitMemory

Check warning on line 190 in pkg/webhook/mutating_pod.go

View check run for this annotation

Codecov / codecov/patch

pkg/webhook/mutating_pod.go#L187-L190

Added lines #L187 - L190 were not covered by tests
}
if requestCPU, ok := gsContainer.Resources.Requests[corev1.ResourceCPU]; ok {
if container.Resources.Requests == nil {
container.Resources.Requests = make(map[corev1.ResourceName]resource.Quantity)
}
container.Resources.Requests[corev1.ResourceCPU] = requestCPU

Check warning on line 196 in pkg/webhook/mutating_pod.go

View check run for this annotation

Codecov / codecov/patch

pkg/webhook/mutating_pod.go#L193-L196

Added lines #L193 - L196 were not covered by tests
}
if requestMemory, ok := gsContainer.Resources.Requests[corev1.ResourceMemory]; ok {
if container.Resources.Requests == nil {
container.Resources.Requests = make(map[corev1.ResourceName]resource.Quantity)
}

Check warning on line 201 in pkg/webhook/mutating_pod.go

View check run for this annotation

Codecov / codecov/patch

pkg/webhook/mutating_pod.go#L200-L201

Added lines #L200 - L201 were not covered by tests
container.Resources.Requests[corev1.ResourceMemory] = requestMemory
}
}
}
containers = append(containers, container)
}
pod.Spec.Containers = containers
}
return pod, nil
}
137 changes: 137 additions & 0 deletions pkg/webhook/mutating_pod_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package webhook

import (
"context"
gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"testing"
)

var (
scheme = runtime.NewScheme()
)

func init() {
utilruntime.Must(gameKruiseV1alpha1.AddToScheme(scheme))
utilruntime.Must(corev1.AddToScheme(scheme))
}

func TestPatchContainers(t *testing.T) {
tests := []struct {
gs *gameKruiseV1alpha1.GameServer
oldPod *corev1.Pod
newContainers []corev1.Container
}{
// case 0
{
gs: nil,
oldPod: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "A",
Image: "A-v1",
},
},
},
},
newContainers: []corev1.Container{
{
Name: "A",
Image: "A-v1",
},
},
},

// case 1
{
gs: &gameKruiseV1alpha1.GameServer{
Spec: gameKruiseV1alpha1.GameServerSpec{
Containers: []gameKruiseV1alpha1.GameServerContainer{
{
Name: "A",
Image: "A-v2",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("1"),
},
},
},
},
},
},
oldPod: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
gameKruiseV1alpha1.GameServerOwnerGssKey: "xxx",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "A",
Image: "A-v1",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("500m"),
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
},
},
{
Name: "B",
Image: "B-v1",
},
},
},
},

newContainers: []corev1.Container{
{
Name: "A",
Image: "A-v2",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("500m"),
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("1"),
},
},
},
{
Name: "B",
Image: "B-v1",
},
},
},
}

for i, test := range tests {
expect := test.newContainers
var objs []client.Object
if test.gs != nil {
objs = append(objs, test.gs)
}
c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).Build()
newPod, err := patchContainers(c, test.oldPod, context.Background())
if err != nil {
t.Error(err)
}
actual := newPod.Spec.Containers
if !reflect.DeepEqual(expect, actual) {
t.Errorf("case %d: expect new containers %v, but actually got %v", i, expect, actual)
}
}
}

0 comments on commit f75df8e

Please sign in to comment.