docs(spec): external-license-resources design proposal (E2)
LSF License Scheduler-like external resource management: xceextresd daemon (flexlm/script collectors) joins the controller with a new HostRole member and pushes server-free counts through the existing resval pipeline (wire unchanged apart from HostRole); EXTERNAL-shared resources skip the inuse term in the scheduler's satisfy check (Slurm-deficit-equivalent). Covers the elim-compat survey (worker resource_sync load-index registry activation, separate D-size item), the license-merge attribution layering (dispatch-ledger quotas + tiered evidence + residual bucket), and the mergeability review against Rongsong's unified resource model specs (P9 resolution proposal: per-resource accounting mode, one writer each). Proposal stage only: no EARS requirements/tasks yet; the cross-team items (P9 mode split, project-config cluster-level pool, peer-auth allowlist) are recorded in design.md section 3.6 for discussion. Not to be merged to Rongsong's repo. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
8ae2b5dc47
commit
68eabd8a4a
3 changed files with 550 additions and 0 deletions
69
.kiro/specs/external-license-resources/brief.md
Normal file
69
.kiro/specs/external-license-resources/brief.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Brief: external-license-resources
|
||||
|
||||
(2026-10-26,E2。LSF License Scheduler 类似功能:管理外部资源的 daemon,
|
||||
一般外部资源类型与用户 app 的 EDA license 统一考虑。设计全文见本目录
|
||||
`design.md`。)
|
||||
|
||||
## Problem
|
||||
XCE 无法调度依赖外部 license server(FlexLM/lmstat 等)的 EDA 应用,
|
||||
也没有承载一般外部资源(云配额、网络带宽等集群外计数)的采集通道。
|
||||
共享池调度机制(sharedInst 两阶段判式)在,但没有任何非 worker 来源
|
||||
能把外部真值推进来(opupd 只收 worker,HostRole 无外部角色,
|
||||
XCE_RESF_EXTERNAL 是死 flag)。
|
||||
|
||||
## Desired Outcome
|
||||
- **xceextresd**:独立采集 daemon(避免 lmstat 阻塞连坐 controller),
|
||||
以新 `HostRole` 成员 join controller,采集器插件:flexlm(lmstat)、
|
||||
script(任意外部资源)。wire 零 schema 变更(复用 ResValList 管线),
|
||||
唯一变更是 HostRole 加成员。
|
||||
- **记账**:daemon 推 free 值;EXTERNAL 资源判式跳过 inuse 项
|
||||
(unsatisfy_ 一处两分支,全局腿+host 腿),与 Slurm deficit 算法同构。
|
||||
- **elim 兼容**:LSF elim(per-host 外部资源脚本)**归 xcecd**——激活
|
||||
resource_sync.c 已有的 load index 注册表死 API + posix_spawn 采集器
|
||||
(D 类并行项,见 design §3.3b)。
|
||||
- **merge 归因**:多 app 共享 license 的归因分层化解——调度准入只用
|
||||
聚合值天然免疫;配额走调度侧派发记账(per-project share);报告层
|
||||
证据分级 Tier 0-3 + 残差桶闭合(见 design §3.3c)。
|
||||
|
||||
## Approach
|
||||
三阶段:Phase 1 核心链路(HostRole+join、opupd SHARED 即收守卫、
|
||||
unsatisfy_ EXTERNAL 分支、seed 不播种、mapping.py external 路由、
|
||||
daemon 骨架+采集器);Phase 2 运维面(stale/last-updated/pend reason
|
||||
名/status 命令);Phase 3 per-project 策略层(映射 project-config
|
||||
share 机制)。
|
||||
|
||||
## 与现有 spec 群的关系(详见 design.md §3.6)
|
||||
- **custom-resource-reporting**:本 spec 的 daemon = 其"外部 reporter
|
||||
角色"的实施;elim = 其 worker script reporter 的实施。可互相印证。
|
||||
- **elastic-shared-resources**:同管线两种记账模式按资源分流
|
||||
(EXTERNAL=server-authoritative,无 flag=elastic base+borrowed)——
|
||||
opupd 守卫"SHARED 即收"预留其写入路径。即对其决策日志 P9 的回答。
|
||||
- **PR-2 proxy**(resource-questions.md):xceextresd 定位为该 proxy 的
|
||||
第一片(只读 report 能力),HostRole 只加一个成员。
|
||||
- **project-config**:per-app 配额→per-project share(project tree 是
|
||||
唯一并发上限处,limits.yaml 退役)。需要其扩展:集群级 pool location。
|
||||
- **peer-authentication-hardening**:extres 角色须上 per-role opcode
|
||||
allowlist(生产化前依赖,t1 测试集群先行)。
|
||||
|
||||
## Boundary Candidates
|
||||
- HostRole_extres join/leave + opupd 准入守卫(controller)
|
||||
- unsatisfy_ EXTERNAL 分支(scheduler)
|
||||
- xceextresd daemon 与采集器插件(新 src/extresd/)
|
||||
- mapping.py `location: [external]` 路由
|
||||
|
||||
## Out of Boundary
|
||||
- elim worker 采集器(独立 D 类项,design §3.3b)
|
||||
- elastic 借还机制(elastic-shared-resources / resource-provisioner)
|
||||
- per-project license share 的池模型扩展(project-config 扩展点)
|
||||
- 归因 Tier 1/1' 的 vendor 适配(按实际 vendor 调研)
|
||||
|
||||
## Upstream / Downstream
|
||||
- **Upstream**: peer-authentication-hardening(角色 allowlist)、
|
||||
custom-resource-reporting(描述符 interval/staleness 字段)
|
||||
- **Downstream**: elastic-shared-resources(同管线第二模式)、
|
||||
provisioning-demand(headroom 需求源)、project-config(集群级池)
|
||||
|
||||
## Open(待用户/跨团队决策)
|
||||
1. 命名:xceextresd(推荐)vs xcelsd。
|
||||
2. 断联策略:fail-open(推荐)vs fail-closed。
|
||||
3. P9 模式拆分与三个扩展点需与 Rongsong 确认(design §3.6)。
|
||||
457
.kiro/specs/external-license-resources/design.md
Normal file
457
.kiro/specs/external-license-resources/design.md
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
# XCE 外部资源 / License Scheduler 调研与设计(2026-10-26)
|
||||
|
||||
> **Status**: 设计提案(design proposal),未进入 requirements/tasks。
|
||||
> 排期 E2;归属排期表见会话记录。已与 Rongsong 统一资源模型 spec 群
|
||||
> 做合并性审查(§3.6)。
|
||||
>
|
||||
> **注意**:§3.6 引用的 Rongsong 文档(`.kiro/steering/resource-model.md`、
|
||||
> `resource-questions.md`、`.kiro/specs/{custom-resource-reporting,
|
||||
> elastic-shared-resources,resource-provisioner,provisioning-demand,
|
||||
> project-config}/brief.md` 等)**不在本分支工作树**——合并策略排除了
|
||||
> `.kiro/` 记账(merge 0c860734、3dbb96d4),`.kiro/specs/` 下同名空目录
|
||||
> 是遗留壳。全文可从 git 历史 `git show 3442a57d:<path>` 或 Rongsong 的
|
||||
> 分支读取。
|
||||
|
||||
对应排期 E2(LSF License Scheduler 类似功能:管理外部资源的 daemon/插件,
|
||||
一般外部资源类型与用户 app 的 EDA license 统一考虑)。
|
||||
|
||||
参考资料:
|
||||
- Slurm 源码 `/home/lchen/code/slurm`(`src/slurmctld/licenses.{h,c}`、`doc/html/licenses.shtml`)
|
||||
- Slurm licenses 文档 https://slurm.schedmd.com/licenses.html
|
||||
- LSF License Scheduler 架构(web 检索:lsld + blcollect + service domain + token distribution)
|
||||
|
||||
---
|
||||
|
||||
## 1. 参考系统模型
|
||||
|
||||
### Slurm
|
||||
- local license:slurm.conf `Licenses=name:N`,静态总量。
|
||||
- remote license:slurmdbd `sacctmgr add resource ... server=X servertype=flexlm
|
||||
allowed=percent|absolute`,按 cluster 分配份额(SharedPool/Absolute flag)。
|
||||
- Dynamic Licenses:外部 cron 跑 lmstat 回填 `LastConsumed`;
|
||||
`LastDeficit = LastConsumed - external - used`,其中
|
||||
`external = count - cluster_total`(licenses.c `_handle_consumed()`,
|
||||
count=全量、total=本 cluster 份额)。调度时避开 deficit,即避开
|
||||
**外部消费者**占用的份额,防止 license "gone rogue"(被集群外进程用光)。
|
||||
- 作业侧请求:`-L name:count`,调度前查 `total - used - reserved - last_deficit`。
|
||||
|
||||
### LSF License Scheduler
|
||||
- lsld 主守护进程 + 每 service domain 一个 blcollect 采集器(proxy/standalone 模式),
|
||||
轮询 lmstat/FlexLM(也支持 LUM、SentinelLM)。
|
||||
- `lsf.licensescheduler`:FEATURES/LIC_TOKENS(token 化)、Classes、Groups、
|
||||
DISTRIBUTION=on-demand;owned(本域独占)/floating(漂移池)token 分布;
|
||||
OVERQ_HARD_LIMIT/SOFT_LIMIT。
|
||||
- 对 XCE 最有参考价值的是"daemon 采集"形态;per-queue/user 分布策略是
|
||||
第三期内容(XCE 已有限制 vres 机制可承载)。
|
||||
|
||||
### 共性结论
|
||||
license 服务器的"当前空闲数"是外部真值;本地只需补一个窗口:
|
||||
**已派发但作业进程尚未向 license server checkout 的在途量**。
|
||||
采纳 Slurm 的记账模型(deficit/外部占用回避)+ LSF 的 daemon 采集形态。
|
||||
|
||||
---
|
||||
|
||||
## 2. XCE 现有管线盘点(2026-10-26 逐点核实)
|
||||
|
||||
### 2.1 已有且可直接复用
|
||||
- **共享资源池机制全链路在**:YAML `resourcemap` 的 `location: [all]` + value 形状
|
||||
→ `mapping.py:220-222` 打 `XCE_RESF_SHARED|XCE_RESF_GLOBAL`(0x08|0x04)→
|
||||
controller `seed_sharedinst_from_config()`(cluster.c:1579)播种 `c->sharedinst` →
|
||||
scheduler 两阶段调度消费(见 2.3)。
|
||||
- **作业 license 请求路径在**:作业 rusage →
|
||||
`calc_wanted_global_resvals()`(lib/resreq.c:825)→ 只取 `XCE_RESF_GLOBAL`
|
||||
资源 → `alloc_req.shared`(PER_TASK val×nslots / PER_JOB val / PER_HOST val×nhosts);
|
||||
seqjob_allocator.c:93、parjob_allocator.c:114 调用。
|
||||
- **值更新管线在(单写点)**:worker push → opupd.c `do_update_resvals()` →
|
||||
`update_resval_list()` → `update_val_res()`(worker.c:65,`XCE_RESF_SHARED`
|
||||
分支写 `c->sharedinst`)→ HOOK_DETAIL_RESOURCE_UPDATE 双出口:
|
||||
- sched.c:716 `sched_resource_hook_fun()` → `NotifyData_data_schedUpdateResvals`
|
||||
notify → scheduler handler.c:289 → enq `XCE_SCHED_OP_UPDATE_RESVALS`;
|
||||
- apiserver.c:560 → apiserver Go `OnResourceLocationUpdated()`(clib.go:584
|
||||
解码)→ store.ResourceLocations → `xce resources` 可见。
|
||||
- **FULL 推送路径在(集群级全量)**:controller → scheduler 的
|
||||
`NotifyData_data_schedUpdateResvals` 载 ResValList;scheduler 侧
|
||||
`XCE_SCHED_OP_UPDATE_RESVALS_FULL`(scheduler.c:1468)逐条
|
||||
`key_find(resmgr)` + `zarray_put(&core->sharedInst, r->idx, val)`,
|
||||
driver.c:420 同译为 `XCE_CORE_EVENT_RESVALS_FULL`(core_events.c:1201,
|
||||
dup_val 语义,bug-1585);`.replay` 侧 ReplayEvent.body.resvalsFull
|
||||
(controller.capnp:1350)+ snapshot_load.c:155 同构 → **录制回放自动覆盖**。
|
||||
- **pend reason 体系在**:PREASON_NO_RESOURCE(2) 带 res detail 行(资源名可见),
|
||||
add_job_preason_once 去重,API 层映射名字。
|
||||
|
||||
### 2.2 缺口(本设计要补的)
|
||||
1. **无外部 daemon join 角色**:`HostRole` enum(controller.capnp:150)
|
||||
只有 client/worker/scheduler/controller/apiserver;join.c:307-330 按 role
|
||||
分发;cluster.c:71-111 leave 分发。且 opupd.c:26
|
||||
`priv->role == HostRole_worker` 才收 resvals 更新 → 外部 daemon 推不进值。
|
||||
2. **无 REST 更新端点**:routers.go:181-186 资源路由只有
|
||||
GET /distribution + GET /definition。
|
||||
3. **`XCE_RESF_EXTERNAL (0x10)` 死 flag**:types.h:85 预留,全仓库唯一出现处
|
||||
repl_info.c:805 的 flag 名表(纯展示)。
|
||||
4. **sharedInst 只有"总量"无 used/deficit 记账**(Slurm licenses_t 有
|
||||
total/used/reserved/remote/last_deficit/last_consumed/last_update)。
|
||||
5. mapping.py 无 external 路由;`seed_sharedinst_from_config` 只播
|
||||
SHARED|GLOBAL 形状。
|
||||
|
||||
### 2.3 调度侧判式(唯一要动算术的地方)
|
||||
`unsatisfy_()`(scheduler/src/resource.c:148):共享池满足性判
|
||||
`total[i] < inuse[i] + uncomit[i] + wanted[i]` → 不满足行进 unmeet
|
||||
(PREASON detail)。其中:
|
||||
- `total` ← `get_resvals(&core->sharedInst, ...)`(resource.c:315);
|
||||
sharedInst 缺条目/非 fvalue 时读 0(get_resvals:40-50)→ daemon 未上线
|
||||
时作业 PEND NO_RESOURCE、detail 带资源名(fail-conservative,天然正确)。
|
||||
- `inuse` ← `core->sharedInUse`,由 `sched_update_account()`
|
||||
(resource.c:708,charge=1/0)按 RUN 作业记账,core_session 重置。
|
||||
- `uncomit` ← 本次调度循环内已选未 commit 的量(uncomit_at_,同轮防超卖)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 设计方案
|
||||
|
||||
### 3.1 架构决策:独立 daemon + 采集器插件
|
||||
|
||||
**xceextresd(外部资源守护进程)**,新 `src/extresd/`,C 实现,
|
||||
照 scheduler 的 join 方式以新 `HostRole_extres` 角色接入 controller。
|
||||
内部采集器插件接口:
|
||||
|
||||
```
|
||||
xceextresd
|
||||
├─ collector/flexlm.c — lmstat -c port@host -f feature 解析(EDA license 主路径)
|
||||
├─ collector/script.c — 用户脚本输出 key=value/JSON(任意外部资源:云配额、网络带宽…)
|
||||
└─ collector/http.c —(预留)
|
||||
```
|
||||
|
||||
- **为什么不是 controller 内插件**:lmstat 是秒级阻塞调用,违反 controller
|
||||
async I/O 红线(delaymgr/vtask 不得阻塞,HA/心跳会被连坐);license server
|
||||
故障必须与 controller 故障域隔离。
|
||||
- **为什么不是 REST 推送**:REST 是 JWT+RBAC 人机面;daemon→controller 直连
|
||||
复用整条 resval 管线(applied-filter bug-1583、hook 双出口、scheduler 推送、
|
||||
apiserver store),零新链路。
|
||||
- **统一"一般外部资源 + EDA license"**:调度面只见"某全局资源的当前可用值",
|
||||
来源由 daemon 插件决定——license 与云配额走同一条管线。
|
||||
|
||||
### 3.2 数据流(wire 零 schema 变更,除 HostRole 加成员)
|
||||
|
||||
```
|
||||
xceextresd (join: HostRole_extres)
|
||||
│ OPCode_update / Update_data_resvals(现 wire:ResourceVal{name, host:"", val})
|
||||
▼
|
||||
opupd.c do_update_resvals ── 放行 extres 角色;flag 守卫只许 SHARED 资源(§3.6)
|
||||
▼
|
||||
update_val_res (worker.c:74) SHARED 分支原样落 c->sharedinst ← 零改动
|
||||
▼
|
||||
HOOK_DETAIL_RESOURCE_UPDATE
|
||||
├─ sched.c → schedUpdateResvals notify → scheduler(FULL 语义:集群级值)← 零改动
|
||||
└─ apiserver.c → OnResourceLocationUpdated → `xce resources` 可见 ← 零改动
|
||||
▼
|
||||
scheduler: XCE_SCHED_OP_UPDATE_RESVALS_FULL → core->sharedInst ← 零改动
|
||||
(ReplayEvent.resvalsFull / snapshot_load 同构 → sim 录制回放自动覆盖 daemon 推送)
|
||||
```
|
||||
|
||||
注意:controller → scheduler 的这条 notify 落在 scheduler 侧是
|
||||
`XCE_SCHED_OP_UPDATE_RESVALS`(DELTA,scheduler.c:1487 / core_events.c:1228
|
||||
RESVALS_DELTA),该路径按 `rv->host` 找 worker、找不到就 WARN 跳过
|
||||
(legacy scheduler.c:1496 `w==NULL continue`;core_events.c:1245 同)。
|
||||
**daemon 推送的共享资源值必须走 host==NULL/"" 的 ResValList**——需要核实
|
||||
`do_update_resvals`→hook→notify 载荷里 host 字段的实际值:worker 推送时
|
||||
带 host 名,daemon 推送时必须不带(host=""),否则会被 DELTA 路径当
|
||||
per-worker 值丢弃。落点差异(FULL vs DELTA / host 空 vs 非空)是实施期
|
||||
第一个要写测试钉死的点。(snapshot_load.c:130-137 已有先例:host==NULL
|
||||
条目进 sharedInst,host 命名条目进 per-worker。)
|
||||
|
||||
**唯一 schema 变更**:controller.capnp `HostRole` 加 `extres @5`。
|
||||
join/leave 处理照 `handle_apiserver_join`(join.c:221)轻量先例:
|
||||
只注册端点 + priv->u.idx,不占 workermgr slot。
|
||||
|
||||
### 3.3 记账模型(free 值 + EXTERNAL 跳过 inuse)
|
||||
|
||||
设 `T = license 总量,O = 外部消费者占用,X = XCE 作业占用,F = 空闲`。
|
||||
daemon 轮询推 **F**(lmstat 直接给 free 数)。
|
||||
|
||||
现有判式若直接把 F 当 total 会把 X 算两次(F 已含扣减,sharedInUse 再扣一次)。
|
||||
修正:**EXTERNAL 资源判式跳过 inuse 项**:
|
||||
|
||||
```
|
||||
EXTERNAL:F ≥ uncomit + wanted(unsatisfy_ 循环内一个 flag 分支;
|
||||
r->flags 在该循环内已有——vget 取 TRACE 名)
|
||||
```
|
||||
|
||||
两窗口验证:
|
||||
- checkout 前(已派发未 checkout):F = T−O,判式 ⟺ T−O ≥ uncomit+wanted;
|
||||
X 已计入 sharedInUse 但被跳过 → 不重复计 ✅(在途窗口靠 uncomit→commit
|
||||
串行化保护,这正是本地记账唯一要管的窗口)
|
||||
- checkout 后:F = T−O−X,判式 ⟺ T−O ≥ X+uncomit+wanted ✅
|
||||
|
||||
与 Slurm `_handle_consumed` 的 deficit 算法同构(都是"避开外部消费者份额"),
|
||||
但更简:不需要 LastConsumed/external 推导,因为 lmstat 直接给 free。
|
||||
|
||||
### 3.3b elim(LSF load index)兼容性 —— 已盘点,归 xcecd 不归 extresd
|
||||
|
||||
**LSF elim 模型**:用户在主机上部署脚本,周期 echo `资源名 数量`,lim 收集后
|
||||
变成可调度资源。per-host 语义。License Scheduler 历史上正是从 elim 派生——
|
||||
elim 的 per-host 模型装不下全局 license 池(多机各报各的,没人知道全局真相),
|
||||
才分出 lsld/blcollect。
|
||||
|
||||
**XCE 现状盘点(2026-10-26)**:worker 里 elim 形状的基建已存在一半(死代码):
|
||||
|
||||
- `resource_sync.c:116 resource_sync_register_load_index(name, valtype,
|
||||
getter_func, is_builtin)` —— load index 注册表,支持非 builtin 注册;
|
||||
`resource_sync_unregister_load_index`(:160)专设 `is_builtin==false`
|
||||
注销分支,**为外部注册而设计但全仓库无外部调用者**。
|
||||
- 内置 11 个 index(r15s/r1m/r15m/ut/pg/ls/it/swp/mem/tmp/io)= LSF load
|
||||
index 名集,按 lim 模型建的。
|
||||
- 上报链路是活的:sync 线程每 15 tick(:336)→ `collect_load_indices()`
|
||||
→ `xcecd_send_load_indices()`(controller_connector.c:2279)→
|
||||
`xcecli_update_resvals` → controller `update_val_res` else 分支
|
||||
(worker.c:79,非 SHARED 落 `w->host.resvals`)→ scheduler DELTA →
|
||||
`sched_check_host_satisfy_`(resource.c:390,`w->resvals` 当 total)。
|
||||
调度请求侧 `calc_wanted_host_resvals`(resreq.c:957,非 GLOBAL 走 host 域)
|
||||
现成。resources.yaml 的 `location: [default]`/主机列表 definition-only
|
||||
形状 mapping.py 已支持(注释原文即"workers report those values at
|
||||
runtime")。
|
||||
|
||||
**缺的只有入口**:无机制让用户把采集逻辑注册成自定义 load index。补齐 =
|
||||
xcecd 加 elim 采集器模块(配置 `external_resources: [{name, command,
|
||||
interval}]`;posix_spawn 跑脚本——xcecd 多线程禁 fork,steering 许可
|
||||
posix_spawn 路径;读 stdout `name value` 行 elim 原格式;调现有 register
|
||||
API 进 registry;脚本超时看门狗 kill)。MAX_LOAD_INDICES=64 够用。
|
||||
工作量 D 类(中型),与 E2 独立可并行。
|
||||
|
||||
**归属决策:elim 支持放 xcecd,不放 xceextresd**。理由:
|
||||
1. 作用域——elim 值 per-host("这台机器有多少"),license 值集群全局
|
||||
("全池还剩多少");worker 本来就站在每台主机上,让 extresd 管主机值
|
||||
是数据绕远路。
|
||||
2. 故障域——elim 脚本挂只影响一台主机上报;license server 断联影响全集群
|
||||
准入。xcecd 采集器继承前者的隔离性。
|
||||
3. LSF 自己也是分开的(lim vs lsld/blcollect),复刻这个分界。
|
||||
4. XCE 架构两条腿天然对齐:worker→per-worker resvals→host 判式;
|
||||
extresd→sharedinst→全局判式。同一资源模型(定义/rusage/pend reason/
|
||||
CLI 展示)。
|
||||
|
||||
**统一记账规则(两条腿共享,同一 flag 位决定)**:EXTERNAL 资源的满足性
|
||||
判式跳过 inuse 项——全局腿在 `unsatisfy_` 共享分支跳 sharedInUse,主机腿
|
||||
在同一函数的 host 分支跳 `w->resInUse`(一个函数两分支,一处改动)。
|
||||
若某资源想让脚本报"总量"、由调度器记账,则不打 EXTERNAL flag,走普通
|
||||
total/inuse 语义。按资源选,一个 flag 决定。
|
||||
|
||||
### 3.3c license merge 问题(多 app 共享同一 license 的归因)—— 分层化解
|
||||
|
||||
**问题**:同一主机两个 app 用同一外部 license,lmstat 只给 (user, host,
|
||||
count) 聚合——无法判定 A=2/B=3 还是 A=3/B=2。LSF License Scheduler 的
|
||||
结构性弱点:其 token 分配策略(per-project DISTRIBUTION)**强迫**回答
|
||||
"checkout 归哪个 project",而服务器侧信息不够,只能静默猜,账永远对不齐。
|
||||
|
||||
**我们的化解**:把归因从"必须精确的路径"上挪走——
|
||||
|
||||
1. **调度准入免疫**:判式只用聚合值(§3.3 `F ≥ uncomit + wanted`),
|
||||
A/B 各持多少不影响"下一作业能否进"。merge 问题只存在于策略执行与
|
||||
报告层。
|
||||
2. **策略层 = 用"我们放行了什么"替代"他们持有什么"**:per-app 配额建在
|
||||
调度侧派发记账上(alloc_req.shared 按作业 project 字段聚合——精确、
|
||||
确定、零归因),映射进现有 limit vres 机制(PREASON_LIMIT_GLOBAL 链)。
|
||||
daemon 每轮对账:Σ(派发中) vs 服务器 used,差值 = 外部消费者 + 泄漏,
|
||||
异常 alert。漂移有界且方向安全:少拿→配额保守不超卖;多拿→服务器
|
||||
total 暴露。
|
||||
3. **报告层 = 证据分级 + 残差桶**(matcher 为 extresd 插件,不猜、显式
|
||||
化"不知道"):
|
||||
- Tier 0:派发记账(app→授予数),精确,永远可用;
|
||||
- Tier 1:vendor checkout 属性(如 Synopsys LM_PROJECT 类,lmstat 可见;
|
||||
XCE 作业 env 已注入 XCE_JOBID,vendor 走 env 即可盖入),精确;
|
||||
- Tier 1':PID 匹配(部分 vendor daemon 报客户端 PID)——jobPidInfo
|
||||
已有 jobid→pid 映射(3545aa0d),extresd 从 controller 拉即可,精确;
|
||||
- Tier 2:(user, host) + checkout start 时间窗 匹配作业记录(LSF 基线
|
||||
法升级:两个独立 XCE 作业通常可按 start 时间拆开),user+host 相同时
|
||||
只能给出"这批作业合计持有 K"的界;
|
||||
- Tier 3:作业自报(rusage 通道声明持有 N),弱信任,与 0/1 交叉验证。
|
||||
- **闭合不变量**:Σ(归因) + unattributed ≡ 服务器 used,每轮强制闭合;
|
||||
unattributed 是一等公民字段,过大 alert。真正信息论边界 = 同
|
||||
user+host+同作业内多 app(服务器侧无任何信息可分),只能靠 Tier 1/3,
|
||||
否则进残差桶。
|
||||
4. **运营层建议(非系统依赖)**:vendor 侧拆池(不同 feature 名/独立
|
||||
pool,归因变路由,最干净);per-app 服务账号(lmstat user 字段可区分,
|
||||
零开发)。
|
||||
|
||||
**落地**:Phase 1 flexlm collector 直接解析 `lmstat -a` checkout 明细并存档
|
||||
(调度不依赖归因);Phase 2 明细表 + Tier 2 匹配 + 残差桶对账 + status
|
||||
显示;Phase 3 Tier 0 接 limit vres 做 per-app 配额、Tier 1/1' 按实际
|
||||
vendor 调研接入。
|
||||
|
||||
### 3.4 配置面
|
||||
|
||||
```yaml
|
||||
# resources.yaml(集群侧:声明,xceadmin config build)
|
||||
resources:
|
||||
- name: Verilog_XL
|
||||
type: float
|
||||
location: [external] # mapping.py 新增分支 → SHARED|GLOBAL|EXTERNAL (0x1C)
|
||||
```
|
||||
|
||||
```yaml
|
||||
# extresd.yaml(daemon 侧:操作)
|
||||
poll_interval: 30s
|
||||
resources:
|
||||
- name: Verilog_XL # 必须与 resources.yaml 同名(控制器侧 unknown-resource 拒)
|
||||
collector: flexlm
|
||||
server: 27000@licsrv
|
||||
feature: Verilog_XL
|
||||
- name: cloud_gpu_quota
|
||||
collector: script
|
||||
command: /etc/xce/quota-report.sh
|
||||
stale_limit: 150s # 连续取不到 → WARN 保旧值(fail-open,可配 fail-closed 清零)
|
||||
```
|
||||
|
||||
职责分界:resources.yaml 管声明(作业能否 rusage),extresd.yaml 管采集
|
||||
(值从哪来)。`seed_sharedinst_from_config` 对 EXTERNAL 资源**不播种**
|
||||
(值只来自 daemon;daemon 未上线时 get_resvals 读 0 → PEND NO_RESOURCE
|
||||
detail 带资源名)。
|
||||
|
||||
### 3.5 分阶段实施
|
||||
|
||||
**Phase 1 — 核心链路**
|
||||
1. controller.capnp HostRole 加 extres;join.c handle_extres_join/leave +
|
||||
cluster.c switch 分支
|
||||
2. opupd.c:extres 角色放行 resvals;update_resval 加 flag 守卫
|
||||
(**SHARED 即收**——EXTERNAL 决定调度算术不决定准入,为 elastic-shared
|
||||
模式留同一条路,见 §3.6;非 SHARED 拒 -EPERM;daemon 场景 w 无意义,
|
||||
只走 sharedinst)
|
||||
3. resource.c unsatisfy_:EXTERNAL 跳过 inuse 项(+trace 行)
|
||||
4. cluster.c seed:EXTERNAL 不播种
|
||||
5. mapping.py:`location: [external]` 路由
|
||||
6. xceextresd 骨架:join/rejoin/failover 重连即全量重推(worker 状态补报
|
||||
9d7628e 同款语义)+ script/flexlm 采集器 + 轮询去抖
|
||||
7. TDD:testsuite 红→绿(opupd 守卫、unsatisfy_ 分支、采集器解析);
|
||||
t1 E2E 用 script 采集器 + 假 license 脚本(不必有真 FlexLM)
|
||||
|
||||
**Phase 2 — 运维面**:stale 标记与 WARN、`xce resources` 展示 last-updated、
|
||||
license 耗尽专用 PEND reason 名(可选新码,现复用 PREASON_NO_RESOURCE 已带
|
||||
资源名)、`xceadmin extres status`。
|
||||
|
||||
**Phase 3 — LSF 策略层**:per-queue/user token 分布 → 映射进现有 limit vres
|
||||
机制(ServiceDomain≈queue,DISTRIBUTION≈limit 配额);多 daemon 冗余
|
||||
(管线 last-writer-wins);手动覆盖 REST(谨慎,与 daemon 推送冲突,默认不做)。
|
||||
|
||||
**E2E 验证清单**:假脚本 license=4,两个 rusage[lic=3] 作业串行(第二个
|
||||
PEND detail 含 lic 名);daemon kill/重启后值全量补推;controller 主备切换
|
||||
daemon 重新 join 新主并补推;`--sim` 录制含 daemon 推送事件且回放一致。
|
||||
|
||||
**elim 归属(与 E2 并行的独立 D 类项)**:LSF elim(per-host 外部资源脚本)
|
||||
归 xcecd(worker)不归 xceextresd——详见 §3.3b。license(全局)与 elim
|
||||
(主机)是两条腿、同一资源模型、同一记账规则(EXTERNAL 跳 inuse)。
|
||||
|
||||
### 3.6 与 Rongsong 统一资源模型 spec 群的合并性审查(2026-10-26)
|
||||
|
||||
对照 `.kiro/steering/resource-model.md`(三阶段统一模型)、
|
||||
`resource-questions.md`(决策日志 R1-R9/P1-P9/PR-1~5,2026-10-13 止于 3442a57d)
|
||||
及 custom-resource-reporting / elastic-shared-resources / resource-provisioner /
|
||||
provisioning-demand / project-config 系列 brief。
|
||||
|
||||
**高度吻合(他的 spec 已预留我们的位置)**:
|
||||
- elim 归 worker:custom-resource-reporting Approach 原文 "Extend the
|
||||
worker's resource_sync with a script reporter, under the no-fork rule",
|
||||
且现状描述精确命中我们盘点(register_load_index 无外部 caller)。
|
||||
我们 §3.3b 等于提前实施它 Phase 2 第一片。
|
||||
- 外部 reporter 报 license free count:同一 brief 明文,Downstream 指向
|
||||
"elastic-shared-resources, which may reuse the reporter role"。
|
||||
- 红线全对齐:scheduler 只读缓存快照永不调 provider;credentials 留在
|
||||
provider 进程;posix_spawn;sim reporter 可注入(我们走 RESVALS_FULL
|
||||
wire → 录制回放天然满足其 "a simulated reporter" in-scope)。
|
||||
|
||||
**唯一真实冲突:共享池 total 的写入权(对应其决策日志 P9,open)**。
|
||||
他的 elastic-shared-resources:total = base + borrowed,provisioner 角色
|
||||
借/还更新,license server observed count 只是可选对账遥测。我们的 E2:
|
||||
daemon 推 free 直接覆盖 sharedInst。两套各写各的 → 同一资源 sharedInst
|
||||
被交替覆盖(管线 last-writer-wins),调度视图随机跳变。
|
||||
**解法:按资源分记账模式,每种模式一个写者**——
|
||||
- server-authoritative(EXTERNAL flag,E2 模型):池被多站点共享,XCE
|
||||
不拥有 total → daemon 推 F,判式跳 inuse。写者 = reporter daemon。
|
||||
- xce-authoritative/elastic(无 EXTERNAL flag,elastic-shared 模型):
|
||||
base+borrowed,provisioner 借还,判式走普通 total/inuse
|
||||
(max(reserved, actual)),license server count 只做对账遥测(即
|
||||
§3.3c Tier-0 对账)。写者 = provisioner。
|
||||
同一更新 opcode、同一角色、按资源 flag 分流。**Phase 1 第 2 步 opupd
|
||||
守卫相应修正:不是 "EXTERNAL|SHARED 才收"而是 "SHARED 就收"**
|
||||
(EXTERNAL 决定调度算术,不决定更新准入)——elastic-shared 将来走同
|
||||
一条路零改动。此设计即对 P9 的回答,作为 proposal 带给 Rongsong 讨论
|
||||
(不代改其 resource-questions.md 决策日志)。
|
||||
|
||||
**术语/机制对齐(不冲突,跟他的走)**:
|
||||
- per-app 配额 → **per-project**:project-config 决策 "project tree 是
|
||||
唯一并发上限处,limits.yaml 退役"(332f86ea);§3.3c Tier-0 配额写成
|
||||
project share/member_quota 由 limit plugin 编译,不发明新机制。
|
||||
- staleness/interval 进**资源描述符**(其 Phase 1 descriptor 字段
|
||||
interval/last_report 已预留),不放 extresd.yaml;daemon 只执行。
|
||||
- 持久化分级:daemon 推的 F 是 dynamic reported value → 非 WAL fact、
|
||||
进 scheduler snapshot + daemon 重连全量补推即安全;将来 elastic 模式
|
||||
的 total 才是 WAL fact。与 persistence.md 一致。
|
||||
- 角色收敛:PR-2 proxy(leaning)统一 reporter+provisioner 为一个 daemon
|
||||
role;**HostRole 只加一个成员**(避免将来再出现第二个同义 enum),
|
||||
xceextresd 定位为 PR-2 proxy 的第一片(只读 report 能力)。
|
||||
- 归因 Tier 1' PID 匹配引用其 jobPidInfo(我们已实施,3545aa0d)。
|
||||
|
||||
**我们的设计对其 spec 的三个扩展点**(他目前装不下,需带回讨论):
|
||||
1. project-config 的池模型只有 host 池(Req 7.2 容量=注册主机静态
|
||||
total)——license 池无主机,per-project license share 需把"集群级池"
|
||||
作为一个 pool location(容量=报告值/elastic total)。真实扩展非冲突。
|
||||
2. peer-authentication-hardening 的 per-role opcode allowlist:extres/
|
||||
provisioner 角色必须上 allowlist(其 P3 open:auth 是否先行)。
|
||||
t1 测试集群用现状 peer key 先行,生产化前依赖该 spec。
|
||||
3. R8 duration/decay(decided:未来 spec)——unsatisfy_ 的 EXTERNAL
|
||||
分支不封死 decay 修正项。
|
||||
|
||||
**带给 Rongsong 讨论的三个提议**:
|
||||
- P9 答案:按资源分模式(server-free 模式 server 赢、elastic 模式 XCE
|
||||
赢、server count 只对账)。
|
||||
- R2 按 "both" 已落地(worker script + 外部 reporter,与其 leaning
|
||||
一致),可请其标 decided。
|
||||
- reporter 与 provisioner 用同一个 HostRole 成员(其 brief 本就写
|
||||
"or reuse the provisioner role")。
|
||||
|
||||
### 3.7 提请决策(未决)
|
||||
1. daemon 命名:`xceextresd`(通用外部资源,推荐)vs `xcelsd`(贴 LSF
|
||||
license 语义);定位为 PR-2 proxy 第一片(见 §3.6)。
|
||||
2. 断联默认策略:fail-open(保旧值+stale WARN,推荐)vs fail-closed(清零停派)。
|
||||
3. P9 模式拆分与三个扩展点需与 Rongsong 确认(§3.6)。
|
||||
|
||||
---
|
||||
|
||||
## 附:核实过的关键代码位置
|
||||
|
||||
| 位置 | 内容 |
|
||||
|---|---|
|
||||
| src/schema/controller.capnp:150-156 | HostRole enum(加 extres @5 的唯一 schema 变更) |
|
||||
| src/schema/controller.capnp:158-164 | JoinData(role + attrs union,daemon join 复用) |
|
||||
| src/schema/controller.capnp:392-402 | ResourceVal{name,host,val}/ResourceVals wire |
|
||||
| src/schema/controller.capnp:1350 | ReplayEvent.resvalsFull(sim 回放载体,已存在) |
|
||||
| src/controller/join.c:221-249 | handle_apiserver_join 轻量先例 |
|
||||
| src/controller/join.c:307-330 | handle_join_ role 分发(加 case 处) |
|
||||
| src/controller/cluster.c:71-111 | endpoint close 的 role 分发(leave 处理处) |
|
||||
| src/controller/opupd.c:11-68 | do_update_resvals:worker-only 门(改放行处) |
|
||||
| src/controller/worker.c:65-143 | update_val_res:SHARED→c->sharedinst 单写点(零改动) |
|
||||
| src/controller/cluster.c:1579-1650 | seed_sharedinst_from_config(EXTERNAL 不播种改动处) |
|
||||
| src/controller/sched.c:716-746 | sched_resource_hook_fun:controller→scheduler notify(零改动) |
|
||||
| src/controller/apiserver.c:560 | apiserver 侧 RESOURCE_UPDATE hook(零改动) |
|
||||
| src/scheduler/src/handler.c:289-309,422-427 | schedUpdateResvals notify 入队 |
|
||||
| src/scheduler/src/scheduler.c:1468-1486 | UPDATE_RESVALS_FULL 应用(key_find+sharedInst) |
|
||||
| src/scheduler/src/scheduler.c:1487-1508 | UPDATE_RESVALS(DELTA):按 host 找 worker,无 host 丢弃 |
|
||||
| src/scheduler/src/core_events.c:1201-1226 | RESVALS_FULL→core.sharedInst(dup_val) |
|
||||
| src/scheduler/src/core_events.c:1228-1263 | RESVALS_DELTA:unknown worker WARN 跳过 |
|
||||
| src/scheduler/src/resource.c:19-53 | get_resvals:缺条目/非 fvalue 读 0 |
|
||||
| src/scheduler/src/resource.c:148-194 | unsatisfy_:判式与 unmeet 行(EXTERNAL 分支改动处) |
|
||||
| src/scheduler/src/resource.c:292-342 | sched_check_shared_satisfy_(total/inuse 装配) |
|
||||
| src/scheduler/src/resource.c:708-747 | sched_update_account(sharedInUse 记账) |
|
||||
| src/scheduler/src/snapshot_load.c:130-157 | host==NULL 条目→sharedInst(载荷形状先例) |
|
||||
| src/scheduler/src/scheduler.c:1058-1083 | sched_init_sharedInst(config resvals 启动播种) |
|
||||
| src/include/xce/types.h:81-89 | 资源 flag 位(EXTERNAL=0x10 死 flag 待激活) |
|
||||
| src/include/preason.h | PREASON 体系(NO_RESOURCE=2 带 res detail) |
|
||||
| src/lib/resreq.c:825-882 | calc_wanted_global_resvals(作业请求入口,零改动) |
|
||||
| src/xceadmin/src/xcecommon/mapping.py:213-222 | location:[all] → SHARED\|GLOBAL(加 external 分支处) |
|
||||
| src/frontend/apiserver/backend/clib.go:584-602 | Go 侧 resval decode→store(零改动) |
|
||||
| src/frontend/apiserver/router/routers.go:181-186 | 资源 REST(现仅 GET;Phase 3 再议更新端点) |
|
||||
| src/worker/src/resource_sync.c:116-153 | register_load_index:elim 注册表(外部注册死 API) |
|
||||
| src/worker/src/resource_sync.c:160-193 | unregister_load_index:is_builtin==false 分支(为 elim 预留) |
|
||||
| src/worker/src/resource_sync.c:60-106 | 内置 11 个 LSF load index(r15s…io) |
|
||||
| src/worker/src/resource_sync.c:195-303 | collect_load_indices:host=virtual_name 上报载荷 |
|
||||
| src/worker/src/resource_sync.c:325-393 | sync 线程:15 tick 周期上报(elim 采集器挂点) |
|
||||
| src/worker/src/controller_connector.c:2279-2304 | xcecd_send_load_indices→xcecli_update_resvals |
|
||||
| src/lib/resreq.c:957-1016 | calc_wanted_host_resvals:host 域请求(elim 消费侧,零改动) |
|
||||
| src/scheduler/src/resource.c:367-416 | sched_check_host_satisfy_:w->resvals 当 total(host 腿) |
|
||||
24
.kiro/specs/external-license-resources/spec.json
Normal file
24
.kiro/specs/external-license-resources/spec.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"feature_name": "external-license-resources",
|
||||
"created_at": "2026-10-26T08:00:00Z",
|
||||
"updated_at": "2026-10-26T08:00:00Z",
|
||||
"language": "zh",
|
||||
"phase": "design-proposal",
|
||||
"phase_note": "调研与设计提案阶段(design.md 完成:Slurm/LSF 参考模型、XCE 管线逐点核实、记账模型、elim 兼容、merge 归因、与 Rongsong 统一资源模型 spec 群的合并性审查)。未生成 EARS requirements.md / tasks.md;开工前按 CLAUDE.md 流程补(或确认按 TDD 直接实施——design §3.5 的 7 步 Phase 1 已可执行)。design.md 为中文,含核实的 file:line 位置表。",
|
||||
"approvals": {
|
||||
"requirements": {
|
||||
"generated": false,
|
||||
"approved": false
|
||||
},
|
||||
"design": {
|
||||
"generated": true,
|
||||
"approved": false,
|
||||
"validation_note": "2026-10-26 与用户逐节讨论定稿(调研→elim 归属→merge 归因→Rongsong spec 群审查)。未走 /kiro-validate-design;跨 spec 合并性人工核对见 design.md §3.6。唯一未决跨团队项:P9 记账模式拆分与三个扩展点待 Rongsong 确认(design.md §3.6)。"
|
||||
},
|
||||
"tasks": {
|
||||
"generated": false,
|
||||
"approved": false
|
||||
}
|
||||
},
|
||||
"ready_for_implementation": false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue