Skip to content

Commit 34567dc

Browse files
committed
Add RayCLuster Oauth Authentication test
1 parent 3a15f46 commit 34567dc

File tree

5 files changed

+180
-20
lines changed

5 files changed

+180
-20
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: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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+
58+
cluster.status()
59+
60+
cluster.details()
61+
62+
self.assert_jobsubmit_withoutLogin(cluster)
63+
64+
self.assert_jobsubmit_withlogin(cluster)
65+
66+
# Assertions
67+
68+
def assert_jobsubmit_withoutLogin(self, cluster):
69+
dashboard_url = cluster.cluster_dashboard_uri()
70+
jobdata = {
71+
"entrypoint": "python mnist.py",
72+
"runtime_env": {
73+
"working_dir": "./tests/e2e/",
74+
"pip": "mnist_pip_requirements.txt",
75+
},
76+
}
77+
try:
78+
response = requests.post(
79+
dashboard_url + "/api/jobs/", verify=False, json=jobdata
80+
)
81+
if response.status_code == 403:
82+
assert True
83+
else:
84+
response.raise_for_status()
85+
assert False
86+
87+
except Exception as e:
88+
print(f"An unexpected error occurred. Error: {e}")
89+
assert False
90+
91+
def assert_jobsubmit_withlogin(self, cluster):
92+
self.assert_appwrapper_exists()
93+
jobdef = DDPJobDefinition(
94+
name="mnist",
95+
script="./tests/e2e/mnist.py",
96+
scheduler_args={"requirements": "./tests/e2e/mnist_pip_requirements.txt"},
97+
)
98+
job = jobdef.submit(cluster)
99+
100+
done = False
101+
time = 0
102+
timeout = 900
103+
while not done:
104+
status = job.status()
105+
if is_terminal(status.state):
106+
break
107+
if not done:
108+
print(status)
109+
if timeout and time >= timeout:
110+
raise TimeoutError(f"job has timed out after waiting {timeout}s")
111+
sleep(5)
112+
time += 5
113+
114+
print(job.status())
115+
self.assert_job_completion(status)
116+
117+
print(job.logs())
118+
119+
cluster.down()
120+
121+
def assert_appwrapper_exists(self):
122+
try:
123+
self.custom_api.get_namespaced_custom_object(
124+
"workload.codeflare.dev",
125+
"v1beta1",
126+
self.namespace,
127+
"appwrappers",
128+
"mnist",
129+
)
130+
print(
131+
f"AppWrapper 'mnist' has been created in the namespace: '{self.namespace}'"
132+
)
133+
assert True
134+
except Exception as e:
135+
print(f"AppWrapper 'mnist' has not been created. Error: {e}")
136+
assert False
137+
138+
def assert_job_completion(self, status):
139+
if status.state == AppState.SUCCEEDED:
140+
print(f"Job has completed: '{status.state}'")
141+
assert True
142+
else:
143+
print(f"Job has completed: '{status.state}'")
144+
assert False

tests/e2e/mnist_raycluster_sdk_test.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,26 @@
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

43-
def create_test_namespace(self):
44-
self.namespace = f"test-ns-{random_choice()}"
45-
namespace_body = client.V1Namespace(
46-
metadata=client.V1ObjectMeta(name=self.namespace)
47-
)
48-
self.api_instance.create_namespace(namespace_body)
49-
return self.namespace
50-
5137
def run_mnist_raycluster_sdk(self):
5238
ray_image = get_ray_image()
5339
host = os.getenv("CLUSTER_HOSTNAME")

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)