* Copyright (c) 2024 Huawei Technologies Co., Ltd.
* openFuyao is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package common
import (
"testing"
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
)
func TestCloneNil(t *testing.T) {
var pdGroupList *PDGroupList
cloned := pdGroupList.Clone()
if cloned == nil {
t.Fatal("expected non-nil cloned result, got nil")
}
clonedList, ok := cloned.(*PDGroupList)
if !ok {
t.Fatal("expected cloned result to be *PDGroupList")
}
if len(clonedList.Groups) != 0 {
t.Fatalf("expected empty groups, got %d", len(clonedList.Groups))
}
}
const (
testEmptyGroupsLength = 0
)
func TestCloneEmpty(t *testing.T) {
pdGroupList := &PDGroupList{
Groups: []PDGroup{},
}
cloned := pdGroupList.Clone()
clonedList, ok := cloned.(*PDGroupList)
if !ok {
t.Fatal("expected cloned result to be *PDGroupList")
}
if len(clonedList.Groups) != testEmptyGroupsLength {
t.Fatalf("expected %d groups, got %d", testEmptyGroupsLength, len(clonedList.Groups))
}
}
const (
testG1ID = "group-1"
testG1P1Name = "prefill-1"
testG1P1IP = "10.0.0.1"
testG1P1Score = 0.5
testG1D1Name = "decode-1"
testG1D1IP = "10.0.1.1"
testG1D1Score = 0.6
testG1LName = "leader-1"
testG1LIP = "10.0.2.1"
testG1LScore = 1.0
testG1SelPName = "selected-prefill-1"
testG1SelPIP = "10.0.3.1"
testG1SelPScore = 0.7
testG1SelDName = "selected-decode-1"
testG1SelDIP = "10.0.4.1"
testG1SelDScore = 0.8
)
func TestCloneSingleGroup(t *testing.T) {
pdGroupList := &PDGroupList{
Groups: []PDGroup{
{
ID: testG1ID,
PrefillPods: []types.ScoredPod{
newScoredPod(testG1P1Name, testG1P1IP, testG1P1Score),
},
DecodePods: []types.ScoredPod{
newScoredPod(testG1D1Name, testG1D1IP, testG1D1Score),
},
LeaderPod: newScoredPod(testG1LName, testG1LIP, testG1LScore),
SelectedPrefillPod: newScoredPod(testG1SelPName, testG1SelPIP, testG1SelPScore),
SelectedDecodePod: newScoredPod(testG1SelDName, testG1SelDIP, testG1SelDScore),
},
},
}
cloned := pdGroupList.Clone()
clonedList, ok := cloned.(*PDGroupList)
if !ok {
t.Fatal("expected cloned result to be *PDGroupList")
}
if len(clonedList.Groups) != 1 {
t.Fatalf("expected 1 group, got %d", len(clonedList.Groups))
}
verifyGroupClone(t, pdGroupList.Groups[0], clonedList.Groups[0])
}
const (
testG2ID = "group-2"
testG2P1Name = "prefill-2-1"
testG2P1IP = "10.0.0.2"
testG2P1Score = 0.3
testG2P2Name = "prefill-2-2"
testG2P2IP = "10.0.0.3"
testG2P2Score = 0.4
testG2D1Name = "decode-2-1"
testG2D1IP = "10.0.1.2"
testG2D1Score = 0.5
testG2LName = "leader-2"
testG2LIP = "10.0.2.2"
testG2LScore = 1.5
testG2SelPName = "selected-prefill-2"
testG2SelPIP = "10.0.3.2"
testG2SelPScore = 0.6
testG2SelDName = "selected-decode-2"
testG2SelDIP = "10.0.4.2"
testG2SelDScore = 0.7
testExpGrpCnt = 2
)
func TestCloneMultipleGroups(t *testing.T) {
pdGroupList := &PDGroupList{
Groups: []PDGroup{
{
ID: testG1ID,
PrefillPods: []types.ScoredPod{
newScoredPod(testG1P1Name, testG1P1IP, testG1P1Score),
},
DecodePods: []types.ScoredPod{
newScoredPod(testG1D1Name, testG1D1IP, testG1D1Score),
},
LeaderPod: newScoredPod(testG1LName, testG1LIP, testG1LScore),
SelectedPrefillPod: newScoredPod(testG1SelPName, testG1SelPIP, testG1SelPScore),
SelectedDecodePod: newScoredPod(testG1SelDName, testG1SelDIP, testG1SelDScore),
},
{
ID: testG2ID,
PrefillPods: []types.ScoredPod{
newScoredPod(testG2P1Name, testG2P1IP, testG2P1Score),
newScoredPod(testG2P2Name, testG2P2IP, testG2P2Score),
},
DecodePods: []types.ScoredPod{
newScoredPod(testG2D1Name, testG2D1IP, testG2D1Score),
},
LeaderPod: newScoredPod(testG2LName, testG2LIP, testG2LScore),
SelectedPrefillPod: newScoredPod(testG2SelPName, testG2SelPIP, testG2SelPScore),
SelectedDecodePod: newScoredPod(testG2SelDName, testG2SelDIP, testG2SelDScore),
},
},
}
cloned := pdGroupList.Clone()
clonedList, ok := cloned.(*PDGroupList)
if !ok {
t.Fatal("expected cloned result to be *PDGroupList")
}
if len(clonedList.Groups) != testExpGrpCnt {
t.Fatalf("expected %d groups, got %d", testExpGrpCnt, len(clonedList.Groups))
}
for i := range pdGroupList.Groups {
verifyGroupClone(t, pdGroupList.Groups[i], clonedList.Groups[i])
}
}
const (
testModifiedScore = 999.0
)
func TestCloneDeepCopy(t *testing.T) {
pdGroupList := &PDGroupList{
Groups: []PDGroup{
{
ID: testG1ID,
PrefillPods: []types.ScoredPod{
newScoredPod(testG1P1Name, testG1P1IP, testG1P1Score),
},
DecodePods: []types.ScoredPod{
newScoredPod(testG1D1Name, testG1D1IP, testG1D1Score),
},
LeaderPod: newScoredPod(testG1LName, testG1LIP, testG1LScore),
SelectedPrefillPod: newScoredPod(testG1SelPName, testG1SelPIP, testG1SelPScore),
SelectedDecodePod: newScoredPod(testG1SelDName, testG1SelDIP, testG1SelDScore),
},
},
}
cloned := pdGroupList.Clone()
clonedList, ok := cloned.(*PDGroupList)
if !ok {
t.Fatal("expected cloned result to be *PDGroupList")
}
pdGroupList.Groups[0].ID = testG2ID
pdGroupList.Groups[0].PrefillPods[0].Score = testModifiedScore
pdGroupList.Groups[0].DecodePods[0].Score = testModifiedScore
pdGroupList.Groups[0].LeaderPod.Score = testModifiedScore
pdGroupList.Groups[0].SelectedPrefillPod.Score = testModifiedScore
pdGroupList.Groups[0].SelectedDecodePod.Score = testModifiedScore
if clonedList.Groups[0].ID == testG2ID {
t.Error("cloned group ID should not be affected by original modification")
}
if clonedList.Groups[0].PrefillPods[0].Score == testModifiedScore {
t.Error("cloned prefill pod score should not be affected by original modification")
}
if clonedList.Groups[0].DecodePods[0].Score == testModifiedScore {
t.Error("cloned decode pod score should not be affected by original modification")
}
if clonedList.Groups[0].LeaderPod.Score == testModifiedScore {
t.Error("cloned leader pod score should not be affected by original modification")
}
if clonedList.Groups[0].SelectedPrefillPod.Score == testModifiedScore {
t.Error("cloned selected prefill pod score should not be affected by original modification")
}
if clonedList.Groups[0].SelectedDecodePod.Score == testModifiedScore {
t.Error("cloned selected decode pod score should not be affected by original modification")
}
}
func newScoredPod(name, address string, score float64) types.ScoredPod {
return types.ScoredPod{
Pod: &types.PodMetrics{
Pod: &backend.Pod{
PodName: name,
Address: address,
},
MetricsState: &metrics.MetricsState{},
},
Score: score,
}
}
func verifyGroupClone(t *testing.T, original, cloned PDGroup) {
if original.ID != cloned.ID {
t.Errorf("expected cloned group ID %s, got %s", original.ID, cloned.ID)
}
if len(original.PrefillPods) != len(cloned.PrefillPods) {
t.Errorf("expected %d prefill pods, got %d", len(original.PrefillPods), len(cloned.PrefillPods))
}
for i := range original.PrefillPods {
if original.PrefillPods[i].Score != cloned.PrefillPods[i].Score {
t.Errorf("prefill pod %d: expected score %f, got %f",
i, original.PrefillPods[i].Score, cloned.PrefillPods[i].Score)
}
}
if len(original.DecodePods) != len(cloned.DecodePods) {
t.Errorf("expected %d decode pods, got %d", len(original.DecodePods), len(cloned.DecodePods))
}
for i := range original.DecodePods {
if original.DecodePods[i].Score != cloned.DecodePods[i].Score {
t.Errorf("decode pod %d: expected score %f, got %f",
i, original.DecodePods[i].Score, cloned.DecodePods[i].Score)
}
}
if original.LeaderPod.Score != cloned.LeaderPod.Score {
t.Errorf("expected leader pod score %f, got %f", original.LeaderPod.Score, cloned.LeaderPod.Score)
}
if original.SelectedPrefillPod.Score != cloned.SelectedPrefillPod.Score {
t.Errorf("expected selected prefill pod score %f, got %f",
original.SelectedPrefillPod.Score, cloned.SelectedPrefillPod.Score)
}
if original.SelectedDecodePod.Score != cloned.SelectedDecodePod.Score {
t.Errorf("expected selected decode pod score %f, got %f",
original.SelectedDecodePod.Score, cloned.SelectedDecodePod.Score)
}
}