Skip to content

Commit 39b48d2

Browse files
committed
Setup existing DW tests to run in CFO
1 parent 1c7f229 commit 39b48d2

10 files changed

+909
-0
lines changed

test/odh/environment.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Copyright 2023.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package odh
18+
19+
import (
20+
"os"
21+
22+
cfosupport "github.com/project-codeflare/codeflare-common/support"
23+
)
24+
25+
const (
26+
// The environment variable for namespace where ODH is installed to.
27+
odhNamespaceEnvVar = "ODH_NAMESPACE"
28+
// The environment variable for ODH Notebook ImageStream name
29+
notebookImageStreamName = "NOTEBOOK_IMAGE_STREAM_NAME"
30+
)
31+
32+
func GetOpenDataHubNamespace() string {
33+
return lookupEnvOrDefault(odhNamespaceEnvVar, "opendatahub")
34+
}
35+
36+
func GetNotebookImageStreamName(t cfosupport.Test) string {
37+
isName, ok := os.LookupEnv(notebookImageStreamName)
38+
if !ok {
39+
t.T().Fatalf("Expected environment variable %s not found, please use this environment variable to specify what ImageStream to use for Notebook.", notebookImageStreamName)
40+
}
41+
return isName
42+
}
43+
44+
func lookupEnvOrDefault(key, value string) string {
45+
if v, ok := os.LookupEnv(key); ok {
46+
return v
47+
}
48+
return value
49+
}

test/odh/mcad_ray_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright 2023.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package odh
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/gomega"
23+
cfosupport "github.com/project-codeflare/codeflare-common/support"
24+
mcadv1beta1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1"
25+
rayv1alpha1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1alpha1"
26+
27+
rbacv1 "k8s.io/api/rbac/v1"
28+
)
29+
30+
func TestMCADRay(t *testing.T) {
31+
test := cfosupport.With(t)
32+
33+
// Create a namespace
34+
namespace := test.NewTestNamespace()
35+
36+
// Test configuration
37+
jupyterNotebookConfigMapFileName := "mnist_ray_mini.ipynb"
38+
config := cfosupport.CreateConfigMap(test, namespace.Name, map[string][]byte{
39+
// MNIST Ray Notebook
40+
jupyterNotebookConfigMapFileName: ReadFile(test, "resources/mnist_ray_mini.ipynb"),
41+
"mnist.py": ReadFile(test, "resources/mnist.py"),
42+
"requirements.txt": ReadFile(test, "resources/requirements.txt"),
43+
})
44+
45+
// Create RBAC, retrieve token for user with limited rights
46+
policyRules := []rbacv1.PolicyRule{
47+
{
48+
Verbs: []string{"get", "create", "delete", "list", "patch", "update"},
49+
APIGroups: []string{mcadv1beta1.GroupName},
50+
Resources: []string{"appwrappers"},
51+
},
52+
{
53+
Verbs: []string{"get", "list"},
54+
APIGroups: []string{rayv1alpha1.GroupVersion.Group},
55+
Resources: []string{"rayclusters", "rayclusters/status"},
56+
},
57+
{
58+
Verbs: []string{"get", "list"},
59+
APIGroups: []string{"route.openshift.io"},
60+
Resources: []string{"routes"},
61+
},
62+
}
63+
sa := cfosupport.CreateServiceAccount(test, namespace.Name)
64+
role := cfosupport.CreateRole(test, namespace.Name, policyRules)
65+
cfosupport.CreateRoleBinding(test, namespace.Name, sa, role)
66+
token := cfosupport.CreateToken(test, namespace.Name, sa)
67+
68+
// Create Notebook CR
69+
createNotebook(test, namespace, token, config.Name, jupyterNotebookConfigMapFileName)
70+
71+
// Make sure the AppWrapper is created and running
72+
test.Eventually(cfosupport.AppWrappers(test, namespace), cfosupport.TestTimeoutLong).
73+
Should(
74+
And(
75+
HaveLen(1),
76+
ContainElement(WithTransform(cfosupport.AppWrapperName, HavePrefix("mnisttest"))),
77+
ContainElement(WithTransform(cfosupport.AppWrapperState, Equal(mcadv1beta1.AppWrapperStateActive))),
78+
),
79+
)
80+
81+
// Make sure the AppWrapper finishes and is deleted
82+
test.Eventually(cfosupport.AppWrappers(test, namespace), cfosupport.TestTimeoutLong).
83+
Should(HaveLen(0))
84+
}

