Skip to content

Commit dadc058

Browse files
Nakshatra SharmaNakshatra Sharma
authored andcommitted
feat: add controller-level topology-aware deletion priority
Implements automated deletion priority calculation based on node topology. Changes: - Added TopologyDeletionPriorityConfig to GameServerSetSpec API - Created topology_rater.go with automatic calculation logic - Integrated into gameserver_manager with ServiceQuality precedence - Added automated example and tests Fixes #198
1 parent 25626ff commit dadc058

8 files changed

Lines changed: 251 additions & 11 deletions

File tree

apis/v1alpha1/gameserverset_types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ const (
4040
InplaceUpdateNotReadyBlocker = "game.kruise.io/inplace-update-not-ready-blocker"
4141
)
4242

43+
type TopologyDeletionPriorityConfig struct {
44+
// +optional
45+
// +kubebuilder:default=100
46+
BasePriority int `json:"basePriority,omitempty"`
47+
48+
// +optional
49+
// +kubebuilder:default=10
50+
PodCountWeight int `json:"podCountWeight,omitempty"`
51+
52+
// +optional
53+
// +kubebuilder:default=5
54+
OwnerCountWeight int `json:"ownerCountWeight,omitempty"`
55+
}
56+
4357
// GameServerSetSpec defines the desired state of GameServerSet
4458
type GameServerSetSpec struct {
4559
// replicas is the desired number of replicas of the given Template.
@@ -54,6 +68,9 @@ type GameServerSetSpec struct {
5468
ServiceName string `json:"serviceName,omitempty"`
5569
ReserveGameServerIds []intstr.IntOrString `json:"reserveGameServerIds,omitempty"`
5670
ServiceQualities []ServiceQuality `json:"serviceQualities,omitempty"`
71+
// +optional
72+
TopologyDeletionPriority *TopologyDeletionPriorityConfig `json:"topologyDeletionPriority,omitempty"`
73+
5774
UpdateStrategy UpdateStrategy `json:"updateStrategy,omitempty"`
5875
ScaleStrategy ScaleStrategy `json:"scaleStrategy,omitempty"`
5976
Network *Network `json:"network,omitempty"`

apis/v1alpha1/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/game.kruise.io_gameserversets.yaml

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ spec:
2121
jsonPath: .spec.replicas
2222
name: DESIRED
2323
type: integer
24-
- description: The number of currently all GameServers.
24+
- description: The total number of current GameServers.
2525
jsonPath: .status.currentReplicas
2626
name: CURRENT
2727
type: integer
@@ -794,7 +794,7 @@ spec:
794794
image:
795795
description: |-
796796
Image indicates the image of the container to update.
797-
When Image updated, pod.spec.containers[*].image will be updated immediately.
797+
When Image is updated, pod.spec.containers[*].image will be updated immediately.
798798
type: string
799799
name:
800800
description: Name indicates the name of the container
@@ -803,8 +803,8 @@ spec:
803803
resources:
804804
description: |-
805805
Resources indicates the resources of the container to update.
806-
When Resources updated, pod.spec.containers[*].Resources will be not updated immediately,
807-
which will be updated when pod recreate.
806+
When Resources are updated, pod.spec.containers[*].Resources will not be updated immediately,
807+
which will be updated when the pod is recreated.
808808
properties:
809809
claims:
810810
description: |-
@@ -882,8 +882,8 @@ spec:
882882
type: string
883883
result:
884884
description: |-
885-
Result indicate the probe message returned by the script.
886-
When Result is defined, it would exec action only when the according Result is actually returns.
885+
Result indicates the probe message returned by the script.
886+
When Result is defined, it would exec action only when the corresponding Result is actually returned.
887887
type: string
888888
state:
889889
type: boolean
@@ -947,6 +947,18 @@ spec:
947947
- permanent
948948
type: object
949949
type: array
950+
topologyDeletionPriority:
951+
properties:
952+
basePriority:
953+
default: 100
954+
type: integer
955+
ownerCountWeight:
956+
default: 5
957+
type: integer
958+
podCountWeight:
959+
default: 10
960+
type: integer
961+
type: object
950962
updateStrategy:
951963
properties:
952964
rollingUpdate:
@@ -957,7 +969,7 @@ spec:
957969
description: |-
958970
UnorderedUpdate contains strategies for non-ordered update.
959971
If it is not nil, pods will be updated with non-ordered sequence.
960-
Noted that UnorderedUpdate can only be allowed to work with Parallel podManagementPolicy
972+
Note that UnorderedUpdate can only be allowed to work with Parallel podManagementPolicy
961973
UnorderedUpdate *kruiseV1beta1.UnorderedUpdateStrategy `json:"unorderedUpdate,omitempty"`
962974
InPlaceUpdateStrategy contains strategies for in-place update.
963975
properties:
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apiVersion: game.kruise.io/v1alpha1
2+
kind: GameServerSet
3+
metadata:
4+
name: gs-topology-automated
5+
namespace: default
6+
spec:
7+
replicas: 10
8+
topologyDeletionPriority:
9+
basePriority: 100
10+
podCountWeight: 10
11+
ownerCountWeight: 5
12+
13+
gameServerTemplate:
14+
spec:
15+
containers:
16+
- name: minecraft
17+
image: minecraft:latest
18+
resources:
19+
requests:
20+
cpu: 100m
21+
memory: 128Mi

pkg/controllers/gameserver/gameserver_manager.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type GameServerManager struct {
7373
client client.Client
7474
eventRecorder record.EventRecorder
7575
logger logr.Logger
76+
topologyRater *TopologyRater
7677
}
7778

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

386-
// patch gs status
387+
if gss.Spec.TopologyDeletionPriority != nil && gs.Spec.DeletionPriority == nil {
388+
if manager.topologyRater != nil {
389+
topologyPriority, err := manager.topologyRater.CalculateDeletionPriority(ctx, gs, pod, gss.Spec.TopologyDeletionPriority)
390+
if err != nil {
391+
manager.logger.Error(err, "failed to calculate topology-based deletion priority",
392+
telemetryfields.FieldGameServerNamespace, gs.GetNamespace(),
393+
telemetryfields.FieldGameServerName, gs.GetName())
394+
} else if topologyPriority != nil {
395+
podDeletePriority = *topologyPriority
396+
manager.logger.V(1).Info("calculated topology-based deletion priority",
397+
telemetryfields.FieldGameServerNamespace, gs.GetNamespace(),
398+
telemetryfields.FieldGameServerName, gs.GetName(),
399+
"priority", topologyPriority.String())
400+
}
401+
}
402+
}
403+
387404
newStatus := gameKruiseV1alpha1.GameServerStatus{
388405
PodStatus: pod.Status,
389406
CurrentState: podGsState,
390407
DesiredState: gameKruiseV1alpha1.Ready,
391408
UpdatePriority: &podUpdatePriority,
392409
DeletionPriority: &podDeletePriority,
410+
393411
ServiceQualitiesCondition: sqConditions,
394412
NetworkStatus: manager.syncNetworkStatus(),
395413
LastTransitionTime: oldGsStatus.LastTransitionTime,
@@ -619,12 +637,13 @@ func (manager GameServerManager) syncPodContainers(gsContainers []gameKruiseV1al
619637
return newContainers
620638
}
621639

622-
func NewGameServerManager(gs *gameKruiseV1alpha1.GameServer, pod *corev1.Pod, c client.Client, recorder record.EventRecorder, logger logr.Logger) Control {
640+
func NewGameServerManager(gameServer *gameKruiseV1alpha1.GameServer, pod *corev1.Pod, c client.Client, eventRecorder record.EventRecorder, logger logr.Logger) Control {
623641
return &GameServerManager{
624-
gameServer: gs,
642+
gameServer: gameServer,
625643
pod: pod,
626644
client: c,
627-
eventRecorder: recorder,
645+
eventRecorder: eventRecorder,
628646
logger: logger,
647+
topologyRater: NewTopologyRater(c),
629648
}
630649
}

pkg/controllers/gameserver/gameserver_manager_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,3 +1208,4 @@ func TestSyncPodToGs(t *testing.T) {
12081208
}
12091209
}
12101210
}
1211+
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package gameserver
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
corev1 "k8s.io/api/core/v1"
8+
"k8s.io/apimachinery/pkg/fields"
9+
"k8s.io/apimachinery/pkg/types"
10+
"k8s.io/apimachinery/pkg/util/intstr"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
13+
gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
14+
)
15+
16+
const (
17+
defaultBasePriority = 100
18+
defaultPodCountWeight = 10
19+
defaultOwnerCountWeight = 5
20+
)
21+
22+
type TopologyRater struct {
23+
client client.Client
24+
}
25+
26+
func NewTopologyRater(c client.Client) *TopologyRater {
27+
return &TopologyRater{client: c}
28+
}
29+
30+
func (r *TopologyRater) CalculateDeletionPriority(
31+
ctx context.Context,
32+
gs *gameKruiseV1alpha1.GameServer,
33+
pod *corev1.Pod,
34+
config *gameKruiseV1alpha1.TopologyDeletionPriorityConfig,
35+
) (*intstr.IntOrString, error) {
36+
if config == nil {
37+
return nil, nil
38+
}
39+
40+
nodeName := pod.Spec.NodeName
41+
if nodeName == "" {
42+
return nil, nil
43+
}
44+
45+
basePriority := defaultBasePriority
46+
podCountWeight := defaultPodCountWeight
47+
ownerCountWeight := defaultOwnerCountWeight
48+
49+
if config.BasePriority != 0 {
50+
basePriority = config.BasePriority
51+
}
52+
if config.PodCountWeight != 0 {
53+
podCountWeight = config.PodCountWeight
54+
}
55+
if config.OwnerCountWeight != 0 {
56+
ownerCountWeight = config.OwnerCountWeight
57+
}
58+
59+
podList := &corev1.PodList{}
60+
listOpts := &client.ListOptions{
61+
Namespace: pod.Namespace,
62+
FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName}),
63+
}
64+
65+
if err := r.client.List(ctx, podList, listOpts); err != nil {
66+
return nil, fmt.Errorf("failed to list pods on node %s: %w", nodeName, err)
67+
}
68+
69+
podCount := len(podList.Items)
70+
ownerSet := make(map[types.UID]struct{})
71+
72+
for _, p := range podList.Items {
73+
ownerRefs := p.GetOwnerReferences()
74+
for _, owner := range ownerRefs {
75+
ownerSet[owner.UID] = struct{}{}
76+
}
77+
}
78+
ownerCount := len(ownerSet)
79+
80+
priority := basePriority - (podCount * podCountWeight) - (ownerCount * ownerCountWeight)
81+
82+
if priority < 0 {
83+
priority = 0
84+
}
85+
86+
result := intstr.FromInt(priority)
87+
return &result, nil
88+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package gameserver
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
"k8s.io/apimachinery/pkg/types"
12+
"sigs.k8s.io/controller-runtime/pkg/client"
13+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
14+
)
15+
16+
// TestTopologyRater_Basic covers the main happy-path calculation using defaults.
17+
func TestTopologyRater_Basic(t *testing.T) {
18+
ctx := context.TODO()
19+
20+
scheme := runtime.NewScheme()
21+
_ = corev1.AddToScheme(scheme)
22+
_ = gameKruiseV1alpha1.AddToScheme(scheme)
23+
24+
pod := &corev1.Pod{
25+
ObjectMeta: metav1.ObjectMeta{
26+
Namespace: "default",
27+
Name: "pod-1",
28+
OwnerReferences: []metav1.OwnerReference{
29+
{UID: types.UID("owner-1")},
30+
},
31+
},
32+
Spec: corev1.PodSpec{
33+
NodeName: "node-a",
34+
},
35+
}
36+
37+
cl := fake.NewClientBuilder().
38+
WithScheme(scheme).
39+
WithObjects(pod).
40+
WithIndex(&corev1.Pod{}, "spec.nodeName", func(obj client.Object) []string {
41+
pod := obj.(*corev1.Pod)
42+
return []string{pod.Spec.NodeName}
43+
}).
44+
Build()
45+
rater := NewTopologyRater(cl)
46+
47+
cfg := &gameKruiseV1alpha1.TopologyDeletionPriorityConfig{}
48+
49+
got, err := rater.CalculateDeletionPriority(ctx, &gameKruiseV1alpha1.GameServer{}, pod, cfg)
50+
if err != nil {
51+
t.Fatalf("CalculateDeletionPriority returned error: %v", err)
52+
}
53+
if got == nil {
54+
t.Fatalf("expected non-nil priority")
55+
}
56+
57+
// With one pod and one owner on the node and default weights:
58+
// priority = 100 - 1*10 - 1*5 = 85.
59+
if got.IntVal != 85 {
60+
t.Fatalf("unexpected priority: got=%d want=%d", got.IntVal, 85)
61+
}
62+
}

0 commit comments

Comments
 (0)