Skip to content
This repository was archived by the owner on Mar 16, 2024. It is now read-only.

Commit ce9166e

Browse files
author
Oscar Ward
authored
fix: handle multiple compute classes in a project (#2459)
1 parent e0bfed6 commit ce9166e

File tree

30 files changed

+382
-93
lines changed

30 files changed

+382
-93
lines changed

integration/helper/project.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"sigs.k8s.io/controller-runtime/pkg/client"
1515
)
1616

17-
func TempProject(t *testing.T, client client.WithWatch) *v1.ProjectInstance {
17+
func TempProjectWithRegions(t *testing.T, client client.WithWatch, regions []string) *v1.ProjectInstance {
1818
t.Helper()
1919
project := &v1.ProjectInstance{
2020
ObjectMeta: metav1.ObjectMeta{
@@ -26,7 +26,7 @@ func TempProject(t *testing.T, client client.WithWatch) *v1.ProjectInstance {
2626
},
2727
Spec: v1.ProjectInstanceSpec{
2828
DefaultRegion: apiv1.LocalRegion,
29-
SupportedRegions: []string{apiv1.LocalRegion},
29+
SupportedRegions: regions,
3030
},
3131
}
3232

@@ -70,6 +70,11 @@ func TempProject(t *testing.T, client client.WithWatch) *v1.ProjectInstance {
7070
return project
7171
}
7272

73+
func TempProject(t *testing.T, client client.WithWatch) *v1.ProjectInstance {
74+
t.Helper()
75+
return TempProjectWithRegions(t, client, []string{apiv1.LocalRegion})
76+
}
77+
7378
// createAllowAllIAR creates an ImageAllowRule that allows all images and has no extra rules
7479
// This is necessary, since while testing IARs, we enable the feature flag and it seems to leak
7580
// into other tests, blocking images there, even though the tests shouldn't run in parallel and the config

integration/run/run_test.go

Lines changed: 307 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,311 @@ func TestDeployParam(t *testing.T) {
931931
assert.Equal(t, "5", appInstance.Status.AppSpec.Containers["foo"].Environment[0].Value)
932932
}
933933

934+
func TestMultipleDefaultComputeClass(t *testing.T) {
935+
helper.StartController(t)
936+
cfg := helper.StartAPI(t)
937+
project := helper.TempProjectWithRegions(t, helper.MustReturn(kclient.Default), []string{apiv1.LocalRegion, "local2"})
938+
kclient := helper.MustReturn(kclient.Default)
939+
c, err := client.New(cfg, "", project.Name)
940+
if err != nil {
941+
t.Fatal(err)
942+
}
943+
944+
ctx := helper.GetCTX(t)
945+
946+
checks := []struct {
947+
name string
948+
noComputeClass bool
949+
testDataDirectory string
950+
region string
951+
computeClasses []adminv1.ProjectComputeClassInstance
952+
expected map[string]v1.Scheduling
953+
waitFor func(obj *v1.AppInstance) bool
954+
fail bool
955+
}{
956+
{
957+
name: "local-default",
958+
testDataDirectory: "./testdata/simple",
959+
region: apiv1.LocalRegion,
960+
computeClasses: []adminv1.ProjectComputeClassInstance{
961+
{
962+
ObjectMeta: metav1.ObjectMeta{
963+
Name: "acorn-test-custom",
964+
Namespace: c.GetNamespace(),
965+
},
966+
Default: true,
967+
Memory: adminv1.ComputeClassMemory{
968+
Default: "512Mi",
969+
Max: "1Gi",
970+
Min: "512Mi",
971+
},
972+
SupportedRegions: []string{apiv1.LocalRegion},
973+
},
974+
{
975+
ObjectMeta: metav1.ObjectMeta{
976+
Name: "acorn-test-custom-2",
977+
Namespace: c.GetNamespace(),
978+
},
979+
Default: true,
980+
Memory: adminv1.ComputeClassMemory{
981+
Default: "513Mi",
982+
Max: "1Gi",
983+
Min: "512Mi",
984+
},
985+
SupportedRegions: []string{"local2"},
986+
},
987+
},
988+
expected: map[string]v1.Scheduling{"simple": {
989+
Requirements: corev1.ResourceRequirements{
990+
Limits: corev1.ResourceList{
991+
corev1.ResourceMemory: resource.MustParse("512Mi")},
992+
Requests: corev1.ResourceList{
993+
corev1.ResourceMemory: resource.MustParse("512Mi"),
994+
},
995+
},
996+
Tolerations: []corev1.Toleration{
997+
{
998+
Key: tolerations.WorkloadTolerationKey,
999+
Operator: corev1.TolerationOpExists,
1000+
},
1001+
}},
1002+
},
1003+
waitFor: func(obj *v1.AppInstance) bool {
1004+
return obj.Status.Condition(v1.AppInstanceConditionParsed).Success &&
1005+
obj.Status.Condition(v1.AppInstanceConditionScheduling).Success
1006+
},
1007+
},
1008+
{
1009+
name: "local2-default",
1010+
testDataDirectory: "./testdata/simple",
1011+
region: "local2",
1012+
computeClasses: []adminv1.ProjectComputeClassInstance{
1013+
{
1014+
ObjectMeta: metav1.ObjectMeta{
1015+
Name: "acorn-test-custom",
1016+
Namespace: c.GetNamespace(),
1017+
},
1018+
Default: true,
1019+
Memory: adminv1.ComputeClassMemory{
1020+
Default: "512Mi",
1021+
Max: "1Gi",
1022+
Min: "512Mi",
1023+
},
1024+
SupportedRegions: []string{apiv1.LocalRegion},
1025+
},
1026+
{
1027+
ObjectMeta: metav1.ObjectMeta{
1028+
Name: "acorn-test-custom-2",
1029+
Namespace: c.GetNamespace(),
1030+
},
1031+
Default: true,
1032+
Memory: adminv1.ComputeClassMemory{
1033+
Default: "513Mi",
1034+
Max: "1Gi",
1035+
Min: "512Mi",
1036+
},
1037+
SupportedRegions: []string{"local2"},
1038+
},
1039+
},
1040+
expected: map[string]v1.Scheduling{"simple": {
1041+
Requirements: corev1.ResourceRequirements{
1042+
Limits: corev1.ResourceList{
1043+
corev1.ResourceMemory: resource.MustParse("513Mi")},
1044+
Requests: corev1.ResourceList{
1045+
corev1.ResourceMemory: resource.MustParse("513Mi"),
1046+
},
1047+
},
1048+
Tolerations: []corev1.Toleration{
1049+
{
1050+
Key: tolerations.WorkloadTolerationKey,
1051+
Operator: corev1.TolerationOpExists,
1052+
},
1053+
}},
1054+
},
1055+
waitFor: func(obj *v1.AppInstance) bool {
1056+
return obj.Status.Condition(v1.AppInstanceConditionParsed).Success &&
1057+
obj.Status.Condition(v1.AppInstanceConditionScheduling).Success
1058+
},
1059+
},
1060+
{
1061+
name: "acornfile-computeclass",
1062+
testDataDirectory: "./testdata/computeclass",
1063+
region: apiv1.LocalRegion,
1064+
computeClasses: []adminv1.ProjectComputeClassInstance{
1065+
{
1066+
ObjectMeta: metav1.ObjectMeta{
1067+
Name: "acorn-test-custom",
1068+
Namespace: c.GetNamespace(),
1069+
},
1070+
Default: true,
1071+
Memory: adminv1.ComputeClassMemory{
1072+
Default: "512Mi",
1073+
Max: "1Gi",
1074+
Min: "512Mi",
1075+
},
1076+
SupportedRegions: []string{apiv1.LocalRegion},
1077+
},
1078+
{
1079+
ObjectMeta: metav1.ObjectMeta{
1080+
Name: "acorn-test-custom-2",
1081+
Namespace: c.GetNamespace(),
1082+
},
1083+
Default: true,
1084+
Memory: adminv1.ComputeClassMemory{
1085+
Default: "513Mi",
1086+
Max: "1Gi",
1087+
Min: "512Mi",
1088+
},
1089+
SupportedRegions: []string{"local2"},
1090+
},
1091+
},
1092+
expected: map[string]v1.Scheduling{"simple": {
1093+
Requirements: corev1.ResourceRequirements{
1094+
Limits: corev1.ResourceList{
1095+
corev1.ResourceMemory: resource.MustParse("512Mi")},
1096+
Requests: corev1.ResourceList{
1097+
corev1.ResourceMemory: resource.MustParse("512Mi"),
1098+
},
1099+
},
1100+
Tolerations: []corev1.Toleration{
1101+
{
1102+
Key: tolerations.WorkloadTolerationKey,
1103+
Operator: corev1.TolerationOpExists,
1104+
},
1105+
}},
1106+
},
1107+
waitFor: func(obj *v1.AppInstance) bool {
1108+
return obj.Status.Condition(v1.AppInstanceConditionParsed).Success &&
1109+
obj.Status.Condition(v1.AppInstanceConditionScheduling).Success
1110+
},
1111+
},
1112+
{
1113+
name: "acornfile-computeclass-wrong-region",
1114+
testDataDirectory: "./testdata/computeclass",
1115+
region: apiv1.LocalRegion,
1116+
computeClasses: []adminv1.ProjectComputeClassInstance{
1117+
{
1118+
ObjectMeta: metav1.ObjectMeta{
1119+
Name: "acorn-test-custom-2",
1120+
Namespace: c.GetNamespace(),
1121+
},
1122+
Default: true,
1123+
Memory: adminv1.ComputeClassMemory{
1124+
Default: "512Mi",
1125+
Max: "1Gi",
1126+
Min: "512Mi",
1127+
},
1128+
SupportedRegions: []string{apiv1.LocalRegion},
1129+
},
1130+
{
1131+
ObjectMeta: metav1.ObjectMeta{
1132+
Name: "acorn-test-custom",
1133+
Namespace: c.GetNamespace(),
1134+
},
1135+
Default: true,
1136+
Memory: adminv1.ComputeClassMemory{
1137+
Default: "513Mi",
1138+
Max: "1Gi",
1139+
Min: "512Mi",
1140+
},
1141+
SupportedRegions: []string{"local2"},
1142+
},
1143+
},
1144+
fail: true,
1145+
},
1146+
}
1147+
1148+
for _, tt := range checks {
1149+
clusterObjects := make([]crClient.Object, 0, len(tt.computeClasses))
1150+
for _, pcc := range tt.computeClasses {
1151+
cc := adminv1.ClusterComputeClassInstance(pcc)
1152+
clusterObjects = append(clusterObjects, cc.DeepCopy())
1153+
}
1154+
projectObjects := make([]crClient.Object, 0, len(tt.computeClasses))
1155+
for _, cc := range tt.computeClasses {
1156+
projectObjects = append(projectObjects, cc.DeepCopy())
1157+
}
1158+
// Perform the same test cases on both Project and Cluster ComputeClasses
1159+
for kind, computeClasses := range map[string][]crClient.Object{"projectcomputeclass": projectObjects, "clustercomputeclass": clusterObjects} {
1160+
testcase := fmt.Sprintf("%v-%v", kind, tt.name)
1161+
t.Run(testcase, func(t *testing.T) {
1162+
if !tt.noComputeClass {
1163+
for _, computeClass := range computeClasses {
1164+
if err := kclient.Create(ctx, computeClass); err != nil {
1165+
t.Fatal(err)
1166+
}
1167+
}
1168+
1169+
// Clean-up and guarantee the computeclass doesn't exist after this test run
1170+
t.Cleanup(func() {
1171+
for _, computeClass := range computeClasses {
1172+
if err = kclient.Delete(context.Background(), computeClass); err != nil && !apierrors.IsNotFound(err) {
1173+
t.Fatal(err)
1174+
}
1175+
err := helper.EnsureDoesNotExist(ctx, func() (crClient.Object, error) {
1176+
lookingFor := computeClass
1177+
err := kclient.Get(ctx, router.Key(computeClass.GetNamespace(), computeClass.GetName()), lookingFor)
1178+
return lookingFor, err
1179+
})
1180+
if err != nil {
1181+
t.Fatal(err)
1182+
}
1183+
}
1184+
})
1185+
}
1186+
1187+
image, err := c.AcornImageBuild(ctx, tt.testDataDirectory+"/Acornfile", &client.AcornImageBuildOptions{
1188+
Cwd: tt.testDataDirectory,
1189+
})
1190+
if err != nil {
1191+
t.Fatal(err)
1192+
}
1193+
1194+
// Assign a name for the test case so no collisions occur
1195+
app, err := c.AppRun(ctx, image.ID, &client.AppRunOptions{
1196+
Name: testcase,
1197+
Region: tt.region,
1198+
})
1199+
if err == nil && tt.fail {
1200+
t.Fatal("expected error, got nil")
1201+
} else if err != nil {
1202+
if !tt.fail {
1203+
t.Fatal(err)
1204+
}
1205+
}
1206+
1207+
// Clean-up and gurantee the app doesn't exist after this test run
1208+
if app != nil {
1209+
t.Cleanup(func() {
1210+
if err = kclient.Delete(context.Background(), app); err != nil && !apierrors.IsNotFound(err) {
1211+
t.Fatal(err)
1212+
}
1213+
err := helper.EnsureDoesNotExist(ctx, func() (crClient.Object, error) {
1214+
lookingFor := app
1215+
err := kclient.Get(ctx, router.Key(app.GetName(), app.GetNamespace()), lookingFor)
1216+
return lookingFor, err
1217+
})
1218+
if err != nil {
1219+
t.Fatal(err)
1220+
}
1221+
})
1222+
}
1223+
1224+
if tt.waitFor != nil {
1225+
appInstance := &v1.AppInstance{
1226+
ObjectMeta: metav1.ObjectMeta{
1227+
Name: app.Name,
1228+
Namespace: app.Namespace,
1229+
},
1230+
}
1231+
appInstance = helper.WaitForObject(t, kclient.Watch, new(v1.AppInstanceList), appInstance, tt.waitFor)
1232+
assert.EqualValues(t, appInstance.Status.Scheduling, tt.expected, "generated scheduling rules are incorrect")
1233+
}
1234+
})
1235+
}
1236+
}
1237+
}
1238+
9341239
func TestUsingComputeClasses(t *testing.T) {
9351240
helper.StartController(t)
9361241
c, _ := helper.ClientAndProject(t)
@@ -1132,7 +1437,7 @@ func TestUsingComputeClasses(t *testing.T) {
11321437
},
11331438
{
11341439
name: "unsupported-region",
1135-
testDataDirectory: "./testdata/simple",
1440+
testDataDirectory: "./testdata/computeclass",
11361441
computeClass: adminv1.ProjectComputeClassInstance{
11371442
ObjectMeta: metav1.ObjectMeta{
11381443
Name: "acorn-test-custom",
@@ -1160,7 +1465,7 @@ func TestUsingComputeClasses(t *testing.T) {
11601465
for _, tt := range checks {
11611466
asClusterComputeClass := adminv1.ClusterComputeClassInstance(tt.computeClass)
11621467
// Perform the same test cases on both Project and Cluster ComputeClasses
1163-
for kind, computeClass := range map[string]crClient.Object{"projectcomputeclass": &tt.computeClass, "clustercomputeclass": &asClusterComputeClass} {
1468+
for kind, computeClass := range map[string]crClient.Object{"projectcomputeclass": &tt.computeClass, "clustercomputeclass": asClusterComputeClass.DeepCopy()} {
11641469
testcase := fmt.Sprintf("%v-%v", kind, tt.name)
11651470
t.Run(testcase, func(t *testing.T) {
11661471
if !tt.noComputeClass {

0 commit comments

Comments
 (0)