- P1 resident tests: P9's inference gate is a precondition of _run_with_vm, so _RealExecCase.make_executor now configures a default inference origin (CHV_INFERENCE_ORIGIN=https://xgapi.lol + CHV_INFERENCE_ALLOW_HOSTS=xgapi.lol) and a public fake resolver (same setup as P9's GateTest). No assertion changed; the gate's fail-closed behavior on missing origin stays pinned by P9's own ask test. 9 resident tests fixed (7 errors + 2 failures). - P2/P4 dry-run fixtures: P7's build.sh hard check requires chv-memory.py in the repo; add it to both fixtures' copy lists. 2 failures fixed. - No production code changed in this commit (test setup only).
744 lines
30 KiB
Python
744 lines
30 KiB
Python
"""CHV 常驻池(feat-chv-muse-p1)测试。
|
||
|
||
覆盖 cloud/chv/resident_pool.py、executor 的常驻接线
|
||
(_run_with_vm 常驻/冷启动两路、finally 语义)与
|
||
worker_adapter.reconcile_resident_pool facade。全部用假
|
||
执行器/假 VMM,绝不起真实 VM、不连真实网络。
|
||
|
||
断言点(按任务书):
|
||
- acquire/resume 失败回落冷启动,失败条目丢弃;
|
||
- release 只 pause 不销毁,pause 失败条目留 running;
|
||
- registry 损坏 quarantine、单坏条目不连坐;
|
||
- 重启后条目一律 orphan 不复用,只能 reconcile 清理;
|
||
- _run_with_vm 常驻命中跑完 pause、冷启动跑完 shutdown,
|
||
客内抛错时两路 finally 都执行;
|
||
- 带密钥 env / 未启用 / 建池失败时回落冷启动,任务不丢;
|
||
- 每用户隔离:release 只动本用户条目;
|
||
- 空闲回收只动超阈值 paused 条目,阈值 0 即关闭;
|
||
- WarmPool 实验模块不动(wired_to_dispatch 仍 False)。
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import re
|
||
import tempfile
|
||
import unittest
|
||
from types import SimpleNamespace
|
||
from unittest import mock
|
||
|
||
from cloud.chv import worker_adapter
|
||
from cloud.chv.errors import ChvFailure
|
||
from cloud.chv.executor import CloudHypervisorExecutor
|
||
from cloud.chv.experimental_pool import WarmPool
|
||
from cloud.chv.resident_pool import (
|
||
ResidentPool,
|
||
reconcile_resident_orphans_for_executor,
|
||
resident_task_for_user,
|
||
resident_user_key,
|
||
)
|
||
|
||
|
||
class _FakeExecutor:
|
||
"""ResidentPool 用的鸭子型执行器:记录调用,可注入失败。
|
||
|
||
wait 失败时模拟真实语义:先自行 shutdown 再抛错
|
||
(wait_guest_ready 的失败路径就是 stop_vm 后 raise)。
|
||
"""
|
||
|
||
def __init__(self, state_dir, env=None):
|
||
self.state_dir = state_dir
|
||
self._env = env or {}
|
||
self.resumes = []
|
||
self.pauses = []
|
||
self.wakes = []
|
||
self.waits = []
|
||
self.shutdowns = []
|
||
self.hibernates = []
|
||
self.emits = []
|
||
self.resume_fail = False
|
||
self.wait_fail = False
|
||
self.wake_fail = False
|
||
self.pause_fail = False
|
||
self.hibernate_fail = False
|
||
self.shutdown_fail = False
|
||
|
||
def vm_name(self, task_or_name):
|
||
raw = task_or_name.get("id") if isinstance(
|
||
task_or_name, dict) else str(task_or_name)
|
||
safe = re.sub(r"[^A-Za-z0-9_.-]", "-", raw)
|
||
if safe.startswith("chv-"):
|
||
return safe
|
||
return "chv-" + safe
|
||
|
||
def resume_vm(self, task_or_name):
|
||
self.resumes.append(self.vm_name(task_or_name))
|
||
if self.resume_fail:
|
||
raise RuntimeError("resume boom")
|
||
return True
|
||
|
||
def pause_vm(self, task_or_name):
|
||
self.pauses.append(self.vm_name(task_or_name))
|
||
if self.pause_fail:
|
||
raise RuntimeError("pause boom")
|
||
return True
|
||
|
||
def wake_vm(self, task_or_name):
|
||
self.wakes.append(self.vm_name(task_or_name))
|
||
if self.wake_fail:
|
||
raise RuntimeError("wake boom")
|
||
return {"vm": self.vm_name(task_or_name), "mode": "cold"}
|
||
|
||
def wait_guest_ready(self, task):
|
||
self.waits.append(self.vm_name(task))
|
||
if self.wait_fail:
|
||
self.shutdown_vm(task)
|
||
raise ChvFailure("guest_not_ready", "ssh 未就绪")
|
||
return True
|
||
|
||
def shutdown_vm(self, task_or_name):
|
||
self.shutdowns.append(self.vm_name(task_or_name))
|
||
if self.shutdown_fail:
|
||
raise RuntimeError("shutdown boom")
|
||
return True
|
||
|
||
def hibernate_vm(self, task_or_name, snapshot_url):
|
||
self.hibernates.append(
|
||
(self.vm_name(task_or_name), snapshot_url))
|
||
if self.hibernate_fail:
|
||
raise RuntimeError("hibernate boom")
|
||
return {"vm": self.vm_name(task_or_name),
|
||
"state": "hibernated", "snapshot": snapshot_url}
|
||
|
||
def _emit(self, event, task, detail="", kind=None):
|
||
self.emits.append(event)
|
||
|
||
|
||
def _public_resolver(host, port):
|
||
"""假解析器:公网 IP(与 P9 测试同款,供推理门放行用)。"""
|
||
return ["93.184.216.34"]
|
||
|
||
|
||
def _write(path, content):
|
||
with open(path, "w") as fh:
|
||
fh.write(content)
|
||
return path
|
||
|
||
|
||
def _registry_path(state_dir):
|
||
return os.path.join(state_dir, "resident-pool.json")
|
||
|
||
|
||
class ResidentKeyTest(unittest.TestCase):
|
||
def test_user_key_sanitizes_and_defaults(self):
|
||
self.assertEqual(resident_user_key(None), "default")
|
||
self.assertEqual(resident_user_key(""), "default")
|
||
self.assertEqual(resident_user_key("alice"), "alice")
|
||
self.assertEqual(resident_user_key("a/b c"), "a-b-c")
|
||
self.assertEqual(resident_task_for_user("alice"),
|
||
{"id": "resident-alice"})
|
||
self.assertEqual(resident_task_for_user(None),
|
||
{"id": "resident-default"})
|
||
|
||
|
||
class ResidentPoolAcquireTest(unittest.TestCase):
|
||
def setUp(self):
|
||
self.tmp = tempfile.TemporaryDirectory()
|
||
self.fake = _FakeExecutor(self.tmp.name)
|
||
|
||
def tearDown(self):
|
||
self.tmp.cleanup()
|
||
|
||
def _provisioned_pool(self, user="alice"):
|
||
pool = ResidentPool(self.fake)
|
||
got = pool.provision(user)
|
||
self.assertEqual(got["mode"], "resident")
|
||
return pool
|
||
|
||
def test_provision_then_acquire_then_release(self):
|
||
pool = self._provisioned_pool()
|
||
self.assertEqual(self.fake.wakes, ["chv-resident-alice"])
|
||
self.assertEqual(self.fake.pauses, ["chv-resident-alice"])
|
||
self.assertEqual(pool.paused_count(), 1)
|
||
got = pool.acquire("alice")
|
||
self.assertEqual(got["mode"], "resident")
|
||
self.assertEqual(got["vm"], "chv-resident-alice")
|
||
self.assertEqual(got["task"], {"id": "resident-alice"})
|
||
self.assertEqual(self.fake.resumes, ["chv-resident-alice"])
|
||
self.assertEqual(self.fake.waits, ["chv-resident-alice"])
|
||
self.assertEqual(pool.paused_count(), 0)
|
||
# running 中的条目不接受第二个 acquire(回落冷启动)。
|
||
self.assertEqual(pool.acquire("alice")["mode"], "cold")
|
||
self.assertEqual(len(self.fake.resumes), 1)
|
||
# release 只 pause,不销毁。
|
||
self.assertTrue(pool.release("alice"))
|
||
self.assertEqual(
|
||
self.fake.pauses,
|
||
["chv-resident-alice", "chv-resident-alice"])
|
||
self.assertEqual(self.fake.shutdowns, [])
|
||
self.assertEqual(pool.paused_count(), 1)
|
||
# 再取仍是同一台 VM。
|
||
got = pool.acquire("alice")
|
||
self.assertEqual(got["vm"], "chv-resident-alice")
|
||
|
||
def test_resume_failure_falls_back_to_cold_and_drops_entry(self):
|
||
pool = self._provisioned_pool()
|
||
self.fake.resume_fail = True
|
||
got = pool.acquire("alice")
|
||
self.assertEqual(got, {"mode": "cold", "vm": None,
|
||
"task": None})
|
||
self.assertEqual(pool.paused_count(), 0)
|
||
with open(_registry_path(self.tmp.name)) as fh:
|
||
self.assertEqual(json.load(fh), {})
|
||
|
||
def test_wait_failure_falls_back_to_cold_and_drops_entry(self):
|
||
pool = self._provisioned_pool()
|
||
self.fake.wait_fail = True
|
||
got = pool.acquire("alice")
|
||
self.assertEqual(got["mode"], "cold")
|
||
self.assertEqual(pool.paused_count(), 0)
|
||
# wait 的失败路径已自行 stop_vm(模拟真实语义)。
|
||
self.assertEqual(self.fake.shutdowns,
|
||
["chv-resident-alice"])
|
||
with open(_registry_path(self.tmp.name)) as fh:
|
||
self.assertEqual(json.load(fh), {})
|
||
|
||
def test_release_without_running_entry_is_false(self):
|
||
pool = self._provisioned_pool()
|
||
self.assertFalse(pool.release("alice"))
|
||
self.assertFalse(pool.release("nobody"))
|
||
self.assertEqual(self.fake.pauses, ["chv-resident-alice"])
|
||
|
||
def test_release_pause_failure_keeps_entry_running(self):
|
||
pool = self._provisioned_pool()
|
||
pool.acquire("alice")
|
||
self.fake.pause_fail = True
|
||
self.assertFalse(pool.release("alice"))
|
||
# 不删条目、不销毁:下次 acquire 仍是 running→cold,
|
||
# VM 本身没被 shutdown,留待 worker 关闭时清理。
|
||
self.assertEqual(self.fake.shutdowns, [])
|
||
self.assertEqual(pool.acquire("alice")["mode"], "cold")
|
||
|
||
def test_provision_is_idempotent_and_per_user_isolated(self):
|
||
pool = ResidentPool(self.fake)
|
||
self.assertEqual(pool.provision("alice")["mode"], "resident")
|
||
self.assertEqual(pool.provision("bob")["mode"], "resident")
|
||
self.assertEqual(pool.provision("alice")["mode"], "resident")
|
||
self.assertEqual(self.fake.wakes.count("chv-resident-alice"), 1)
|
||
self.assertEqual(self.fake.wakes.count("chv-resident-bob"), 1)
|
||
self.assertEqual(pool.paused_count(), 2)
|
||
# release 只动本用户条目。
|
||
self.assertFalse(pool.release("bob")) # bob 未 running
|
||
pool.acquire("alice")
|
||
self.assertTrue(pool.release("alice"))
|
||
self.assertEqual(pool.paused_count(), 2)
|
||
got = pool.acquire("bob")
|
||
self.assertEqual(got["vm"], "chv-resident-bob")
|
||
self.assertEqual(pool.paused_count(), 1)
|
||
self.assertEqual(self.fake.resumes, ["chv-resident-alice",
|
||
"chv-resident-bob"])
|
||
|
||
def test_provision_wake_failure_falls_back_and_cleans_up(self):
|
||
pool = ResidentPool(self.fake)
|
||
self.fake.wake_fail = True
|
||
got = pool.provision("alice")
|
||
self.assertEqual(got, {"mode": "cold", "vm": None,
|
||
"task": None})
|
||
# 建池失败尽力 shutdown,不留半醒 VM 与条目。
|
||
self.assertEqual(self.fake.shutdowns,
|
||
["chv-resident-alice"])
|
||
self.assertEqual(pool.paused_count(), 0)
|
||
with open(_registry_path(self.tmp.name)) as fh:
|
||
self.assertEqual(json.load(fh), {})
|
||
|
||
|
||
class ResidentPoolRegistryTest(unittest.TestCase):
|
||
def setUp(self):
|
||
self.tmp = tempfile.TemporaryDirectory()
|
||
self.fake = _FakeExecutor(self.tmp.name)
|
||
|
||
def tearDown(self):
|
||
self.tmp.cleanup()
|
||
|
||
def test_corrupt_registry_is_quarantined(self):
|
||
path = _write(_registry_path(self.tmp.name), "{not json")
|
||
pool = ResidentPool(self.fake)
|
||
self.assertTrue(pool.registry_corrupt)
|
||
self.assertEqual(pool.paused_count(), 0)
|
||
self.assertEqual(pool.orphan_count(), 0)
|
||
self.assertTrue(os.path.exists(path + ".corrupt"))
|
||
self.assertFalse(os.path.exists(path))
|
||
|
||
def test_top_level_non_dict_is_corrupt(self):
|
||
path = _write(_registry_path(self.tmp.name), "[1, 2]")
|
||
pool = ResidentPool(self.fake)
|
||
self.assertTrue(pool.registry_corrupt)
|
||
self.assertTrue(os.path.exists(path + ".corrupt"))
|
||
|
||
def test_bad_entries_dropped_without_tainting_good_ones(self):
|
||
_write(_registry_path(self.tmp.name), json.dumps({
|
||
"good": {"state": "paused", "vm": "chv-resident-good",
|
||
"user_id": "good"},
|
||
"no-vm": {"state": "paused"},
|
||
"not-a-dict": "x",
|
||
"wrong-state": {"state": "flying",
|
||
"vm": "chv-resident-x"},
|
||
}))
|
||
pool = ResidentPool(self.fake)
|
||
self.assertEqual(pool.registry_corrupt_entries, 3)
|
||
self.assertEqual(pool.orphan_count(), 1)
|
||
self.assertEqual(pool.paused_count(), 0)
|
||
|
||
def test_restart_never_reuses_entries(self):
|
||
pool1 = ResidentPool(self.fake)
|
||
pool1.provision("alice")
|
||
pool2 = ResidentPool(self.fake)
|
||
self.assertEqual(pool2.orphan_count(), 1)
|
||
self.assertEqual(pool2.paused_count(), 0)
|
||
# 新池绝不把上一世条目发给任务。
|
||
self.assertEqual(pool2.acquire("alice")["mode"], "cold")
|
||
self.assertEqual(self.fake.resumes, [])
|
||
cleaned = pool2.reconcile_orphans()
|
||
self.assertEqual(cleaned, ["chv-resident-alice"])
|
||
self.assertEqual(self.fake.shutdowns,
|
||
["chv-resident-alice"])
|
||
with open(_registry_path(self.tmp.name)) as fh:
|
||
self.assertEqual(json.load(fh), {})
|
||
|
||
def test_idle_minutes_env_parsing(self):
|
||
self.assertEqual(
|
||
ResidentPool(self.fake).idle_minutes, 0)
|
||
fake = _FakeExecutor(
|
||
self.tmp.name, env={"CHV_RESIDENT_IDLE_MINUTES": "45"})
|
||
self.assertEqual(ResidentPool(fake).idle_minutes, 45)
|
||
fake = _FakeExecutor(
|
||
self.tmp.name, env={"CHV_RESIDENT_IDLE_MINUTES": "junk"})
|
||
self.assertEqual(ResidentPool(fake).idle_minutes, 0)
|
||
fake = _FakeExecutor(
|
||
self.tmp.name, env={"CHV_RESIDENT_IDLE_MINUTES": "-5"})
|
||
self.assertEqual(ResidentPool(fake).idle_minutes, 0)
|
||
pool = ResidentPool(self.fake, idle_minutes=10)
|
||
self.assertEqual(pool.idle_minutes, 10)
|
||
|
||
|
||
class ResidentPoolIdleTest(unittest.TestCase):
|
||
def setUp(self):
|
||
self.tmp = tempfile.TemporaryDirectory()
|
||
self.fake = _FakeExecutor(self.tmp.name)
|
||
|
||
def tearDown(self):
|
||
self.tmp.cleanup()
|
||
|
||
def test_idle_disabled_by_default(self):
|
||
pool = ResidentPool(self.fake)
|
||
pool.provision("alice")
|
||
import time
|
||
self.assertEqual(
|
||
pool.reclaim_idle(now=time.time() + 10 ** 9), [])
|
||
self.assertEqual(self.fake.hibernates, [])
|
||
|
||
def test_overdue_paused_entry_is_hibernated(self):
|
||
import time
|
||
pool = ResidentPool(self.fake, idle_minutes=30)
|
||
pool.provision("alice")
|
||
pool.provision("bob")
|
||
now = time.time() + 31 * 60
|
||
reclaimed = pool.reclaim_idle(now=now)
|
||
self.assertEqual(reclaimed, ["chv-resident-alice",
|
||
"chv-resident-bob"])
|
||
self.assertEqual(len(self.fake.hibernates), 2)
|
||
for vm, url in self.fake.hibernates:
|
||
self.assertTrue(url.startswith("file://"))
|
||
self.assertIn(self.tmp.name, url)
|
||
self.assertTrue(url.endswith(
|
||
os.path.join(vm, "resident.snapshot")))
|
||
self.assertEqual(pool.paused_count(), 0)
|
||
with open(_registry_path(self.tmp.name)) as fh:
|
||
self.assertEqual(json.load(fh), {})
|
||
|
||
def test_not_yet_idle_entry_is_kept(self):
|
||
import time
|
||
pool = ResidentPool(self.fake, idle_minutes=30)
|
||
pool.provision("alice")
|
||
self.assertEqual(
|
||
pool.reclaim_idle(now=time.time() + 10 * 60), [])
|
||
self.assertEqual(pool.paused_count(), 1)
|
||
|
||
def test_hibernate_failure_keeps_entry_for_retry(self):
|
||
import time
|
||
pool = ResidentPool(self.fake, idle_minutes=30)
|
||
pool.provision("alice")
|
||
self.fake.hibernate_fail = True
|
||
self.assertEqual(
|
||
pool.reclaim_idle(now=time.time() + 31 * 60), [])
|
||
self.assertEqual(pool.paused_count(), 1)
|
||
|
||
|
||
class ResidentFacadeTest(unittest.TestCase):
|
||
def setUp(self):
|
||
self.tmp = tempfile.TemporaryDirectory()
|
||
self.fake = _FakeExecutor(self.tmp.name)
|
||
|
||
def tearDown(self):
|
||
self.tmp.cleanup()
|
||
|
||
def test_module_facade_cleans_and_empties_registry(self):
|
||
_write(_registry_path(self.tmp.name), json.dumps({
|
||
"a": {"state": "paused", "vm": "chv-resident-a"},
|
||
"b": {"state": "running", "vm": "chv-resident-b"},
|
||
"c": {"state": "provisioning", "vm": "chv-resident-c"},
|
||
"d": {"state": "warm", "vm": "chv-resident-d"},
|
||
"e": {"state": "paused"},
|
||
"f": "not-a-dict",
|
||
}))
|
||
cleaned = reconcile_resident_orphans_for_executor(self.fake)
|
||
self.assertEqual(cleaned, ["chv-resident-a", "chv-resident-b",
|
||
"chv-resident-c"])
|
||
self.assertEqual(self.fake.shutdowns, cleaned)
|
||
with open(_registry_path(self.tmp.name)) as fh:
|
||
self.assertEqual(json.load(fh), {})
|
||
self.assertIn("resident_pool_reconcile", self.fake.emits)
|
||
|
||
def test_module_facade_without_registry_is_empty(self):
|
||
self.assertEqual(
|
||
reconcile_resident_orphans_for_executor(self.fake), [])
|
||
self.assertEqual(self.fake.shutdowns, [])
|
||
|
||
def test_module_facade_without_state_dir_is_empty(self):
|
||
fake = _FakeExecutor(None)
|
||
fake.state_dir = None
|
||
self.assertEqual(
|
||
reconcile_resident_orphans_for_executor(fake), [])
|
||
|
||
class WorkerAdapterFacadeTest(unittest.TestCase):
|
||
def setUp(self):
|
||
self.tmp = tempfile.TemporaryDirectory()
|
||
self.fake = _FakeExecutor(self.tmp.name)
|
||
self.audits = []
|
||
|
||
def tearDown(self):
|
||
self.tmp.cleanup()
|
||
|
||
def _audit(self, project_id, event, detail):
|
||
self.audits.append((project_id, event, detail))
|
||
|
||
def test_facade_cleans_resident_registry(self):
|
||
# worker_adapter 的 isinstance 门要求真执行器:用假
|
||
# 的 shutdown 替身包一层真执行器实例(只借类型)。
|
||
ex = _RealExecCase.make_executor(self.tmp.name, self.tmp.name)
|
||
ex.shutdown_vm = self.fake.shutdown_vm
|
||
ex._emit = self.fake._emit
|
||
_write(_registry_path(self.tmp.name), json.dumps({
|
||
"a": {"state": "paused", "vm": "chv-resident-a"},
|
||
"b": {"state": "running", "vm": "chv-resident-b"},
|
||
}))
|
||
cleaned = worker_adapter.reconcile_resident_pool(
|
||
ex, self._audit, "startup")
|
||
self.assertEqual(cleaned, ["chv-resident-a", "chv-resident-b"])
|
||
self.assertEqual(self.fake.shutdowns, cleaned)
|
||
self.assertTrue(any(
|
||
event == "chv_resident_pool_reconcile"
|
||
for _p, event, _d in self.audits))
|
||
with open(_registry_path(self.tmp.name)) as fh:
|
||
self.assertEqual(json.load(fh), {})
|
||
|
||
def test_facade_never_constructs_resident_pool(self):
|
||
ex = _RealExecCase.make_executor(self.tmp.name, self.tmp.name)
|
||
ex.shutdown_vm = self.fake.shutdown_vm
|
||
ex._emit = self.fake._emit
|
||
_write(_registry_path(self.tmp.name), json.dumps({
|
||
"a": {"state": "paused", "vm": "chv-resident-a"},
|
||
}))
|
||
with mock.patch(
|
||
"cloud.chv.resident_pool.ResidentPool",
|
||
side_effect=AssertionError("must not construct")):
|
||
cleaned = worker_adapter.reconcile_resident_pool(
|
||
ex, self._audit, "shutdown")
|
||
self.assertEqual(cleaned, ["chv-resident-a"])
|
||
|
||
def test_facade_ignores_non_chv_executor(self):
|
||
cleaned = worker_adapter.reconcile_resident_pool(
|
||
object(), self._audit, "startup")
|
||
self.assertEqual(cleaned, [])
|
||
self.assertEqual(self.audits, [])
|
||
|
||
def test_facade_does_not_touch_warm_pool_reconcile(self):
|
||
# reconcile_warm_pool 仍走 warm-pool.json,与常驻无关。
|
||
ex = _RealExecCase.make_executor(self.tmp.name, self.tmp.name)
|
||
ex.shutdown_vm = self.fake.shutdown_vm
|
||
ex._emit = self.fake._emit
|
||
warm_path = os.path.join(self.tmp.name, "warm-pool.json")
|
||
_write(warm_path, json.dumps({
|
||
"w": {"state": "warm", "vm": "chv-warm-1",
|
||
"task_id": "w"},
|
||
}))
|
||
cleaned = worker_adapter.reconcile_warm_pool(
|
||
ex, self._audit, "startup")
|
||
self.assertEqual(cleaned, ["chv-warm-1"])
|
||
self.assertFalse(os.path.exists(_registry_path(self.tmp.name)))
|
||
|
||
|
||
def _write_file(path, content=b"x"):
|
||
with open(path, "wb") as fh:
|
||
fh.write(content)
|
||
os.chmod(path, 0o755)
|
||
return path
|
||
|
||
|
||
class _RealExecCase(unittest.TestCase):
|
||
"""真执行器 + 全方法替身:测 _run_with_vm 的接线本身,
|
||
不测 VMM。"""
|
||
|
||
@staticmethod
|
||
def make_executor(state_dir, base_dir, env_extra=None):
|
||
# round-12:P9 推理门是 _run_with_vm 的前置条件——本类
|
||
# 的任务都不带 model_provider,须配默认推理 origin +
|
||
# 公网解析器才放行(与 P9 GateTest 同款;未配时门应
|
||
# fail-closed,见 test_chv_muse_p9 的 ask 用例——这里
|
||
# 测的是常驻接线本身,不是门的行为,所以配好放行)。
|
||
env = {"CHV_EGRESS_PROXY": "http://10.0.0.1:8888",
|
||
"CHV_HOST_KEY_MODE": "keyscan",
|
||
"CHV_SSH_FORWARD": "1",
|
||
"CHV_INFERENCE_ORIGIN": "https://xgapi.lol",
|
||
"CHV_INFERENCE_ALLOW_HOSTS": "xgapi.lol"}
|
||
if env_extra:
|
||
env.update(env_extra)
|
||
cpuinfo = os.path.join(base_dir, "cpuinfo")
|
||
with open(cpuinfo, "w") as fh:
|
||
fh.write("flags : fpu vmx sse\n")
|
||
ex = CloudHypervisorExecutor(
|
||
binary=_write_file(os.path.join(base_dir,
|
||
"cloud-hypervisor")),
|
||
ch_remote=_write_file(os.path.join(base_dir, "ch-remote")),
|
||
kernel=_write_file(os.path.join(base_dir, "vmlinux")),
|
||
rootfs=_write_file(os.path.join(base_dir, "disk.raw"),
|
||
b"R" * 100),
|
||
ssh_key=_write_file(os.path.join(base_dir, "id_chv")),
|
||
kvm_path=_write_file(os.path.join(base_dir, "kvm")),
|
||
cpuinfo_path=cpuinfo,
|
||
state_dir=state_dir,
|
||
sleeper=lambda s: None, env=env)
|
||
ex.audit_callback = lambda e, t, d, k=None: None
|
||
ex.sentinel_resolver = _public_resolver
|
||
return ex
|
||
|
||
def setUp(self):
|
||
self.tmp = tempfile.TemporaryDirectory()
|
||
self.state_dir = os.path.join(self.tmp.name, "state")
|
||
self.calls = []
|
||
|
||
def tearDown(self):
|
||
self.tmp.cleanup()
|
||
|
||
def _stubbed_exec(self, env_extra=None, wake_fail_resident=False):
|
||
ex = self.make_executor(self.state_dir, self.tmp.name, env_extra)
|
||
calls = self.calls
|
||
|
||
def wake(task, extra_env=None):
|
||
calls.append(("wake", ex.vm_name(task)))
|
||
if wake_fail_resident and ex.vm_name(task).startswith(
|
||
"chv-resident-"):
|
||
raise RuntimeError("provision boom")
|
||
return {"vm": ex.vm_name(task), "mode": "cold"}
|
||
|
||
def pause(task_or_name):
|
||
calls.append(("pause", ex.vm_name(task_or_name)))
|
||
return True
|
||
|
||
def resume(task_or_name):
|
||
calls.append(("resume", ex.vm_name(task_or_name)))
|
||
return True
|
||
|
||
def wait(task):
|
||
calls.append(("wait", ex.vm_name(task)))
|
||
return True
|
||
|
||
def shutdown(task_or_name):
|
||
calls.append(("shutdown", ex.vm_name(task_or_name)))
|
||
return True
|
||
|
||
def run_guest(task, inner_args, extra_env=None, **kwargs):
|
||
calls.append(("guest", task["id"], list(inner_args)))
|
||
return {"task_id": task["id"], "status": "completed",
|
||
"stdout": "ok", "vm": ex.vm_name(task)}
|
||
|
||
def run_guest_goal(task, inner_args, extra_env=None, **kwargs):
|
||
calls.append(("guest-goal", task["id"], list(inner_args)))
|
||
return {"task_id": task["id"], "status": "completed",
|
||
"goal_met": True, "vm": ex.vm_name(task)}
|
||
|
||
ex.wake_vm = wake
|
||
ex.pause_vm = pause
|
||
ex.resume_vm = resume
|
||
ex.wait_guest_ready = wait
|
||
ex.shutdown_vm = shutdown
|
||
ex._run_guest = run_guest
|
||
ex._run_guest_goal = run_guest_goal
|
||
return ex
|
||
|
||
|
||
class RunWithVmResidentTest(_RealExecCase):
|
||
def test_first_task_provisions_and_second_reuses(self):
|
||
ex = self._stubbed_exec()
|
||
task = {"id": "task-1", "prompt": "hi"}
|
||
result = ex._run_with_vm(task, ["dispatch", "--task", "hi"])
|
||
self.assertEqual(result["status"], "completed")
|
||
# 常驻伪任务跑命令,但 task_id 改回真实任务。
|
||
self.assertEqual(result["task_id"], "task-1")
|
||
self.assertEqual(result["vm"], "chv-resident-default")
|
||
kinds = [c[0] for c in self.calls]
|
||
self.assertEqual(kinds, ["wake", "pause", "resume", "wait",
|
||
"guest", "pause"])
|
||
self.assertEqual(self.calls[0][1], "chv-resident-default")
|
||
self.assertEqual(self.calls[4][1], "resident-default")
|
||
# 第二个任务复用同一台常驻 VM,不再 wake。
|
||
self.calls.clear()
|
||
task2 = {"id": "task-2", "prompt": "hi2"}
|
||
result2 = ex._run_with_vm(task2, ["dispatch", "--task", "hi2"])
|
||
self.assertEqual(result2["task_id"], "task-2")
|
||
self.assertEqual(result2["vm"], "chv-resident-default")
|
||
self.assertEqual([c[0] for c in self.calls],
|
||
["resume", "wait", "guest", "pause"])
|
||
# 落盘条目是 paused,可供下次 acquire。
|
||
with open(_registry_path(self.state_dir)) as fh:
|
||
data = json.load(fh)
|
||
self.assertEqual(data["default"]["state"], "paused")
|
||
self.assertEqual(data["default"]["vm"], "chv-resident-default")
|
||
|
||
def test_goal_path_runs_on_resident_and_rewrites_task_id(self):
|
||
ex = self._stubbed_exec()
|
||
task = {"id": "goal-1", "prompt": "g"}
|
||
result = ex._run_with_vm(task, ["goal", "--task", "g"],
|
||
goal=True)
|
||
self.assertEqual(result["status"], "completed")
|
||
self.assertEqual(result["task_id"], "goal-1")
|
||
self.assertTrue(result["goal_met"])
|
||
self.assertIn(("guest-goal", "resident-default",
|
||
["goal", "--task", "g"]), self.calls)
|
||
|
||
def test_dispatch_task_flows_through_resident(self):
|
||
ex = self._stubbed_exec()
|
||
result = ex.dispatch_task({"id": "d-1", "prompt": "hello"})
|
||
self.assertEqual(result["status"], "completed")
|
||
self.assertEqual(result["task_id"], "d-1")
|
||
self.assertEqual(result["vm"], "chv-resident-default")
|
||
|
||
def test_per_user_vms_are_isolated(self):
|
||
ex = self._stubbed_exec()
|
||
ex._run_with_vm({"id": "t-a", "user_id": "alice",
|
||
"prompt": "x"}, ["dispatch"])
|
||
ex._run_with_vm({"id": "t-b", "user_id": "bob",
|
||
"prompt": "x"}, ["dispatch"])
|
||
wakes = [c[1] for c in self.calls if c[0] == "wake"]
|
||
self.assertEqual(wakes, ["chv-resident-alice",
|
||
"chv-resident-bob"])
|
||
with open(_registry_path(self.state_dir)) as fh:
|
||
data = json.load(fh)
|
||
self.assertEqual(set(data), {"alice", "bob"})
|
||
|
||
def test_disabled_flag_goes_cold(self):
|
||
ex = self._stubbed_exec(
|
||
env_extra={"CHV_RESIDENT_ENABLED": "0"})
|
||
result = ex._run_with_vm({"id": "task-1", "prompt": "hi"},
|
||
["dispatch"])
|
||
self.assertEqual(result["status"], "completed")
|
||
self.assertEqual(result["task_id"], "task-1")
|
||
kinds = [c[0] for c in self.calls]
|
||
self.assertEqual(kinds, ["wake", "guest", "shutdown"])
|
||
self.assertEqual(self.calls[0][1], "chv-task-1")
|
||
self.assertIsNone(getattr(ex, "_resident_pool", None))
|
||
self.assertFalse(os.path.exists(
|
||
_registry_path(self.state_dir)))
|
||
|
||
def test_secret_env_goes_cold(self):
|
||
ex = self._stubbed_exec()
|
||
result = ex._run_with_vm(
|
||
{"id": "task-1", "prompt": "hi"}, ["dispatch"],
|
||
extra_env={"SUB2API_API_KEY": "sekret"})
|
||
self.assertEqual(result["status"], "completed")
|
||
kinds = [c[0] for c in self.calls]
|
||
self.assertEqual(kinds, ["wake", "guest", "shutdown"])
|
||
self.assertFalse(os.path.exists(
|
||
_registry_path(self.state_dir)))
|
||
|
||
def test_provision_failure_falls_back_to_cold(self):
|
||
ex = self._stubbed_exec(wake_fail_resident=True)
|
||
result = ex._run_with_vm({"id": "task-1", "prompt": "hi"},
|
||
["dispatch"])
|
||
self.assertEqual(result["status"], "completed")
|
||
self.assertEqual(result["task_id"], "task-1")
|
||
kinds = [c[0] for c in self.calls]
|
||
# 建池 wake 失败(尽力 shutdown)→ 冷启动照常跑任务。
|
||
self.assertEqual(kinds, ["wake", "shutdown", "wake",
|
||
"guest", "shutdown"])
|
||
self.assertEqual(self.calls[1][1], "chv-resident-default")
|
||
self.assertEqual(self.calls[2][1], "chv-task-1")
|
||
|
||
def test_guest_error_still_pauses_resident_in_finally(self):
|
||
ex = self._stubbed_exec()
|
||
|
||
def boom(task, inner_args, extra_env=None, **kwargs):
|
||
raise RuntimeError("guest boom")
|
||
|
||
ex._run_guest = boom
|
||
with self.assertRaises(RuntimeError):
|
||
ex._run_with_vm({"id": "task-1", "prompt": "hi"},
|
||
["dispatch"])
|
||
kinds = [c[0] for c in self.calls]
|
||
self.assertEqual(kinds, ["wake", "pause", "resume", "wait",
|
||
"pause"])
|
||
self.assertEqual(self.calls[-1][1], "chv-resident-default")
|
||
|
||
def test_guest_error_still_shuts_down_cold_in_finally(self):
|
||
ex = self._stubbed_exec(
|
||
env_extra={"CHV_RESIDENT_ENABLED": "0"})
|
||
|
||
def boom(task, inner_args, extra_env=None, **kwargs):
|
||
raise RuntimeError("guest boom")
|
||
|
||
ex._run_guest = boom
|
||
with self.assertRaises(RuntimeError):
|
||
ex._run_with_vm({"id": "task-1", "prompt": "hi"},
|
||
["dispatch"])
|
||
self.assertEqual([c[0] for c in self.calls],
|
||
["wake", "shutdown"])
|
||
|
||
def test_wake_failure_propagates_without_shutdown(self):
|
||
ex = self._stubbed_exec(
|
||
env_extra={"CHV_RESIDENT_ENABLED": "0"})
|
||
|
||
def wake_fail(task, extra_env=None):
|
||
raise ChvFailure("vm_create_failed", "no vm")
|
||
|
||
ex.wake_vm = wake_fail
|
||
with self.assertRaises(ChvFailure):
|
||
ex._run_with_vm({"id": "task-1", "prompt": "hi"},
|
||
["dispatch"])
|
||
self.assertEqual(self.calls, [])
|
||
|
||
|
||
class WarmPoolUntouchedTest(unittest.TestCase):
|
||
def test_wired_to_dispatch_still_false(self):
|
||
self.assertFalse(WarmPool.wired_to_dispatch)
|
||
|
||
def test_executor_source_never_imports_experimental(self):
|
||
import ast
|
||
from pathlib import Path
|
||
tree = ast.parse((Path(__file__).resolve().parents[2]
|
||
/ "cloud" / "chv" / "executor.py").read_text())
|
||
for node in ast.walk(tree):
|
||
if isinstance(node, ast.Import):
|
||
for alias in node.names:
|
||
self.assertNotIn(alias.name, (
|
||
"cloud.chv.pool",
|
||
"cloud.chv.experimental_pool",
|
||
"cloud.chv.orphans"))
|
||
if isinstance(node, ast.ImportFrom):
|
||
self.assertNotIn(node.module, (
|
||
"cloud.chv.pool",
|
||
"cloud.chv.experimental_pool",
|
||
"cloud.chv.orphans"))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|