From 23b07672d66ec71b7d1c4007f908d6a46ce01f9b Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Sun, 23 Aug 2026 06:46:57 +0300 Subject: [PATCH] Point gpu-rent logs at gpu-rent-ollama and harden Assistent bin probes. Stock ollama.service is disabled by install; journal aliases must use gpu-rent-ollama. Prefer bin/{Debug,Release}/net* when locating the extension DLL and Sqlite private deps. Co-authored-by: Cursor --- src/gpu_rent/debug_assistent.py | 19 +++++++++++++++---- src/gpu_rent/provision.py | 10 ++++++++-- src/gpu_rent/vm_logs.py | 21 ++++++++++++--------- tests/test_vm_logs.py | 16 +++++++++++----- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/gpu_rent/debug_assistent.py b/src/gpu_rent/debug_assistent.py index f04f49e..cdd0c5c 100644 --- a/src/gpu_rent/debug_assistent.py +++ b/src/gpu_rent/debug_assistent.py @@ -30,10 +30,18 @@ for root in roots: for p in sorted(root.iterdir()): if "assistent" not in p.name.lower(): continue - dlls = sorted(p.glob("bin/**/SwarmAssistentExtension.dll")) + # Prefer TFM output dirs (Debug/Release net*), then any bin/** fallback + dlls = [] + for cfg_name in ("Debug", "Release"): + dlls.extend(sorted(p.glob(f"bin/{cfg_name}/net*/SwarmAssistentExtension.dll"))) + if not dlls: + dlls = sorted(p.glob("bin/**/SwarmAssistentExtension.dll")) if not dlls: # Older layout / alternate assembly name - dlls = sorted(p.glob("bin/**/*Assistent*.dll")) + for cfg_name in ("Debug", "Release"): + dlls.extend(sorted(p.glob(f"bin/{cfg_name}/net*/*Assistent*.dll"))) + if not dlls: + dlls = sorted(p.glob("bin/**/*Assistent*.dll")) dll = dlls[0] if dlls else None dll_dir = dll.parent if dll else None sqlite_dll = (dll_dir / "Microsoft.Data.Sqlite.dll") if dll_dir else None @@ -335,9 +343,12 @@ def collect_assistent_extension(cfg: Config, *, fs: dict[str, Any] | None = None hints.append(f"{e.get('name')}: нет DLL — compile fail / Swarm не билдил") ok = False elif not e.get("sqlite_dll"): + where = e.get("dll_dir") or "bin/{Debug,Release}/net*" hints.append( - f"{e.get('name')}: DLL есть, но нет Microsoft.Data.Sqlite.dll — " - "чат/memory API могут падать (нужен ≥0.13.1 + seed-extensions)" + f"{e.get('name')}: DLL есть ({where}), но рядом нет " + "Microsoft.Data.Sqlite.dll (+ SQLitePCLRaw*) — чат/memory API упадут. " + "Нужен swarm-assistent ≥0.13.1 (CopyLocalLockFileAssemblies) + " + "gpu-rent seed-extensions + restart SwarmUI" ) ok = False if not e.get("tab_html") or not e.get("bundle_js"): diff --git a/src/gpu_rent/provision.py b/src/gpu_rent/provision.py index 25af499..b3e5d69 100644 --- a/src/gpu_rent/provision.py +++ b/src/gpu_rent/provision.py @@ -237,7 +237,11 @@ for root in roots: for p in sorted(root.iterdir()): if "assistent" not in p.name.lower(): continue - dlls = sorted(p.glob("bin/**/SwarmAssistentExtension.dll")) + dlls = [] + for cfg_name in ("Debug", "Release"): + dlls.extend(sorted(p.glob(f"bin/{cfg_name}/net*/SwarmAssistentExtension.dll"))) + if not dlls: + dlls = sorted(p.glob("bin/**/SwarmAssistentExtension.dll")) dll = dlls[0] if dlls else None dll_dir = dll.parent if dll else None sqlite = (dll_dir / "Microsoft.Data.Sqlite.dll") if dll_dir else None @@ -246,6 +250,7 @@ for root in roots: "path": str(p), "name": p.name, "dll": str(dll) if dll else None, + "dll_dir": str(dll_dir) if dll_dir else None, "sqlite_dll": bool(sqlite and sqlite.is_file()), "sqlitepcl": bool(pcl), }) @@ -330,8 +335,9 @@ def verify_assistent_sqlite_bins(cfg: Config, host: str, log: Log) -> bool: log(f"Assistent {name}: Microsoft.Data.Sqlite есть (SQLitePCLRaw не найден)") ok_any = True else: + where = row.get("dll_dir") or "bin/{Debug,Release}/net*" log( - f"⚠ Assistent {name}: DLL есть, но нет Microsoft.Data.Sqlite.dll — " + f"⚠ Assistent {name}: DLL в {where}, но нет Microsoft.Data.Sqlite.dll — " "чат/memory API упадут. Нужен ≥0.13.1 + seed-extensions + restart" ) return ok_any diff --git a/src/gpu_rent/vm_logs.py b/src/gpu_rent/vm_logs.py index 938f752..89c4d6b 100644 --- a/src/gpu_rent/vm_logs.py +++ b/src/gpu_rent/vm_logs.py @@ -13,20 +13,23 @@ Log = Callable[[str], None] DIGEST_LINES = 20 FULL_LINES_DEFAULT = 80 +# Stock `ollama.service` is disabled by install_ollama.sh; we run gpu-rent-ollama. +OLLAMA_UNIT = "gpu-rent-ollama" +KILLER_UNIT = "gpu-rent-idle-killer" + UNIT_ALIASES: dict[str, str] = { "all": "all", "swarm": "swarmui", "swarmui": "swarmui", - "ollama": "ollama", - "killer": "gpu-rent-idle-killer", - "idle-killer": "gpu-rent-idle-killer", - "idle": "gpu-rent-idle-killer", + "ollama": OLLAMA_UNIT, + "gpu-rent-ollama": OLLAMA_UNIT, + "killer": KILLER_UNIT, + "idle-killer": KILLER_UNIT, + "idle": KILLER_UNIT, "cloud-init": "cloud-init", "cloud": "cloud-init", } -KILLER_UNIT = "gpu-rent-idle-killer" - def units_for(cfg: Config) -> list[str]: """Journal units enabled for this stack (no cloud-init).""" @@ -34,7 +37,7 @@ def units_for(cfg: Config) -> list[str]: if bool(getattr(cfg, "enable_swarmui", True)): units.append("swarmui") if normalize_runtime(getattr(cfg, "llm_runtime", "none")) == "ollama": - units.append("ollama") + units.append(OLLAMA_UNIT) units.append(KILLER_UNIT) return units @@ -81,7 +84,7 @@ def journal_units_for_target(target: str, *, cfg: Config | None = None) -> list[ if target == "all": if cfg is not None: return units_for(cfg) - return ["swarmui", "ollama", KILLER_UNIT] + return ["swarmui", OLLAMA_UNIT, KILLER_UNIT] return [target] @@ -115,7 +118,7 @@ def fetch_logs_for_cli( target = resolve_unit_alias(unit) # Full dump keeps historical all=swarm+ollama+killer regardless of cfg. if target == "all": - journal_units = ["swarmui", "ollama", KILLER_UNIT] + journal_units = ["swarmui", OLLAMA_UNIT, KILLER_UNIT] else: journal_units = journal_units_for_target(target) cmd = build_logs_remote_cmd( diff --git a/tests/test_vm_logs.py b/tests/test_vm_logs.py index 9add3c8..9fcbbc1 100644 --- a/tests/test_vm_logs.py +++ b/tests/test_vm_logs.py @@ -7,6 +7,7 @@ import pytest from gpu_rent.vm_logs import ( DIGEST_LINES, KILLER_UNIT, + OLLAMA_UNIT, build_logs_remote_cmd, fetch_unit_logs, resolve_unit_alias, @@ -23,7 +24,7 @@ class _Cfg: def test_units_for_swarm_and_ollama(): assert units_for(_Cfg(enable_swarmui=True, llm_runtime="ollama")) == [ "swarmui", - "ollama", + OLLAMA_UNIT, KILLER_UNIT, ] @@ -37,7 +38,7 @@ def test_units_for_swarm_only(): def test_units_for_llm_only(): assert units_for(_Cfg(enable_swarmui=False, llm_runtime="ollama")) == [ - "ollama", + OLLAMA_UNIT, KILLER_UNIT, ] @@ -51,7 +52,9 @@ def test_digest_cmd_has_journal_no_cloud_init(): ) assert "cloud-init" not in cmd assert "journalctl -u swarmui" in cmd - assert "journalctl -u ollama" in cmd + assert f"journalctl -u {OLLAMA_UNIT}" in cmd + # Stock ollama.service is disabled — digest must not query bare unit name + assert "journalctl -u ollama -n" not in cmd assert f"journalctl -u {KILLER_UNIT}" in cmd assert f"-n {DIGEST_LINES}" in cmd assert "systemctl is-active swarmui" in cmd @@ -61,15 +64,18 @@ def test_full_logs_cmd_includes_cloud_init(): cmd = build_logs_remote_cmd( lines=80, include_cloud_init=True, - journal_units=["swarmui", "ollama", KILLER_UNIT], + journal_units=["swarmui", OLLAMA_UNIT, KILLER_UNIT], ) assert "cloud-init-output.log" in cmd assert "journalctl -u swarmui" in cmd + assert f"journalctl -u {OLLAMA_UNIT}" in cmd def test_resolve_unit_alias(): assert resolve_unit_alias(None) == "all" assert resolve_unit_alias("swarm") == "swarmui" + assert resolve_unit_alias("ollama") == OLLAMA_UNIT + assert resolve_unit_alias("gpu-rent-ollama") == OLLAMA_UNIT assert resolve_unit_alias("killer") == KILLER_UNIT with pytest.raises(ValueError, match="неизвестный"): resolve_unit_alias("bogus") @@ -90,4 +96,4 @@ def test_fetch_unit_logs_uses_ssh(monkeypatch): assert "cloud-init" not in calls[0][1] assert "journalctl -u swarmui" in calls[0][1] assert f"journalctl -u {KILLER_UNIT}" in calls[0][1] - assert "journalctl -u ollama" not in calls[0][1] + assert f"journalctl -u {OLLAMA_UNIT}" not in calls[0][1]