Skip to content
Open
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
17 changes: 17 additions & 0 deletions apis/v1alpha1/gameserverset_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ const (
InplaceUpdateNotReadyBlocker = "game.kruise.io/inplace-update-not-ready-blocker"
)

type TopologyDeletionPriorityConfig struct {
// +optional
// +kubebuilder:default=100
BasePriority int `json:"basePriority,omitempty"`

// +optional
// +kubebuilder:default=10
PodCountWeight int `json:"podCountWeight,omitempty"`

// +optional
// +kubebuilder:default=5
OwnerCountWeight int `json:"ownerCountWeight,omitempty"`
}

// GameServerSetSpec defines the desired state of GameServerSet
type GameServerSetSpec struct {
// replicas is the desired number of replicas of the given Template.
Expand All @@ -54,6 +68,9 @@ type GameServerSetSpec struct {
ServiceName string `json:"serviceName,omitempty"`
ReserveGameServerIds []intstr.IntOrString `json:"reserveGameServerIds,omitempty"`
ServiceQualities []ServiceQuality `json:"serviceQualities,omitempty"`
// +optional
TopologyDeletionPriority *TopologyDeletionPriorityConfig `json:"topologyDeletionPriority,omitempty"`

UpdateStrategy UpdateStrategy `json:"updateStrategy,omitempty"`
ScaleStrategy ScaleStrategy `json:"scaleStrategy,omitempty"`
Network *Network `json:"network,omitempty"`
Expand Down
20 changes: 20 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.

26 changes: 19 additions & 7 deletions config/crd/bases/game.kruise.io_gameserversets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ spec:
jsonPath: .spec.replicas
name: DESIRED
type: integer
- description: The number of currently all GameServers.
- description: The total number of current GameServers.
jsonPath: .status.currentReplicas
name: CURRENT
type: integer
Expand Down Expand Up @@ -794,7 +794,7 @@ spec:
image:
description: |-
Image indicates the image of the container to update.
When Image updated, pod.spec.containers[*].image will be updated immediately.
When Image is updated, pod.spec.containers[*].image will be updated immediately.
type: string
name:
description: Name indicates the name of the container
Expand All @@ -803,8 +803,8 @@ spec:
resources:
description: |-
Resources indicates the resources of the container to update.
When Resources updated, pod.spec.containers[*].Resources will be not updated immediately,
which will be updated when pod recreate.
When Resources are updated, pod.spec.containers[*].Resources will not be updated immediately,
which will be updated when the pod is recreated.
properties:
claims:
description: |-
Expand Down Expand Up @@ -882,8 +882,8 @@ spec:
type: string
result:
description: |-
Result indicate the probe message returned by the script.
When Result is defined, it would exec action only when the according Result is actually returns.
Result indicates the probe message returned by the script.
When Result is defined, it would exec action only when the corresponding Result is actually returned.
type: string
state:
type: boolean
Expand Down Expand Up @@ -947,6 +947,18 @@ spec:
- permanent
type: object
type: array
topologyDeletionPriority:
properties:
basePriority:
default: 100
type: integer
ownerCountWeight:
default: 5
type: integer
podCountWeight:
default: 10
type: integer
type: object
updateStrategy:
properties:
rollingUpdate:
Expand All @@ -957,7 +969,7 @@ spec:
description: |-
UnorderedUpdate contains strategies for non-ordered update.
If it is not nil, pods will be updated with non-ordered sequence.
Noted that UnorderedUpdate can only be allowed to work with Parallel podManagementPolicy
Note that UnorderedUpdate can only be allowed to work with Parallel podManagementPolicy
UnorderedUpdate *kruiseV1beta1.UnorderedUpdateStrategy `json:"unorderedUpdate,omitempty"`
InPlaceUpdateStrategy contains strategies for in-place update.
properties:
Expand Down
21 changes: 21 additions & 0 deletions examples/topology-deletion-priority-automated/gameserverset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: gs-topology-automated
namespace: default
spec:
replicas: 10
topologyDeletionPriority:
basePriority: 100
podCountWeight: 10
ownerCountWeight: 5

gameServerTemplate:
spec:
containers:
- name: minecraft
image: minecraft:latest
resources:
requests:
cpu: 100m
memory: 128Mi
27 changes: 23 additions & 4 deletions pkg/controllers/gameserver/gameserver_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type GameServerManager struct {
client client.Client
eventRecorder record.EventRecorder
logger logr.Logger
topologyRater *TopologyRater
}

func isNeedToSyncMetadata(gss *gameKruiseV1alpha1.GameServerSet, gs *gameKruiseV1alpha1.GameServer) bool {
Expand Down Expand Up @@ -383,13 +384,30 @@ func (manager GameServerManager) SyncPodToGs(ctx context.Context, gss *gameKruis
return err
}

// patch gs status
if gss.Spec.TopologyDeletionPriority != nil && gs.Spec.DeletionPriority == nil {
if manager.topologyRater != nil {
topologyPriority, err := manager.topologyRater.CalculateDeletionPriority(ctx, gs, pod, gss.Spec.TopologyDeletionPriority)
if err != nil {
manager.logger.Error(err, "failed to calculate topology-based deletion priority",
telemetryfields.FieldGameServerNamespace, gs.GetNamespace(),
telemetryfields.FieldGameServerName, gs.GetName())
} else if topologyPriority != nil {
podDeletePriority = *topologyPriority
manager.logger.V(1).Info("calculated topology-based deletion priority",
telemetryfields.FieldGameServerNamespace, gs.GetNamespace(),
telemetryfields.FieldGameServerName, gs.GetName(),
"priority", topologyPriority.String())
}
}
}

newStatus := gameKruiseV1alpha1.GameServerStatus{
PodStatus: pod.Status,
CurrentState: podGsState,
DesiredState: gameKruiseV1alpha1.Ready,
UpdatePriority: &podUpdatePriority,
DeletionPriority: &podDeletePriority,

ServiceQualitiesCondition: sqConditions,
NetworkStatus: manager.syncNetworkStatus(),
LastTransitionTime: oldGsStatus.LastTransitionTime,
Expand Down Expand Up @@ -619,12 +637,13 @@ func (manager GameServerManager) syncPodContainers(gsContainers []gameKruiseV1al
return newContainers
}

func NewGameServerManager(gs *gameKruiseV1alpha1.GameServer, pod *corev1.Pod, c client.Client, recorder record.EventRecorder, logger logr.Logger) Control {
func NewGameServerManager(gameServer *gameKruiseV1alpha1.GameServer, pod *corev1.Pod, c client.Client, eventRecorder record.EventRecorder, logger logr.Logger) Control {
return &GameServerManager{
gameServer: gs,
gameServer: gameServer,
pod: pod,
client: c,
eventRecorder: recorder,
eventRecorder: eventRecorder,
logger: logger,
topologyRater: NewTopologyRater(c),
}
}
1 change: 1 addition & 0 deletions pkg/controllers/gameserver/gameserver_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1208,3 +1208,4 @@ func TestSyncPodToGs(t *testing.T) {
}
}
}

88 changes: 88 additions & 0 deletions pkg/controllers/gameserver/topology_rater.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package gameserver

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"

gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
)

const (
defaultBasePriority = 100
defaultPodCountWeight = 10
defaultOwnerCountWeight = 5
)

type TopologyRater struct {
client client.Client
}

func NewTopologyRater(c client.Client) *TopologyRater {
return &TopologyRater{client: c}
}

func (r *TopologyRater) CalculateDeletionPriority(
ctx context.Context,
gs *gameKruiseV1alpha1.GameServer,
pod *corev1.Pod,
config *gameKruiseV1alpha1.TopologyDeletionPriorityConfig,
) (*intstr.IntOrString, error) {
if config == nil {
return nil, nil
}

nodeName := pod.Spec.NodeName
if nodeName == "" {
return nil, nil
}

basePriority := defaultBasePriority
podCountWeight := defaultPodCountWeight
ownerCountWeight := defaultOwnerCountWeight

if config.BasePriority != 0 {
basePriority = config.BasePriority
}
if config.PodCountWeight != 0 {
podCountWeight = config.PodCountWeight
}
if config.OwnerCountWeight != 0 {
ownerCountWeight = config.OwnerCountWeight
}

podList := &corev1.PodList{}
listOpts := &client.ListOptions{
Namespace: pod.Namespace,
FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName}),
}

if err := r.client.List(ctx, podList, listOpts); err != nil {
return nil, fmt.Errorf("failed to list pods on node %s: %w", nodeName, err)
}

podCount := len(podList.Items)
ownerSet := make(map[types.UID]struct{})

for _, p := range podList.Items {
ownerRefs := p.GetOwnerReferences()
for _, owner := range ownerRefs {
ownerSet[owner.UID] = struct{}{}
}
}
ownerCount := len(ownerSet)

priority := basePriority - (podCount * podCountWeight) - (ownerCount * ownerCountWeight)

if priority < 0 {
priority = 0
}

result := intstr.FromInt(priority)
return &result, nil
}
62 changes: 62 additions & 0 deletions pkg/controllers/gameserver/topology_rater_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package gameserver

