Files
2026-07-13 13:17:40 +08:00

128 lines
3.9 KiB
Python

from types import ModuleType
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ray_release.exception import ClusterEnvCreateError
from ray_release.logger import logger
from ray_release.util import get_anyscale_sdk
if TYPE_CHECKING:
from anyscale.sdk.anyscale_client.sdk import AnyscaleSDK
LAST_LOGS_LENGTH = 100
class Anyscale:
"""
A wrapper class for latest version of Anyscale SDK.
Methods of this class can be overwritten for testing.
"""
def __init__(self):
# We need to late-import Anyscale SDK as merely importing it
# will trigger credential lookup on the system.
self._anyscale_pkg = None
def _anyscale(self) -> Any:
if self._anyscale_pkg is None:
import anyscale
self._anyscale_pkg = anyscale
return self._anyscale_pkg
@property
def compute_config(self) -> ModuleType:
return self._anyscale().compute_config
@property
def cloud(self) -> ModuleType:
return self._anyscale().cloud
def project_name_by_id(self, project_id: str) -> str:
return self._anyscale().project.get(project_id).name
# Global singleton for the V2 Anyscale SDK.
the_v2_sdk = Anyscale()
def get_project_name(
project_id: str, sdk: Optional[Union[Anyscale, "AnyscaleSDK"]] = None
) -> str:
sdk = sdk or the_v2_sdk
if not isinstance(sdk, Anyscale):
# Fallback to old SDK if provided.
# TODO(aslonnie): remove this once we fully migrate to new SDK.
return sdk.get_project(project_id).result.name
return sdk.project_name_by_id(project_id)
def get_custom_cluster_env_name(image: str, test_name: str) -> str:
# TODO(aslonnie): remove this; new SDK does not need creating cluster envs anymore.
image_normalized = image.replace("/", "_").replace(":", "_").replace(".", "_")
return f"test_env_{image_normalized}_{test_name}"
def create_cluster_env_from_image(
image: str,
test_name: str,
runtime_env: Dict[str, Any],
sdk: Optional["AnyscaleSDK"] = None,
cluster_env_id: Optional[str] = None,
cluster_env_name: Optional[str] = None,
) -> str:
# TODO(aslonnie): remove this; new SDK does not need creating cluster envs anymore.
anyscale_sdk = sdk or get_anyscale_sdk()
if not cluster_env_name:
cluster_env_name = get_custom_cluster_env_name(image, test_name)
# Find whether there is identical cluster env
paging_token = None
while not cluster_env_id:
result = anyscale_sdk.search_cluster_environments(
dict(
name=dict(equals=cluster_env_name),
paging=dict(count=50, paging_token=paging_token),
project_id=None,
)
)
paging_token = result.metadata.next_paging_token
for res in result.results:
if res.name == cluster_env_name:
cluster_env_id = res.id
logger.info(f"Cluster env already exists with ID " f"{cluster_env_id}")
break
if not paging_token or cluster_env_id:
break
if not cluster_env_id:
logger.info("Cluster env not found. Creating new one.")
try:
result = anyscale_sdk.create_byod_cluster_environment(
dict(
name=cluster_env_name,
config_json=dict(
docker_image=image,
ray_version="nightly",
),
)
)
cluster_env_id = result.result.id
except Exception as e:
logger.warning(
f"Got exception when trying to create cluster "
f"env: {e}. Sleeping for 10 seconds with jitter and then "
f"try again..."
)
raise ClusterEnvCreateError("Could not create cluster env.") from e
logger.info(f"Cluster env created with ID {cluster_env_id}")
return cluster_env_id