enhance: ServiceQuality supports multiple results returned by a single probe (#117)

Signed-off-by: ChrisLiu <chrisliu1995@163.com>
This commit is contained in:
ChrisLiu 2023-12-27 15:22:07 +08:00 committed by GitHub
parent 1181b22e4e
commit 4013d3e597
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 14 deletions

View File

@ -77,15 +77,20 @@ type ServiceQuality struct {
} }
type ServiceQualityCondition struct { type ServiceQualityCondition struct {
Name string `json:"name"` Name string `json:"name"`
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
// Result indicate the probe message returned by the script
Result string `json:"result,omitempty"`
LastProbeTime metav1.Time `json:"lastProbeTime,omitempty"` LastProbeTime metav1.Time `json:"lastProbeTime,omitempty"`
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
LastActionTransitionTime metav1.Time `json:"lastActionTransitionTime,omitempty"` LastActionTransitionTime metav1.Time `json:"lastActionTransitionTime,omitempty"`
} }
type ServiceQualityAction struct { type ServiceQualityAction struct {
State bool `json:"state"` State bool `json:"state"`
// 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 string `json:"result,omitempty"`
GameServerSpec `json:",inline"` GameServerSpec `json:",inline"`
} }

View File

@ -855,6 +855,10 @@ spec:
type: string type: string
name: name:
type: string type: string
result:
description: Result indicate the probe message returned by the
script
type: string
status: status:
type: string type: string
required: required:

View File

@ -544,6 +544,11 @@ spec:
type: boolean type: boolean
opsState: opsState:
type: string 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.
type: string
state: state:
type: boolean type: boolean
updatePriority: updatePriority:

View File

