Skip to content

Commit

Permalink
Add Task.request_external_endpoint() to request external endpoints on…
Browse files Browse the repository at this point in the history
… supported backends
  • Loading branch information
clearml committed Oct 9, 2024
1 parent 2b6ab4e commit 5e20133
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
7 changes: 7 additions & 0 deletions clearml/backend_api/session/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,13 @@ def get_worker_host_name(cls):
def get_clients(cls):
return cls._client

@classmethod
def verify_feature_set(cls, feature_set):
if isinstance(feature_set, str):
feature_set = [feature_set]
if cls.feature_set not in feature_set:
raise ValueError("ClearML-server does not support requested feature set '{}'".format(feature_set))

@staticmethod
def _version_tuple(v):
v = tuple(map(int, (v.split("."))))
Expand Down
117 changes: 117 additions & 0 deletions clearml/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,123 @@ def should_connect(*keys):
task._set_startup_info()
return task

def request_external_endpoint(
self, port, protocol="http", wait=False, wait_interval_seconds=3.0, wait_timeout_seconds=90.0
):
# type: (int, str, bool, float, float) -> Optional[Dict]
"""
Request an external endpoint for an application
:param port: Port the application is listening to
:param protocol: As of now, only `http` is supported
:param wait: If True, wait for the endpoint to be assigned
:param wait_interval_seconds: The poll frequency when waiting for the endpoint
:param wait_timeout_seconds: If this timeout is exceeded while waiting for the endpoint,
the method will no longer wait and None will be returned
:return: If wait is False, this method will return None.
If no endpoint could be found while waiting, this mehtod returns None.
Otherwise, it returns a dictionary containing the following values:
- endpoint - raw endpoint. One might need to authenticate in order to use this endpoint
- browser_endpoint - endpoint to be used in browser. Authentication will be handled via the browser
- port - the port exposed by the application
- protocol - the protocol used by the endpoint
"""
Session.verify_feature_set("advanced")
if not getattr(self, "_external_endpoint_port", None):
self.reload()
assigned_port = self._get_runtime_properties().get("_PORT")
if assigned_port:
self._external_endpoint_port = assigned_port
if getattr(self, "_external_endpoint_port", None):
if self._external_endpoint_port != port: # noqa
raise ValueError(
"Only one endpoint can be requested at the moment. Port already exposed is: {}".format(
self._external_endpoint_port
)
)
return
# noinspection PyProtectedMember
self._set_runtime_properties(
{"_SERVICE": "EXTERNAL", "_ADDRESS": get_private_ip(), "_PORT": port}
)
self.set_system_tags((self.get_system_tags() or []) + ["external_service"])
self._external_endpoint_port = port
if wait:
return self.wait_for_external_endpoint(wait_interval_seconds=wait_interval_seconds)
return None

def wait_for_external_endpoint(self, wait_interval_seconds=3.0, wait_timeout_seconds=90.0):
# type: (float) -> Optional[Dict]
"""
Wait for an external endpoint to be assigned
:param wait_interval_seconds: The poll frequency when waiting for the endpoint
:param wait_timeout_seconds: If this timeout is exceeded while waiting for the endpoint,
the method will no longer wait
:return: If no endpoint could be found while waiting, this mehtod returns None.
Otherwise, it returns a dictionary containing the following values:
- endpoint - raw endpoint. One might need to authenticate in order to use this endpoint
- browser_endpoint - endpoint to be used in browser. Authentication will be handled via the browser
- port - the port exposed by the application
- protocol - the protocol used by the endpoint
"""
Session.verify_feature_set("advanced")
if not getattr(self, "_external_endpoint_port", None):
LoggerRoot.get_base_logger().warning("No external endpoints have been requested")
return None
start_time = time.time()
while True:
self.reload()
# noinspection PyProtectedMember
runtime_props = self._get_runtime_properties()
endpoint = runtime_props.get("endpoint")
browser_endpoint = runtime_props.get("browser_endpoint")
if not getattr(self, "_external_endpoint_port", None):
self._external_endpoint_port = runtime_props.get("_PORT")
if endpoint or browser_endpoint:
return {
"endpoint": endpoint,
"browser_endpoint": browser_endpoint,
"port": self._external_endpoint_port,
"protocol": "http",
}
if time.time() >= start_time + wait_timeout_seconds:
LoggerRoot.get_base_logger().warning("Timeout exceeded while waiting for endpoint")
return None
time.sleep(wait_interval_seconds)

def list_external_endpoints(self):
# type: () -> List[Dict]
"""
List all external endpoints assigned
:return: A list of dictionaries. Each dictionary contains the following values:
- endpoint - raw endpoint. One might need to authenticate in order to use this endpoint
- browser_endpoint - endpoint to be used in browser. Authentication will be handled via the browser
- port - the port exposed by the application
- protocol - the protocol used by the endpoint
"""
Session.verify_feature_set("advanced")
if not getattr(self, "_external_endpoint_port", None):
self.reload()
self._external_endpoint_port = self._get_runtime_properties().get("_PORT")
if not getattr(self, "_external_endpoint_port", None):
LoggerRoot.get_base_logger().warning("No external endpoints have been requested")
return []
runtime_props = self._get_runtime_properties()
endpoint = runtime_props.get("endpoint")
browser_endpoint = runtime_props.get("browser_endpoint")
return [
{
"endpoint": endpoint,
"browser_endpoint": browser_endpoint,
"port": self._external_endpoint_port,
"protocol": "http",
}
]

@classmethod
def create(
cls,
Expand Down

0 comments on commit 5e20133

Please sign in to comment.