chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,131 @@
import copy
import os
from typing import Any, Dict, Tuple
from ray._common.utils import get_default_ray_temp_dir
from ray.autoscaler._private.cli_logger import cli_logger
unsupported_field_message = "The field {} is not supported for on-premise clusters."
LOCAL_CLUSTER_NODE_TYPE = "local.cluster.node"
def prepare_local(config: Dict[str, Any]) -> Tuple[Dict[str, Any], bool]:
"""
Prepare local cluster config for ingestion by cluster launcher and
autoscaler.
"""
config = copy.deepcopy(config)
for field in "head_node", "worker_nodes", "available_node_types":
if config.get(field):
# If the config already contains the internal node type, it's been prepared via ray up already hence return as-is.
if (
field == "available_node_types"
and LOCAL_CLUSTER_NODE_TYPE in config.get(field, {})
):
return config, False
err_msg = unsupported_field_message.format(field)
cli_logger.abort(err_msg)
# We use a config with a single node type for on-prem clusters.
# Resources internally detected by Ray are not overridden by the autoscaler
# (see NodeProvider.do_update)
config["available_node_types"] = {
LOCAL_CLUSTER_NODE_TYPE: {"node_config": {}, "resources": {}}
}
config["head_node_type"] = LOCAL_CLUSTER_NODE_TYPE
if "coordinator_address" in config["provider"]:
config = prepare_coordinator(config)
else:
config = prepare_manual(config)
return config, True
def prepare_coordinator(config: Dict[str, Any]) -> Dict[str, Any]:
config = copy.deepcopy(config)
# User should explicitly set the max number of workers for the coordinator
# to allocate.
if "max_workers" not in config:
cli_logger.abort(
"The field `max_workers` is required when using an "
"automatically managed on-premise cluster."
)
node_type = config["available_node_types"][LOCAL_CLUSTER_NODE_TYPE]
# The autoscaler no longer uses global `min_workers`.
# Move `min_workers` to the node_type config.
node_type["min_workers"] = config.pop("min_workers", 0)
node_type["max_workers"] = config["max_workers"]
return config
def prepare_manual(config: Dict[str, Any]) -> Dict[str, Any]:
"""Validates and sets defaults for configs of manually managed on-prem
clusters.
- Checks for presence of required `worker_ips` and `head_ips` fields.
- Defaults min and max workers to the number of `worker_ips`.
- Caps min and max workers at the number of `worker_ips`.
- Writes min and max worker info into the single worker node type.
"""
config = copy.deepcopy(config)
if ("worker_ips" not in config["provider"]) or (
"head_ip" not in config["provider"]
):
cli_logger.abort(
"Please supply a `head_ip` and list of `worker_ips`. "
"Alternatively, supply a `coordinator_address`."
)
num_ips = len(config["provider"]["worker_ips"])
node_type = config["available_node_types"][LOCAL_CLUSTER_NODE_TYPE]
# Default to keeping all provided ips in the cluster.
config.setdefault("max_workers", num_ips)
# The autoscaler no longer uses global `min_workers`.
# We will move `min_workers` to the node_type config.
min_workers = config.pop("min_workers", num_ips)
max_workers = config["max_workers"]
if min_workers > num_ips:
cli_logger.warning(
f"The value of `min_workers` supplied ({min_workers}) is greater"
f" than the number of available worker ips ({num_ips})."
f" Setting `min_workers={num_ips}`."
)
node_type["min_workers"] = num_ips
else:
node_type["min_workers"] = min_workers
if max_workers > num_ips:
cli_logger.warning(
f"The value of `max_workers` supplied ({max_workers}) is greater"
f" than the number of available worker ips ({num_ips})."
f" Setting `max_workers={num_ips}`."
)
node_type["max_workers"] = num_ips
config["max_workers"] = num_ips
else:
node_type["max_workers"] = max_workers
if max_workers < num_ips:
cli_logger.warning(
f"The value of `max_workers` supplied ({max_workers}) is less"
f" than the number of available worker ips ({num_ips})."
f" At most {max_workers} Ray worker nodes will connect to the cluster."
)
return config
def get_lock_path(cluster_name: str) -> str:
return os.path.join(
get_default_ray_temp_dir(), "cluster-{}.lock".format(cluster_name)
)
def get_state_path(cluster_name: str) -> str:
return os.path.join(
get_default_ray_temp_dir(), "cluster-{}.state".format(cluster_name)
)
def bootstrap_local(config: Dict[str, Any]) -> Dict[str, Any]:
return config
@@ -0,0 +1,110 @@
import json
import logging
from http.client import RemoteDisconnected
from ray.autoscaler.node_provider import NodeProvider
from ray.autoscaler.tags import TAG_RAY_CLUSTER_NAME
logger = logging.getLogger(__name__)
class CoordinatorSenderNodeProvider(NodeProvider):
"""NodeProvider for automatically managed private/local clusters.
The cluster management is handled by a remote coordinating server.
The server listens on <coordinator_address>, therefore, the address
should be provided in the provider section in the cluster config.
The server receieves HTTP requests from this class and uses
LocalNodeProvider to get their responses.
"""
def __init__(self, provider_config, cluster_name):
NodeProvider.__init__(self, provider_config, cluster_name)
self.coordinator_address = provider_config["coordinator_address"]
def _get_http_response(self, request):
headers = {
"Content-Type": "application/json",
}
request_message = json.dumps(request).encode()
http_coordinator_address = "http://" + self.coordinator_address
try:
import requests # `requests` is not part of stdlib.
from requests.exceptions import ConnectionError
r = requests.get(
http_coordinator_address,
data=request_message,
headers=headers,
timeout=None,
)
except (RemoteDisconnected, ConnectionError):
logger.exception(
"Could not connect to: "
+ http_coordinator_address
+ ". Did you run python coordinator_server.py"
+ " --ips <list_of_node_ips> --host <HOST> --port <PORT>?"
)
raise
except ImportError:
logger.exception(
"Not all Ray Autoscaler dependencies were found. "
"In Ray 1.4+, the Ray CLI, autoscaler, and dashboard will "
'only be usable via `pip install "ray[default]"`. Please '
"update your install command."
)
raise
response = r.json()
return response
def non_terminated_nodes(self, tag_filters):
# Only get the non terminated nodes associated with this cluster name.
tag_filters[TAG_RAY_CLUSTER_NAME] = self.cluster_name
request = {"type": "non_terminated_nodes", "args": (tag_filters,)}
return self._get_http_response(request)
def is_running(self, node_id):
request = {"type": "is_running", "args": (node_id,)}
return self._get_http_response(request)
def is_terminated(self, node_id):
request = {"type": "is_terminated", "args": (node_id,)}
return self._get_http_response(request)
def node_tags(self, node_id):
request = {"type": "node_tags", "args": (node_id,)}
return self._get_http_response(request)
def external_ip(self, node_id):
request = {"type": "external_ip", "args": (node_id,)}
response = self._get_http_response(request)
return response
def internal_ip(self, node_id):
request = {"type": "internal_ip", "args": (node_id,)}
response = self._get_http_response(request)
return response
def create_node(self, node_config, tags, count):
# Tag the newly created node with this cluster name. Helps to get
# the right nodes when calling non_terminated_nodes.
tags[TAG_RAY_CLUSTER_NAME] = self.cluster_name
request = {
"type": "create_node",
"args": (node_config, tags, count),
}
self._get_http_response(request)
def set_node_tags(self, node_id, tags):
request = {"type": "set_node_tags", "args": (node_id, tags)}
self._get_http_response(request)
def terminate_node(self, node_id):
request = {"type": "terminate_node", "args": (node_id,)}
self._get_http_response(request)
def terminate_nodes(self, node_ids):
request = {"type": "terminate_nodes", "args": (node_ids,)}
self._get_http_response(request)
@@ -0,0 +1,329 @@
import json
import logging
import os
import socket
from threading import RLock
from filelock import FileLock
from ray.autoscaler._private.local.config import (
LOCAL_CLUSTER_NODE_TYPE,
bootstrap_local,
get_lock_path,
get_state_path,
)
from ray.autoscaler.node_provider import NodeProvider
from ray.autoscaler.tags import (
NODE_KIND_HEAD,
NODE_KIND_WORKER,
STATUS_UP_TO_DATE,
TAG_RAY_NODE_KIND,
TAG_RAY_NODE_NAME,
TAG_RAY_NODE_STATUS,
TAG_RAY_USER_NODE_TYPE,
)
logger = logging.getLogger(__name__)
filelock_logger = logging.getLogger("filelock")
filelock_logger.setLevel(logging.WARNING)
class ClusterState:
def __init__(self, lock_path, save_path, provider_config):
self.lock = RLock()
os.makedirs(os.path.dirname(lock_path), exist_ok=True)
self.file_lock = FileLock(lock_path)
self.save_path = save_path
with self.lock:
with self.file_lock:
if os.path.exists(self.save_path):
workers = json.loads(open(self.save_path).read())
head_config = workers.get(provider_config["head_ip"])
if (
not head_config
or head_config.get("tags", {}).get(TAG_RAY_NODE_KIND)
!= NODE_KIND_HEAD
):
workers = {}
logger.info("Head IP changed - recreating cluster.")
else:
workers = {}
logger.info(
"ClusterState: Loaded cluster state: {}".format(list(workers))
)
for worker_ip in provider_config["worker_ips"]:
if worker_ip not in workers:
workers[worker_ip] = {
"tags": {TAG_RAY_NODE_KIND: NODE_KIND_WORKER},
"state": "terminated",
}
else:
assert (
workers[worker_ip]["tags"][TAG_RAY_NODE_KIND]
== NODE_KIND_WORKER
)
if provider_config["head_ip"] not in workers:
workers[provider_config["head_ip"]] = {
"tags": {TAG_RAY_NODE_KIND: NODE_KIND_HEAD},
"state": "terminated",
}
else:
assert (
workers[provider_config["head_ip"]]["tags"][TAG_RAY_NODE_KIND]
== NODE_KIND_HEAD
)
# Relevant when a user reduces the number of workers
# without changing the headnode.
list_of_node_ips = list(provider_config["worker_ips"])
list_of_node_ips.append(provider_config["head_ip"])
for worker_ip in list(workers):
if worker_ip not in list_of_node_ips:
del workers[worker_ip]
# Set external head ip, if provided by user.
# Necessary if calling `ray up` from outside the network.
# Refer to LocalNodeProvider.external_ip function.
external_head_ip = provider_config.get("external_head_ip")
if external_head_ip:
head = workers[provider_config["head_ip"]]
head["external_ip"] = external_head_ip
assert len(workers) == len(provider_config["worker_ips"]) + 1
with open(self.save_path, "w") as f:
logger.debug(
"ClusterState: Writing cluster state: {}".format(workers)
)
f.write(json.dumps(workers))
def get(self):
with self.lock:
with self.file_lock:
workers = json.loads(open(self.save_path).read())
return workers
def put(self, worker_id, info):
assert "tags" in info
assert "state" in info
with self.lock:
with self.file_lock:
workers = self.get()
workers[worker_id] = info
with open(self.save_path, "w") as f:
logger.info(
"ClusterState: "
"Writing cluster state: {}".format(list(workers))
)
f.write(json.dumps(workers))
class OnPremCoordinatorState(ClusterState):
"""Generates & updates the state file of CoordinatorSenderNodeProvider.
Unlike ClusterState, which generates a cluster specific file with
predefined head and worker ips, OnPremCoordinatorState overwrites
ClusterState's __init__ function to generate and manage a unified
file of the status of all the nodes for multiple clusters.
"""
def __init__(self, lock_path, save_path, list_of_node_ips):
self.lock = RLock()
self.file_lock = FileLock(lock_path)
self.save_path = save_path
with self.lock:
with self.file_lock:
if os.path.exists(self.save_path):
nodes = json.loads(open(self.save_path).read())
else:
nodes = {}
logger.info(
"OnPremCoordinatorState: "
"Loaded on prem coordinator state: {}".format(nodes)
)
# Filter removed node ips.
for node_ip in list(nodes):
if node_ip not in list_of_node_ips:
del nodes[node_ip]
for node_ip in list_of_node_ips:
if node_ip not in nodes:
nodes[node_ip] = {
"tags": {},
"state": "terminated",
}
assert len(nodes) == len(list_of_node_ips)
with open(self.save_path, "w") as f:
logger.info(
"OnPremCoordinatorState: "
"Writing on prem coordinator state: {}".format(nodes)
)
f.write(json.dumps(nodes))
class LocalNodeProvider(NodeProvider):
"""NodeProvider for private/local clusters.
`node_id` is overloaded to also be `node_ip` in this class.
When `cluster_name` is provided, it manages a single cluster in a cluster
specific state file. But when `cluster_name` is None, it manages multiple
clusters in a unified state file that requires each node to be tagged with
TAG_RAY_CLUSTER_NAME in create and non_terminated_nodes function calls to
associate each node with the right cluster.
The current use case of managing multiple clusters is by
OnPremCoordinatorServer which receives node provider HTTP requests
from CoordinatorSenderNodeProvider and uses LocalNodeProvider to get
the responses.
"""
def __init__(self, provider_config, cluster_name):
NodeProvider.__init__(self, provider_config, cluster_name)
if cluster_name:
lock_path = get_lock_path(cluster_name)
state_path = get_state_path(cluster_name)
self.state = ClusterState(
lock_path,
state_path,
provider_config,
)
self.use_coordinator = False
else:
# LocalNodeProvider with a coordinator server.
self.state = OnPremCoordinatorState(
"/tmp/coordinator.lock",
"/tmp/coordinator.state",
provider_config["list_of_node_ips"],
)
self.use_coordinator = True
def non_terminated_nodes(self, tag_filters):
workers = self.state.get()
matching_ips = []
for worker_ip, info in workers.items():
if info["state"] == "terminated":
continue
ok = True
for k, v in tag_filters.items():
if info["tags"].get(k) != v:
ok = False
break
if ok:
matching_ips.append(worker_ip)
return matching_ips
def nodes_for_teardown(self, tag_filters):
"""Return all known node ids matching tag_filters regardless of state.
The local state file on the machine invoking ``ray down`` may show
workers as terminated because only the head node's autoscaler updates
them to running. During teardown we still need to reach these nodes
to stop their Docker containers.
Nodes that have already been fully torn down (i.e. terminate_node was
called, setting teardown_complete) are excluded so they are not
targeted again on subsequent teardown passes.
"""
workers = self.state.get()
return [
worker_ip
for worker_ip, info in workers.items()
if all(info["tags"].get(k) == v for k, v in tag_filters.items())
and not info.get("teardown_complete", False)
]
def is_running(self, node_id):
return self.state.get()[node_id]["state"] == "running"
def is_terminated(self, node_id):
return not self.is_running(node_id)
def node_tags(self, node_id):
return self.state.get()[node_id]["tags"]
def external_ip(self, node_id):
"""Returns an external ip if the user has supplied one.
Otherwise, use the same logic as internal_ip below.
This can be used to call ray up from outside the network, for example
if the Ray cluster exists in an AWS VPC and we're interacting with
the cluster from a laptop (where using an internal_ip will not work).
Useful for debugging the local node provider with cloud VMs."""
node_state = self.state.get()[node_id]
ext_ip = node_state.get("external_ip")
if ext_ip:
return ext_ip
else:
return socket.gethostbyname(node_id)
def internal_ip(self, node_id):
return socket.gethostbyname(node_id)
def set_node_tags(self, node_id, tags):
with self.state.lock:
with self.state.file_lock:
info = self.state.get()[node_id]
info["tags"].update(tags)
self.state.put(node_id, info)
def create_node(self, node_config, tags, count):
"""Creates min(count, currently available) nodes."""
node_type = tags[TAG_RAY_NODE_KIND]
with self.state.lock:
with self.state.file_lock:
workers = self.state.get()
for node_id, info in workers.items():
if info["state"] == "terminated" and (
self.use_coordinator
or info["tags"][TAG_RAY_NODE_KIND] == node_type
):
info["tags"] = tags
info["state"] = "running"
info.pop("teardown_complete", None)
self.state.put(node_id, info)
count = count - 1
if count == 0:
return
def terminate_node(self, node_id):
workers = self.state.get()
info = workers[node_id]
info["state"] = "terminated"
info["teardown_complete"] = True
self.state.put(node_id, info)
@staticmethod
def bootstrap_config(cluster_config):
return bootstrap_local(cluster_config)
def record_local_head_state_if_needed(local_provider: LocalNodeProvider) -> None:
"""This function is called on the Ray head from StandardAutoscaler.reset
to record the head node's own existence in the cluster state file.
This is necessary because `provider.create_node` in
`commands.get_or_create_head_node` records the head state on the
cluster-launching machine but not on the head.
"""
head_ip = local_provider.provider_config["head_ip"]
cluster_name = local_provider.cluster_name
# If the head node is not marked as created in the cluster state file,
if head_ip not in local_provider.non_terminated_nodes({}):
# These tags are based on the ones in commands.get_or_create_head_node;
# keep in sync.
head_tags = {
TAG_RAY_NODE_KIND: NODE_KIND_HEAD,
TAG_RAY_USER_NODE_TYPE: LOCAL_CLUSTER_NODE_TYPE,
TAG_RAY_NODE_NAME: "ray-{}-head".format(cluster_name),
TAG_RAY_NODE_STATUS: STATUS_UP_TO_DATE,
}
# Mark the head node as created in the cluster state file.
local_provider.create_node(node_config={}, tags=head_tags, count=1)
assert head_ip in local_provider.non_terminated_nodes({})