Skip to content

Commit 864ab59

Browse files
committed
Adding unit tests for apply
1 parent 554d7f2 commit 864ab59

File tree

5 files changed

+80
-15
lines changed

5 files changed

+80
-15
lines changed

src/codeflare_sdk/common/utils/unit_test_support.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@
2727

2828

2929
def createClusterConfig():
30+
config = createClusterConfigWithNumWorkers()
31+
return config
32+
33+
34+
def createClusterConfigWithNumWorkers(num_workers=2):
3035
config = ClusterConfiguration(
3136
name="unit-test-cluster",
3237
namespace="ns",
33-
num_workers=2,
38+
num_workers=num_workers,
3439
worker_cpu_requests=3,
3540
worker_cpu_limits=4,
3641
worker_memory_requests=5,
@@ -41,13 +46,18 @@ def createClusterConfig():
4146
return config
4247

4348

44-
def createClusterWithConfig(mocker):
49+
def createClusterWithConfigAndNumWorkers(mocker, num_workers=2):
4550
mocker.patch("kubernetes.config.load_kube_config", return_value="ignore")
4651
mocker.patch(
4752
"kubernetes.client.CustomObjectsApi.get_cluster_custom_object",
4853
return_value={"spec": {"domain": "apps.cluster.awsroute.org"}},
4954
)
50-
cluster = Cluster(createClusterConfig())
55+
cluster = Cluster(createClusterConfigWithNumWorkers(num_workers))
56+
return cluster
57+
58+
59+
def createClusterWithConfig(mocker):
60+
cluster = createClusterWithConfigAndNumWorkers(mocker)
5161
return cluster
5262

5363

src/codeflare_sdk/ray/cluster/cluster.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def up(self):
144144
the Kueue localqueue.
145145
"""
146146
# TODO: Add deprecation message in favor of apply()
147-
# print( "WARNING: The up() is planned for deprecation in favor of apply().")
147+
# print( "WARNING: The up() is planned for deprecation in favor of apply().")
148148

149149
# check if RayCluster CustomResourceDefinition exists if not throw RuntimeError
150150
self._throw_for_no_raycluster()
@@ -182,7 +182,6 @@ def up(self):
182182
except Exception as e: # pragma: no cover
183183
return _kube_api_error_handling(e)
184184

185-
186185
def apply(self, force=False):
187186
"""
188187
Applies the Cluster yaml using server-side apply.
@@ -197,15 +196,14 @@ def apply(self, force=False):
197196
try:
198197
# Get the RayCluster custom resource definition
199198
api = dynamic_client.resources.get(
200-
api_version="ray.io/v1",
201-
kind="RayCluster"
199+
api_version="ray.io/v1", kind="RayCluster"
202200
)
203201
except Exception as e:
204202
raise RuntimeError("Failed to get RayCluster resource: " + str(e))
205203

206204
# Read the YAML file and parse it into a dictionary
207205
try:
208-
with open(self.resource_yaml, 'r') as f:
206+
with open(self.resource_yaml, "r") as f:
209207
resource_body = yaml.safe_load(f)
210208
except FileNotFoundError:
211209
raise RuntimeError(f"Resource YAML file '{self.resource_yaml}' not found.")
@@ -224,7 +222,7 @@ def apply(self, force=False):
224222
name=resource_name,
225223
namespace=self.config.namespace,
226224
field_manager="cluster-manager",
227-
force_conflicts=force # Allow forcing conflicts if needed
225+
force_conflicts=force, # Allow forcing conflicts if needed
228226
)
229227
print(f"Cluster '{self.config.name}' applied successfully.")
230228
except ApiException as e:
@@ -234,7 +232,9 @@ def apply(self, force=False):
234232
"To force the patch, set 'force=True' in the apply() method."
235233
)
236234
elif e.status == 404:
237-
print(f"Namespace '{self.config.namespace}' or resource '{resource_name}' not found. Verify the namespace or CRD.")
235+
print(
236+
f"Namespace '{self.config.namespace}' or resource '{resource_name}' not found. Verify the namespace or CRD."
237+
)
238238
else:
239239
raise RuntimeError(f"Failed to apply cluster: {e.reason}")
240240

src/codeflare_sdk/ray/cluster/test_cluster.py

+42-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
)
2121
from codeflare_sdk.common.utils.unit_test_support import (
2222
createClusterWithConfig,
23+
createClusterWithConfigAndNumWorkers,
2324
arg_check_del_effect,
2425
ingress_retrieval,
2526
arg_check_apply_effect,
@@ -67,11 +68,49 @@ def test_cluster_up_down(mocker):
6768
"kubernetes.client.CustomObjectsApi.list_namespaced_custom_object",
6869
return_value=get_local_queue("kueue.x-k8s.io", "v1beta1", "ns", "localqueues"),
6970
)
70-
cluster = cluster = createClusterWithConfig(mocker)
71+
cluster = createClusterWithConfig(mocker)
7172
cluster.up()
7273
cluster.down()
7374

