215 lines
7.0 KiB
Go
215 lines
7.0 KiB
Go
/*
|
|
Copyright 2022 The Kruise Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package gameserver
|
|
|
|
import (
|
|
"context"
|
|
kruisePub "github.com/openkruise/kruise-api/apps/pub"
|
|
gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
|
|
"github.com/openkruise/kruise-game/pkg/util"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
"k8s.io/apimachinery/pkg/util/json"
|
|
"k8s.io/klog/v2"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
)
|
|
|
|
type Control interface {
|
|
SyncToPod() (bool, error)
|
|
SyncToGs(qualities []gameKruiseV1alpha1.ServiceQuality) error
|
|
}
|
|
|
|
type GameServerManager struct {
|
|
gameServer *gameKruiseV1alpha1.GameServer
|
|
pod *corev1.Pod
|
|
client client.Client
|
|
}
|
|
|
|
func (manager GameServerManager) SyncToPod() (bool, error) {
|
|
// compare GameServer Spec With Pod
|
|
pod := manager.pod
|
|
gs := manager.gameServer
|
|
podLabels := pod.GetLabels()
|
|
podDeletePriority := podLabels[gameKruiseV1alpha1.GameServerDeletePriorityKey]
|
|
podUpdatePriority := podLabels[gameKruiseV1alpha1.GameServerUpdatePriorityKey]
|
|
podGsOpsState := podLabels[gameKruiseV1alpha1.GameServerOpsStateKey]
|
|
podGsState := podLabels[gameKruiseV1alpha1.GameServerStateKey]
|
|
|
|
updated := false
|
|
newLabels := make(map[string]string)
|
|
if gs.Spec.DeletionPriority.String() != podDeletePriority {
|
|
newLabels[gameKruiseV1alpha1.GameServerDeletePriorityKey] = gs.Spec.DeletionPriority.String()
|
|
updated = true
|
|
}
|
|
if gs.Spec.UpdatePriority.String() != podUpdatePriority {
|
|
newLabels[gameKruiseV1alpha1.GameServerUpdatePriorityKey] = gs.Spec.UpdatePriority.String()
|
|
updated = true
|
|
}
|
|
if string(gs.Spec.OpsState) != podGsOpsState {
|
|
newLabels[gameKruiseV1alpha1.GameServerOpsStateKey] = string(gs.Spec.OpsState)
|
|
updated = true
|
|
}
|
|
|
|
var gsState gameKruiseV1alpha1.GameServerState
|
|
switch pod.Status.Phase {
|
|
case corev1.PodRunning:
|
|
// GameServer Updating
|
|
lifecycleState, exist := pod.GetLabels()[kruisePub.LifecycleStateKey]
|
|
if exist && (lifecycleState == string(kruisePub.LifecycleStateUpdating) || lifecycleState == string(kruisePub.LifecycleStatePreparingUpdate)) {
|
|
gsState = gameKruiseV1alpha1.Updating
|
|
break
|
|
}
|
|
// GameServer Deleting
|
|
if !pod.DeletionTimestamp.IsZero() {
|
|
gsState = gameKruiseV1alpha1.Deleting
|
|
break
|
|
}
|
|
// GameServer Ready / NotReady
|
|
for _, con := range pod.Status.Conditions {
|
|
if con.Type == corev1.PodReady {
|
|
if con.Status == corev1.ConditionTrue {
|
|
gsState = gameKruiseV1alpha1.Ready
|
|
} else {
|
|
gsState = gameKruiseV1alpha1.NotReady
|
|
}
|
|
break
|
|
}
|
|
}
|
|
case corev1.PodFailed:
|
|
gsState = gameKruiseV1alpha1.Crash
|
|
case corev1.PodPending:
|
|
gsState = gameKruiseV1alpha1.Creating
|
|
default:
|
|
gsState = gameKruiseV1alpha1.Unknown
|
|
}
|
|
if string(gsState) != podGsState {
|
|
newLabels[gameKruiseV1alpha1.GameServerStateKey] = string(gsState)
|
|
updated = true
|
|
}
|
|
|
|
if updated {
|
|
patchPod := map[string]interface{}{"metadata": map[string]map[string]string{"labels": newLabels}}
|
|
patchPodBytes, err := json.Marshal(patchPod)
|
|
if err != nil {
|
|
return updated, err
|
|
}
|
|
err = manager.client.Patch(context.TODO(), pod, client.RawPatch(types.StrategicMergePatchType, patchPodBytes))
|
|
if err != nil && !errors.IsNotFound(err) {
|
|
klog.Errorf("failed to patch Pod %s in %s,because of %s.", pod.GetName(), pod.GetNamespace(), err.Error())
|
|
return updated, err
|
|
}
|
|
}
|
|
|
|
return updated, nil
|
|
}
|
|
|
|
func (manager GameServerManager) SyncToGs(qualities []gameKruiseV1alpha1.ServiceQuality) error {
|
|
gs := manager.gameServer
|
|
pod := manager.pod
|
|
podLabels := pod.GetLabels()
|
|
podDeletePriority := intstr.FromString(podLabels[gameKruiseV1alpha1.GameServerDeletePriorityKey])
|
|
podUpdatePriority := intstr.FromString(podLabels[gameKruiseV1alpha1.GameServerUpdatePriorityKey])
|
|
podGsState := gameKruiseV1alpha1.GameServerState(podLabels[gameKruiseV1alpha1.GameServerStateKey])
|
|
|
|
gsConditions := gs.Status.ServiceQualitiesCondition
|
|
podConditions := pod.Status.Conditions
|
|
var sqNames []string
|
|
for _, sq := range qualities {
|
|
sqNames = append(sqNames, sq.Name)
|
|
}
|
|
var toExec map[string]string
|
|
var newGsConditions []gameKruiseV1alpha1.ServiceQualityCondition
|
|
for _, pc := range podConditions {
|
|
if util.IsStringInList(string(pc.Type), sqNames) {
|
|
toExecAction := true
|
|
lastActionTransitionTime := metav1.Now()
|
|
for _, gsc := range gsConditions {
|
|
if gsc.Name == string(pc.Type) && gsc.Status == string(pc.Status) {
|
|
toExecAction = false
|
|
lastActionTransitionTime = gsc.LastActionTransitionTime
|
|
break
|
|
}
|
|
}
|
|
serviceQualityCondition := gameKruiseV1alpha1.ServiceQualityCondition{
|
|
Name: string(pc.Type),
|
|
Status: string(pc.Status),
|
|
LastProbeTime: pc.LastProbeTime,
|
|
LastTransitionTime: pc.LastTransitionTime,
|
|
LastActionTransitionTime: lastActionTransitionTime,
|
|
}
|
|
newGsConditions = append(newGsConditions, serviceQualityCondition)
|
|
if toExecAction {
|
|
toExec[string(pc.Type)] = string(pc.Status)
|
|
}
|
|
}
|
|
}
|
|
if toExec != nil {
|
|
var spec gameKruiseV1alpha1.GameServerSpec
|
|
for _, sq := range qualities {
|
|
for name := range toExec {
|
|
if sq.Name == name {
|
|
// TODO exec action
|
|
}
|
|
}
|
|
}
|
|
|
|
patchSpec := map[string]interface{}{"spec": spec}
|
|
jsonPatchSpec, err := json.Marshal(patchSpec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = manager.client.Patch(context.TODO(), gs, client.RawPatch(types.MergePatchType, jsonPatchSpec))
|
|
if err != nil && !errors.IsNotFound(err) {
|
|
klog.Errorf("failed to patch GameServer spec %s in %s,because of %s.", gs.GetName(), gs.GetNamespace(), err.Error())
|
|
return err
|
|
}
|
|
}
|
|
|
|
status := gameKruiseV1alpha1.GameServerStatus{
|
|
PodStatus: pod.Status,
|
|
CurrentState: podGsState,
|
|
DesiredState: gameKruiseV1alpha1.Ready,
|
|
UpdatePriority: &podUpdatePriority,
|
|
DeletionPriority: &podDeletePriority,
|
|
ServiceQualitiesCondition: newGsConditions,
|
|
LastTransitionTime: metav1.Now(),
|
|
}
|
|
patchStatus := map[string]interface{}{"status": status}
|
|
jsonPatchStatus, err := json.Marshal(patchStatus)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = manager.client.Status().Patch(context.TODO(), gs, client.RawPatch(types.MergePatchType, jsonPatchStatus))
|
|
if err != nil && !errors.IsNotFound(err) {
|
|
klog.Errorf("failed to patch GameServer Status %s in %s,because of %s.", gs.GetName(), gs.GetNamespace(), err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewGameServerManager(gs *gameKruiseV1alpha1.GameServer, pod *corev1.Pod, c client.Client) Control {
|
|
return &GameServerManager{
|
|
gameServer: gs,
|
|
pod: pod,
|
|
client: c,
|
|
}
|
|
}
|