LLMVideoPipeline/pipeline/remote.py

116 lines
4.2 KiB
Python
Raw Permalink Normal View History

2026-08-16 09:39:36 -06:00
"""
Remote PC manager with SSH, SCP, and HTTP API helpers.
"""
import subprocess
import requests
import logging
from typing import Dict, Any, Optional, Tuple
logger = logging.getLogger("YTFactory.Remote")
class RemotePC:
def __init__(self, name: str, host: str, ssh_user: str, services: Optional[Dict[str, Any]] = None):
self.name = name
self.host = host
self.ssh_user = ssh_user
self.services = services or {}
self.ssh_base = [
"ssh",
"-o", "StrictHostKeyChecking=no",
"-o", "ConnectTimeout=5",
"-o", "BatchMode=yes",
f"{ssh_user}@{host}"
]
def check(self, port: int) -> bool:
"""Check if service is responding on port."""
for path in ("/health", "/system_stats", "/v1/models", "/"):
try:
r = requests.get(f"http://{self.host}:{port}{path}", timeout=3)
if r.status_code in (200, 204, 404): # 404 still means server is alive
return True
except Exception:
pass
return False
def post_json(self, port: int, endpoint: str, payload: Dict[str, Any], timeout: int = 180) -> Optional[Dict[str, Any]]:
"""Send JSON POST request to service."""
url = f"http://{self.host}:{port}{endpoint}"
try:
r = requests.post(url, json=payload, timeout=timeout)
r.raise_for_status()
return r.json()
except Exception as e:
logger.debug(f"POST {self.name}:{port}{endpoint} failed: {e}")
return None
def post_binary(self, port: int, endpoint: str, payload: Dict[str, Any], timeout: int = 180) -> Optional[bytes]:
"""Send JSON POST request expecting binary audio/image output."""
url = f"http://{self.host}:{port}{endpoint}"
try:
r = requests.post(url, json=payload, timeout=timeout)
r.raise_for_status()
return r.content
except Exception as e:
logger.debug(f"POST binary {self.name}:{port}{endpoint} failed: {e}")
return None
def get_json(self, port: int, endpoint: str, timeout: int = 30) -> Optional[Dict[str, Any]]:
"""Send GET request expecting JSON."""
url = f"http://{self.host}:{port}{endpoint}"
try:
r = requests.get(url, timeout=timeout)
r.raise_for_status()
return r.json()
except Exception as e:
logger.debug(f"GET {self.name}:{port}{endpoint} failed: {e}")
return None
def run(self, cmd: str, timeout: int = 600) -> Tuple[int, str, str]:
"""Execute command over SSH."""
try:
r = subprocess.run(
self.ssh_base + [cmd],
capture_output=True,
text=True,
timeout=timeout
)
return r.returncode, r.stdout, r.stderr
except subprocess.TimeoutExpired:
logger.error(f"SSH command timed out on {self.name}: {cmd[:80]}")
return -1, "", "TimeoutExpired"
except Exception as e:
logger.error(f"SSH command failed on {self.name}: {e}")
return -1, "", str(e)
def scp_to(self, local_path: str, remote_path: str) -> bool:
"""Upload file via SCP."""
cmd = [
"scp",
"-o", "StrictHostKeyChecking=no",
"-o", "ConnectTimeout=10",
local_path,
f"{self.ssh_user}@{self.host}:{remote_path}"
]
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
logger.error(f"SCP to {self.name}:{remote_path} failed: {r.stderr}")
return False
return True
def scp_from(self, remote_path: str, local_path: str) -> bool:
"""Download file via SCP."""
cmd = [
"scp",
"-o", "StrictHostKeyChecking=no",
"-o", "ConnectTimeout=10",
f"{self.ssh_user}@{self.host}:{remote_path}",
local_path
]
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
logger.error(f"SCP from {self.name}:{remote_path} failed: {r.stderr}")
return False
return True