Skip to content

Commit 2dd4293

Browse files
committed
Updated job byoc api.
1 parent 9bbabf6 commit 2dd4293

File tree

6 files changed

+226
-52
lines changed

6 files changed

+226
-52
lines changed

ads/jobs/builders/infrastructure/dsc_job_runtime.py

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8; -*-
33

4-
# Copyright (c) 2021, 2023 Oracle and/or its affiliates.
4+
# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
55
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
66
"""Contains classes for conversion between ADS runtime and OCI Data Science Job implementation.
77
This module is for ADS developers only.
@@ -157,6 +157,21 @@ def _translate_env(self, runtime: Runtime) -> dict:
157157
"""
158158
return runtime.envs
159159

160+
def _translate_env_config(self, runtime: Runtime) -> dict:
161+
"""Translate the environment configuration details for container runtime.
162+
163+
OCI Data Science job requires ``jobEnvironmentConfigurationDetails`` payload if job is running in custom container.
164+
This method is designed to handle the conversion of the ADS runtime properties to ``jobEnvironmentConfigurationDetails`` payload.
165+
By default, no conversion is made in this method.
166+
Sub-class should override this method to add conversion logic.
167+
168+
Returns
169+
-------
170+
dict
171+
A dictionary storing the ``jobEnvironmentConfigurationDetails`` payload for OCI data science job.
172+
"""
173+
return None
174+
160175
def _translate_config(self, runtime: Runtime) -> dict:
161176
"""Prepares the job configuration from runtime specifications.
162177
@@ -305,10 +320,29 @@ def extract(self, dsc_job):
305320
self._extract_envs,
306321
self._extract_artifact,
307322
self._extract_runtime_minutes,
323+
self._extract_properties,
308324
]
309325
for extraction in extractions:
310326
runtime_spec.update(extraction(dsc_job))
311327
return self.RUNTIME_CLASS(self._format_env_var(runtime_spec))
328+
329+
def _extract_properties(self, dsc_job) -> dict:
330+
"""Extract the job runtime properties from data science job.
331+
332+
This is the base method which does not extract the job runtime properties.
333+
Sub-class should implement the extraction if needed.
334+
335+
Parameters
336+
----------
337+
dsc_job : DSCJob or oci.datascience.models.Job
338+
The data science job containing runtime information.
339+
340+
Returns
341+
-------
342+
dict
343+
A runtime specification dictionary for initializing a runtime.
344+
"""
345+
return {}
312346

313347
def _extract_args(self, dsc_job) -> dict:
314348
"""Extracts the command line arguments from data science job.
@@ -942,9 +976,12 @@ def _extract_artifact(self, dsc_job):
942976
class ContainerRuntimeHandler(RuntimeHandler):
943977
RUNTIME_CLASS = ContainerRuntime
944978
CMD_DELIMITER = ","
945-
CONST_CONTAINER_IMAGE = "CONTAINER_CUSTOM_IMAGE"
946-
CONST_CONTAINER_ENTRYPOINT = "CONTAINER_ENTRYPOINT"
947-
CONST_CONTAINER_CMD = "CONTAINER_CMD"
979+
980+
def translate(self, runtime: Runtime) -> dict:
981+
payload = super().translate(runtime)
982+
job_env_config = self._translate_env_config(runtime)
983+
payload["job_environment_configuration_details"] = job_env_config
984+
return payload
948985

