Skip to content

Commit 361a9ec

Browse files
committed
Add RayCLuster Oauth Authentication test
1 parent 3a15f46 commit 361a9ec

File tree

5 files changed

+194
-12
lines changed

5 files changed

+194
-12
lines changed

docs/e2e.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,7 @@ Pre-requisite for KinD clusters: please add in your local `/etc/hosts` file `127
7777
poetry install --with test,docs
7878
poetry run pytest -v -s ./tests/e2e/mnist_raycluster_sdk_test.py
7979
```
80+
- To run the multiple tests based on the cluster environment, we can run the e2e tests by marking -m with cluster environment (kind or openshift)
81+
```
82+
poetry run pytest -v -s ./tests/e2e -m openshift
83+
```

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,7 @@ filterwarnings = [
4949
"ignore::DeprecationWarning:pkg_resources",
5050
"ignore:pkg_resources is deprecated as an API:DeprecationWarning",
5151
]
52+
markers = [
53+
"kind",
54+
"openshift"
55+
]
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import requests
2+
3+
from time import sleep
4+
5+
from torchx.specs.api import AppState, is_terminal
6+
7+
from codeflare_sdk.cluster.cluster import Cluster, ClusterConfiguration
8+
from codeflare_sdk.job.jobs import DDPJobDefinition
9+
10+
import pytest
11+
12+
from support import *
13+
14+
# This test Creates a Ray cluster with openshift_oauth enable and covers the Ray Job submission with authentication and without authentication functionality
15+
16+
17+
@pytest.mark.openshift
18+
class TestRayClusterSDKOauth:
19+
def setup_method(self):
20+
initialize_kubernetes_client(self)
21+
22+
def teardown_method(self):
23+
delete_namespace(self)
24+
25+
def test_mnist_ray_cluster_sdk_auth(self):
26+
self.setup_method()
27+
create_namespace(self)
28+
self.run_mnist_raycluster_sdk_oauth()
29+
30+
def run_mnist_raycluster_sdk_oauth(self):
31+
ray_image = get_ray_image()
32+
33+
cluster = Cluster(
34+
ClusterConfiguration(
35+
name="mnist",
36+
namespace=self.namespace,
37+
num_workers=1,
38+
head_cpus="500m",
39+
head_memory=2,
40+
min_cpus="500m",
41+
max_cpus=1,
42+
min_memory=1,
43+
max_memory=2,
44+
num_gpus=0,
45+
instascale=False,
46+
image=ray_image,
47+
openshift_oauth=True,
48+
)
49+
)
50+
51+
cluster.up()
52+
self.assert_appwrapper_exists()
53+
54+
cluster.status()
55+
56+
cluster.wait_ready()
57+
self.assert_raycluster_exists()
58+
59+
cluster.status()
60+
61+
cluster.details()
62+
63+
self.assert_jobsubmit_withoutLogin(cluster)
64+
65+
self.assert_jobsubmit_withlogin(cluster)
66+
67+
# Assertions
68+
69+
def assert_jobsubmit_withoutLogin(self, cluster):
70+
dashboard_url = cluster.cluster_dashboard_uri()
71+
jobdata = {
72+
"entrypoint": "python mnist.py",
73+
"runtime_env": {
74+
"working_dir": "./tests/e2e/",
75+
"pip": "mnist_pip_requirements.txt",
76+
},
77+
}
78+
try:
79+
response = requests.post(
80+
dashboard_url + "/api/jobs/", verify=False, json=jobdata
81+
)
82+
if response.status_code == 403:
83+
assert True
84+
else:
85+
response.raise_for_status()
86+
assert False
87+
88+
except Exception as e:
89+
print(f"An unexpected error occurred. Error: {e}")
90+
assert False
91+
92+
def assert_jobsubmit_withlogin(self, cluster):
93+
self.assert_appwrapper_exists()
94+
jobdef = DDPJobDefinition(
95+
name="mnist",
96+
script="./tests/e2e/mnist.py",
97+
scheduler_args={"requirements": "./tests/e2e/mnist_pip_requirements.txt"},
98+
)
99+
job = jobdef.submit(cluster)
100+
101+
done = False
102+
time = 0
103+
timeout = 900
104+
while not done:
105+
status = job.status()
106+
if is_terminal(status.state):
107+
break
108+
if not done:
109+
print(status)
110+
if timeout and time >= timeout:
111+
raise TimeoutError(f"job has timed out after waiting {timeout}s")
112+
sleep(5)
113+
time += 5
114+
115+
print(job.status())
116+
self.assert_job_completion(status)
117+
118+
print(job.logs())
119+
120+
cluster.down()
121+
122+
def assert_appwrapper_exists(self):
123+
try:
124+
self.custom_api.get_namespaced_custom_object(
125+
"workload.codeflare.dev",
126+
"v1beta1",
127+
self.namespace,
128+
"appwrappers",
129+
"mnist",
130+
)
131+
print(
132+
f"AppWrapper 'mnist' has been created in the namespace: '{self.namespace}'"
133+
)
134+
assert True
135+
except Exception as e:
136+
print(f"AppWrapper 'mnist' has not been created. Error: {e}")
137+
assert False
138+
139+
def assert_raycluster_exists(self):
140+
try:
141+
self.custom_api.get_namespaced_custom_object(
142+
"ray.io", "v1", self.namespace, "rayclusters", "mnist"
143+
)
144+
print(
145+
f"RayCluster 'mnist' created successfully in the namespace: '{self.namespace}'"
146+
)
147+
assert True
148+
except Exception as e:
149+
print(f"RayCluster 'mnist' has not been created. Error: {e}")
150+
assert False
151+
152+
def assert_job_completion(self, status):
153+
if status.state == AppState.SUCCEEDED:
154+
print(f"Job has completed: '{status.state}'")
155+
assert True
156+
else:
157+
print(f"Job has completed: '{status.state}'")
158+
assert False

