kruise-game/pkg/controllers/gameserver/gameserver_manager.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,
}
}