2021-11-10 14:14:55 -08:00
|
|
|
from dataclasses import dataclass
|
|
|
|
from enum import Enum
|
|
|
|
from typing import Any, Dict, Optional
|
|
|
|
import pickle
|
2021-11-10 20:24:53 -08:00
|
|
|
from ray import ray_constants
|
2021-11-10 14:14:55 -08:00
|
|
|
from ray.experimental.internal_kv import (
|
|
|
|
_internal_kv_initialized,
|
|
|
|
_internal_kv_get,
|
|
|
|
_internal_kv_put,
|
|
|
|
)
|
|
|
|
|
|
|
|
# NOTE(edoakes): constant should be considered a public API because it's
|
|
|
|
# exposed in the snapshot API.
|
|
|
|
JOB_ID_METADATA_KEY = "job_submission_id"
|
|
|
|
|
|
|
|
JOBS_API_PREFIX = "/api/jobs/"
|
|
|
|
JOBS_API_ROUTE_LOGS = JOBS_API_PREFIX + "logs"
|
|
|
|
JOBS_API_ROUTE_SUBMIT = JOBS_API_PREFIX + "submit"
|
|
|
|
JOBS_API_ROUTE_STOP = JOBS_API_PREFIX + "stop"
|
|
|
|
JOBS_API_ROUTE_STATUS = JOBS_API_PREFIX + "status"
|
|
|
|
JOBS_API_ROUTE_PACKAGE = JOBS_API_PREFIX + "package"
|
|
|
|
|
|
|
|
|
|
|
|
class JobStatus(str, Enum):
|
|
|
|
def __str__(self):
|
|
|
|
return f"{self.value}"
|
|
|
|
|
|
|
|
DOES_NOT_EXIST = "DOES_NOT_EXIST"
|
|
|
|
PENDING = "PENDING"
|
|
|
|
RUNNING = "RUNNING"
|
|
|
|
STOPPED = "STOPPED"
|
|
|
|
SUCCEEDED = "SUCCEEDED"
|
|
|
|
FAILED = "FAILED"
|
|
|
|
|
|
|
|
|
|
|
|
class JobStatusStorageClient:
|
|
|
|
"""
|
|
|
|
Handles formatting of status storage key given job id.
|
|
|
|
"""
|
|
|
|
JOB_STATUS_KEY = "_ray_internal_job_status_{job_id}"
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
assert _internal_kv_initialized()
|
|
|
|
|
|
|
|
def put_status(self, job_id: str, status: JobStatus):
|
|
|
|
assert isinstance(status, JobStatus)
|
|
|
|
_internal_kv_put(
|
2021-11-10 20:24:53 -08:00
|
|
|
self.JOB_STATUS_KEY.format(job_id=job_id),
|
|
|
|
pickle.dumps(status),
|
|
|
|
namespace=ray_constants.KV_NAMESPACE_JOB)
|
2021-11-10 14:14:55 -08:00
|
|
|
|
|
|
|
def get_status(self, job_id: str) -> JobStatus:
|
|
|
|
pickled_status = _internal_kv_get(
|
2021-11-10 20:24:53 -08:00
|
|
|
self.JOB_STATUS_KEY.format(job_id=job_id),
|
|
|
|
namespace=ray_constants.KV_NAMESPACE_JOB)
|
2021-11-10 14:14:55 -08:00
|
|
|
if pickled_status is None:
|
|
|
|
return JobStatus.DOES_NOT_EXIST
|
|
|
|
else:
|
|
|
|
return pickle.loads(pickled_status)
|
|
|
|
|
|
|
|
|
|
|
|
# ==== Get Package ====
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class GetPackageResponse:
|
|
|
|
package_exists: bool
|
|
|
|
|
|
|
|
|
|
|
|
# ==== Job Submit ====
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class JobSubmitRequest:
|
|
|
|
# Dict to setup execution environment.
|
|
|
|
runtime_env: Dict[str, Any]
|
|
|
|
# Command to start execution, ex: "python script.py"
|
|
|
|
entrypoint: str
|
|
|
|
# Optional job_id to specify for the job. If the job_id is not specified,
|
|
|
|
# one will be generated. If a job with the same job_id already exists, it
|
|
|
|
# will be rejected.
|
|
|
|
job_id: Optional[str]
|
|
|
|
# Metadata to pass in to the JobConfig.
|
|
|
|
metadata: Dict[str, str]
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class JobSubmitResponse:
|
|
|
|
job_id: str
|
|
|
|
|
|
|
|
|
|
|
|
# ==== Job Stop ====
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class JobStopResponse:
|
|
|
|
stopped: bool
|
|
|
|
|
|
|
|
|
|
|
|
# ==== Job Status ====
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class JobStatusResponse:
|
|
|
|
job_status: JobStatus
|
|
|
|
|
|
|
|
|
|
|
|
# ==== Job Logs ====
|
|
|
|
|
|
|
|
|
|
|
|
# TODO(jiaodong): Support log streaming #19415
|
|
|
|
@dataclass
|
|
|
|
class JobLogsResponse:
|
|
|
|
logs: str
|