949986
def _translate_artifact(self, runtime: Runtime):
950987
"""Specifies a dummy script as the job artifact.
@@ -964,29 +1001,34 @@ def _translate_artifact(self, runtime: Runtime):
9641001
os.path.dirname(__file__), "../../templates", "container.py"
9651002
)
9661003

967-
def _translate_env(self, runtime: ContainerRuntime) -> dict:
968-
"""Translate the environment variable.
1004+
def _translate_env_config(self, runtime: Runtime) -> dict:
1005+
"""Converts runtime properties to ``jobEnvironmentConfigurationDetails`` payload required by OCI Data Science job.
9691006
9701007
Parameters
9711008
----------
972-
runtime : GitPythonRuntime
973-
An instance of GitPythonRuntime
1009+
runtime : Runtime
1010+
The runtime containing the properties to be converted.
9741011
9751012
Returns
9761013
-------
9771014
dict
978-
A dictionary containing environment variables for OCI data science job.
1015+
A dictionary storing the ``jobEnvironmentConfigurationDetails`` payload for OCI data science job.
9791016
"""
980-
if not runtime.image:
981-
raise ValueError("Specify container image for ContainerRuntime.")
982-
envs = super()._translate_env(runtime)
983-
spec_mappings = {
984-
ContainerRuntime.CONST_IMAGE: self.CONST_CONTAINER_IMAGE,
985-
ContainerRuntime.CONST_ENTRYPOINT: self.CONST_CONTAINER_ENTRYPOINT,
986-
ContainerRuntime.CONST_CMD: self.CONST_CONTAINER_CMD,
1017+
job_environment_configuration_details = {
1018+
"job_environment_type": runtime.job_env_type
9871019
}
988-
envs.update(self._translate_specs(runtime, spec_mappings, self.CMD_DELIMITER))
989-
return envs
1020+
1021+
for key, value in ContainerRuntime.attribute_map.items():
1022+
property = runtime.get_spec(key, None)
1023+
if key in [
1024+
ContainerRuntime.CONST_CMD,
1025+
ContainerRuntime.CONST_ENTRYPOINT
1026+
] and isinstance(property, str):
1027+
property = self.split_args(property)
1028+
if property is not None:
1029+
job_environment_configuration_details[value] = property
1030+
1031+
return job_environment_configuration_details
9901032

9911033
@staticmethod
9921034
def split_args(args: str) -> list:
@@ -1031,17 +1073,37 @@ def _extract_envs(self, dsc_job):
10311073
"""
10321074
spec = super()._extract_envs(dsc_job)
10331075
envs = spec.pop(ContainerRuntime.CONST_ENV_VAR, {})
1034-
if self.CONST_CONTAINER_IMAGE not in envs:
1035-
raise IncompatibleRuntime()
1036-
spec[ContainerRuntime.CONST_IMAGE] = envs.pop(self.CONST_CONTAINER_IMAGE)
1037-
cmd = self.split_args(envs.pop(self.CONST_CONTAINER_CMD, ""))
1038-
if cmd:
1039-
spec[ContainerRuntime.CONST_CMD] = cmd
1040-
entrypoint = self.split_args(envs.pop(self.CONST_CONTAINER_ENTRYPOINT, ""))
1041-
if entrypoint:
1042-
spec[ContainerRuntime.CONST_ENTRYPOINT] = entrypoint
1076+
10431077
if envs:
10441078
spec[ContainerRuntime.CONST_ENV_VAR] = envs
1079+
1080+
return spec
1081+
1082+
def _extract_properties(self, dsc_job) -> dict:
1083+
"""Extract the runtime properties from data science job.
1084+
1085+
Parameters
1086+
----------
1087+
dsc_job : DSCJob or oci.datascience.models.Job
1088+
The data science job containing runtime information.
1089+
1090+
Returns
1091+
-------
1092+
dict
1093+
A runtime specification dictionary for initializing a runtime.
1094+
"""
1095+
spec = super()._extract_envs(dsc_job)
1096+
1097+
job_env_config = getattr(dsc_job, "job_environment_configuration_details", None)
1098+
job_env_type = getattr(job_env_config, "job_environment_type", None)
1099+
1100+
if not (job_env_config and job_env_type == "OCIR_CONTAINER"):
1101+
raise IncompatibleRuntime()
1102+
1103+
for key, value in ContainerRuntime.attribute_map.items():
1104+
property = getattr(job_env_config, value, None)
1105+
if property is not None:
1106+
spec[key] = property
10451107
return spec
10461108

10471109

ads/jobs/builders/runtimes/container_runtime.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,36 @@
33

44
# Copyright (c) 2021, 2024 Oracle and/or its affiliates.
55
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
6+
import logging
67
from typing import Union
78
from ads.jobs.builders.runtimes.base import MultiNodeRuntime
89

10+
logger = logging.getLogger(__name__)
11+
912

1013
class ContainerRuntime(MultiNodeRuntime):
1114
"""Represents a container job runtime
1215
1316
To define container runtime:
1417
1518
>>> ContainerRuntime()
16-
>>> .with_image("iad.ocir.io/<your_tenancy>/<your_image>")
19+
>>> .with_image("iad.ocir.io/<your_tenancy>/<your_image>:<tag>")
1720
>>> .with_cmd("sleep 5 && echo Hello World")
1821
>>> .with_entrypoint(["/bin/sh", "-c"])
22+
>>> .with_image_digest("<image_digest>")
23+
>>> .with_image_signature_id("<image_signature_id>")
1924
>>> .with_environment_variable(MY_ENV="MY_VALUE")
2025
21-
Alternatively, you can define the ``entrypoint`` and ``cmd`` along with the image.
26+
Alternatively, you can define the ``entrypoint``, ``cmd``,
27+
``image_digest``and ``image_signature_id`` along with the image.
2228
2329
>>> ContainerRuntime()
2430
>>> .with_image(
25-
>>> "iad.ocir.io/<your_tenancy>/<your_image>",
31+
>>> "iad.ocir.io/<your_tenancy>/<your_image>:<tag>",
2632
>>> entrypoint=["/bin/sh", "-c"],
2733
>>> cmd="sleep 5 && echo Hello World",
34+
>>> image_digest="<image_digest>",
35+
>>> image_signature_id="<image_signature_id>",
2836
>>> )
2937
>>> .with_environment_variable(MY_ENV="MY_VALUE")
3038
@@ -46,20 +54,34 @@ class ContainerRuntime(MultiNodeRuntime):
4654
CONST_IMAGE = "image"
4755
CONST_ENTRYPOINT = "entrypoint"
4856
CONST_CMD = "cmd"
57+
CONST_IMAGE_DIGEST = "imageDigest"
58+
CONST_IMAGE_SIGNATURE_ID = "imageSignatureId"
4959
attribute_map = {
5060
CONST_IMAGE: CONST_IMAGE,
5161
CONST_ENTRYPOINT: CONST_ENTRYPOINT,
5262
CONST_CMD: CONST_CMD,
63+
CONST_IMAGE_DIGEST: "image_digest",
64+
CONST_IMAGE_SIGNATURE_ID: "image_signature_id",
5365
}
5466
attribute_map.update(MultiNodeRuntime.attribute_map)
5567

68+
@property
69+
def job_env_type(self) -> str:
70+
"""The container type"""
71+
return "OCIR_CONTAINER"
72+
5673
@property
5774
def image(self) -> str:
5875
"""The container image"""
5976
return self.get_spec(self.CONST_IMAGE)
6077

6178
def with_image(
62-
self, image: str, entrypoint: Union[str, list, None] = None, cmd: str = None
79+
self,
80+
image: str,
81+
entrypoint: Union[str, list, None] = None,
82+
cmd: str = None,
83+
image_digest: str = None,
84+
image_signature_id: str = None,
6385
) -> "ContainerRuntime":
6486
"""Specify the image for the container job.
6587
@@ -71,16 +93,69 @@ def with_image(
7193
Entrypoint for the job, by default None (the entrypoint defined in the image will be used).
7294
cmd : str, optional
7395
Command for the job, by default None.
96+
image_digest: str, optional
97+
The image digest, by default None.
98+
image_signature_id: str, optional
99+
The image signature id, by default None.
74100
75101
Returns
76102
-------
77103
ContainerRuntime
78104
The runtime instance.
79105
"""
106+
if image.find(":") < 0:
107+
logger.warning(
108+
"Tag is required for custom image. Accepted format: iad.ocir.io/<tenancy>/<image>:<tag>."
109+
)
80110
self.with_entrypoint(entrypoint)
81111
self.set_spec(self.CONST_CMD, cmd)
112+
self.with_image_digest(image_digest)
113+
self.with_image_signature_id(image_signature_id)
82114
return self.set_spec(self.CONST_IMAGE, image)
83115

116+
@property
117+
def image_digest(self) -> str:
118+
"""The container image digest."""
119+
return self.get_spec(self.CONST_IMAGE_DIGEST)
120+
121+
def with_image_digest(self, image_digest: str) -> "ContainerRuntime":
122+
"""Sets the digest of custom image.
123+
124+
Parameters
125+
----------
126+
image_digest: str
127+
The image digest.
128+
129+
Returns
130+
-------
131+
ContainerRuntime
132+
The runtime instance.
133+
"""
134+
return self.set_spec(self.CONST_IMAGE_DIGEST, image_digest)
135+
136+
@property
137+
def image_signature_id(self) -> str:
138+
"""The container image signature id."""
139+
return self.get_spec(self.CONST_IMAGE_SIGNATURE_ID)
140+
141+
def with_image_signature_id(self, image_signature_id: str) -> "ContainerRuntime":
142+
"""Sets the signature id of custom image.
143+
144+
Parameters
145+
----------
146+
image_signature_id: str
147+
The image signature id.
148+
149+
Returns
150+
-------
151+
ContainerRuntime
152+
The runtime instance.
153+
"""
154+
return self.set_spec(
155+
self.CONST_IMAGE_SIGNATURE_ID,
156+
image_signature_id
157+
)
158+
84159
@property
85160
def entrypoint(self) -> str:
86161
"""Entrypoint of the container job"""

docs/source/user_guide/jobs/run_container.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Here is an example to create and run a container job:
2222

2323
To configure ``ContainerRuntime``, you must specify the container ``image``.
2424
Similar to other runtime, you can add environment variables.
25-
You can optionally specify the `entrypoint` and `cmd` for running the container.
25+
You can optionally specify the `entrypoint`, `cmd`, `image_digest` and `image_signature_id` for running the container.
2626

2727
See also:
2828

docs/source/user_guide/jobs/tabs/container_runtime.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
)
2828
.with_runtime(
2929
ContainerRuntime()
30-
.with_image("<region>.ocir.io/<your_tenancy>/<your_image>")
30+
.with_image("<region>.ocir.io/<tenancy>/<image>:<tag>")
31+
.with_image_digest("<image_digest>")
32+
.with_image_signature_id("<image_signature_id>")
3133
.with_environment_variable(GREETINGS="Welcome to OCI Data Science")
3234
.with_entrypoint(["/bin/sh", "-c"])
3335
.with_cmd("sleep 5 && echo $GREETINGS")

0 commit comments

Comments
 (0)