Skip to content

Commit c24924a

Browse files
authored
Added unit test
1 parent e6a6c61 commit c24924a

File tree

1 file changed

+396
-0
lines changed

1 file changed

+396
-0
lines changed
Lines changed: 396 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,396 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8; -*-
3+
4+
# Copyright (c) 2023 Oracle and/or its affiliates.
5+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
6+
import copy
7+
from unittest.mock import MagicMock, patch
8+
import oci
9+
import unittest
10+
import pytest
11+
12+
from ads.jobs.ads_job import Job
13+
from ads.jobs.builders.infrastructure import DSCFileStorage, DataScienceJob
14+
from ads.jobs.builders.runtimes.python_runtime import PythonRuntime
15+
16+
try:
17+
from oci.data_science.models import FileStorageMountConfigurationDetails
18+
except (ImportError, AttributeError) as e:
19+
raise unittest.SkipTest(
20+
"Support for mounting file systems to OCI Job is not available. Skipping the Job tests."
21+
)
22+
23+
dsc_job_payload = oci.data_science.models.Job(
24+
compartment_id="test_compartment_id",
25+
created_by="test_created_by",
26+
description="test_description",
27+
display_name="test_display_name",
28+
freeform_tags={"test_key": "test_value"},
29+
id="test_id",
30+
job_configuration_details=oci.data_science.models.DefaultJobConfigurationDetails(
31+
**{
32+
"command_line_arguments": [],
33+
"environment_variables": {"key": "value"},
34+
"job_type": "DEFAULT",
35+
"maximum_runtime_in_minutes": 10,
36+
}
37+
),
38+
job_log_configuration_details=oci.data_science.models.JobLogConfigurationDetails(
39+
**{
40+
"enable_auto_log_creation": False,
41+
"enable_logging": True,
42+
"log_group_id": "test_log_group_id",
43+
"log_id": "test_log_id",
44+
},
45+
),
46+
job_storage_mount_configuration_details_list=[
47+
{
48+
"destinationDirectoryName": "test_destination_directory_name_from_dsc",
49+
"exportId": "export_id_from_dsc",
50+
"mountTargetId": "mount_target_id_from_dsc",
51+
"storageType": "FILE_STORAGE",
52+
},
53+
{
54+
"destinationDirectoryName": "test_destination_directory_name_from_dsc",
55+
"exportId": "export_id_from_dsc",
56+
"mountTargetId": "mount_target_id_from_dsc",
57+
"storageType": "FILE_STORAGE",
58+
},
59+
],
60+
lifecycle_details="ACTIVE",
61+
lifecycle_state="STATE",
62+
project_id="test_project_id",
63+
)
64+
65+
job = (
66+
Job(name="My Job")
67+
.with_infrastructure(
68+
DataScienceJob()
69+
.with_subnet_id("ocid1.subnet.oc1.iad.xxxx")
70+
.with_shape_name("VM.Standard.E3.Flex")
71+
.with_shape_config_details(memory_in_gbs=16, ocpus=1)
72+
.with_block_storage_size(50)
73+
.with_storage_mount(
74+
DSCFileStorage(
75+
destination_directory_name="test_mount_one",
76+
mount_target="test_mount_target_one",
77+
export_path="test_export_path_one",
78+
),
79+
{
80+
"destination_directory_name": "test_mount_two",
81+
"mount_target": "test_mount_target_two",
82+
"export_path": "test_export_path_two",
83+
"storage_type": "FILE_STORAGE",
84+
},
85+
)
86+
)
87+
.with_runtime(
88+
PythonRuntime()
89+
.with_service_conda("pytorch110_p38_cpu_v1")
90+
.with_source("custom_script.py")
91+
.with_environment_variable(NAME="Welcome to OCI Data Science.")
92+
)
93+
)
94+
95+
job_yaml_string = """
96+
kind: job
97+
spec:
98+
infrastructure:
99+
kind: infrastructure
100+
spec:
101+
blockStorageSize: 50
102+
jobType: DEFAULT
103+
shapeConfigDetails:
104+
memoryInGBs: 16
105+
ocpus: 1
106+
shapeName: VM.Standard.E3.Flex
107+
storageMount:
108+
- destinationDirectoryName: test_mount_one
109+
mountTarget: test_mount_target_one
110+
exportPath: test_export_path_one
111+
storageType: FILE_STORAGE
112+
- destinationDirectoryName: test_mount_two
113+
mountTarget: test_mount_target_two
114+
exportPath: test_export_path_two
115+
storageType: FILE_STORAGE
116+
subnetId: ocid1.subnet.oc1.iad.xxxx
117+
type: dataScienceJob
118+
name: My Job
119+
runtime:
120+
kind: runtime
121+
spec:
122+
conda:
123+
slug: pytorch110_p38_cpu_v1
124+
type: service
125+
env:
126+
- name: NAME
127+
value: Welcome to OCI Data Science.
128+
scriptPathURI: custom_script.py
129+
type: python
130+
"""
131+
132+
133+
class TestDataScienceJobMountFileSystem(unittest.TestCase):
134+
def test_data_science_job_initialize(self):
135+
assert isinstance(job.infrastructure.storage_mount, list)
136+
dsc_file_storage_one = job.infrastructure.storage_mount[0]
137+
assert isinstance(dsc_file_storage_one, DSCFileStorage)
138+
assert dsc_file_storage_one.storage_type == "FILE_STORAGE"
139+
assert dsc_file_storage_one.destination_directory_name == "test_mount_one"
140+
assert dsc_file_storage_one.mount_target == "test_mount_target_one"
141+
assert dsc_file_storage_one.export_path == "test_export_path_one"
142+
143+
dsc_file_storage_two = job.infrastructure.storage_mount[1]
144+
assert isinstance(dsc_file_storage_two, DSCFileStorage)
145+
assert dsc_file_storage_two.storage_type == "FILE_STORAGE"
146+
assert dsc_file_storage_two.destination_directory_name == "test_mount_two"
147+
assert dsc_file_storage_two.mount_target == "test_mount_target_two"
148+
assert dsc_file_storage_two.export_path == "test_export_path_two"
149+
150+
def test_data_science_job_from_yaml(self):
151+
job_from_yaml = Job.from_yaml(job_yaml_string)
152+
153+
assert isinstance(job_from_yaml.infrastructure.storage_mount, list)
154+
dsc_file_storage_one = job_from_yaml.infrastructure.storage_mount[0]
155+
assert isinstance(dsc_file_storage_one, DSCFileStorage)
156+
assert dsc_file_storage_one.storage_type == "FILE_STORAGE"
157+
assert dsc_file_storage_one.destination_directory_name == "test_mount_one"
158+
assert dsc_file_storage_one.mount_target == "test_mount_target_one"
159+
assert dsc_file_storage_one.export_path == "test_export_path_one"
160+
161+
dsc_file_storage_two = job.infrastructure.storage_mount[1]
162+
assert isinstance(dsc_file_storage_two, DSCFileStorage)
163+
assert dsc_file_storage_two.storage_type == "FILE_STORAGE"
164+
assert dsc_file_storage_two.destination_directory_name == "test_mount_two"
165+
assert dsc_file_storage_two.mount_target == "test_mount_target_two"
166+
assert dsc_file_storage_two.export_path == "test_export_path_two"
167+
168+
def test_data_science_job_to_dict(self):
169+
assert job.to_dict() == {
170+
"kind": "job",
171+
"spec": {
172+
"name": "My Job",
173+
"runtime": {
174+
"kind": "runtime",
175+
"type": "python",
176+
"spec": {
177+
"conda": {"type": "service", "slug": "pytorch110_p38_cpu_v1"},
178+
"scriptPathURI": "custom_script.py",
179+
"env": [
180+
{"name": "NAME", "value": "Welcome to OCI Data Science."}
181+
],
182+
},
183+
},
184+
"infrastructure": {
185+
"kind": "infrastructure",
186+
"type": "dataScienceJob",
187+
"spec": {
188+
"jobType": "DEFAULT",
189+
"subnetId": "ocid1.subnet.oc1.iad.xxxx",
190+
"shapeName": "VM.Standard.E3.Flex",
191+
"shapeConfigDetails": {"ocpus": 1, "memoryInGBs": 16},
192+
"blockStorageSize": 50,
193+
"storageMount": [
194+
{
195+
"destinationDirectoryName": "test_mount_one",
196+
"mountTarget": "test_mount_target_one",
197+
"exportPath": "test_export_path_one",
198+
"storageType": "FILE_STORAGE",
199+
},
200+
{
201+
"destinationDirectoryName": "test_mount_two",
202+
"mountTarget": "test_mount_target_two",
203+
"exportPath": "test_export_path_two",
204+
"storageType": "FILE_STORAGE",
205+
},
206+
],
207+
},
208+
},
209+
},
210+
}
211+
212+
def test_mount_file_system_failed(self):
213+
with pytest.raises(
214+
ValueError,
215+
match="Either parameter `export_path` or `export_id` must be provided to mount file system.",
216+
):
217+
DSCFileStorage(
218+
destination_directory_name="test_mount",
219+
mount_target_id="ocid1.mounttarget.oc1.iad.xxxx",
220+
)
221+
222+
with pytest.raises(
223+
ValueError,
224+
match="Either parameter `mount_target` or `mount_target_id` must be provided to mount file system.",
225+
):
226+
DSCFileStorage(
227+
destination_directory_name="test_mount",
228+
export_id="ocid1.export.oc1.iad.xxxx",
229+
)
230+
231+
with pytest.raises(
232+
ValueError,
233+
match="Parameter `destination_directory_name` must be provided to mount file system.",
234+
):
235+
DSCFileStorage(
236+
mount_target_id="ocid1.mounttarget.oc1.iad.xxxx",
237+
export_id="ocid1.export.oc1.iad.xxxx",
238+
)
239+
240+
job_copy = copy.deepcopy(job)
241+
dsc_file_storage = DSCFileStorage(
242+
destination_directory_name="test_mount",
243+
mount_target="test_mount_target",
244+
export_id="ocid1.export.oc1.iad.xxxx",
245+
)
246+
storage_mount_list = [dsc_file_storage] * 6
247+
with pytest.raises(
248+
ValueError,
249+
match="A maximum number of 5 file systems are allowed to be mounted at this time for a job.",
250+
):
251+
job_copy.infrastructure.with_storage_mount(*storage_mount_list)
252+
253+
job_copy = copy.deepcopy(job)
254+
with pytest.raises(
255+
ValueError,
256+
match="Parameter `storage_mount` should be a list of either DSCFileSystem instances or dictionaries.",
257+
):
258+
job_copy.infrastructure.with_storage_mount(dsc_file_storage, [1, 2, 3])
259+
260+
job_copy = copy.deepcopy(job)
261+
with pytest.raises(
262+
ValueError,
263+
match="Parameter `storage_type` must be provided for each file system to be mounted.",
264+
):
265+
job_copy.infrastructure.with_storage_mount(
266+
dsc_file_storage,
267+
{
268+
"destination_directory_name": "test_mount",
269+
"mount_target_id": "ocid1.mounttarget.oc1.iad.xxxx",
270+
"export_id": "ocid1.export.oc1.iad.xxxx",
271+
},
272+
)
273+
274+
job_copy = copy.deepcopy(job)
275+
wrong_type = "WRONG_TYPE"
276+
wrong_file_system_dict = {
277+
"destination_directory_name": "test_mount",
278+
"mount_target_id": "ocid1.mounttarget.oc1.iad.xxxx",
279+
"export_id": "ocid1.export.oc1.iad.xxxx",
280+
"storage_type": wrong_type,
281+
}
282+
with pytest.raises(
283+
ValueError, match=f"Storage type {wrong_type} is not supprted."
284+
):
285+
job_copy.infrastructure.with_storage_mount(
286+
dsc_file_storage, wrong_file_system_dict
287+
)
288+
289+
@patch.object(oci.file_storage.FileStorageClient, "get_export")
290+
@patch.object(oci.file_storage.FileStorageClient, "get_mount_target")
291+
def test_update_storage_mount_from_dsc_model(
292+
self, mock_get_mount_target, mock_get_export
293+
):
294+
mount_target_mock = MagicMock()
295+
mount_target_mock.data = MagicMock()
296+
mount_target_mock.data.display_name = "mount_target_from_dsc"
297+
mock_get_mount_target.return_value = mount_target_mock
298+
299+
export_mock = MagicMock()
300+
export_mock.data = MagicMock()
301+
export_mock.data.path = "export_path_from_dsc"
302+
mock_get_export.return_value = export_mock
303+
job_copy = copy.deepcopy(job)
304+
infrastructure = job_copy.infrastructure
305+
infrastructure._update_from_dsc_model(dsc_job_payload)
306+
307+
assert len(infrastructure.storage_mount) == 2
308+
assert isinstance(infrastructure.storage_mount[0], DSCFileStorage)
309+
assert isinstance(infrastructure.storage_mount[1], DSCFileStorage)
310+
assert infrastructure.storage_mount[0].to_dict() == {
311+
"destinationDirectoryName": "test_destination_directory_name_from_dsc",
312+
"exportId": "export_id_from_dsc",
313+
"exportPath": "export_path_from_dsc",
314+
"mountTarget": "mount_target_from_dsc",
315+
"mountTargetId": "mount_target_id_from_dsc",
316+
"storageType": "FILE_STORAGE",
317+
}
318+
assert infrastructure.storage_mount[1].to_dict() == {
319+
"destinationDirectoryName": "test_destination_directory_name_from_dsc",
320+
"exportId": "export_id_from_dsc",
321+
"exportPath": "export_path_from_dsc",
322+
"mountTarget": "mount_target_from_dsc",
323+
"mountTargetId": "mount_target_id_from_dsc",
324+
"storageType": "FILE_STORAGE",
325+
}
326+
327+
@patch.object(oci.file_storage.FileStorageClient, "list_exports")
328+
@patch.object(oci.file_storage.FileStorageClient, "list_mount_targets")
329+
@patch.object(oci.identity.IdentityClient, "list_availability_domains")
330+
def test_update_job_infra(
331+
self, mock_list_availability_domains, mock_list_mount_targets, mock_list_exports
332+
):
333+
job_copy = copy.deepcopy(job)
334+
dsc_job_payload_copy = copy.deepcopy(dsc_job_payload)
335+
336+
list_availability_domains_mock = MagicMock()
337+
list_availability_domains_mock.data = [
338+
oci.identity.models.availability_domain.AvailabilityDomain(
339+
compartment_id=job_copy.infrastructure.compartment_id,
340+
name="NNFR:US-ASHBURN-AD-1",
341+
id="test_id_one",
342+
)
343+
]
344+
345+
mock_list_availability_domains.return_value = list_availability_domains_mock
346+
347+
list_mount_targets_mock = MagicMock()
348+
list_mount_targets_mock.data = [
349+
oci.file_storage.models.mount_target_summary.MountTargetSummary(
350+
**{
351+
"availability_domain": "NNFR:US-ASHBURN-AD-1",
352+
"compartment_id": job_copy.infrastructure.compartment_id,
353+
"display_name": "test_mount_target_one",
354+
"id": "test_mount_target_id_one",
355+
}
356+
),
357+
]
358+
mock_list_mount_targets.return_value = list_mount_targets_mock
359+
360+
list_exports_mock = MagicMock()
361+
list_exports_mock.data = [
362+
oci.file_storage.models.export.Export(
363+
**{
364+
"id": "test_export_id_one",
365+
"path": "test_export_path_one",
366+
}
367+
),
368+
oci.file_storage.models.export.Export(
369+
**{
370+
"id": "test_export_id_two",
371+
"path": "test_export_path_two",
372+
}
373+
),
374+
]
375+
mock_list_exports.return_value = list_exports_mock
376+
377+
dsc_job_payload_copy.job_storage_mount_configuration_details_list = []
378+
infrastructure = job_copy.infrastructure
379+
with pytest.raises(
380+
ValueError,
381+
match="No `mount_target` with value test_mount_target_two found under compartment test_compartment_id.",
382+
):
383+
infrastructure._update_job_infra(dsc_job_payload_copy)
384+
385+
assert (
386+
len(dsc_job_payload_copy.job_storage_mount_configuration_details_list)
387+
== 1
388+
)
389+
assert dsc_job_payload_copy.job_storage_mount_configuration_details_list[
390+
0
391+
] == {
392+
"destinationDirectoryName": "test_destination_directory_name_from_dsc",
393+
"exportId": "test_export_id_one",
394+
"mountTargetId": "test_mount_target_id_one",
395+
"storageType": "FILE_STORAGE",
396+
}

0 commit comments

Comments
 (0)