@ -33,6 +33,7 @@ import (
"reflect" "reflect"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"strconv" "strconv"
"strings"
"time" "time"
) )
@ -316,25 +317,29 @@ func syncServiceQualities(serviceQualities []gameKruiseV1alpha1.ServiceQuality,
for _, sqc := range sqConditions { for _, sqc := range sqConditions {
sqConditionsMap[sqc.Name] = sqc sqConditionsMap[sqc.Name] = sqc
} }
timeNow := metav1.Now()
for _, sq := range serviceQualities { for _, sq := range serviceQualities {
var newSqCondition gameKruiseV1alpha1.ServiceQualityCondition var newSqCondition gameKruiseV1alpha1.ServiceQualityCondition
newSqCondition.Name = sq.Name newSqCondition.Name = sq.Name
index, podCondition := util.GetPodConditionFromList(podConditions, corev1.PodConditionType(util.AddPrefixGameKruise(sq.Name))) index, podCondition := util.GetPodConditionFromList(podConditions, corev1.PodConditionType(util.AddPrefixGameKruise(sq.Name)))
if index != -1 { if index != -1 {
podConditionMessage := strings.ReplaceAll(podCondition.Message, "|", "")
podConditionMessage = strings.ReplaceAll(podConditionMessage, "\n", "")
newSqCondition.Status = string(podCondition.Status) newSqCondition.Status = string(podCondition.Status)
newSqCondition.Result = podConditionMessage
newSqCondition.LastProbeTime = podCondition.LastProbeTime newSqCondition.LastProbeTime = podCondition.LastProbeTime
var lastActionTransitionTime metav1.Time var lastActionTransitionTime metav1.Time
sqCondition, exist := sqConditionsMap[sq.Name] sqCondition, exist := sqConditionsMap[sq.Name]
if !exist || (sqCondition.Status != string(podCondition.Status) && (sqCondition.LastActionTransitionTime.IsZero() || !sq.Permanent)) { if !exist || ((sqCondition.Status != string(podCondition.Status) || (sqCondition.Result != podConditionMessage)) && (sqCondition.LastActionTransitionTime.IsZero() || !sq.Permanent)) {
// exec action // exec action
for _, action := range sq.ServiceQualityAction { for _, action := range sq.ServiceQualityAction {
state, err := strconv.ParseBool(string(podCondition.Status)) state, err := strconv.ParseBool(string(podCondition.Status))
if err == nil && state == action.State { if err == nil && state == action.State && (action.Result == "" || podConditionMessage == action.Result) {
spec.DeletionPriority = action.DeletionPriority spec.DeletionPriority = action.DeletionPriority
spec.UpdatePriority = action.UpdatePriority spec.UpdatePriority = action.UpdatePriority
spec.OpsState = action.OpsState spec.OpsState = action.OpsState
spec.NetworkDisabled = action.NetworkDisabled spec.NetworkDisabled = action.NetworkDisabled
lastActionTransitionTime = metav1.Now() lastActionTransitionTime = timeNow
} }
} }
} else { } else {
@ -342,7 +347,13 @@ func syncServiceQualities(serviceQualities []gameKruiseV1alpha1.ServiceQuality,
} }
newSqCondition.LastActionTransitionTime = lastActionTransitionTime newSqCondition.LastActionTransitionTime = lastActionTransitionTime
} }
newSqCondition.LastTransitionTime = metav1.Now()
// Set LastTransitionTime, which depends on which value, the LastActionTransitionTime or LastProbeTime, is closer to the current time.
if timeNow.Sub(newSqCondition.LastActionTransitionTime.Time) < timeNow.Sub(newSqCondition.LastProbeTime.Time) {
newSqCondition.LastTransitionTime = newSqCondition.LastActionTransitionTime
} else {
newSqCondition.LastTransitionTime = newSqCondition.LastProbeTime
}
newGsConditions = append(newGsConditions, newSqCondition) newGsConditions = append(newGsConditions, newSqCondition)
} }
return spec, newGsConditions return spec, newGsConditions

View File

@ -42,6 +42,7 @@ func TestSyncServiceQualities(t *testing.T) {
spec gameKruiseV1alpha1.GameServerSpec spec gameKruiseV1alpha1.GameServerSpec
newSqConditions []gameKruiseV1alpha1.ServiceQualityCondition newSqConditions []gameKruiseV1alpha1.ServiceQualityCondition
}{ }{
//case 0
{ {
serviceQualities: []gameKruiseV1alpha1.ServiceQuality{ serviceQualities: []gameKruiseV1alpha1.ServiceQuality{
{ {
@ -88,6 +89,7 @@ func TestSyncServiceQualities(t *testing.T) {
}, },
}, },
}, },
// case 1
{ {
serviceQualities: []gameKruiseV1alpha1.ServiceQuality{ serviceQualities: []gameKruiseV1alpha1.ServiceQuality{
{ {
@ -139,6 +141,7 @@ func TestSyncServiceQualities(t *testing.T) {
}, },
}, },
}, },
// case 2
{ {
serviceQualities: []gameKruiseV1alpha1.ServiceQuality{ serviceQualities: []gameKruiseV1alpha1.ServiceQuality{
{ {
@ -175,6 +178,7 @@ func TestSyncServiceQualities(t *testing.T) {
}, },
}, },
}, },
// case 3
{ {
serviceQualities: []gameKruiseV1alpha1.ServiceQuality{ serviceQualities: []gameKruiseV1alpha1.ServiceQuality{
{ {
@ -212,6 +216,7 @@ func TestSyncServiceQualities(t *testing.T) {
}, },
}, },
}, },
// case 4
{ {
serviceQualities: []gameKruiseV1alpha1.ServiceQuality{ serviceQualities: []gameKruiseV1alpha1.ServiceQuality{
{ {
@ -265,17 +270,131 @@ func TestSyncServiceQualities(t *testing.T) {
}, },
}, },
}, },
// case 5
{
serviceQualities: []gameKruiseV1alpha1.ServiceQuality{
{
Name: "multi-return",
Permanent: false,
ServiceQualityAction: []gameKruiseV1alpha1.ServiceQualityAction{
{
State: true,
Result: "A",
GameServerSpec: gameKruiseV1alpha1.GameServerSpec{
OpsState: "A",
},
},
{
State: true,
Result: "B",
GameServerSpec: gameKruiseV1alpha1.GameServerSpec{
OpsState: "B",
},
},
{
State: true,
Result: "C",
GameServerSpec: gameKruiseV1alpha1.GameServerSpec{
OpsState: "C",
},
},
},
},
},
podConditions: []corev1.PodCondition{
{
Type: "game.kruise.io/multi-return",
Status: corev1.ConditionTrue,
Message: "B",
LastProbeTime: fakeProbeTime,
},
},
sqConditions: nil,
spec: gameKruiseV1alpha1.GameServerSpec{
OpsState: "B",
},
newSqConditions: []gameKruiseV1alpha1.ServiceQualityCondition{
{
Name: "multi-return",
Result: "B",
Status: string(corev1.ConditionTrue),
LastProbeTime: fakeProbeTime,
LastActionTransitionTime: fakeActionTime,
},
},
},
// case 6
{
serviceQualities: []gameKruiseV1alpha1.ServiceQuality{
{
Name: "multi-return",
Permanent: false,
ServiceQualityAction: []gameKruiseV1alpha1.ServiceQualityAction{
{
State: true,
Result: "A",
GameServerSpec: gameKruiseV1alpha1.GameServerSpec{
OpsState: "A",
},
},
{
State: true,
Result: "B",
GameServerSpec: gameKruiseV1alpha1.GameServerSpec{
OpsState: "B",
},
},
{
State: true,
Result: "C",
GameServerSpec: gameKruiseV1alpha1.GameServerSpec{
OpsState: "C",
},
},
},
},
},
podConditions: []corev1.PodCondition{
{
Type: "game.kruise.io/multi-return",
Status: corev1.ConditionTrue,
Message: "A",
LastProbeTime: fakeProbeTime,
},
},
sqConditions: []gameKruiseV1alpha1.ServiceQualityCondition{
{
Name: "multi-return",
Result: "B",
Status: string(corev1.ConditionTrue),
LastProbeTime: fakeProbeTime,
LastActionTransitionTime: fakeActionTime,
},
},
spec: gameKruiseV1alpha1.GameServerSpec{
OpsState: "A",
},
newSqConditions: []gameKruiseV1alpha1.ServiceQualityCondition{
{
Name: "multi-return",
Result: "A",
Status: string(corev1.ConditionTrue),
LastProbeTime: fakeProbeTime,
LastActionTransitionTime: fakeActionTime,
},
},
},
} }
for _, test := range tests { for i, test := range tests {
actualSpec, actualNewSqConditions := syncServiceQualities(test.serviceQualities, test.podConditions, test.sqConditions) actualSpec, actualNewSqConditions := syncServiceQualities(test.serviceQualities, test.podConditions, test.sqConditions)
expectSpec := test.spec expectSpec := test.spec
expectNewSqConditions := test.newSqConditions expectNewSqConditions := test.newSqConditions
if !reflect.DeepEqual(actualSpec, expectSpec) { if !reflect.DeepEqual(actualSpec, expectSpec) {
t.Errorf("expect spec %v but got %v", expectSpec, actualSpec) t.Errorf("case %d: expect spec %v but got %v", i, expectSpec, actualSpec)
} }
if len(actualNewSqConditions) != len(expectNewSqConditions) { if len(actualNewSqConditions) != len(expectNewSqConditions) {
t.Errorf("expect sq conditions len %v but got %v", len(expectNewSqConditions), len(actualNewSqConditions)) t.Errorf("case %d: expect sq conditions len %v but got %v", i, len(expectNewSqConditions), len(actualNewSqConditions))
} }
for _, expectNewSqCondition := range expectNewSqConditions { for _, expectNewSqCondition := range expectNewSqConditions {
exist := false exist := false
@ -283,19 +402,19 @@ func TestSyncServiceQualities(t *testing.T) {
if actualNewSqCondition.Name == expectNewSqCondition.Name { if actualNewSqCondition.Name == expectNewSqCondition.Name {
exist = true exist = true
if actualNewSqCondition.Status != expectNewSqCondition.Status { if actualNewSqCondition.Status != expectNewSqCondition.Status {
t.Errorf("expect sq condition status %v but got %v", expectNewSqCondition.Status, actualNewSqCondition.Status) t.Errorf("case %d: expect sq condition status %v but got %v", i, expectNewSqCondition.Status, actualNewSqCondition.Status)
} }
if actualNewSqCondition.LastProbeTime != expectNewSqCondition.LastProbeTime { if actualNewSqCondition.LastProbeTime != expectNewSqCondition.LastProbeTime {
t.Errorf("expect sq condition LastProbeTime %v but got %v", expectNewSqCondition.LastProbeTime, actualNewSqCondition.LastProbeTime) t.Errorf("case %d: expect sq condition LastProbeTime %v but got %v", i, expectNewSqCondition.LastProbeTime, actualNewSqCondition.LastProbeTime)
} }
if actualNewSqCondition.LastActionTransitionTime.IsZero() != expectNewSqCondition.LastActionTransitionTime.IsZero() { if actualNewSqCondition.LastActionTransitionTime.IsZero() != expectNewSqCondition.LastActionTransitionTime.IsZero() {
t.Errorf("expect sq condition LastActionTransitionTime IsZero %v but got %v", expectNewSqCondition.LastActionTransitionTime.IsZero(), actualNewSqCondition.LastActionTransitionTime.IsZero()) t.Errorf("case %d: expect sq condition LastActionTransitionTime IsZero %v but got %v", i, expectNewSqCondition.LastActionTransitionTime.IsZero(), actualNewSqCondition.LastActionTransitionTime.IsZero())
} }
break break
} }
} }
if !exist { if !exist {
t.Errorf("expect sq condition %s exist, but actually not", expectNewSqCondition.Name) t.Errorf("case %d: expect sq condition %s exist, but actually not", i, expectNewSqCondition.Name)
} }
} }
} }