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

Commit

Permalink
fix: handle multiple compute classes in a project (#2459)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oscar Ward authored Jan 30, 2024
1 parent e0bfed6 commit ce9166e
Show file tree
Hide file tree
Showing 30 changed files with 382 additions and 93 deletions.
9 changes: 7 additions & 2 deletions integration/helper/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

func TempProject(t *testing.T, client client.WithWatch) *v1.ProjectInstance {
func TempProjectWithRegions(t *testing.T, client client.WithWatch, regions []string) *v1.ProjectInstance {
t.Helper()
project := &v1.ProjectInstance{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -26,7 +26,7 @@ func TempProject(t *testing.T, client client.WithWatch) *v1.ProjectInstance {
},
Spec: v1.ProjectInstanceSpec{
DefaultRegion: apiv1.LocalRegion,
SupportedRegions: []string{apiv1.LocalRegion},
SupportedRegions: regions,
},
}

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

func TempProject(t *testing.T, client client.WithWatch) *v1.ProjectInstance {
t.Helper()
return TempProjectWithRegions(t, client, []string{apiv1.LocalRegion})
}

// createAllowAllIAR creates an ImageAllowRule that allows all images and has no extra rules
// This is necessary, since while testing IARs, we enable the feature flag and it seems to leak
// into other tests, blocking images there, even though the tests shouldn't run in parallel and the config
Expand Down
309 changes: 307 additions & 2 deletions integration/run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,311 @@ func TestDeployParam(t *testing.T) {
assert.Equal(t, "5", appInstance.Status.AppSpec.Containers["foo"].Environment[0].Value)
}

func TestMultipleDefaultComputeClass(t *testing.T) {
helper.StartController(t)
cfg := helper.StartAPI(t)
project := helper.TempProjectWithRegions(t, helper.MustReturn(kclient.Default), []string{apiv1.LocalRegion, "local2"})
kclient := helper.MustReturn(kclient.Default)
c, err := client.New(cfg, "", project.Name)
if err != nil {
t.Fatal(err)
}

ctx := helper.GetCTX(t)

checks := []struct {
name string
noComputeClass bool
testDataDirectory string
region string
computeClasses []adminv1.ProjectComputeClassInstance
expected map[string]v1.Scheduling
waitFor func(obj *v1.AppInstance) bool
fail bool
}{
{
name: "local-default",
testDataDirectory: "./testdata/simple",
region: apiv1.LocalRegion,
computeClasses: []adminv1.ProjectComputeClassInstance{
{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom",
Namespace: c.GetNamespace(),
},
Default: true,
Memory: adminv1.ComputeClassMemory{
Default: "512Mi",
Max: "1Gi",
Min: "512Mi",
},
SupportedRegions: []string{apiv1.LocalRegion},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom-2",
Namespace: c.GetNamespace(),
},
Default: true,
Memory: adminv1.ComputeClassMemory{
Default: "513Mi",
Max: "1Gi",
Min: "512Mi",
},
SupportedRegions: []string{"local2"},
},
},
expected: map[string]v1.Scheduling{"simple": {
Requirements: corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi")},
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi"),
},
},
Tolerations: []corev1.Toleration{
{
Key: tolerations.WorkloadTolerationKey,
Operator: corev1.TolerationOpExists,
},
}},
},
waitFor: func(obj *v1.AppInstance) bool {
return obj.Status.Condition(v1.AppInstanceConditionParsed).Success &&
obj.Status.Condition(v1.AppInstanceConditionScheduling).Success
},
},
{
name: "local2-default",
testDataDirectory: "./testdata/simple",
region: "local2",
computeClasses: []adminv1.ProjectComputeClassInstance{
{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom",
Namespace: c.GetNamespace(),
},
Default: true,
Memory: adminv1.ComputeClassMemory{
Default: "512Mi",
Max: "1Gi",
Min: "512Mi",
},
SupportedRegions: []string{apiv1.LocalRegion},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom-2",
Namespace: c.GetNamespace(),
},
Default: true,
Memory: adminv1.ComputeClassMemory{
Default: "513Mi",
Max: "1Gi",
Min: "512Mi",
},
SupportedRegions: []string{"local2"},
},
},
expected: map[string]v1.Scheduling{"simple": {
Requirements: corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("513Mi")},
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("513Mi"),
},
},
Tolerations: []corev1.Toleration{
{
Key: tolerations.WorkloadTolerationKey,
Operator: corev1.TolerationOpExists,
},
}},
},
waitFor: func(obj *v1.AppInstance) bool {
return obj.Status.Condition(v1.AppInstanceConditionParsed).Success &&
obj.Status.Condition(v1.AppInstanceConditionScheduling).Success
},
},
{
name: "acornfile-computeclass",
testDataDirectory: "./testdata/computeclass",
region: apiv1.LocalRegion,
computeClasses: []adminv1.ProjectComputeClassInstance{
{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom",
Namespace: c.GetNamespace(),
},
Default: true,
Memory: adminv1.ComputeClassMemory{
Default: "512Mi",
Max: "1Gi",
Min: "512Mi",
},
SupportedRegions: []string{apiv1.LocalRegion},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom-2",
Namespace: c.GetNamespace(),
},
Default: true,
Memory: adminv1.ComputeClassMemory{
Default: "513Mi",
Max: "1Gi",
Min: "512Mi",
},
SupportedRegions: []string{"local2"},
},
},
expected: map[string]v1.Scheduling{"simple": {
Requirements: corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi")},
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi"),
},
},
Tolerations: []corev1.Toleration{
{
Key: tolerations.WorkloadTolerationKey,
Operator: corev1.TolerationOpExists,
},
}},
},
waitFor: func(obj *v1.AppInstance) bool {
return obj.Status.Condition(v1.AppInstanceConditionParsed).Success &&
obj.Status.Condition(v1.AppInstanceConditionScheduling).Success
},
},
{
name: "acornfile-computeclass-wrong-region",
testDataDirectory: "./testdata/computeclass",
region: apiv1.LocalRegion,
computeClasses: []adminv1.ProjectComputeClassInstance{
{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom-2",
Namespace: c.GetNamespace(),
},
Default: true,
Memory: adminv1.ComputeClassMemory{
Default: "512Mi",
Max: "1Gi",
Min: "512Mi",
},
SupportedRegions: []string{apiv1.LocalRegion},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom",
Namespace: c.GetNamespace(),
},
Default: true,
Memory: adminv1.ComputeClassMemory{
Default: "513Mi",
Max: "1Gi",
Min: "512Mi",
},
SupportedRegions: []string{"local2"},
},
},
fail: true,
},
}