import (
"context"
"testing"

gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

// TestTopologyRater_Basic covers the main happy-path calculation using defaults.
func TestTopologyRater_Basic(t *testing.T) {
ctx := context.TODO()

scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = gameKruiseV1alpha1.AddToScheme(scheme)

pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "pod-1",
OwnerReferences: []metav1.OwnerReference{
{UID: types.UID("owner-1")},
},
},
Spec: corev1.PodSpec{
NodeName: "node-a",
},
}

cl := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(pod).
WithIndex(&corev1.Pod{}, "spec.nodeName", func(obj client.Object) []string {
pod := obj.(*corev1.Pod)
return []string{pod.Spec.NodeName}
}).
Build()
rater := NewTopologyRater(cl)

cfg := &gameKruiseV1alpha1.TopologyDeletionPriorityConfig{}

got, err := rater.CalculateDeletionPriority(ctx, &gameKruiseV1alpha1.GameServer{}, pod, cfg)
if err != nil {
t.Fatalf("CalculateDeletionPriority returned error: %v", err)
}
if got == nil {
t.Fatalf("expected non-nil priority")
}

// With one pod and one owner on the node and default weights:
// priority = 100 - 1*10 - 1*5 = 85.
if got.IntVal != 85 {
t.Fatalf("unexpected priority: got=%d want=%d", got.IntVal, 85)
}
}
Loading