Skip to content

Commit c0d7fac

Browse files
committed
Add RayCLuster Oauth Authentication test
1 parent a4590ea commit c0d7fac

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed

test/e2e/mnist_raycluster_sdk_auth.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import sys
2+
import os
3+
4+
from time import sleep
5+
6+
from codeflare_sdk.cluster.cluster import Cluster, ClusterConfiguration
7+
from codeflare_sdk.job.jobs import DDPJobDefinition
8+
9+
namespace = sys.argv[1]
10+
ray_image = os.getenv('RAY_IMAGE')
11+
12+
cluster = Cluster(ClusterConfiguration(
13+
name='mnist',
14+
namespace=namespace,
15+
num_workers=1,
16+
head_cpus='500m',
17+
head_memory=2,
18+
min_cpus='500m',
19+
max_cpus=1,
20+
min_memory=0.5,
21+
max_memory=2,
22+
num_gpus=0,
23+
instascale=False,
24+
image=ray_image,
25+
openshift_oauth=True,
26+
))
27+
28+
cluster.up()
29+
30+
cluster.status()
31+
32+
cluster.wait_ready()
33+
34+
cluster.status()
35+
36+
cluster.details()
37+
38+
jobdef = DDPJobDefinition(
39+
name="mnist",
40+
script="mnist.py",
41+
scheduler_args={"requirements": "requirements.txt"},
42+
)
43+
job = jobdef.submit(cluster)
44+
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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 e2e
18+
19+
import (
20+
"testing"
21+
22+
"github.com/onsi/gomega"
23+
. "github.com/onsi/gomega"
24+
. "github.com/project-codeflare/codeflare-common/support"
25+
mcadv1beta1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1"
26+
rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
27+
28+
batchv1 "k8s.io/api/batch/v1"
29+
corev1 "k8s.io/api/core/v1"
30+
rbacv1 "k8s.io/api/rbac/v1"
31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32+
)
33+
34+
// This test covers the Ray Cluster creation authentication functionality with openshift_oauth
35+
func TestRayClusterSDKAuth(t *testing.T) {
36+
test := With(t)
37+
test.T().Parallel()
38+
if GetClusterType(test) == KindCluster {
39+
test.T().Skipf("Skipping test as not running on an openshift cluster")
40+
}
41+
42+
// Create a namespace
43+
namespace := test.NewTestNamespace()
44+
45+
// Test configuration
46+
config := CreateConfigMap(test, namespace.Name, map[string][]byte{
47+
// SDK script
48+
"mnist_raycluster_sdk_auth.py": ReadFile(test, "mnist_raycluster_sdk_auth.py"),
49+
// pip requirements
50+
"requirements.txt": ReadFile(test, "mnist_pip_requirements.txt"),
51+
// MNIST training script
52+
"mnist.py": ReadFile(test, "mnist.py"),
53+
// codeflare-sdk installation script
54+
"install-codeflare-sdk.sh": ReadFile(test, "install-codeflare-sdk.sh"),
55+
})
56+
57+
// // Create RBAC, retrieve token for user with limited rights
58+
policyRules := []rbacv1.PolicyRule{
59+
{
60+
Verbs: []string{"get", "create", "delete", "list", "patch", "update"},
61+
APIGroups: []string{mcadv1beta1.GroupName},
62+
Resources: []string{"appwrappers"},
63+
},
64+
{
65+
Verbs: []string{"get", "list"},
66+
APIGroups: []string{rayv1.GroupVersion.Group},
67+
Resources: []string{"rayclusters", "rayclusters/status"},
68+
},
69+
{
70+
Verbs: []string{"get", "list"},
71+
APIGroups: []string{"route.openshift.io"},
72+
Resources: []string{"routes"},
73+
},
74+
{
75+
Verbs: []string{"get", "list"},
76+
APIGroups: []string{"networking.k8s.io"},
77+
Resources: []string{"ingresses"},
78+
},
79+
}
80+
81+
// // Create cluster wide RBAC, required for SDK OpenShift check
82+
// // TODO reevaluate once SDK change OpenShift detection logic
83+
clusterPolicyRules := []rbacv1.PolicyRule{
84+
{
85+
Verbs: []string{"get", "list"},
86+
APIGroups: []string{"config.openshift.io"},
87+
Resources: []string{"ingresses"},
88+
ResourceNames: []string{"cluster"},
89+
},
90+
}
91+
92+
sa := CreateServiceAccount(test, namespace.Name)
93+
role := CreateRole(test, namespace.Name, policyRules)
94+
CreateRoleBinding(test, namespace.Name, sa, role)
95+
clusterRole := CreateClusterRole(test, clusterPolicyRules)
96+
CreateClusterRoleBinding(test, sa, clusterRole)
97+
98+
job := &batchv1.Job{
99+
TypeMeta: metav1.TypeMeta{
100+
APIVersion: batchv1.SchemeGroupVersion.String(),
101+
Kind: "Job",
102+
},
103+
ObjectMeta: metav1.ObjectMeta{
104+
Name: "sdk",
105+
Namespace: namespace.Name,
106+
},
107+
Spec: batchv1.JobSpec{
108+
Completions: Ptr(int32(1)),
109+
Parallelism: Ptr(int32(1)),
110+
BackoffLimit: Ptr(int32(0)),
111+
Template: corev1.PodTemplateSpec{
112+
Spec: corev1.PodSpec{
113+
Containers: []corev1.Container{
114+
{
115+
Name: "test",
116+
// FIXME: switch to base Python image once the dependency on OpenShift CLI is removed
117+
// See https://github.com/project-codeflare/codeflare-sdk/pull/146
118+
Image: "quay.io/opendatahub/notebooks:jupyter-minimal-ubi8-python-3.8-4c8f26e",
119+
Env: []corev1.EnvVar{
120+
{Name: "PYTHONUSERBASE", Value: "/workdir"},
121+
{Name: "RAY_IMAGE", Value: GetRayImage()},
122+
},
123+
Command: []string{"/bin/sh", "-c", "cp /test/* . && chmod +x install-codeflare-sdk.sh && ./install-codeflare-sdk.sh && python mnist_raycluster_sdk_auth.py" + " " + namespace.Name},
124+
VolumeMounts: []corev1.VolumeMount{
125+
{
126+
Name: "test",
127+
MountPath: "/test",
128+
},
129+
{
130+
Name: "codeflare-sdk",
131+
MountPath: "/codeflare-sdk",
132+
},
133+
{
134+
Name: "workdir",
135+
MountPath: "/workdir",
136+
},
137+
},
138+
WorkingDir: "/workdir",
139+
SecurityContext: &corev1.SecurityContext{
140+
AllowPrivilegeEscalation: Ptr(false),
141+
SeccompProfile: &corev1.SeccompProfile{
142+
Type: "RuntimeDefault",
143+
},
144+
Capabilities: &corev1.Capabilities{
145+
Drop: []corev1.Capability{"ALL"},
146+
},
147+
RunAsNonRoot: Ptr(true),
148+
},
149+
},
150+
},
151+
Volumes: []corev1.Volume{
152+
{
153+
Name: "test",
154+
VolumeSource: corev1.VolumeSource{
155+
ConfigMap: &corev1.ConfigMapVolumeSource{
156+
LocalObjectReference: corev1.LocalObjectReference{
157+
Name: config.Name,
158+
},
159+
},
160+
},
161+
},
162+
{
163+
Name: "codeflare-sdk",
164+
VolumeSource: corev1.VolumeSource{
165+
EmptyDir: &corev1.EmptyDirVolumeSource{},
166+
},
167+
},
168+
{
169+
Name: "workdir",
170+
VolumeSource: corev1.VolumeSource{
171+
EmptyDir: &corev1.EmptyDirVolumeSource{},
172+
},
173+
},
174+
},
175+
RestartPolicy: corev1.RestartPolicyNever,
176+
ServiceAccountName: sa.Name,
177+
},
178+
},
179+
},
180+
}
181+
182+
job, err := test.Client().Core().BatchV1().Jobs(namespace.Name).Create(test.Ctx(), job, metav1.CreateOptions{})
183+
test.Expect(err).NotTo(HaveOccurred())
184+
test.T().Logf("Created Job %s/%s successfully", job.Namespace, job.Name)
185+
186+
test.T().Logf("Waiting for Job %s/%s to complete", job.Namespace, job.Name)
187+
test.Eventually(Job(test, job.Namespace, job.Name), TestTimeoutLong).Should(
188+
WithTransform(ConditionStatus(batchv1.JobFailed), Equal(corev1.ConditionTrue)))
189+
190+
// get Pod associated with created job
191+
options := metav1.ListOptions{
192+
LabelSelector: "job-name=sdk",
193+
}
194+
pods := GetPods(test, namespace.Name, options)
195+
196+
// get job pod logs and compare the expected error in logs
197+
for _, pod := range pods {
198+
podLogs := GetPodLogs(test, &pod, corev1.PodLogOptions{})
199+
expectedLogError := "kubernetes.client.exceptions.ApiException: (403)\nReason: Forbidden"
200+
test.Expect(string(podLogs)).To(gomega.ContainSubstring(expectedLogError))
201+
}
202+
203+
}

0 commit comments

Comments
 (0)