for _, tt := range checks {
clusterObjects := make([]crClient.Object, 0, len(tt.computeClasses))
for _, pcc := range tt.computeClasses {
cc := adminv1.ClusterComputeClassInstance(pcc)
clusterObjects = append(clusterObjects, cc.DeepCopy())
}
projectObjects := make([]crClient.Object, 0, len(tt.computeClasses))
for _, cc := range tt.computeClasses {
projectObjects = append(projectObjects, cc.DeepCopy())
}
// Perform the same test cases on both Project and Cluster ComputeClasses
for kind, computeClasses := range map[string][]crClient.Object{"projectcomputeclass": projectObjects, "clustercomputeclass": clusterObjects} {
testcase := fmt.Sprintf("%v-%v", kind, tt.name)
t.Run(testcase, func(t *testing.T) {
if !tt.noComputeClass {
for _, computeClass := range computeClasses {
if err := kclient.Create(ctx, computeClass); err != nil {
t.Fatal(err)
}
}

// Clean-up and guarantee the computeclass doesn't exist after this test run
t.Cleanup(func() {
for _, computeClass := range computeClasses {
if err = kclient.Delete(context.Background(), computeClass); err != nil && !apierrors.IsNotFound(err) {
t.Fatal(err)
}
err := helper.EnsureDoesNotExist(ctx, func() (crClient.Object, error) {
lookingFor := computeClass
err := kclient.Get(ctx, router.Key(computeClass.GetNamespace(), computeClass.GetName()), lookingFor)
return lookingFor, err
})
if err != nil {
t.Fatal(err)
}
}
})
}

image, err := c.AcornImageBuild(ctx, tt.testDataDirectory+"/Acornfile", &client.AcornImageBuildOptions{
Cwd: tt.testDataDirectory,
})
if err != nil {
t.Fatal(err)
}

// Assign a name for the test case so no collisions occur
app, err := c.AppRun(ctx, image.ID, &client.AppRunOptions{
Name: testcase,
Region: tt.region,
})
if err == nil && tt.fail {
t.Fatal("expected error, got nil")
} else if err != nil {
if !tt.fail {
t.Fatal(err)
}
}

// Clean-up and gurantee the app doesn't exist after this test run
if app != nil {
t.Cleanup(func() {
if err = kclient.Delete(context.Background(), app); err != nil && !apierrors.IsNotFound(err) {
t.Fatal(err)
}
err := helper.EnsureDoesNotExist(ctx, func() (crClient.Object, error) {
lookingFor := app
err := kclient.Get(ctx, router.Key(app.GetName(), app.GetNamespace()), lookingFor)
return lookingFor, err
})
if err != nil {
t.Fatal(err)
}
})
}

if tt.waitFor != nil {
appInstance := &v1.AppInstance{
ObjectMeta: metav1.ObjectMeta{
Name: app.Name,
Namespace: app.Namespace,
},
}
appInstance = helper.WaitForObject(t, kclient.Watch, new(v1.AppInstanceList), appInstance, tt.waitFor)
assert.EqualValues(t, appInstance.Status.Scheduling, tt.expected, "generated scheduling rules are incorrect")
}
})
}
}
}

func TestUsingComputeClasses(t *testing.T) {
helper.StartController(t)
c, _ := helper.ClientAndProject(t)
Expand Down Expand Up @@ -1132,7 +1437,7 @@ func TestUsingComputeClasses(t *testing.T) {
},
{
name: "unsupported-region",
testDataDirectory: "./testdata/simple",
testDataDirectory: "./testdata/computeclass",
computeClass: adminv1.ProjectComputeClassInstance{
ObjectMeta: metav1.ObjectMeta{
Name: "acorn-test-custom",
Expand Down Expand Up @@ -1160,7 +1465,7 @@ func TestUsingComputeClasses(t *testing.T) {
for _, tt := range checks {
asClusterComputeClass := adminv1.ClusterComputeClassInstance(tt.computeClass)
// Perform the same test cases on both Project and Cluster ComputeClasses
for kind, computeClass := range map[string]crClient.Object{"projectcomputeclass": &tt.computeClass, "clustercomputeclass": &asClusterComputeClass} {
for kind, computeClass := range map[string]crClient.Object{"projectcomputeclass": &tt.computeClass, "clustercomputeclass": asClusterComputeClass.DeepCopy()} {
testcase := fmt.Sprintf("%v-%v", kind, tt.name)
t.Run(testcase, func(t *testing.T) {
if !tt.noComputeClass {
Expand Down
Loading

0 comments on commit ce9166e

Please sign in to comment.