tests/e2e/mnist_raycluster_sdk_test.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,24 @@
1414

1515
import pytest
1616

17-
from support import random_choice, get_ray_image
17+
from support import *
1818

1919
# Creates a Ray cluster, and trains the MNIST dataset using the CodeFlare SDK.
2020
# Asserts creation of AppWrapper, RayCluster, and successful completion of the training job.
2121
# Covers successfull installation of CodeFlare-SDK
2222

2323

24+
@pytest.mark.kind
25+
@pytest.mark.openshift
2426
class TestMNISTRayClusterSDK:
2527
def setup_method(self):
26-
# Load the kube config from the environment or Kube config file.
27-
config.load_kube_config()
28-
29-
# Initialize Kubernetes client
30-
self.api_instance = client.CoreV1Api()
31-
self.custom_api = kubernetes.client.CustomObjectsApi(
32-
self.api_instance.api_client
33-
)
28+
initialize_kubernetes_client(self)
3429

3530
def teardown_method(self):
36-
if hasattr(self, "namespace"):
37-
self.api_instance.delete_namespace(self.namespace)
31+
delete_namespace(self)
3832

3933
def test_mnist_ray_cluster_sdk(self):
40-
self.create_test_namespace()
34+
create_namespace(self)
4135
self.run_mnist_raycluster_sdk()
4236

4337
def create_test_namespace(self):

tests/e2e/support.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
22
import random
33
import string
4+
from kubernetes import client, config
5+
import kubernetes.client
46

57

68
def get_ray_image():
@@ -11,3 +13,23 @@ def get_ray_image():
1113
def random_choice():
1214
alphabet = string.ascii_lowercase + string.digits
1315
return "".join(random.choices(alphabet, k=5))
16+
17+
18+
def create_namespace(self):
19+
self.namespace = f"test-ns-{random_choice()}"
20+
namespace_body = client.V1Namespace(
21+
metadata=client.V1ObjectMeta(name=self.namespace)
22+
)
23+
self.api_instance.create_namespace(namespace_body)
24+
25+
26+
def delete_namespace(self):
27+
if hasattr(self, "namespace"):
28+
self.api_instance.delete_namespace(self.namespace)
29+
30+
31+
def initialize_kubernetes_client(self):
32+
config.load_kube_config()
33+
# Initialize Kubernetes client
34+
self.api_instance = client.CoreV1Api()
35+
self.custom_api = kubernetes.client.CustomObjectsApi(self.api_instance.api_client)

0 commit comments

Comments
 (0)