Skip to content

Commit a4a3d9a

Browse files
authored
Added support for customizing path for job mount file system (#368)
2 parents abb3140 + 3c2a007 commit a4a3d9a

File tree

3 files changed

+99
-31
lines changed

3 files changed

+99
-31
lines changed

ads/common/dsc_file_system.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
66
import ads
77
import oci
8+
import os
89
import ipaddress
910

1011
from dataclasses import dataclass
@@ -20,6 +21,7 @@ class DSCFileSystem:
2021
dest: str = None
2122
storage_type: str = None
2223
destination_directory_name: str = None
24+
destination_path: str = None
2325

2426
def update_to_dsc_model(self) -> dict:
2527
"""Updates arguments to dsc model.
@@ -47,6 +49,29 @@ def update_from_dsc_model(cls, dsc_model) -> dict:
4749
"""
4850
pass
4951

52+
@staticmethod
53+
def get_destination_path_and_name(dest: str) -> (str, str):
54+
"""Gets the destination path and destination directory name from dest.
55+
Example:
56+
dir - "fss" & path - "/opc" : mount happens under "/opc/fss"
57+
dir - "fss" & path - "/" : mount happens under "/fss"
58+
dir - "fss" & path - omitted : mount happens under "/mnt/fss" (for backward compatibility)
59+
60+
Parameters
61+
----------
62+
dest: str
63+
The dest path to which to mount the file system.
64+
65+
Returns
66+
-------
67+
tuple
68+
A tuple of destination path and destination directory name.
69+
"""
70+
return (
71+
os.path.dirname(dest.rstrip("/")) or None, # when destination path is omitted, oci api requires it to be None
72+
os.path.basename(dest.rstrip("/"))
73+
)
74+
5075

5176
@dataclass
5277
class OCIFileStorage(DSCFileSystem):
@@ -65,8 +90,10 @@ def update_to_dsc_model(self) -> dict:
6590
dict:
6691
A dictionary of arguments.
6792
"""
93+
path, directory_name = self.get_destination_path_and_name(self.dest)
6894
arguments = {
69-
"destinationDirectoryName" : self.dest,
95+
"destinationDirectoryName" : directory_name,
96+
"destinationPath" : path,
7097
"storageType" : self.storage_type
7198
}
7299

@@ -177,10 +204,14 @@ def update_from_dsc_model(cls, dsc_model) -> dict:
177204
raise ValueError(
178205
"Missing parameter `destination_directory_name` from service. Check service log to see the error."
179206
)
207+
208+
dest = dsc_model.destination_directory_name
209+
if dsc_model.destination_path:
210+
dest = f"{dsc_model.destination_path.rstrip('/')}/{dsc_model.destination_directory_name}"
180211

181212
return {
182213
"src" : f"{dsc_model.mount_target_id}:{dsc_model.export_id}",
183-
"dest" : dsc_model.destination_directory_name
214+
"dest" : dest
184215
}
185216

186217
@dataclass
@@ -189,8 +220,10 @@ class OCIObjectStorage(DSCFileSystem):
189220
storage_type: str = OBJECT_STORAGE_TYPE
190221

191222
def update_to_dsc_model(self) -> dict:
223+
path, directory_name = self.get_destination_path_and_name(self.dest)
192224
arguments = {
193-
"destinationDirectoryName" : self.dest,
225+
"destinationDirectoryName" : directory_name,
226+
"destinationPath" : path,
194227
"storageType" : self.storage_type
195228
}
196229
src_list = self.src.split("@")
@@ -220,9 +253,13 @@ def update_from_dsc_model(cls, dsc_model) -> dict:
220253
"Missing parameter `destination_directory_name` from service. Check service log to see the error."
221254
)
222255

256+
dest = dsc_model.destination_directory_name
257+
if dsc_model.destination_path:
258+
dest = f"{dsc_model.destination_path.rstrip('/')}/{dsc_model.destination_directory_name}"
259+
223260
return {
224261
"src" : f"oci://{dsc_model.bucket}@{dsc_model.namespace}/{dsc_model.prefix or ''}",
225-
"dest" : dsc_model.destination_directory_name
262+
"dest" : dest
226263
}
227264

228265

ads/jobs/builders/infrastructure/dsc_job.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1482,7 +1482,7 @@ def _update_job_infra(self, dsc_job: DSCJob) -> DataScienceJob:
14821482

14831483
if self.storage_mount:
14841484
if not hasattr(
1485-
oci.data_science.models, "JobStorageMountConfigurationDetails"
1485+
oci.data_science.models, "StorageMountConfigurationDetails"
14861486
):
14871487
raise EnvironmentError(
14881488
"Storage mount hasn't been supported in the current OCI SDK installed."

tests/unitary/default_setup/jobs/test_jobs_mount_file_system.py

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from ads.jobs.builders.runtimes.python_runtime import PythonRuntime
1616

1717
try:
18-
from oci.data_science.models import JobStorageMountConfigurationDetails
1918
from oci.data_science.models import FileStorageMountConfigurationDetails
2019
from oci.data_science.models import ObjectStorageMountConfigurationDetails
2120
except (ImportError, AttributeError) as e:
@@ -50,6 +49,7 @@
5049
FileStorageMountConfigurationDetails(
5150
**{
5251
"destination_directory_name": "test_destination_directory_name_from_dsc",
52+
"destination_path": "/test_destination_path",
5353
"export_id": "export_id_from_dsc",
5454
"mount_target_id": "mount_target_id_from_dsc",
5555
"storage_type": "FILE_STORAGE",
@@ -58,6 +58,7 @@
5858
FileStorageMountConfigurationDetails(
5959
**{
6060
"destination_directory_name": "test_destination_directory_name_from_dsc",
61+
"destination_path": "/test_destination_path",
6162
"export_id": "export_id_from_dsc",
6263
"mount_target_id": "mount_target_id_from_dsc",
6364
"storage_type": "FILE_STORAGE",
@@ -80,15 +81,15 @@
8081
.with_storage_mount(
8182
{
8283
"src" : "1.1.1.1:test_export_path_one",
83-
"dest" : "test_mount_one",
84+
"dest" : "/test_path_one/test_mount_one",
8485
},
8586
{
8687
"src" : "2.2.2.2:test_export_path_two",
87-
"dest" : "test_mount_two",
88+
"dest" : "/test_path_two/test_mount_two",
8889
},
8990
{
9091
"src" : "oci://bucket_name@namespace/synthetic/",
91-
"dest" : "test_mount_three",
92+
"dest" : "/test_path_three/test_mount_three",
9293
}
9394
)
9495
)
@@ -114,11 +115,11 @@
114115
shapeName: VM.Standard.E3.Flex
115116
storageMount:
116117
- src: 1.1.1.1:test_export_path_one
117-
dest: test_mount_one
118+
dest: /test_path_one/test_mount_one
118119
- src: 2.2.2.2:test_export_path_two
119-
dest: test_mount_two
120+
dest: /test_path_two/test_mount_two
120121
- src: oci://bucket_name@namespace/synthetic/
121-
dest: test_mount_three
122+
dest: /test_path_three/test_mount_three
122123
subnetId: ocid1.subnet.oc1.iad.xxxx
123124
type: dataScienceJob
124125
name: My Job
@@ -142,17 +143,17 @@ def test_data_science_job_initialize(self):
142143
dsc_file_storage_one = job.infrastructure.storage_mount[0]
143144
assert isinstance(dsc_file_storage_one, dict)
144145
assert dsc_file_storage_one["src"] == "1.1.1.1:test_export_path_one"
145-
assert dsc_file_storage_one["dest"] == "test_mount_one"
146+
assert dsc_file_storage_one["dest"] == "/test_path_one/test_mount_one"
146147

147148
dsc_file_storage_two = job.infrastructure.storage_mount[1]
148149
assert isinstance(dsc_file_storage_two, dict)
149150
assert dsc_file_storage_two["src"] == "2.2.2.2:test_export_path_two"
150-
assert dsc_file_storage_two["dest"] == "test_mount_two"
151+
assert dsc_file_storage_two["dest"] == "/test_path_two/test_mount_two"
151152

152153
dsc_object_storage = job.infrastructure.storage_mount[2]
153154
assert isinstance(dsc_object_storage, dict)
154155
assert dsc_object_storage["src"] == "oci://bucket_name@namespace/synthetic/"
155-
assert dsc_object_storage["dest"] == "test_mount_three"
156+
assert dsc_object_storage["dest"] == "/test_path_three/test_mount_three"
156157

157158
def test_data_science_job_from_yaml(self):
158159
job_from_yaml = Job.from_yaml(job_yaml_string)
@@ -161,17 +162,17 @@ def test_data_science_job_from_yaml(self):
161162
dsc_file_storage_one = job_from_yaml.infrastructure.storage_mount[0]
162163
assert isinstance(dsc_file_storage_one, dict)
163164
assert dsc_file_storage_one["src"] == "1.1.1.1:test_export_path_one"
164-
assert dsc_file_storage_one["dest"] == "test_mount_one"
165+
assert dsc_file_storage_one["dest"] == "/test_path_one/test_mount_one"
165166

166167
dsc_file_storage_two = job.infrastructure.storage_mount[1]
167168
assert isinstance(dsc_file_storage_two, dict)
168169
assert dsc_file_storage_two["src"] == "2.2.2.2:test_export_path_two"
169-
assert dsc_file_storage_two["dest"] == "test_mount_two"
170+
assert dsc_file_storage_two["dest"] == "/test_path_two/test_mount_two"
170171

171172
dsc_object_storage = job.infrastructure.storage_mount[2]
172173
assert isinstance(dsc_object_storage, dict)
173174
assert dsc_object_storage["src"] == "oci://bucket_name@namespace/synthetic/"
174-
assert dsc_object_storage["dest"] == "test_mount_three"
175+
assert dsc_object_storage["dest"] == "/test_path_three/test_mount_three"
175176

176177
def test_data_science_job_to_dict(self):
177178
assert job.to_dict() == {
@@ -201,15 +202,15 @@ def test_data_science_job_to_dict(self):
201202
"storageMount": [
202203
{
203204
"src" : "1.1.1.1:test_export_path_one",
204-
"dest" : "test_mount_one",
205+
"dest" : "/test_path_one/test_mount_one",
205206
},
206207
{
207208
"src" : "2.2.2.2:test_export_path_two",
208-
"dest" : "test_mount_two",
209+
"dest" : "/test_path_two/test_mount_two",
209210
},
210211
{
211212
"src" : "oci://bucket_name@namespace/synthetic/",
212-
"dest" : "test_mount_three",
213+
"dest" : "/test_path_three/test_mount_three",
213214
}
214215
],
215216
},
@@ -260,11 +261,11 @@ def test_update_storage_mount_from_dsc_model(
260261
assert isinstance(infrastructure.storage_mount[1], dict)
261262
assert infrastructure.storage_mount[0] == {
262263
"src" : "mount_target_id_from_dsc:export_id_from_dsc",
263-
"dest" : "test_destination_directory_name_from_dsc"
264+
"dest" : "/test_destination_path/test_destination_directory_name_from_dsc"
264265
}
265266
assert infrastructure.storage_mount[1] == {
266267
"src" : "mount_target_id_from_dsc:export_id_from_dsc",
267-
"dest" : "test_destination_directory_name_from_dsc"
268+
"dest" : "/test_destination_path/test_destination_directory_name_from_dsc"
268269
}
269270

270271
@patch.object(OCIFileStorage, "update_to_dsc_model")
@@ -276,6 +277,7 @@ def test_update_job_infra(
276277

277278
mock_update_to_dsc_model.return_value = {
278279
"destinationDirectoryName": "test_destination_directory_name_from_dsc",
280+
"destination_path": "/test_destination_path",
279281
"exportId": "test_export_id_one",
280282
"mountTargetId": "test_mount_target_id_one",
281283
"storageType": "FILE_STORAGE",
@@ -293,6 +295,7 @@ def test_update_job_infra(
293295
0
294296
] == {
295297
"destinationDirectoryName": "test_destination_directory_name_from_dsc",
298+
"destination_path": "/test_destination_path",
296299
"exportId": "test_export_id_one",
297300
"mountTargetId": "test_mount_target_id_one",
298301
"storageType": "FILE_STORAGE",
@@ -358,7 +361,7 @@ def test_file_manager_process_data_error(self):
358361
def test_dsc_object_storage(self):
359362
object_storage = OCIObjectStorage(
360363
src="oci://bucket@namespace/prefix",
361-
dest="test_dest",
364+
dest="/test_path/test_dest",
362365
)
363366

364367
result = object_storage.update_to_dsc_model()
@@ -367,10 +370,12 @@ def test_dsc_object_storage(self):
367370
assert result["prefix"] == "prefix"
368371
assert result["storageType"] == "OBJECT_STORAGE"
369372
assert result["destinationDirectoryName"] == "test_dest"
373+
assert result["destinationPath"] == "/test_path"
370374

371375
dsc_model = ObjectStorageMountConfigurationDetails(
372376
**{
373377
"destination_directory_name": "test_destination_directory_name_from_dsc",
378+
"destination_path": "/test_destination_path",
374379
"storage_type": "OBJECT_STORAGE",
375380
"bucket": "bucket",
376381
"namespace": "namespace",
@@ -380,17 +385,18 @@ def test_dsc_object_storage(self):
380385

381386
result = OCIObjectStorage.update_from_dsc_model(dsc_model)
382387
assert result["src"] == "oci://bucket@namespace/prefix"
383-
assert result["dest"] == "test_destination_directory_name_from_dsc"
388+
assert result["dest"] == "/test_destination_path/test_destination_directory_name_from_dsc"
384389

385390
def test_dsc_object_storage_error(self):
386391
error_messages = {
387392
"namespace" : "Missing parameter `namespace` from service. Check service log to see the error.",
388393
"bucket" : "Missing parameter `bucket` from service. Check service log to see the error.",
389-
"destination_directory_name" : "Missing parameter `destination_directory_name` from service. Check service log to see the error."
394+
"destination_directory_name" : "Missing parameter `destination_directory_name` from service. Check service log to see the error.",
390395
}
391396

392397
dsc_model_dict = {
393398
"destination_directory_name": "test_destination_directory_name_from_dsc",
399+
"destination_path": "/test_path",
394400
"storage_type": "OBJECT_STORAGE",
395401
"bucket": "bucket",
396402
"namespace": "namespace",
@@ -412,19 +418,20 @@ def test_dsc_object_storage_error(self):
412418
def test_dsc_file_storage(self, mock_search_resources):
413419
file_storage = OCIFileStorage(
414420
src="ocid1.mounttarget.oc1.iad.xxxx:ocid1.export.oc1.iad.xxxx",
415-
dest="test_dest",
421+
dest="/test_path/test_dest",
416422
)
417423
file_storage = file_storage.update_to_dsc_model()
418424
assert file_storage == {
419425
"destinationDirectoryName" : "test_dest",
426+
"destinationPath" : "/test_path",
420427
"exportId" : "ocid1.export.oc1.iad.xxxx",
421428
"mountTargetId" : "ocid1.mounttarget.oc1.iad.xxxx",
422429
"storageType" : "FILE_STORAGE"
423430
}
424431

425432
file_storage = OCIFileStorage(
426433
src="1.1.1.1:/test_export",
427-
dest="test_dest",
434+
dest="/test_path/test_dest",
428435
)
429436

430437
items = [
@@ -477,6 +484,7 @@ def test_dsc_file_storage(self, mock_search_resources):
477484
file_storage = file_storage.update_to_dsc_model()
478485
assert file_storage == {
479486
"destinationDirectoryName" : "test_dest",
487+
"destinationPath" : "/test_path",
480488
"exportId" : "ocid1.export.oc1.iad.xxxx",
481489
"mountTargetId" : "ocid1.mounttarget.oc1.iad.xxxx",
482490
"storageType" : "FILE_STORAGE"
@@ -485,24 +493,26 @@ def test_dsc_file_storage(self, mock_search_resources):
485493
dsc_model = FileStorageMountConfigurationDetails(
486494
**{
487495
"destination_directory_name": "test_dest",
496+
"destination_path" : "/test_path",
488497
"storage_type": "FILE_STORAGE",
489498
"export_id": "ocid1.export.oc1.iad.xxxx",
490499
"mount_target_id": "ocid1.mounttarget.oc1.iad.xxxx"
491500
}
492501
)
493502
result = OCIFileStorage.update_from_dsc_model(dsc_model)
494503
assert result["src"] == "ocid1.mounttarget.oc1.iad.xxxx:ocid1.export.oc1.iad.xxxx"
495-
assert result["dest"] == "test_dest"
504+
assert result["dest"] == "/test_path/test_dest"
496505

497506
def test_dsc_file_storage_error(self):
498507
error_messages = {
499508
"mount_target_id" : "Missing parameter `mount_target_id` from service. Check service log to see the error.",
500509
"export_id" : "Missing parameter `export_id` from service. Check service log to see the error.",
501-
"destination_directory_name" : "Missing parameter `destination_directory_name` from service. Check service log to see the error."
510+
"destination_directory_name" : "Missing parameter `destination_directory_name` from service. Check service log to see the error.",
502511
}
503512

504513
dsc_model_dict = {
505514
"destination_directory_name": "test_destination_directory_name_from_dsc",
515+
"destination_path": "/test_path",
506516
"storage_type": "FILE_STORAGE",
507517
"mount_target_id": "ocid1.mounttarget.oc1.iad.xxxx",
508518
"export_id": "ocid1.export.oc1.iad.xxxx",
@@ -517,4 +527,25 @@ def test_dsc_file_storage_error(self):
517527
dsc_model_copy.pop(error)
518528
OCIFileStorage.update_from_dsc_model(
519529
FileStorageMountConfigurationDetails(**dsc_model_copy)
520-
)
530+
)
531+
532+
def test_get_destination_path_and_name(self):
533+
path, directory = OCIFileStorage.get_destination_path_and_name("abc")
534+
535+
assert path == None
536+
assert directory == "abc"
537+
538+
path, directory = OCIFileStorage.get_destination_path_and_name("/abc")
539+
540+
assert path == "/"
541+
assert directory == "abc"
542+
543+
path, directory = OCIFileStorage.get_destination_path_and_name("/abc/def")
544+
545+
assert path == "/abc"
546+
assert directory == "def"
547+
548+
path, directory = OCIFileStorage.get_destination_path_and_name("/abc/def/ghi")
549+
550+
assert path == "/abc/def"
551+
assert directory == "ghi"

0 commit comments

Comments
 (0)