"""gpu-rent hold: pause idle-killer via hold-until file on the data volume.""" from __future__ import annotations from datetime import datetime, timezone from collections.abc import Callable from gpu_rent.config import Config from gpu_rent.errors import GpuRentError from gpu_rent.ssh_ops import put_text, remote_exists, run_ssh Log = Callable[[str], None] HOLD_PATH = "/mnt/swarm_data/.gpu-rent-hold-until" def _parse_until(value: str) -> int: text = value.strip() if text.isdigit(): return int(text) try: dt = datetime.fromisoformat(text.replace("Z", "+00:00")) except ValueError as exc: raise GpuRentError(f"не разобрать --until {value!r}: нужен ISO или unix ts") from exc if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return int(dt.timestamp()) def set_hold( cfg: Config, host: str, *, minutes: int | None = None, until: str | None = None, log: Log | None = None, ) -> int: """Write hold-until unix ts. Default = now + IDLE_MINUTES.""" if minutes is not None and until is not None: raise GpuRentError("укажи либо --minutes, либо --until, не оба") now = int(datetime.now(timezone.utc).timestamp()) if until: ts = _parse_until(until) else: mins = minutes if minutes is not None else cfg.idle_minutes if mins <= 0: raise GpuRentError("--minutes должен быть > 0") ts = now + mins * 60 if ts <= now: raise GpuRentError("hold уже в прошлом — увеличь время или используй --clear") put_text(cfg, host, HOLD_PATH, f"{ts}\n") # readable by ubuntu + root run_ssh(cfg, host, f"sudo -n chmod 644 {HOLD_PATH}", check=False) iso = datetime.fromtimestamp(ts, tz=timezone.utc).isoformat() if log: log(f"hold до {iso} (unix {ts})") return ts def clear_hold(cfg: Config, host: str, log: Log | None = None) -> None: run_ssh(cfg, host, f"sudo -n rm -f {HOLD_PATH}", check=False) if log: log("hold снят") def read_hold(cfg: Config, host: str) -> int | None: if not remote_exists(cfg, host, HOLD_PATH): return None raw = run_ssh(cfg, host, f"cat {HOLD_PATH}", check=False).strip() try: return int(float(raw.split()[0])) except (ValueError, IndexError): return None