merge(chv-hybrid): 合入 B 轨——租约 + 排队 + 升级提示 + 网关门

合入 feat-chv-hybrid-b (667a427)。executor.py 两处内容冲突按 hunk
解决,全部取 B 侧;合入时发现 merge 复制出第二个 suspend_after_task
定义(A 版截断体),已删除。

集成统一(A/B 语义冲突三处,设计 §4.1 的 state 模型为准):
- state 模型:A 的 flag 模型(resident: True + legacy state)与 B
  的 state 模型(state ∈ resident-active/paused/hibernated/demoted)
  统一为 state 模型;_mark_resident_record 标记常驻时同时置
  resident-active,_ensure_resident_capacity 同时认 flag 与 state。
- 容量等待:A 的 bool 版 _wait_for_capacity 改名 _poll_capacity,
  B 的 raise 版保留原名;_wait_for_resident_slot 豁免已有常驻登记
  的租户(它已占一个槽位,再等等于把自己的槽位算成满——A 轨
  test_resident_cap_blocks_new_resident 第二腿在合入时因此失败,
  已修并通过)。
- 档位大小写:A 用小写 paid/heavy,B 用 Paid/Heavy;比较一律走
  _norm_tier + HYBRID_RESIDENT_TIERS(resident_tier_eligible 与
  promote_to_resident 两处),fail-closed 语义不变。
- 严格快照校验翻为默认开启(设计 §2.2):_snapshot_strict_tenant
  默认 True,显式 CHV_SNAPSHOT_STRICT_TENANT=0 才关闭;旧恢复
  测试的 fixture 已登记带匹配 tenant 的快照,A 轨 legacy 测试改
  为显式 opt-out 覆盖。

生产接线:worker 只调 dispatch_task,dispatch_task_hybrid 在生产
路径上无调用方;dispatch_task 顶部加一道门,warm_pool_size>=1 时
走 dispatch_task_hybrid,==0 时保持旧冷启语义(不成环:hybrid 的
<=0 分支退化为 dispatch_task_persistent,后者 <=0 调回 dispatch_task)。

旧网关测试更新(B 的语义变更):test_worker_gateway_guard_allows_chv
前加常驻登记;新增 test_worker_gateway_guard_denies_pool_vm。

测试:test_chv_hybrid_a/b 64 项 + 受影响旧测试 16 项(Gateway、
RestoreNoFallback、RestoreSnapshotTest)全部通过;全量待跑。
This commit is contained in:
host committed 2026-11-08 22:00:12 +00:00
commit 99d9e5771b
5 files changed
+1659 -120

No files matched your search

+20
View File
@@ -26,6 +26,7 @@ from pathlib import Path
from unittest import mock
try:
from cloud.chv.disk import record_snapshot
from cloud.chv.executor import CloudHypervisorExecutor
from cloud.chv.hostkeys import dirty_marker_path
from cloud.chv.worker_adapter import (
@@ -38,6 +39,7 @@ try:
from cloud.sandbox import ChvFailure
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.chv.disk import record_snapshot
from cloud.chv.executor import CloudHypervisorExecutor
from cloud.chv.hostkeys import dirty_marker_path
from cloud.chv.worker_adapter import (
@@ -288,8 +290,20 @@ class RestoreSnapshotTest(CloseupCase):
fh.write(b"S" * 60)
return "file://" + path, path
def _register_snapshot(self, url, tenant="proj1", vm="chv-abc123"):
# 严格快照校验(设计 §2.2,默认开启):恢复须登记带
# tenant_id 且与请求方一致;_task() 的 project_id 是
# proj1,即请求租户。登记用 record_snapshot(与
# snapshot_vm 同款登记口径)。vm 参数只定登记落在哪
# 个 chv-* 目录(find_snapshot_entry 全盘扫描),默认
# chv-abc123;net_register 失败测试须用别的 vm 名,
# 否则登记建的目录会撞它同名 blocker 文件。
record_snapshot(self.state_dir, vm, url, 20.0,
tenant_id=tenant)
def test_restore_failure_closes_vm(self):
url, _ = self._snap_url()
self._register_snapshot(url)
ex = self.make_exec()
def runner(cmd, timeout=None):
@@ -305,6 +319,7 @@ class RestoreSnapshotTest(CloseupCase):
def test_restore_success_and_wake_from_hibernate(self):
url, _ = self._snap_url()
self._register_snapshot(url)
ex = self.make_exec()
out = ex.restore_vm(self._task(), url)
self.assertEqual(out["mode"], "restored")
@@ -327,6 +342,7 @@ class RestoreSnapshotTest(CloseupCase):
# 已登记 VM 静默复用 tap/mac/ip,且自身不登记、后
# 来的 wake 也看不到它,两边都漏判。
url, _ = self._snap_url()
self._register_snapshot(url)
self._register_other_vm_net()
ex = self.make_exec(env_extra={
"CHV_NET_JSON": '[{"tap": "tap0", '
@@ -343,6 +359,7 @@ class RestoreSnapshotTest(CloseupCase):
# feat-chv-cover100:恢复成功后登记 net.json,后来
# 的 wake 才能看到恢复出的 VM 占用的 tap/mac/ip。
url, _ = self._snap_url()
self._register_snapshot(url)
ex = self.make_exec(env_extra={
"CHV_NET_JSON": '[{"tap": "tap0", '
'"mac": "aa:bb:cc:dd:ee:01", '
@@ -361,6 +378,7 @@ class RestoreSnapshotTest(CloseupCase):
#(P2-19 语义),restore_vm 的 except 会 stop_vm
# 清掉半恢复 VM(shutdown 已发出)。
url, _ = self._snap_url()
self._register_snapshot(url, vm="chv-zzz")
blocker = os.path.join(self.state_dir, "chv-abc123")
os.makedirs(self.state_dir, exist_ok=True)
with open(blocker, "w") as fh:
@@ -377,6 +395,7 @@ class RestoreSnapshotTest(CloseupCase):
def test_restore_without_net_json_writes_no_registry(self):
url, _ = self._snap_url()
self._register_snapshot(url)
ex = self.make_exec()
out = ex.restore_vm(self._task(), url)
self.assertEqual(out["mode"], "restored")
@@ -385,6 +404,7 @@ class RestoreSnapshotTest(CloseupCase):
def test_restore_provisioned_missing_known_hosts(self):
url, _ = self._snap_url()
self._register_snapshot(url)
ex = self.make_exec(env_extra={"CHV_HOST_KEY_MODE": "provisioned"})
with self.assertRaises(ChvFailure) as ctx:
ex.restore_vm(self._task(), url)