1004 lines
39 KiB
Cython
1004 lines
39 KiB
Cython
# cython: profile=False
|
|
# distutils: language = c++
|
|
# cython: embedsignature = True
|
|
# cython: language_level = 3
|
|
# cython: c_string_encoding = default
|
|
|
|
"""
|
|
Binding of C++ ray::gcs::GcsClient.
|
|
"""
|
|
|
|
# This file is a .pxi which is included in _raylet.pyx. This means any already-imported
|
|
# symbols can directly be used here. This is not ideal, but we can't easily split this
|
|
# out to a separate translation unit because we need to access the singleton thread.
|
|
#
|
|
# We need to best-effort import everything we need.
|
|
#
|
|
# For how async API are implemented, see src/ray/common/python_callbacks.h
|
|
from asyncio import Future
|
|
from ray._common.utils import get_or_create_event_loop
|
|
from typing import Dict, List, Sequence, Tuple
|
|
from libcpp.utility cimport move
|
|
import concurrent.futures
|
|
from ray.core.generated.gcs_service_pb2 import GetAllResourceUsageReply
|
|
from ray.includes.common cimport (
|
|
CGcsClient,
|
|
CGetAllResourceUsageReply,
|
|
ConnectOnSingletonIoContext,
|
|
MultiItemPyCallback,
|
|
OptionalItemPyCallback,
|
|
StatusPyCallback,
|
|
CGetClusterStatusReply,
|
|
CStatusOr,
|
|
CGcsNodeState,
|
|
CNodeSelector,
|
|
CGcsNodeInfo,
|
|
CAddEventsRequest,
|
|
CAddEventsReply,
|
|
CRayStatus,
|
|
)
|
|
from ray.includes.optional cimport optional, make_optional
|
|
from ray.core.generated import gcs_pb2, autoscaler_pb2
|
|
from ray.core.generated.gcs_service_pb2 import GetAllNodeInfoRequest
|
|
from cython.operator import dereference, postincrement
|
|
cimport cpython
|
|
|
|
|
|
cdef class InnerGcsClient:
|
|
cdef:
|
|
shared_ptr[CGcsClient] inner
|
|
|
|
# Creates and connects a standalone GcsClient.
|
|
# cluster_id is in hex, if any.
|
|
# TODO(ryw): we can also reuse the CoreWorker's GcsClient to save resources.
|
|
@staticmethod
|
|
def standalone(gcs_address: str,
|
|
cluster_id: Optional[str],
|
|
timeout_ms: int) -> "InnerGcsClient":
|
|
cdef GcsClientOptions gcs_options = None
|
|
if cluster_id:
|
|
gcs_options = GcsClientOptions.create(
|
|
gcs_address, cluster_id, allow_cluster_id_nil=False,
|
|
fetch_cluster_id_if_nil=False)
|
|
else:
|
|
gcs_options = GcsClientOptions.create(
|
|
gcs_address, None, allow_cluster_id_nil=True,
|
|
fetch_cluster_id_if_nil=True)
|
|
cdef CGcsClientOptions* native = gcs_options.native()
|
|
cdef shared_ptr[CGcsClient] inner = make_shared[CGcsClient](
|
|
dereference(native))
|
|
cdef int64_t c_timeout_ms = timeout_ms
|
|
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
ConnectOnSingletonIoContext(dereference(inner), c_timeout_ms))
|
|
|
|
gcs_client = InnerGcsClient()
|
|
gcs_client.inner = inner
|
|
return gcs_client
|
|
|
|
@property
|
|
def address(self) -> str:
|
|
cdef c_pair[c_string, int] pair = self.inner.get().GetGcsServerAddress()
|
|
host = pair.first.decode("utf-8")
|
|
port = pair.second
|
|
return build_address(host, port)
|
|
|
|
@property
|
|
def cluster_id(self) -> ray.ClusterID:
|
|
cdef CClusterID cluster_id = self.inner.get().GetClusterId()
|
|
return ray.ClusterID.from_binary(cluster_id.Binary())
|
|
|
|
#############################################################
|
|
# Internal KV sync methods
|
|
#############################################################
|
|
def internal_kv_get(
|
|
self, c_string key, namespace=None, timeout=None
|
|
) -> Optional[bytes]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
optional[c_string] opt_value = c_string()
|
|
CRayStatus status
|
|
with nogil:
|
|
status = self.inner.get().InternalKV().Get(
|
|
ns, key, timeout_ms, opt_value.value())
|
|
return raise_or_return(
|
|
convert_optional_str_none_for_not_found(status, move(opt_value)))
|
|
|
|
def internal_kv_multi_get(
|
|
self, keys: List[bytes], namespace=None, timeout=None
|
|
) -> Dict[bytes, bytes]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
c_vector[c_string] c_keys = [key for key in keys]
|
|
optional[unordered_map[c_string, c_string]] opt_values = \
|
|
unordered_map[c_string, c_string]()
|
|
CRayStatus status
|
|
with nogil:
|
|
status = self.inner.get().InternalKV().MultiGet(
|
|
ns, c_keys, timeout_ms, opt_values.value())
|
|
return raise_or_return(convert_optional_multi_get(status, move(opt_values)))
|
|
|
|
def internal_kv_put(self, c_string key, c_string value, c_bool overwrite=False,
|
|
namespace=None, timeout=None) -> int:
|
|
"""
|
|
Returns 1 if the key is newly added, 0 if the key is overwritten.
|
|
"""
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
optional[c_bool] opt_added = 0
|
|
CRayStatus status
|
|
with nogil:
|
|
status = self.inner.get().InternalKV().Put(
|
|
ns, key, value, overwrite, timeout_ms, opt_added.value())
|
|
added = raise_or_return(convert_optional_bool(status, move(opt_added)))
|
|
return 1 if added else 0
|
|
|
|
def internal_kv_del(self, c_string key, c_bool del_by_prefix,
|
|
namespace=None, timeout=None) -> int:
|
|
"""
|
|
Returns number of keys deleted.
|
|
"""
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
optional[int] opt_num_deleted = 0
|
|
CRayStatus status
|
|
with nogil:
|
|
status = self.inner.get().InternalKV().Del(
|
|
ns, key, del_by_prefix, timeout_ms, opt_num_deleted.value())
|
|
return raise_or_return(convert_optional_int(status, move(opt_num_deleted)))
|
|
|
|
def internal_kv_keys(
|
|
self, c_string prefix, namespace=None, timeout=None
|
|
) -> List[bytes]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
optional[c_vector[c_string]] opt_keys = c_vector[c_string]()
|
|
CRayStatus status
|
|
with nogil:
|
|
status = self.inner.get().InternalKV().Keys(
|
|
ns, prefix, timeout_ms, opt_keys.value())
|
|
return raise_or_return(convert_optional_vector_str(status, move(opt_keys)))
|
|
|
|
def internal_kv_exists(self, c_string key, namespace=None, timeout=None) -> bool:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
optional[c_bool] opt_exists = 0
|
|
CRayStatus status
|
|
with nogil:
|
|
status = self.inner.get().InternalKV().Exists(
|
|
ns, key, timeout_ms, opt_exists.value())
|
|
return raise_or_return(convert_optional_bool(status, move(opt_exists)))
|
|
|
|
#############################################################
|
|
# Internal KV async methods
|
|
#############################################################
|
|
|
|
def async_internal_kv_get(
|
|
self, c_string key, namespace=None, timeout=None
|
|
) -> Future[Optional[bytes]]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
fut = incremented_fut()
|
|
with nogil:
|
|
self.inner.get().InternalKV().AsyncInternalKVGet(
|
|
ns, key, timeout_ms,
|
|
OptionalItemPyCallback[c_string](
|
|
&convert_optional_str_none_for_not_found,
|
|
assign_and_decrement_fut,
|
|
fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
def async_internal_kv_multi_get(
|
|
self, keys: List[bytes], namespace=None, timeout=None
|
|
) -> Future[Dict[bytes, bytes]]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
c_vector[c_string] c_keys = [key for key in keys]
|
|
fut = incremented_fut()
|
|
with nogil:
|
|
self.inner.get().InternalKV().AsyncInternalKVMultiGet(
|
|
ns, c_keys, timeout_ms,
|
|
OptionalItemPyCallback[unordered_map[c_string, c_string]](
|
|
&convert_optional_multi_get,
|
|
assign_and_decrement_fut,
|
|
fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
def async_internal_kv_put(
|
|
self, c_string key, c_string value, c_bool overwrite=False, namespace=None,
|
|
timeout=None
|
|
) -> Future[bool]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
fut = incremented_fut()
|
|
with nogil:
|
|
self.inner.get().InternalKV().AsyncInternalKVPut(
|
|
ns, key, value, overwrite, timeout_ms,
|
|
OptionalItemPyCallback[c_bool](
|
|
&convert_optional_bool,
|
|
assign_and_decrement_fut,
|
|
fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
def async_internal_kv_del(self, c_string key, c_bool del_by_prefix,
|
|
namespace=None, timeout=None) -> Future[int]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
fut = incremented_fut()
|
|
with nogil:
|
|
self.inner.get().InternalKV().AsyncInternalKVDel(
|
|
ns, key, del_by_prefix, timeout_ms,
|
|
OptionalItemPyCallback[int](
|
|
&convert_optional_int,
|
|
assign_and_decrement_fut,
|
|
fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
def async_internal_kv_keys(self, c_string prefix, namespace=None, timeout=None
|
|
) -> Future[List[bytes]]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
fut = incremented_fut()
|
|
with nogil:
|
|
self.inner.get().InternalKV().AsyncInternalKVKeys(
|
|
ns, prefix, timeout_ms,
|
|
OptionalItemPyCallback[c_vector[c_string]](
|
|
&convert_optional_vector_str,
|
|
assign_and_decrement_fut,
|
|
fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
def async_internal_kv_exists(self, c_string key, namespace=None, timeout=None
|
|
) -> Future[bool]:
|
|
cdef:
|
|
c_string ns = namespace or b""
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
fut = incremented_fut()
|
|
with nogil:
|
|
self.inner.get().InternalKV().AsyncInternalKVExists(
|
|
ns, key, timeout_ms,
|
|
OptionalItemPyCallback[c_bool](
|
|
&convert_optional_bool,
|
|
assign_and_decrement_fut,
|
|
fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
#############################################################
|
|
# NodeInfo methods
|
|
#############################################################
|
|
def check_alive(
|
|
self, node_ids: List[NodeID], timeout: Optional[int | float] = None
|
|
) -> List[bool]:
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
c_vector[CNodeID] c_node_ids;
|
|
c_vector[c_bool] results
|
|
CRayStatus status
|
|
c_node_ids.reserve(len(node_ids));
|
|
for node_id in node_ids:
|
|
c_node_ids.push_back((<NodeID>node_id).native())
|
|
with nogil:
|
|
status = self.inner.get().Nodes().CheckAlive(
|
|
c_node_ids, timeout_ms, results)
|
|
return raise_or_return(convert_multi_bool(status, move(results)))
|
|
|
|
def async_check_alive(
|
|
self, node_ids: List[NodeID], timeout: Optional[int | float] = None
|
|
) -> Future[List[bool]]:
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
c_vector[CNodeID] c_node_ids;
|
|
fut = incremented_fut()
|
|
c_node_ids.reserve(len(node_ids));
|
|
for node_id in node_ids:
|
|
c_node_ids.push_back((<NodeID>node_id).native())
|
|
with nogil:
|
|
self.inner.get().Nodes().AsyncCheckAlive(
|
|
c_node_ids, timeout_ms,
|
|
MultiItemPyCallback[c_bool](
|
|
&convert_multi_bool,
|
|
assign_and_decrement_fut,
|
|
fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
def drain_nodes(
|
|
self, node_ids: Sequence[bytes], timeout: Optional[int | float] = None
|
|
) -> List[bytes]:
|
|
"""returns a list of node_ids that are successfully drained."""
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
c_vector[CNodeID] c_node_ids
|
|
c_vector[c_string] results
|
|
CRayStatus status
|
|
c_node_ids.reserve(len(node_ids));
|
|
for node_id in node_ids:
|
|
c_node_ids.push_back(<CNodeID>CUniqueID.FromBinary(node_id))
|
|
with nogil:
|
|
status = self.inner.get().Nodes().DrainNodes(
|
|
c_node_ids, timeout_ms, results)
|
|
return raise_or_return(convert_multi_str(status, move(results)))
|
|
|
|
def get_all_node_info(
|
|
self, timeout: Optional[int | float] = None,
|
|
node_selectors: Optional[List[GetAllNodeInfoRequest.NodeSelector]] = None,
|
|
state_filter: Optional[int] = None,
|
|
) -> Dict[NodeID, gcs_pb2.GcsNodeInfo]:
|
|
"""Get all node info with optional filters.
|
|
|
|
Args:
|
|
timeout: Timeout in seconds
|
|
node_selectors: Optional list of GetAllNodeInfoRequest.NodeSelector
|
|
proto objects to filter nodes.
|
|
state_filter: Optional int representing the GcsNodeState enum value
|
|
to filter by node state.
|
|
|
|
Returns:
|
|
Dictionary mapping NodeID to GcsNodeInfo
|
|
"""
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
c_vector[CGcsNodeInfo] reply
|
|
CRayStatus status
|
|
optional[CStatusOr[c_vector[CGcsNodeInfo]]] status_or
|
|
optional[CGcsNodeState] c_state_filter = nullopt
|
|
c_vector[CNodeSelector] c_node_selectors
|
|
CNodeSelector c_node_selector
|
|
|
|
# Convert state_filter int to CGcsNodeState
|
|
if state_filter is not None:
|
|
c_state_filter.emplace(<CGcsNodeState>state_filter)
|
|
|
|
# Convert Python NodeSelector protos to C++ CNodeSelector
|
|
if node_selectors is not None:
|
|
for py_selector in node_selectors:
|
|
c_node_selector = CNodeSelector()
|
|
c_node_selector.ParseFromString(py_selector.SerializeToString())
|
|
c_node_selectors.push_back(c_node_selector)
|
|
|
|
with nogil:
|
|
status_or = self.inner.get().Nodes().GetAllNoCache(timeout_ms, c_state_filter, c_node_selectors)
|
|
status = status_or.value().status()
|
|
if status_or.value().ok():
|
|
reply = move(status_or.value().value())
|
|
return raise_or_return(convert_get_all_node_info(status, move(reply)))
|
|
|
|
def async_get_all_node_info(
|
|
self, timeout: Optional[int | float] = None,
|
|
node_selectors: Optional[List[GetAllNodeInfoRequest.NodeSelector]] = None,
|
|
state_filter: Optional[int] = None,
|
|
limit: Optional[int] = None,
|
|
) -> Future[Tuple[Dict[NodeID, gcs_pb2.GcsNodeInfo], int]]:
|
|
"""Asynchronously get all node info with filter metrics
|
|
|
|
Args:
|
|
timeout: Timeout in seconds
|
|
node_selectors: Optional list of GetAllNodeInfoRequest.NodeSelector
|
|
proto objects to filter nodes.
|
|
state_filter: Optional int representing the GcsNodeState enum value
|
|
to filter by node state.
|
|
limit: Maximum number of nodes to return
|
|
|
|
Returns:
|
|
A tuple of (node_infos, num_filtered) where:
|
|
- node_infos: Dict[NodeID, GcsNodeInfo] mapping node IDs to their info
|
|
- num_filtered: int, the number of nodes filtered out by the query
|
|
"""
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
optional[CGcsNodeState] c_state_filter = nullopt
|
|
c_vector[CNodeSelector] c_node_selectors
|
|
CNodeSelector c_node_selector
|
|
optional[int64_t] c_limit = nullopt
|
|
fut = incremented_fut()
|
|
|
|
# Convert state_filter int to CGcsNodeState
|
|
if state_filter is not None:
|
|
c_state_filter.emplace(<CGcsNodeState>state_filter)
|
|
|
|
# Convert Python NodeSelector protos to C++ CNodeSelector
|
|
if node_selectors is not None:
|
|
for py_selector in node_selectors:
|
|
c_node_selector = CNodeSelector()
|
|
c_node_selector.ParseFromString(py_selector.SerializeToString())
|
|
c_node_selectors.push_back(c_node_selector)
|
|
|
|
# Set limit if provided
|
|
if limit is not None:
|
|
c_limit = <int64_t>limit
|
|
|
|
with nogil:
|
|
self.inner.get().Nodes().AsyncGetAll(
|
|
OptionalItemPyCallback[c_pair[c_vector[CGcsNodeInfo], int64_t]](
|
|
convert_get_all_node_info_results,
|
|
assign_and_decrement_fut,
|
|
fut),
|
|
timeout_ms,
|
|
c_state_filter,
|
|
c_node_selectors,
|
|
c_limit)
|
|
return asyncio.wrap_future(fut)
|
|
|
|
#############################################################
|
|
# NodeResources methods
|
|
#############################################################
|
|
def get_all_resource_usage(
|
|
self, timeout: Optional[int | float] = None
|
|
) -> GetAllResourceUsageReply:
|
|
cdef int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
cdef CGetAllResourceUsageReply c_reply
|
|
cdef c_string serialized_reply
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get()
|
|
.NodeResources()
|
|
.GetAllResourceUsage(timeout_ms, c_reply)
|
|
)
|
|
serialized_reply = c_reply.SerializeAsString()
|
|
ret = GetAllResourceUsageReply()
|
|
ret.ParseFromString(serialized_reply)
|
|
return ret
|
|
|
|
#############################################################
|
|
# Actor methods
|
|
#############################################################
|
|
|
|
def async_get_all_actor_info(
|
|
self,
|
|
actor_id: Optional[ActorID] = None,
|
|
job_id: Optional[JobID] = None,
|
|
actor_state_name: Optional[str] = None,
|
|
timeout: Optional[int | float] = None
|
|
) -> Future[Dict[ActorID, gcs_pb2.ActorTableData]]:
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
optional[CActorID] c_actor_id
|
|
optional[CJobID] c_job_id
|
|
optional[c_string] c_actor_state_name
|
|
fut = incremented_fut()
|
|
if actor_id is not None:
|
|
c_actor_id = (<ActorID>actor_id).native()
|
|
if job_id is not None:
|
|
c_job_id = (<JobID>job_id).native()
|
|
if actor_state_name is not None:
|
|
c_actor_state_name = <c_string>actor_state_name.encode()
|
|
|
|
with nogil:
|
|
self.inner.get().Actors().AsyncGetAllByFilter(
|
|
c_actor_id, c_job_id, c_actor_state_name,
|
|
MultiItemPyCallback[CActorTableData](
|
|
&convert_get_all_actor_info,
|
|
assign_and_decrement_fut,
|
|
fut),
|
|
timeout_ms)
|
|
return asyncio.wrap_future(fut)
|
|
|
|
#############################################################
|
|
# Job methods
|
|
#############################################################
|
|
|
|
def get_all_job_info(
|
|
self, *, job_or_submission_id: Optional[str] = None,
|
|
skip_submission_job_info_field: bool = False,
|
|
skip_is_running_tasks_field: bool = False,
|
|
timeout: Optional[int | float] = None
|
|
) -> Dict[JobID, gcs_pb2.JobTableData]:
|
|
cdef c_string c_job_or_submission_id
|
|
cdef optional[c_string] c_optional_job_or_submission_id = nullopt
|
|
cdef int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
cdef c_bool c_skip_submission_job_info_field = skip_submission_job_info_field
|
|
cdef c_bool c_skip_is_running_tasks_field = skip_is_running_tasks_field
|
|
cdef CRayStatus status
|
|
cdef c_vector[CJobTableData] reply
|
|
if job_or_submission_id:
|
|
c_job_or_submission_id = job_or_submission_id
|
|
c_optional_job_or_submission_id = \
|
|
make_optional[c_string](c_job_or_submission_id)
|
|
with nogil:
|
|
status = self.inner.get().Jobs().GetAll(
|
|
c_optional_job_or_submission_id, c_skip_submission_job_info_field,
|
|
c_skip_is_running_tasks_field, reply, timeout_ms)
|
|
return raise_or_return((convert_get_all_job_info(status, move(reply))))
|
|
|
|
def async_get_all_job_info(
|
|
self, *, job_or_submission_id: Optional[str] = None,
|
|
skip_submission_job_info_field: bool = False,
|
|
skip_is_running_tasks_field: bool = False,
|
|
timeout: Optional[int | float] = None
|
|
) -> Future[Dict[JobID, gcs_pb2.JobTableData]]:
|
|
cdef:
|
|
c_string c_job_or_submission_id
|
|
optional[c_string] c_optional_job_or_submission_id = nullopt
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
c_bool c_skip_submission_job_info_field = skip_submission_job_info_field
|
|
c_bool c_skip_is_running_tasks_field = skip_is_running_tasks_field
|
|
fut = incremented_fut()
|
|
if job_or_submission_id:
|
|
c_job_or_submission_id = job_or_submission_id
|
|
c_optional_job_or_submission_id = \
|
|
make_optional[c_string](c_job_or_submission_id)
|
|
with nogil:
|
|
self.inner.get().Jobs().AsyncGetAll(
|
|
c_optional_job_or_submission_id,
|
|
c_skip_submission_job_info_field,
|
|
c_skip_is_running_tasks_field,
|
|
MultiItemPyCallback[CJobTableData](
|
|
&convert_get_all_job_info,
|
|
assign_and_decrement_fut,
|
|
fut),
|
|
timeout_ms)
|
|
return asyncio.wrap_future(fut)
|
|
|
|
#############################################################
|
|
# Runtime Env methods
|
|
#############################################################
|
|
def pin_runtime_env_uri(self, str uri, int expiration_s, timeout=None):
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
c_string c_uri = uri.encode()
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get()
|
|
.RuntimeEnvs()
|
|
.PinRuntimeEnvUri(c_uri, expiration_s, timeout_ms)
|
|
)
|
|
|
|
#############################################################
|
|
# Autoscaler methods
|
|
#############################################################
|
|
def request_cluster_resource_constraint(
|
|
self,
|
|
bundles: c_vector[unordered_map[c_string, cython.double]],
|
|
label_selectors: c_vector[unordered_map[c_string, c_string]],
|
|
count_array: c_vector[int64_t],
|
|
timeout_s=None):
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout_s) if timeout_s else -1
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get()
|
|
.Autoscaler()
|
|
.RequestClusterResourceConstraint(timeout_ms, bundles, label_selectors, count_array)
|
|
)
|
|
|
|
def get_cluster_resource_state(
|
|
self,
|
|
timeout_s=None):
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout_s) if timeout_s else -1
|
|
c_string serialized_reply
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get()
|
|
.Autoscaler()
|
|
.GetClusterResourceState(timeout_ms, serialized_reply)
|
|
)
|
|
|
|
return serialized_reply
|
|
|
|
def get_cluster_status(
|
|
self,
|
|
timeout_s=None):
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout_s) if timeout_s else -1
|
|
c_string serialized_reply
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get()
|
|
.Autoscaler()
|
|
.GetClusterStatus(timeout_ms, serialized_reply)
|
|
)
|
|
|
|
return serialized_reply
|
|
|
|
def async_get_cluster_status(
|
|
self,
|
|
timeout_s=None
|
|
) -> Future[autoscaler_pb2.GetClusterStatusReply]:
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout_s) if timeout_s else -1
|
|
fut = incremented_fut()
|
|
with nogil:
|
|
self.inner.get().Autoscaler().AsyncGetClusterStatus(
|
|
timeout_ms,
|
|
OptionalItemPyCallback[CGetClusterStatusReply](
|
|
&convert_get_cluster_status_reply,
|
|
assign_and_decrement_fut,
|
|
fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
def report_autoscaling_state(
|
|
self,
|
|
serialzied_state: c_string,
|
|
timeout_s=None
|
|
):
|
|
"""Report autoscaling state to GCS"""
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout_s) if timeout_s else -1
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get()
|
|
.Autoscaler()
|
|
.ReportAutoscalingState(timeout_ms, serialzied_state)
|
|
)
|
|
|
|
def drain_node(
|
|
self,
|
|
node_id: c_string,
|
|
reason: int32_t,
|
|
reason_message: c_string,
|
|
deadline_timestamp_ms: int64_t):
|
|
"""Send a DrainNode request to GCS to gracefully terminate a node.
|
|
|
|
Used by the `ray drain-node` CLI command and by autoscaler v2's
|
|
ray_stopper for idle and preemption-based node termination.
|
|
|
|
Args:
|
|
node_id: Binary node ID of the target node.
|
|
reason: A `DrainNodeReason` enum value. `IDLE_TERMINATION`
|
|
requests are rejectable by the raylet; `PREEMPTION`
|
|
requests are non-rejectable.
|
|
reason_message: Human-readable explanation, used for
|
|
observability.
|
|
deadline_timestamp_ms: Timestamp (ms) when the node will be
|
|
force-killed. Used as a hint so workloads can drain
|
|
before the deadline.
|
|
|
|
Returns:
|
|
Tuple of (is_accepted, rejection_reason_message). When
|
|
`is_accepted` is False, `rejection_reason_message` describes
|
|
why the raylet rejected the request.
|
|
"""
|
|
cdef:
|
|
int64_t timeout_ms = -1
|
|
c_bool is_accepted = False
|
|
c_string rejection_reason_message
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(self.inner.get().Autoscaler().DrainNode(
|
|
node_id, reason, reason_message,
|
|
deadline_timestamp_ms, timeout_ms, is_accepted,
|
|
rejection_reason_message))
|
|
|
|
return (is_accepted, rejection_reason_message.decode())
|
|
|
|
def resize_raylet_resource_instances(
|
|
self,
|
|
node_id: c_string,
|
|
resources: unordered_map[c_string, cython.double],
|
|
timeout_s=None):
|
|
"""Send the ResizeRayletResourceInstances request to GCS."""
|
|
cdef:
|
|
int64_t timeout_ms = round(1000 * timeout_s) if timeout_s else -1
|
|
unordered_map[c_string, cython.double] total_resources
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get().Autoscaler().ResizeRayletResourceInstances(
|
|
node_id, resources, timeout_ms, total_resources
|
|
)
|
|
)
|
|
return {key.decode(): value for key, value in total_resources}
|
|
|
|
#############################################################
|
|
# Publisher methods
|
|
#############################################################
|
|
|
|
def publish_error(self, key_id: bytes, error_type: str, message: str,
|
|
job_id: Optional[JobID] = None, timeout = None):
|
|
cdef:
|
|
CErrorTableData error_info
|
|
c_string c_key_id = key_id
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
|
|
if job_id is None:
|
|
job_id = ray.JobID.nil()
|
|
error_info.set_job_id(job_id.binary())
|
|
error_info.set_type(error_type)
|
|
error_info.set_error_message(message)
|
|
error_info.set_timestamp(time.time())
|
|
|
|
with nogil:
|
|
self.inner.get().Publisher().PublishError(
|
|
move(c_key_id), move(error_info), timeout_ms)
|
|
|
|
def publish_logs(self, log_json: dict, timeout = None):
|
|
cdef:
|
|
CLogBatch log_batch
|
|
c_string c_key_id
|
|
int64_t timeout_ms = round(1000 * timeout) if timeout else -1
|
|
|
|
job_id = log_json.get("job")
|
|
log_batch.set_ip(log_json.get("ip") if log_json.get("ip") else b"")
|
|
log_batch.set_pid(
|
|
str(log_json.get("pid")).encode() if log_json.get("pid") else b"")
|
|
log_batch.set_job_id(job_id.encode() if job_id else b"")
|
|
log_batch.set_is_error(bool(log_json.get("is_err")))
|
|
for line in log_json.get("lines", []):
|
|
log_batch.add_lines(line)
|
|
actor_name = log_json.get("actor_name")
|
|
log_batch.set_actor_name(actor_name.encode() if actor_name else b"")
|
|
task_name = log_json.get("task_name")
|
|
log_batch.set_task_name(task_name.encode() if task_name else b"")
|
|
|
|
c_key_id = job_id.encode() if job_id else b""
|
|
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get().Publisher().PublishLogs(
|
|
move(c_key_id), move(log_batch), timeout_ms))
|
|
|
|
def async_publish_node_resource_usage(
|
|
self, key_id: str, node_resource_usage_json: str) -> Future[None]:
|
|
cdef:
|
|
c_string c_key_id = key_id
|
|
c_string c_node_resource_usage_json = node_resource_usage_json.encode()
|
|
fut = incremented_fut()
|
|
with nogil:
|
|
self.inner.get().Publisher().AsyncPublishNodeResourceUsage(
|
|
move(c_key_id),
|
|
move(c_node_resource_usage_json),
|
|
StatusPyCallback(convert_status, assign_and_decrement_fut, fut))
|
|
return asyncio.wrap_future(fut)
|
|
|
|
def report_cluster_config(
|
|
self,
|
|
serialized_cluster_config: c_string):
|
|
"""Report cluster config to GCS"""
|
|
cdef:
|
|
int64_t timeout_ms = -1
|
|
with nogil:
|
|
check_status_timeout_as_rpc_error(
|
|
self.inner.get().Autoscaler().ReportClusterConfig(
|
|
timeout_ms, serialized_cluster_config
|
|
)
|
|
)
|
|
|
|
#############################################################
|
|
# TaskInfo methods
|
|
#############################################################
|
|
async def async_add_events(self, serialized_request: bytes, timeout_s=None, executor=None):
|
|
"""Send async AddEvents request to GCS."""
|
|
cdef:
|
|
CAddEventsRequest c_req
|
|
int64_t timeout_ms
|
|
fut = incremented_fut()
|
|
timeout_ms = round(1000 * timeout_s) if timeout_s else -1
|
|
|
|
# Parse the protobuf payload
|
|
cdef c_string payload = serialized_request
|
|
cdef bint parsed = False
|
|
if executor is not None:
|
|
parsed = await get_or_create_event_loop().run_in_executor(
|
|
executor,
|
|
lambda: c_req.ParseFromString(payload),
|
|
)
|
|
else:
|
|
parsed = c_req.ParseFromString(payload)
|
|
|
|
if not parsed:
|
|
# Fail fast on parse error
|
|
assign_and_decrement_fut((None, ValueError("Invalid AddEventsRequest payload")), fut)
|
|
return await asyncio.wrap_future(fut)
|
|
|
|
with nogil:
|
|
self.inner.get().Tasks().AsyncAddEvents(
|
|
move(c_req),
|
|
StatusPyCallback(convert_status, assign_and_decrement_fut, fut),
|
|
timeout_ms)
|
|
return await asyncio.wrap_future(fut)
|
|
|
|
#############################################################
|
|
# Converter functions: C++ types -> Python types, use by both Sync and Async APIs.
|
|
# They have to be defined here as pure functions because a function pointer is passed
|
|
# to C++ for Async APIs.
|
|
#
|
|
# Each function accepts what the C++ callback passes, typically a Status and a value.
|
|
# Returns `Tuple[object, Optional[Exception]]` (we are all gophers now lol).
|
|
# Must not raise exceptions, or it crashes the process.
|
|
#############################################################
|
|
|
|
cdef convert_get_all_node_info(
|
|
CRayStatus status, c_vector[CGcsNodeInfo] c_data) with gil:
|
|
# -> Dict[NodeID, gcs_pb2.GcsNodeInfo]
|
|
# No GIL block for C++ looping && serialization.
|
|
# GIL block for Pyhton deserialization and dict building.
|
|
# Not doing per-object GIL lock because it's expensive.
|
|
cdef c_vector[c_string] serialized_reply
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
with nogil:
|
|
serialized_reply.reserve(c_data.size())
|
|
for c_proto in c_data:
|
|
serialized_reply.push_back(c_proto.SerializeAsString())
|
|
node_table_data = {}
|
|
for b in serialized_reply:
|
|
proto = gcs_pb2.GcsNodeInfo()
|
|
proto.ParseFromString(b)
|
|
node_table_data[NodeID.from_binary(proto.node_id)] = proto
|
|
return node_table_data, None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_get_all_node_info_results(
|
|
CRayStatus status, optional[c_pair[c_vector[CGcsNodeInfo], int64_t]] c_data) with gil:
|
|
# -> Tuple[Dict[NodeID, GcsNodeInfo], int], Exception
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
if not c_data.has_value():
|
|
raise ValueError("Reply is empty")
|
|
node_table_data, exc = convert_get_all_node_info(
|
|
status, c_data.value().first)
|
|
if exc is not None:
|
|
return None, exc
|
|
return (node_table_data, c_data.value().second), None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_get_all_job_info(
|
|
CRayStatus status, c_vector[CJobTableData] c_data) with gil:
|
|
# -> Dict[JobID, gcs_pb2.JobTableData]
|
|
# No GIL block for C++ looping && serialization.
|
|
# GIL block for Pyhton deserialization and dict building.
|
|
# Not doing per-object GIL lock because it's expensive.
|
|
cdef c_vector[c_string] serialized_reply
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
with nogil:
|
|
serialized_reply.reserve(c_data.size())
|
|
for c_proto in c_data:
|
|
serialized_reply.push_back(c_proto.SerializeAsString())
|
|
job_table_data = {}
|
|
for b in serialized_reply:
|
|
proto = gcs_pb2.JobTableData()
|
|
proto.ParseFromString(b)
|
|
job_table_data[JobID.from_binary(proto.job_id)] = proto
|
|
return job_table_data, None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_get_all_actor_info(
|
|
CRayStatus status, c_vector[CActorTableData] c_data) with gil:
|
|
# -> Dict[ActorID, gcs_pb2.ActorTableData]
|
|
cdef c_vector[c_string] serialized_reply
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
with nogil:
|
|
serialized_reply.reserve(c_data.size())
|
|
for c_proto in c_data:
|
|
serialized_reply.push_back(c_proto.SerializeAsString())
|
|
actor_table_data = {}
|
|
for b in serialized_reply:
|
|
proto = gcs_pb2.ActorTableData()
|
|
proto.ParseFromString(b)
|
|
actor_table_data[ActorID.from_binary(proto.actor_id)] = proto
|
|
return actor_table_data, None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_get_cluster_status_reply(
|
|
CRayStatus status, optional[CGetClusterStatusReply] c_data
|
|
) with gil: # -> Tuple[autoscaler_pb2.GetClusterStatusReply, Exception]
|
|
cdef c_string serialized_reply
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
assert c_data.has_value()
|
|
with nogil:
|
|
serialized_reply = c_data.value().SerializeAsString()
|
|
proto = autoscaler_pb2.GetClusterStatusReply()
|
|
proto.ParseFromString(serialized_reply)
|
|
return proto, None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_status(CRayStatus status) with gil:
|
|
# This function is currently only used by `async_kill_actor` to
|
|
# convert RayStatus to an HTTP status code.
|
|
#
|
|
# Returns:
|
|
# Tuple[int, Optional[Exception]]:
|
|
# - int: HTTP status code.
|
|
# (1) 200: Success
|
|
# (2) 404: Actor not found
|
|
# (3) 500: Other errors
|
|
# - Optional[Exception]: Exception raised by RayStatus
|
|
try:
|
|
if status.IsNotFound():
|
|
return 404, None
|
|
check_status_timeout_as_rpc_error(status)
|
|
return 200, None
|
|
except Exception as e:
|
|
return 500, e
|
|
|
|
cdef convert_optional_str_none_for_not_found(
|
|
CRayStatus status, optional[c_string] c_str) with gil:
|
|
# If status is NotFound, return None.
|
|
# If status is OK, return the value.
|
|
# Else, raise exception.
|
|
# -> Optional[bytes]
|
|
try:
|
|
if status.IsNotFound():
|
|
return None, None
|
|
check_status_timeout_as_rpc_error(status)
|
|
assert c_str.has_value()
|
|
return move(c_str.value()), None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_optional_multi_get(
|
|
CRayStatus status,
|
|
optional[unordered_map[c_string, c_string]] c_map) with gil:
|
|
# -> Dict[str, str]
|
|
cdef unordered_map[c_string, c_string].iterator it
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
assert c_map.has_value()
|
|
|
|
result = {}
|
|
it = c_map.value().begin()
|
|
while it != c_map.value().end():
|
|
result[dereference(it).first] = move(dereference(it).second)
|
|
postincrement(it)
|
|
return result, None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_optional_int(CRayStatus status, optional[int] c_int) with gil:
|
|
# -> int
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
assert c_int.has_value()
|
|
return c_int.value(), None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_optional_vector_str(
|
|
CRayStatus status, optional[c_vector[c_string]] c_vec) with gil:
|
|
# -> List[bytes]
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
return convert_multi_str(status, move(c_vec.value()))
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_optional_bool(CRayStatus status, optional[c_bool] b) with gil:
|
|
# -> bool
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
assert b.has_value()
|
|
return b.value(), None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_multi_bool(CRayStatus status, c_vector[c_bool] c_data) with gil:
|
|
# -> List[bool]
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
return [b for b in c_data], None
|
|
except Exception as e:
|
|
return None, e
|
|
|
|
cdef convert_multi_str(CRayStatus status, c_vector[c_string] vec) with gil:
|
|
# -> List[bytes]
|
|
cdef c_vector[c_string].iterator it
|
|
try:
|
|
check_status_timeout_as_rpc_error(status)
|
|
|
|
it = vec.begin()
|
|
result = []
|
|
while it != vec.end():
|
|
result.append(move(dereference(it)))
|
|
postincrement(it)
|
|
return result, None
|
|
except Exception as e:
|
|
return None, e
|