Enhance SwarmUI integration and GPU environment verification
- Updated CLI documentation to reflect the new handling of `CIVITAI_API_TOKEN`, which is now automatically passed to SwarmUI user settings during startup. - Improved the `render_access_panel` function to include additional warnings for idle-killer failures and stack errors, enhancing user feedback. - Introduced a new function `seed_swarmui_api_keys` to manage API key injection into SwarmUI, ensuring seamless integration with the Model Downloader. - Enhanced GPU environment verification logic to include fail-fast checks for critical components like CUDA, improving error handling and user notifications. - Updated tests to validate the new API key handling and access panel behavior, ensuring robustness in the integration process.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from gpu_rent.access_card import collect_access_links, mcp_snippet_lines
|
||||
from gpu_rent.access_card import collect_access_links, mcp_snippet_lines, render_access_panel
|
||||
|
||||
|
||||
class _Cfg:
|
||||
@@ -6,6 +6,7 @@ class _Cfg:
|
||||
llm_runtime = "ollama"
|
||||
ollama_local_port = 17811
|
||||
llamacpp_local_port = 17812
|
||||
enable_swarmui = True
|
||||
|
||||
|
||||
def test_collect_links_swarm_and_ollama(monkeypatch):
|
||||
@@ -29,3 +30,16 @@ def test_collect_links_no_tunnel():
|
||||
def test_mcp_snippet_json():
|
||||
lines = mcp_snippet_lines(_Cfg())
|
||||
assert any("17801/mcp" in line for line in lines)
|
||||
|
||||
|
||||
def test_access_panel_red_when_killer_failed(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.access_card.load_state",
|
||||
lambda: type(
|
||||
"S",
|
||||
(),
|
||||
{"notes": {"idle_killer": "failed", "idle_killer_error": "no cred"}},
|
||||
)(),
|
||||
)
|
||||
panel = render_access_panel(_Cfg(), tunneled=True)
|
||||
assert panel.border_style == "red"
|
||||
|
||||
@@ -14,5 +14,8 @@ def test_bootstrap_script_is_native_swarmui():
|
||||
assert "Data/Autocompletions" in script
|
||||
assert "mkfs.ext4" in script
|
||||
assert "apt-get upgrade" not in script
|
||||
assert ".gpu-rent-ready" in script
|
||||
assert "src/BuiltinExtensions/ComfyUIBackend/DLNodes" in script
|
||||
assert "GPU_RENT_BOOTSTRAP_LIGHT" in script
|
||||
assert "GPU_RENT_SKIP_SWARMUI" in script
|
||||
assert "light bootstrap — пропускаем apt-get" in script
|
||||
# llm-only re-up can skip apt when data marker exists (not only Swarm boot marker)
|
||||
assert 'MARKER_DATA' in script or ".gpu-rent-ready" in script
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Tests for remote swarmui_set_api_keys."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REMOTE = ROOT / "src" / "gpu_rent" / "remote" / "swarmui_set_api_keys.py"
|
||||
|
||||
|
||||
def _load():
|
||||
spec = importlib.util.spec_from_file_location("swarmui_set_api_keys", REMOTE)
|
||||
assert spec and spec.loader
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def test_set_civitai_key(tmp_path, monkeypatch):
|
||||
mod = _load()
|
||||
keys = tmp_path / "keys.json"
|
||||
keys.write_text(json.dumps({"civitai_api": "tok-123"}), encoding="utf-8")
|
||||
monkeypatch.setattr(mod, "KEYS_PATH", keys)
|
||||
|
||||
calls = []
|
||||
|
||||
def fake_post(path, payload, timeout=15.0):
|
||||
calls.append((path, payload))
|
||||
if path == "/API/GetNewSession":
|
||||
return {"session_id": "sid-1"}
|
||||
if path == "/API/SetAPIKey":
|
||||
assert payload["session_id"] == "sid-1"
|
||||
assert payload["keyType"] == "civitai_api"
|
||||
assert payload["key"] == "tok-123"
|
||||
return {"success": True}
|
||||
raise AssertionError(path)
|
||||
|
||||
monkeypatch.setattr(mod, "post", fake_post)
|
||||
monkeypatch.setattr(mod, "wait_session", lambda _d: "sid-1")
|
||||
assert mod.main() == 0
|
||||
assert not keys.exists()
|
||||
assert any(c[0] == "/API/SetAPIKey" for c in calls)
|
||||
|
||||
|
||||
def test_skip_empty_keys(tmp_path, monkeypatch):
|
||||
mod = _load()
|
||||
keys = tmp_path / "keys.json"
|
||||
keys.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.setattr(mod, "KEYS_PATH", keys)
|
||||
assert mod.main() == 0
|
||||
@@ -0,0 +1,33 @@
|
||||
from gpu_rent.timing import PhaseTimes, WaitLog, format_duration
|
||||
|
||||
|
||||
def test_format_duration():
|
||||
assert format_duration(5) == "5s"
|
||||
assert format_duration(65) == "1m 5s"
|
||||
assert format_duration(60) == "1m"
|
||||
assert format_duration(3661) == "1h 1m"
|
||||
|
||||
|
||||
def test_phase_times_summary(monkeypatch):
|
||||
times = iter([100.0, 110.0, 130.0, 190.0])
|
||||
monkeypatch.setattr("gpu_rent.timing.time.monotonic", lambda: next(times))
|
||||
clock = PhaseTimes()
|
||||
clock.mark("SSH")
|
||||
clock.mark("bootstrap")
|
||||
line = clock.summary_line()
|
||||
assert "SSH 10s" in line
|
||||
assert "bootstrap 20s" in line
|
||||
assert "всего" in line
|
||||
|
||||
|
||||
def test_wait_log_throttles(monkeypatch):
|
||||
logs: list[str] = []
|
||||
t = {"now": 0.0}
|
||||
monkeypatch.setattr("gpu_rent.timing.time.monotonic", lambda: t["now"])
|
||||
w = WaitLog(logs.append, every=30.0)
|
||||
w.tick("a")
|
||||
t["now"] = 10.0
|
||||
w.tick("b")
|
||||
t["now"] = 31.0
|
||||
w.tick("c")
|
||||
assert logs == ["a", "c"]
|
||||
@@ -86,3 +86,50 @@ def test_pip_ok_patches_extra_args(tmp_path, monkeypatch):
|
||||
assert marker["pip_ok"] is True
|
||||
assert "--use-sage-attention" in marker["extra_args"]
|
||||
assert "--use-sage-attention" in backends.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_pip_fail_retries_next_run(tmp_path, monkeypatch):
|
||||
mod = _load()
|
||||
data = tmp_path
|
||||
backends = data / "Data" / "Backends.fds"
|
||||
backends.parent.mkdir(parents=True)
|
||||
backends.write_text("ExtraArgs: \n", encoding="utf-8")
|
||||
gpu_json = data / ".gpu-rent-gpu.json"
|
||||
gpu_json.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"vram_mib": 24576,
|
||||
"compute_cap": "8.9",
|
||||
"uuid": "gpu-1",
|
||||
"name": "RTX",
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
marker = data / ".gpu-rent-perf-tuned"
|
||||
marker.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"uuid": "gpu-1",
|
||||
"extra_args": "",
|
||||
"pip_ok": False,
|
||||
"tier": "high",
|
||||
"name": "RTX",
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
pip = data / "fake-pip"
|
||||
pip.write_text("#!/bin/sh\n", encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(mod, "DATA", data)
|
||||
monkeypatch.setattr(mod, "GPU_JSON", gpu_json)
|
||||
monkeypatch.setattr(mod, "MARKER", marker)
|
||||
monkeypatch.setattr(mod, "BACKENDS", backends)
|
||||
monkeypatch.setattr(mod, "find_pip", lambda: pip)
|
||||
monkeypatch.setattr(mod, "pip_install_sage", lambda _p: True)
|
||||
|
||||
assert mod.main() == 0
|
||||
new_m = json.loads(marker.read_text(encoding="utf-8"))
|
||||
assert new_m["pip_ok"] is True
|
||||
assert "--use-sage-attention" in backends.read_text(encoding="utf-8")
|
||||
|
||||
@@ -95,6 +95,35 @@ def test_verify_gpu_env_ok(monkeypatch):
|
||||
assert any("GPU-стека" in line for line in logs)
|
||||
|
||||
|
||||
def test_verify_gpu_env_fail_fast_cuda(monkeypatch):
|
||||
import json
|
||||
|
||||
import gpu_rent.ssh_ops as ssh_ops
|
||||
|
||||
payload = {
|
||||
"ok": False,
|
||||
"checks": [
|
||||
{"name": "nvidia-smi", "required": True, "ok": True, "detail": "ok"},
|
||||
{"name": "cuda", "required": True, "ok": False, "detail": "нет libcuda"},
|
||||
{"name": "torch", "required": True, "ok": False, "detail": "no venv"},
|
||||
],
|
||||
}
|
||||
calls = {"n": 0}
|
||||
|
||||
def fake(*a, **k):
|
||||
calls["n"] += 1
|
||||
return json.dumps(payload)
|
||||
|
||||
monkeypatch.setattr(ssh_ops, "run_python", fake)
|
||||
logs: list[str] = []
|
||||
try:
|
||||
verify_gpu_env(_Cfg(), "1.2.3.4", logs.append, timeout=600.0, poll_every=0.1)
|
||||
assert False, "expected CloudError"
|
||||
except CloudError as exc:
|
||||
assert "fail-fast" in str(exc).lower() or "cuda" in str(exc).lower()
|
||||
assert calls["n"] == 1
|
||||
|
||||
|
||||
def test_verify_gpu_env_fails_without_cuda(monkeypatch):
|
||||
import json
|
||||
|
||||
|
||||
Reference in New Issue
Block a user