import argparse
import ipaddress
import json
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional, Union
@dataclass
class Host:
rank: int
ssh_hostname: str
ips: list[str]
rdma: list[Optional[Union[str, list[str]]]]
@dataclass
class Hostfile:
hosts: list[Host]
backend: str = ""
envs: list[str] = field(default_factory=list)
def to_json(self):
return {
"backend": self.backend,
"envs": self.envs,
"hosts": [
{"ssh": h.ssh_hostname, "ips": h.ips, "rdma": h.rdma}
for h in self.hosts
],
}
@classmethod
def from_file(cls, hostfile):
hostfile = Path(hostfile)
if not hostfile.exists():
raise ValueError(f"Hostfile {str(hostfile)} doesn't exist")
try:
data = json.load(open(hostfile))
backend = ""
envs = []
hosts = []
if isinstance(data, dict):
backend = data["backend"]
envs = data["envs"]
hosts = data["hosts"]
elif isinstance(data, list):
hosts = data
hosts = [
Host(i, h["ssh"], h.get("ips", []), h.get("rdma", []))
for i, h in enumerate(hosts)
]
return cls(hosts, backend, envs)
except Exception as e:
raise ValueError(
f"Failed to parse hostfile {str(hostfile)} ({str(e)})"
) from e
@classmethod
def from_list(cls, hostlist, repeats=1):
hosts = []
for i, h in enumerate(hostlist.split(",")):
if h == "":
raise ValueError("Hostname cannot be empty")
try:
ipaddress.ip_address(h)
ips = [h]
except ValueError:
ips = []
for i in range(repeats):
hosts.append(Host(i, h, ips, []))
return cls(hosts)
class OptionalBoolAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if option_string.startswith("--no-"):
setattr(namespace, self.dest, False)
else:
setattr(namespace, self.dest, True)
def positive_number(x):
x = int(x)
if x <= 0:
raise ValueError("Number should be positive")
return x
def log(verbose, *args, **kwargs):
if not verbose:
return
kwargs["file"] = sys.stderr
print("\033[32m[INFO]", *args, "\033[0m", **kwargs)
def log_warning(*args, **kwargs):
kwargs["file"] = sys.stderr
print("\033[33m[WARN]", *args, "\033[0m", **kwargs)
def log_error(*args, **kwargs):
kwargs["file"] = sys.stderr
print("\033[31m[ERROR]", *args, "\033[0m", **kwargs)