test/odh/notebook.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
Copyright 2023.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package odh
18+
19+
import (
20+
"bytes"
21+
"html/template"
22+
23+
gomega "github.com/onsi/gomega"
24+
cfosupport "github.com/project-codeflare/codeflare-common/support"
25+
26+
imagev1 "github.com/openshift/api/image/v1"
27+
corev1 "k8s.io/api/core/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30+
"k8s.io/apimachinery/pkg/runtime/schema"
31+
"k8s.io/apimachinery/pkg/util/yaml"
32+
)
33+
34+
const recommendedTagAnnotation = "opendatahub.io/workbench-image-recommended"
35+
36+
var notebookResource = schema.GroupVersionResource{Group: "kubeflow.org", Version: "v1", Resource: "notebooks"}
37+
38+
type NotebookProps struct {
39+
IngressDomain string
40+
OpenShiftApiUrl string
41+
KubernetesBearerToken string
42+
Namespace string
43+
OpenDataHubNamespace string
44+
ImageStreamName string
45+
ImageStreamTag string
46+
NotebookConfigMapName string
47+
NotebookConfigMapFileName string
48+
NotebookPVC string
49+
}
50+
51+
func createNotebook(test cfosupport.Test, namespace *corev1.Namespace, notebookToken, jupyterNotebookConfigMapName, jupyterNotebookConfigMapFileName string) {
52+
// Create PVC for Notebook
53+
notebookPVC := cfosupport.CreatePersistentVolumeClaim(test, namespace.Name, "10Gi", corev1.ReadWriteOnce)
54+
55+
// Retrieve ImageStream tag for
56+
is := cfosupport.GetImageStream(test, GetOpenDataHubNamespace(), GetNotebookImageStreamName(test))
57+
recommendedTagName := getRecommendedImageStreamTag(test, is)
58+
59+
// Read the Notebook CR from resources and perform replacements for custom values using go template
60+
notebookProps := NotebookProps{
61+
IngressDomain: cfosupport.GetOpenShiftIngressDomain(test),
62+
OpenShiftApiUrl: cfosupport.GetOpenShiftApiUrl(test),
63+
KubernetesBearerToken: notebookToken,
64+
Namespace: namespace.Name,
65+
OpenDataHubNamespace: GetOpenDataHubNamespace(),
66+
ImageStreamName: GetNotebookImageStreamName(test),
67+
ImageStreamTag: recommendedTagName,
68+
NotebookConfigMapName: jupyterNotebookConfigMapName,
69+
NotebookConfigMapFileName: jupyterNotebookConfigMapFileName,
70+
NotebookPVC: notebookPVC.Name,
71+
}
72+
notebookTemplate, err := files.ReadFile("resources/custom-nb-small.yaml")
73+
test.Expect(err).NotTo(gomega.HaveOccurred())
74+
parsedNotebookTemplate, err := template.New("notebook").Parse(string(notebookTemplate))
75+
test.Expect(err).NotTo(gomega.HaveOccurred())
76+
77+
// Filter template and store results to the buffer
78+
notebookBuffer := new(bytes.Buffer)
79+
err = parsedNotebookTemplate.Execute(notebookBuffer, notebookProps)
80+
test.Expect(err).NotTo(gomega.HaveOccurred())
81+
82+
// Create Notebook CR
83+
notebookCR := &unstructured.Unstructured{}
84+
err = yaml.NewYAMLOrJSONDecoder(notebookBuffer, 8192).Decode(notebookCR)
85+
test.Expect(err).NotTo(gomega.HaveOccurred())
86+
_, err = test.Client().Dynamic().Resource(notebookResource).Namespace(namespace.Name).Create(test.Ctx(), notebookCR, metav1.CreateOptions{})
87+
test.Expect(err).NotTo(gomega.HaveOccurred())
88+
}
89+
90+
func getRecommendedImageStreamTag(test cfosupport.Test, is *imagev1.ImageStream) (tagName string) {
91+
for _, tag := range is.Spec.Tags {
92+
if tag.Annotations[recommendedTagAnnotation] == "true" {
93+
return tag.Name
94+
}
95+
}
96+
test.T().Fatalf("tag with annotation '%s' not found in ImageStream %s", recommendedTagAnnotation, is.Name)
97+
return
98+
}

test/odh/pytorch_mcad_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
Copyright 2023.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package odh
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/gomega"
23+
cfosupport "github.com/project-codeflare/codeflare-common/support"
24+
mcadv1beta1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1"
25+
26+
rbacv1 "k8s.io/api/rbac/v1"
27+
)
28+
29+
func TestMnistPyTorchMCAD(t *testing.T) {
30+
test := cfosupport.With(t)
31+
32+
// Create a namespace
33+
namespace := test.NewTestNamespace()
34+
35+
// Test configuration
36+
jupyterNotebookConfigMapFileName := "mnist_mcad_mini.ipynb"
37+
config := cfosupport.CreateConfigMap(test, namespace.Name, map[string][]byte{
38+
// MNIST MCAD Notebook
39+
jupyterNotebookConfigMapFileName: ReadFile(test, "resources/mnist_mcad_mini.ipynb"),
40+
})
41+
42+
// Create RBAC, retrieve token for user with limited rights
43+
policyRules := []rbacv1.PolicyRule{
44+
{
45+
Verbs: []string{"get", "create", "delete", "list", "patch", "update"},
46+
APIGroups: []string{mcadv1beta1.GroupName},
47+
Resources: []string{"appwrappers"},
48+
},
49+
// Needed for job.logs()
50+
{
51+
Verbs: []string{"get"},
52+
APIGroups: []string{""},
53+
Resources: []string{"pods/log"},
54+
},
55+
}
56+
sa := cfosupport.CreateServiceAccount(test, namespace.Name)
57+
role := cfosupport.CreateRole(test, namespace.Name, policyRules)
58+
cfosupport.CreateRoleBinding(test, namespace.Name, sa, role)
59+
token := cfosupport.CreateToken(test, namespace.Name, sa)
60+
61+
// Create Notebook CR
62+
createNotebook(test, namespace, token, config.Name, jupyterNotebookConfigMapFileName)
63+
64+
// Make sure the AppWrapper is created and running
65+
test.Eventually(cfosupport.AppWrappers(test, namespace), cfosupport.TestTimeoutLong).
66+
Should(
67+
And(
68+
HaveLen(1),
69+
ContainElement(WithTransform(cfosupport.AppWrapperName, HavePrefix("mnistjob"))),
70+
ContainElement(WithTransform(cfosupport.AppWrapperState, Equal(mcadv1beta1.AppWrapperStateActive))),
71+
),
72+
)
73+
74+
// Make sure the AppWrapper finishes and is deleted
75+
test.Eventually(cfosupport.AppWrappers(test, namespace), cfosupport.TestTimeoutLong).
76+
Should(HaveLen(0))
77+
}

0 commit comments

Comments
 (0)