7475

76+
def test_cluster_apply_scale_up_scale_down(mocker):
77+
mocker.patch("kubernetes.client.ApisApi.get_api_versions")
78+
mocker.patch("kubernetes.config.load_kube_config", return_value="ignore")
79+
mocker.patch("codeflare_sdk.ray.cluster.cluster.Cluster._throw_for_no_raycluster")
80+
mocker.patch(
81+
"kubernetes.client.CustomObjectsApi.get_cluster_custom_object",
82+
return_value={"spec": {"domain": ""}},
83+
)
84+
mocker.patch(
85+
"kubernetes.client.CustomObjectsApi.create_namespaced_custom_object",
86+
side_effect=arg_check_apply_effect,
87+
)
88+
mocker.patch(
89+
"kubernetes.client.CustomObjectsApi.delete_namespaced_custom_object",
90+
side_effect=arg_check_del_effect,
91+
)
92+
mocker.patch(
93+
"kubernetes.client.CustomObjectsApi.list_cluster_custom_object",
94+
return_value={"items": []},
95+
)
96+
mocker.patch(
97+
"kubernetes.client.CustomObjectsApi.list_namespaced_custom_object",
98+
return_value=get_local_queue("kueue.x-k8s.io", "v1beta1", "ns", "localqueues"),
99+
)
100+
initial_num_workers = 1
101+
scaled_up_num_workers = 2
102+
cluster = createClusterWithConfigAndNumWorkers(mocker, initial_num_workers)
103+
cluster.apply()
104+
cluster.wait_ready(timeout=5)
105+
cluster = createClusterWithConfigAndNumWorkers(mocker, scaled_up_num_workers)
106+
cluster.apply()
107+
cluster.wait_ready(timeout=5)
108+
cluster = createClusterWithConfigAndNumWorkers(mocker, initial_num_workers)
109+
cluster.apply()
110+
cluster.wait_ready(timeout=5)
111+
cluster.down()
112+
113+
75114
def test_cluster_up_down_no_mcad(mocker):
76115
mocker.patch("codeflare_sdk.ray.cluster.cluster.Cluster._throw_for_no_raycluster")
77116
mocker.patch("kubernetes.config.load_kube_config", return_value="ignore")
@@ -117,7 +156,7 @@ def test_cluster_uris(mocker):
117156
"kubernetes.client.CustomObjectsApi.list_namespaced_custom_object",
118157
return_value=get_local_queue("kueue.x-k8s.io", "v1beta1", "ns", "localqueues"),
119158
)
120-
cluster = cluster = createClusterWithConfig(mocker)
159+
cluster = createClusterWithConfig(mocker)
121160
mocker.patch(
122161
"kubernetes.client.NetworkingV1Api.list_namespaced_ingress",
123162
return_value=ingress_retrieval(
@@ -159,7 +198,7 @@ def ray_addr(self, *args):
159198
"kubernetes.client.CustomObjectsApi.list_namespaced_custom_object",
160199
return_value=get_local_queue("kueue.x-k8s.io", "v1beta1", "ns", "localqueues"),
161200
)
162-
cluster = cluster = createClusterWithConfig(mocker)
201+
cluster = createClusterWithConfig(mocker)
163202
mocker.patch(
164203
"ray.job_submission.JobSubmissionClient._check_connection_and_version_with_url",
165204
return_value="None",

tests/e2e/cluster_apply_test.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ def test_cluster_apply(self):
8282
# Wait for the updated cluster to be ready
8383
cluster.wait_ready()
8484
updated_status = cluster.status()
85-
assert updated_status["ready"], f"Cluster {cluster_name} is not ready after update: {updated_status}"
85+
assert updated_status[
86+
"ready"
87+
], f"Cluster {cluster_name} is not ready after update: {updated_status}"
8688

8789
# Verify the cluster is updated
8890
updated_ray_cluster = get_ray_cluster(cluster_name, namespace)
@@ -152,4 +154,3 @@ def test_apply_invalid_update(self):
152154

153155
# Clean up
154156
cluster.down()
155-

tests/e2e/support.py

+15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@
1010
_kube_api_error_handling,
1111
)
1212

13+
def get_ray_cluster(cluster_name, namespace):
14+
api = client.CustomObjectsApi()
15+
try:
16+
return api.get_namespaced_custom_object(
17+
group="ray.io",
18+
version="v1",
19+
namespace=namespace,
20+
plural="rayclusters",
21+
name=cluster_name,
22+
)
23+
except client.exceptions.ApiException as e:
24+
if e.status == 404:
25+
return None
26+
raise
27+
1328

1429
def get_ray_image():
1530
default_ray_image = "quay.io/modh/ray@sha256:0d715f92570a2997381b7cafc0e224cfa25323f18b9545acfd23bc2b71576d06"

0 commit comments

Comments
 (0)