Files
yuanshuai c91a64fddb
CI / Python 3.12 (push) Waiting to run
CI / Python 3.9 (push) Waiting to run
chore(repo): initialize team collaboration repository
2026-07-27 20:40:12 +08:00

91 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Adapter SDK:产品扩张的关键边界
方案 §6.4 的判断是对的——**适配器是产品扩张的关键**。每接一个客户,
新增的工作量应该收敛到"写一个适配器",而不是改引擎。这一波把这条边界钉死。
实现:`services/host-agent/flashops_agent/adapters/`
## 契约(7 个方法,一个都不能少)
```python
class ToolAdapter(Protocol):
name: str
version: str
def discover(self, ctx: AdapterContext) -> Capabilities: ...
def precheck(self, ctx: AdapterContext) -> CheckResult: ...
def execute(self, step: StepSpec, ctx: AdapterContext) -> ExecutionHandle: ...
def collect(self, handle: ExecutionHandle) -> EvidenceArtifact: ...
def cancel(self, handle: ExecutionHandle) -> None: ...
def health(self) -> HealthStatus: ...
def normalize_result(self, raw: RawResult) -> UnifiedResult: ...
```
| 方法 | 职责 | 什么时候被调 |
|---|---|---|
| `discover` | 报告本工具在这台主机上能干什么(版本、支持的设备、能力位) | Agent 启动 / 资产刷新 |
| `precheck` | 执行前自检:工具在不在、权限够不够、设备在不在、参数合不合法 | 每个步骤执行前,**失败即拒绝执行** |
| `execute` | 启动执行,**立即返回句柄,不阻塞** | 步骤开始 |
| `collect` | 从句柄收集产物:stdout/stderr、结果文件、设备日志 | 步骤结束 / 中途取证 |
| `cancel` | 终止执行(含进程树),保证不留孤儿进程 | 紧急停止 / 超时 / 恢复前 |
| `health` | 适配器自身健康(工具还在吗、许可证过期没) | 心跳周期 |
| `normalize_result` | 把工具原生输出翻译成统一结果模型 | `collect` 之后 |
## `normalize_result` 是最重要的一个
它决定了失败签名能不能跨工具聚类。原生输出千奇百怪,统一模型只有一种:
```python
@dataclass
class UnifiedResult:
outcome: Literal["PASS", "FAIL", "ERROR", "TIMEOUT"]
error_codes: List[str] # 归一化后的错误码,如 ["NVME_STATUS_0x2002"]
log_templates: List[str] # 日志模板化后的指纹,数字/路径已替换为占位符
metrics: Dict[str, float] # iops / latency_p99 / temperature_c ...
device_state: DeviceState # 枚举状态、固件版本、SMART 关键项
integrity: IntegrityState # OK / MISMATCH / NOT_CHECKED
artifacts: List[str] # 产物 URI
```
**适配器的实现者只需要保证一件事**:同样的故障,`error_codes`
`log_templates` 要稳定。不稳定 → 签名散开 → 聚类失效 → 同一个问题重复提单,
客户第一时间就会发现这个系统在制造噪音。
日志模板化规则在 `flashops_agent/adapters/templating.py`
数字 → `<N>`,十六进制 → `<HEX>`,路径 → `<PATH>`UUID → `<UUID>`
时间戳 → `<TS>`。所以
`"nvme0n1: I/O error, sector 12345678 at 2026-07-27T03:14:15"`
归一为 `"nvme<N>n<N>: I/O error, sector <N> at <TS>"`
## 这一波带的适配器
| 适配器 | 状态 | 说明 |
|---|---|---|
| `ShellAdapter` | ✅ 真实 | 执行签名命令模板,进程树管理、超时、输出捕获 |
| `NvmeCliAdapter` | ⚠️ 真实骨架 + 模拟回退 | 命令与解析是真的;无真实设备时走模拟器 |
| `FioAdapter` | ⚠️ 真实骨架 + 模拟回退 | 解析 fio `--output-format=json` |
| `SimulatedDutAdapter` | ✅ 模拟 | 可注入故障的假 DUT,让全链路无硬件可跑 |
| `VendorFlashAdapter` | ⬜ 占位 | 客户私有刷写工具——**这个必须等拿到真实 SOP 再写** |
`VendorFlashAdapter` 故意留空并在导入时抛 `NotImplementedError`
猜客户的刷写工具长什么样是纯浪费——报告 Phase 0 的结论是先拿 SOP 再写代码。
## 写一个新适配器的清单
1. 继承 `BaseAdapter`,实现 7 个方法
2.`adapters/__init__.py``REGISTRY` 里注册(key 就是工作流 YAML 里的 `adapter:`
3. 危险命令**必须**在控制平面 `safety/templates.py` 注册签名模板;
适配器里不允许拼接任意命令行——参数只能来自模板 schema 校验过的字典
4. 写一个 `tests/adapters/test_<name>.py`:至少覆盖
`precheck` 失败路径、超时路径、`normalize_result` 的两个不同故障输出
5.`make test` 里的适配器契约测试(`test_adapter_contract.py` 会对
`REGISTRY` 里每个适配器自动断言 7 个方法齐全且签名正确)
## 边界纪律
- 适配器**不知道** Run、Workflow、状态机的存在。它只认 `StepSpec``AdapterContext`
- 适配器**不做**重试和恢复决策。挂了就如实报告,重试与恢复是控制平面的事。
- 适配器**不写**数据库,产物写本地临时目录,由 Agent 上传。
- 适配器里**不允许**出现 `if customer == "X"` 这种分支。客户差异靠不同适配器 +
工作流参数表达,不靠代码里的 if。