fix(schedulerclient): node memory_bytes_total no longer ×1e6 (bug-1514)
The apiserver's host max_mem is already in bytes (the worker reads sysinfo().totalram × mem_unit → bytes and passes it through unchanged via controller.capnp), but ListNodes treated it as MB and multiplied by mbToBytes, inflating each node's reported memory to ~8.2e15 (~7.3 PiB). Now reports the raw bytes (~7.7 GiB per node). - httpclient.go: drop ×mbToBytes for node max_mem; clarify mbToBytes is job-submit-path only (job max_mem is never populated → stays 0) - ordermap_test.go: correct assertion that had encoded the buggy value - httpclient_test.go: regression test TestListNodeMemoryBytesKeepsByteUnit - buglog.json: bug-1514 + close-tags for the bug-1494 family (prior session) Also add MCP demo deliverables: - docs/claude-code-demo-guide.md: live Claude Code demo guide (7 scenes) - docs/mcp-demo-design.md: leadership demo design - docs/mcp-demo-run.py: add --slow/--color flags for screen recording Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bbe8ed3335
commit
06887985b4
7 changed files with 423 additions and 36 deletions
57
src/docs/claude-code-demo-guide.md
Normal file
57
src/docs/claude-code-demo-guide.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Claude Code + MCP 演示指南(自然语言管理 HPC 集群)
|
||||
|
||||
> 相比录屏视频,直接在 **Claude Code** 里演示更直观:你用人话问,Claude 自动调用
|
||||
> `xce-scheduler` MCP 工具查集群、查作业、诊断 PEND、提交/取消作业。
|
||||
|
||||
## 前置条件
|
||||
|
||||
| 项 | 要求 |
|
||||
|---|---|
|
||||
| MCP 二进制 | `/tmp/xce-mcp`(最新构建,Claude Code 通过它连 apiserver)|
|
||||
| API key | 有效的 mcp_operator key(90 天)|
|
||||
| 环境变量 | 启动 Claude Code **前** export `XCE_API_URL` + `XCE_API_KEY` |
|
||||
| 集群 | t1/t2/t3 3 节点健康,无残留作业 |
|
||||
| 项目目录 | 在 `/home/lchen/code/xce` 下启动(加载 `.mcp.json`)|
|
||||
|
||||
## 启动步骤
|
||||
|
||||
```bash
|
||||
export XCE_API_URL='http://172.31.1.30:8080'
|
||||
export XCE_API_KEY='<mcp_operator key,见 /tmp/xce_apikey>' # 用当前有效 key
|
||||
cd /home/lchen/code/xce
|
||||
claude
|
||||
```
|
||||
|
||||
启动后可用 `/mcp` 确认 `xce-scheduler` 已连接(工具 `scheduler_list_nodes`、`scheduler_submit` 等 15 个)。
|
||||
|
||||
**关键点**:`.mcp.json` 里 `XCE_API_KEY` 用的是 `${XCE_API_KEY}` 环境变量,所以必须在启动
|
||||
Claude Code 的 shell 里 export。没 export 的话 MCP 工具会加载失败或报鉴权错。
|
||||
|
||||
## 演示流程(7 个场景,约 5 分钟)
|
||||
|
||||
按顺序输入以下自然语言即可(工具已预批准,写操作直接执行):
|
||||
|
||||
| # | 提问(直接复制粘贴) | 触发工具 | 亮点 |
|
||||
|---|---|---|---|
|
||||
| 1 | 看看集群现在有哪些节点、资源怎么样 | list_nodes + cluster_metrics | 一句话看集群健康 |
|
||||
| 2 | 列出当前所有作业,特别是排队的 | list_jobs | 作业队列 |
|
||||
| 3 | 帮我看看 pending 的作业为什么还没跑 | pend_reasons | **核心**:PEND 原因直读 |
|
||||
| 4 | 帮我在 xce 队列提交一个 sleep 20 的测试作业 | submit | 自然语言提交 |
|
||||
| 5 | 等刚才那个作业跑完 | wait_job | 阻塞等待并汇报 |
|
||||
| 6 | 我要跑一个科学计算,帮我看看需要多少资源 | suggest_resources | 结合实时负载给建议 |
|
||||
| 7 | 把刚才那个测试作业取消掉 | cancel | 自然语言取消 |
|
||||
|
||||
## 给领导演示的讲解要点
|
||||
|
||||
1. **无需记命令**:作业查询/提交/取消都是日常高频操作,从"背命令+查文档"变成"一句话"。
|
||||
2. **透明**:PEND 原因(配额/资源/匹配)、资源建议都有可读解释,不再黑盒。
|
||||
3. **安全**:MCP 写工具走 RBAC(mcp_operator 角色),Claude Code 会话里也能看到每次工具调用。
|
||||
4. **标准协议**:MCP 是开放标准,工具可扩展,未来可接更多集群运维能力。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **key 失效**:apiserver 重启会清空内存中的 key,需重新生成(admin JWT → POST /xce/v1/apikeys)。
|
||||
- **演示前清理**:先删历史作业(场景 3 需要一个 PEND 作业才有看头——可临时提交一个超配额作业)。
|
||||
- **模型选择**:当前会话模型为 `deepseek-v4-flash[1m]`(见状态栏),不影响 MCP 功能。
|
||||
- **自动录屏版**:`/tmp/cc_demo_record.sh`(Xvfb + lxterminal + xdotool 自动驱动 7 场景,产出
|
||||
`/tmp/cc-mcp-demo-final.mp4`),适合无法现场演示时用。
|
||||
90
src/docs/mcp-demo-design.md
Normal file
90
src/docs/mcp-demo-design.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# XCE MCP 演示设计(面向领导与同事)
|
||||
|
||||
## 目标
|
||||
|
||||
展示「自然语言操作 HPC 集群」:不用命令行、不用记语法,通过 MCP 让 AI 助手直接查询、提交、诊断、调整作业。观众无需理解 XCE/集群内部机制,只需看到「一句话 → 集群动作 → 结果」。
|
||||
|
||||
## 叙事主线(一个工程师的日常)
|
||||
|
||||
> 一位工程师("我")用对话的方式管理一批计算作业:查状态 → 看为什么排队 → 提交新作业 → 观察运行 → 诊断失败 → 清理。全程用自然语言,AI 助手调用集群工具完成。
|
||||
|
||||
## 环境与前置
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| 集群 | t1/t2/t3(3 计算节点),controller/scheduler/apiserver 健康 |
|
||||
| MCP server | `/tmp/xce-mcp`(最新构建)|
|
||||
| API key | `export XCE_API_KEY=<mcp_operator key>`(90 天,见 `/tmp/xce_apikey`)|
|
||||
| 演示机 | 任意可 ssh 到 t1 的终端,用 python 驱动 MCP stdio 或直接对话 |
|
||||
|
||||
**演示前清理**:删除历史 EXIT/DONE 作业(保持列表干净),确保 worker 在线。
|
||||
|
||||
## 演示流程(约 10 分钟)
|
||||
|
||||
### 场景 1:总览——"我的集群现在怎么样"(2 分钟)
|
||||
- **问**:「看看集群现在有哪些节点、资源怎么样」
|
||||
- **工具**:`scheduler_cluster_metrics` + `scheduler_list_nodes`
|
||||
- **展示**:3 节点在线、总核数、空闲资源,直观的集群健康度
|
||||
- **讲解点**:AI 能读懂集群状态并总结,不用查文档
|
||||
|
||||
### 场景 2:作业队列——"都有什么作业在跑/排队"(1 分钟)
|
||||
- **问**:「列出当前所有作业,特别是排队的」
|
||||
- **工具**:`scheduler_list_jobs`
|
||||
- **展示**:作业列表(名字/状态/队列/提交人),PEND 的作业一眼可见
|
||||
- **讲解点**:MCP 把「作业列表」这种高频操作变成一句话
|
||||
|
||||
### 场景 3:为什么排队——"这作业为什么还没跑"(2 分钟)
|
||||
- **问**:「有一个作业一直 pending,帮我看看为什么」
|
||||
- **工具**:`scheduler_pend_reasons`(bjobs -p 等价)
|
||||
- **展示**:返回「waiting: no host matches... host t1: match_fail」可读解释
|
||||
- **讲解点**:**核心价值**——以前要翻日志猜原因,现在 AI 直接给出原因(配额/资源/匹配)
|
||||
|
||||
### 场景 4:提交作业——"帮我把这个任务跑起来"(2 分钟)
|
||||
- **问**:「提交一个 sleep 60 的作业到 xce 队列」
|
||||
- **工具**:`scheduler_submit`(先 dry_run 预览 → 再真实提交)
|
||||
- **展示**:先展示「将要提交的规格」,确认后提交,返回 jobid
|
||||
- **讲解点**:写操作有**预览 + 确认**安全机制,不是盲目执行
|
||||
|
||||
### 场景 5:等待完成——"帮我等这个作业跑完"(1 分钟)
|
||||
- **问**:「等刚才那个作业跑完」
|
||||
- **工具**:`scheduler_wait_job`
|
||||
- **展示**:阻塞等待 → 返回最终状态(succeeded)
|
||||
- **讲解点**:AI 能「等待并汇报结果」,交互式而非一次性
|
||||
|
||||
### 场景 6:资源建议——"帮我估算一下要多少资源"(1 分钟)
|
||||
- **问**:「我要跑一个 8 小时的科学计算,帮我看看要多少核/内存」
|
||||
- **工具**:`scheduler_suggest_resources` + `scheduler_estimate_wait_time`
|
||||
- **展示**:根据集群容量给出建议核数/预估等待
|
||||
- **讲解点**:AI 结合集群实时状态给建议,不只是套公式
|
||||
|
||||
### 场景 7:清理——"把不需要的作业都取消"(收尾,1 分钟)
|
||||
- **问**:「取消刚才提交的那个测试作业」
|
||||
- **工具**:`scheduler_cancel`
|
||||
- **展示**:干净收尾,展示「有确认机制」的安全感
|
||||
- **讲解点**:破坏性操作有保护
|
||||
|
||||
## 讲解要点(给观众的价值主张)
|
||||
|
||||
1. **效率**:5 个常用操作(查/排/看/提/等)从「命令+文档」变成「一句话」
|
||||
2. **透明**:PEND 原因、资源建议都有可读解释,不再黑盒
|
||||
3. **安全**:写操作(提交/取消/挂起)先预览、需确认,不会误操作
|
||||
4. **可扩展**:MCP 协议标准,未来可接更多集群操作
|
||||
|
||||
## 演示脚本(MCP stdio 驱动,python)
|
||||
|
||||
见 `mcp-demo-run.py`(同目录)——自动执行场景 1-7,输出每一步的「用户问 → 工具 → 结果」。
|
||||
|
||||
## 备选/延伸(时间充裕时)
|
||||
|
||||
- **多作业调度**:提交 3 个作业,展示负载均衡(各节点分配)
|
||||
- **故障演示**:模拟一个作业失败(命令故意报错),`scheduler_analyze_failure` 诊断
|
||||
- **挂起/恢复**:`scheduler_hold`/`release` 演示管理员控制
|
||||
|
||||
## 风险与规避
|
||||
|
||||
| 风险 | 规避 |
|
||||
|---|---|
|
||||
| 演示中作业意外失败 | 用 `sleep 60` 这类可靠命令,避免真实计算 |
|
||||
| 网络/集群抖动 | 演示前确认 3 节点 OK + worker 在线 |
|
||||
| 观众不懂 HPC | 全程用「作业/排队/节点」类比「任务/等待/机器」,不讲协议 |
|
||||
| key 失效 | 演示前重新生成 mcp_operator key |
|
||||
160
src/docs/mcp-demo-run.py
Normal file
160
src/docs/mcp-demo-run.py
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
XCE MCP 演示脚本 — 面向领导与同事。
|
||||
|
||||
自动执行 7 个场景,每个场景打印:
|
||||
[用户问] <自然语言>
|
||||
[调用] tool_name(args)
|
||||
[结果] ...(截断显示)
|
||||
|
||||
前置:export XCE_API_URL / XCE_API_KEY,MCP 二进制在 /tmp/xce-mcp。
|
||||
用法:python3 mcp-demo-run.py [--full] [--slow] [--color]
|
||||
--full 显示完整结果而非截断
|
||||
--slow 场景间停顿,便于观看(录屏用)
|
||||
--color ANSI 上色(录屏用)
|
||||
"""
|
||||
import json, subprocess, os, select, time, sys
|
||||
|
||||
BASE = os.environ.get("XCE_API_URL", "http://172.31.1.30:8080")
|
||||
KEY = os.environ.get("XCE_API_KEY", "")
|
||||
BIN = os.environ.get("XCE_MCP_BIN", "/tmp/xce-mcp")
|
||||
FULL = "--full" in sys.argv # 显示完整结果而非截断
|
||||
SLOW = "--slow" in sys.argv # 场景间停顿,便于观看(录屏用)
|
||||
COLOR = "--color" in sys.argv # ANSI 上色(录屏用)
|
||||
|
||||
# ANSI 颜色表;非 --color 时全部为空串,输出保持纯文本
|
||||
C = {
|
||||
"reset": "\033[0m",
|
||||
"bold": "\033[1m",
|
||||
"dim": "\033[2m",
|
||||
"green": "\033[32m",
|
||||
"cyan": "\033[36m",
|
||||
"yellow": "\033[33m",
|
||||
"magenta": "\033[35m",
|
||||
"red": "\033[31m",
|
||||
} if COLOR else {k: "" for k in ("reset", "bold", "dim", "green",
|
||||
"cyan", "yellow", "magenta", "red")}
|
||||
|
||||
if not KEY:
|
||||
sys.exit("set XCE_API_KEY first (export XCE_API_KEY=...)")
|
||||
|
||||
p = subprocess.Popen([BIN], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL, text=True)
|
||||
|
||||
def send(o):
|
||||
p.stdin.write(json.dumps(o) + "\n")
|
||||
p.stdin.flush()
|
||||
|
||||
_reqid = [100] # 递增请求 id,避免响应串扰
|
||||
|
||||
def call(name, args, timeout=15):
|
||||
_reqid[0] += 1
|
||||
i = _reqid[0]
|
||||
send({"jsonrpc": "2.0", "id": i, "method": "tools/call",
|
||||
"params": {"name": name, "arguments": args}})
|
||||
end = time.time() + timeout
|
||||
while time.time() < end:
|
||||
ready, _, _ = select.select([p.stdout], [], [], 0.5)
|
||||
if ready:
|
||||
line = p.stdout.readline()
|
||||
if not line:
|
||||
return None
|
||||
d = json.loads(line)
|
||||
if d.get("id") == i:
|
||||
res = d.get("result", {})
|
||||
text = "".join(x.get("text", "") for x in res.get("content", []))
|
||||
return {"isError": res.get("isError"), "text": text}
|
||||
return {"timeout": True}
|
||||
|
||||
def show(question, name, args, maxlen=400, timeout=15):
|
||||
print("\n" + C["dim"] + "=" * 70 + C["reset"])
|
||||
print(f"{C['bold']}{C['green']} [用户问] {question}{C['reset']}")
|
||||
print(f"{C['cyan']} [调用] scheduler_{name.split('_',1)[1]}({json.dumps(args, ensure_ascii=False)}){C['reset']}")
|
||||
print(C["dim"] + "-" * 70 + C["reset"])
|
||||
r = call(name, args, timeout=timeout)
|
||||
if not r:
|
||||
print(f"{C['red']} [结果] 无响应{C['reset']}")
|
||||
return r
|
||||
txt = r.get("text", "")
|
||||
err = r.get("isError")
|
||||
head = C["red"] + " [结果] (ERROR)" if err else C["yellow"] + " [结果]"
|
||||
print(head + C["reset"])
|
||||
if FULL:
|
||||
print(txt)
|
||||
else:
|
||||
print(txt[:maxlen].replace("\n", "\n "))
|
||||
if SLOW:
|
||||
time.sleep(2.5)
|
||||
return r
|
||||
|
||||
# ── 初始化 ──
|
||||
send({"jsonrpc": "2.0", "id": 1, "method": "initialize",
|
||||
"params": {"protocolVersion": "2024-11-05", "capabilities": {},
|
||||
"clientInfo": {"name": "demo", "version": "1.0"}}})
|
||||
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
|
||||
|
||||
print("\n" + C["magenta"] + "#" * 70 + C["reset"])
|
||||
print(C["magenta"] + C["bold"] + "# XCE MCP 演示 — 用自然语言管理 HPC 集群" + C["reset"])
|
||||
print(C["magenta"] + "#" * 70 + C["reset"])
|
||||
if SLOW:
|
||||
print(C["dim"] + "# 将自动演示 7 个场景:总览 → 作业 → 排队原因 → 提交 → 等待 → 资源建议 → 取消" + C["reset"])
|
||||
time.sleep(3)
|
||||
|
||||
# 场景 1: 集群总览
|
||||
show("看看集群现在有哪些节点、资源怎么样",
|
||||
"scheduler_cluster_metrics", {})
|
||||
|
||||
# 场景 2: 作业队列
|
||||
show("列出当前所有作业,特别是排队的",
|
||||
"scheduler_list_jobs", {})
|
||||
|
||||
# 场景 3: 为什么排队
|
||||
show("帮我看看 pending 的作业为什么还没跑",
|
||||
"scheduler_pend_reasons", {})
|
||||
|
||||
# 场景 4: 提交作业(先预览再提交)
|
||||
r = show("提交一个 sleep 20 的作业到 xce 队列(先预览)",
|
||||
"scheduler_submit",
|
||||
{"name": "demo_job", "command": "sleep 20", "queue": "xce", "dry_run": True},
|
||||
maxlen=300)
|
||||
import re
|
||||
m = re.search(r'"jobid": (\d+)', r.get("text", "") if r else "")
|
||||
if not m:
|
||||
# 预览阶段没有 jobid,正式提交
|
||||
r = show("确认提交(dry_run=false)",
|
||||
"scheduler_submit",
|
||||
{"name": "demo_job", "command": "sleep 20", "queue": "xce", "dry_run": False},
|
||||
maxlen=300)
|
||||
m = re.search(r'"jobid": (\d+)', r.get("text", "") if r else "")
|
||||
jid = m.group(1) if m else None
|
||||
print(f"\n [提交成功] jobid = {jid}")
|
||||
|
||||
# 场景 5: 等待完成(sleep 20 → 等 ~25s)
|
||||
if jid:
|
||||
show(f"等作业 {jid} 跑完",
|
||||
"scheduler_wait_job", {"id": jid, "timeout_s": 30}, maxlen=200, timeout=35)
|
||||
|
||||
# 场景 6: 资源建议
|
||||
show("我要跑一个科学计算,帮我看看需要多少资源",
|
||||
"scheduler_suggest_resources", {"command": "scientific_compute"},
|
||||
maxlen=250)
|
||||
|
||||
# 场景 7: 清理 — 演示 cancel 对运行中的作业生效
|
||||
# 先提交一个长作业(仍运行中),再 cancel 它。
|
||||
r = show("再提交一个长作业(sleep 300)以便演示取消",
|
||||
"scheduler_submit",
|
||||
{"name": "demo_long", "command": "sleep 300", "queue": "xce", "dry_run": False},
|
||||
maxlen=200)
|
||||
m = re.search(r'"jobid": (\d+)', r.get("text", "") if r else "")
|
||||
long_jid = m.group(1) if m else None
|
||||
if long_jid:
|
||||
show(f"把这个作业 {long_jid} 取消掉",
|
||||
"scheduler_cancel", {"id": long_jid}, maxlen=200)
|
||||
elif jid:
|
||||
# 兜底:若无长作业,说明演示作业已完成无需取消
|
||||
print(f"\n [说明] 演示作业 {jid} 已完成,无需取消(cancel 仅对运行/排队中的作业生效)")
|
||||
|
||||
p.terminate()
|
||||
print("\n" + C["magenta"] + "#" * 70 + C["reset"])
|
||||
print(C["magenta"] + C["bold"] + "# 演示结束 — 7 个场景全部完成" + C["reset"])
|
||||
print(C["magenta"] + "#" * 70 + C["reset"])
|
||||
|
|
@ -195,7 +195,7 @@ func (hc *HTTPClient) ListNodes(ctx context.Context) ([]*URMNode, error) {
|
|||
Status: NodeStatusFromString(rawStatus),
|
||||
CoresTotal: h.rowInt("num_cpus"),
|
||||
CoresUsed: numRun, // running jobs ≈ cores used
|
||||
MemoryBytesTotal: int64(h.rowInt("max_mem")) * mbToBytes,
|
||||
MemoryBytesTotal: int64(h.rowInt("max_mem")), // max_mem is already bytes (worker reports sysinfo().totalram bytes); do NOT ×1e6
|
||||
MemoryBytesUsed: 0, // apiserver does not expose live used-memory for hosts
|
||||
GPUCount: 0, // TODO: parse from extensions/topology when available
|
||||
Features: nil,
|
||||
|
|
@ -393,7 +393,10 @@ func (hc *HTTPClient) getJobs(req *resty.Request, url string, filter JobFilter)
|
|||
|
||||
// ── internal types for JSON decoding (apiserver response shapes) ────────────
|
||||
|
||||
const mbToBytes = 1_000_000 // MaxMem from apiserver is in MB → stored as bytes in URM
|
||||
// mbToBytes scales a value that is genuinely in MB to bytes. It applies ONLY
|
||||
// to the job submit path where mem_limit is formatted as "<MB> MB"
|
||||
// (write.go). Node host max_mem is already in bytes — do NOT scale it.
|
||||
const mbToBytes = 1_000_000
|
||||
|
||||
// apiEnvelope is the standard top-level response: {"code":200,"msg":"ok","data":...}
|
||||
type apiEnvelope struct {
|
||||
|
|
|
|||
51
src/frontend/schedulerclient/httpclient_test.go
Normal file
51
src/frontend/schedulerclient/httpclient_test.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package schedulerclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestListNodeMemoryBytesKeepsByteUnit verifies the node total memory is NOT
|
||||
// re-scaled by the client: the apiserver's `max_mem` field is already in bytes
|
||||
// (the worker reads sysinfo().totalram * mem_unit → bytes and passes it through
|
||||
// unchanged), so scheduler_list_nodes must report memory_bytes_total ≈ 8.2e9
|
||||
// for a ~7.7 GiB node — not the ×1e6-inflated ~8.2e15 that httpclient.go:198
|
||||
// produced when it wrongly treated apiserver bytes as MB.
|
||||
func TestListNodeMemoryBytesKeepsByteUnit(t *testing.T) {
|
||||
// 8224825344 bytes = the real MemTotal of the t1 node (7.66 GiB).
|
||||
const realMemBytes = 8224825344
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got := r.Header.Get("Authorization"); got == "" {
|
||||
t.Errorf("expected Authorization header, got empty")
|
||||
}
|
||||
if got := r.URL.Path; got != "/xce/v1/hosts" {
|
||||
t.Errorf("unexpected path %q", got)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
body := `{"code":200,"msg":"ok","data":{"count":1,"rows":[` +
|
||||
`{"fields":["host_name","host_status","num_cpus","num_run","max_mem"],` +
|
||||
`"data":{"host_name":"t1","host_status":"idle","num_cpus":8,"num_run":0,"max_mem":8224825344}}` +
|
||||
`]}}`
|
||||
_, _ = w.Write([]byte(body))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
hc := NewClient(srv.URL, "xce_ak_test")
|
||||
nodes, err := hc.ListNodes(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("ListNodes: %v", err)
|
||||
}
|
||||
if len(nodes) != 1 {
|
||||
t.Fatalf("expected 1 node, got %d", len(nodes))
|
||||
}
|
||||
n := nodes[0]
|
||||
if n.Name != "t1" || n.CoresTotal != 8 {
|
||||
t.Fatalf("unexpected node: %+v", n)
|
||||
}
|
||||
if n.MemoryBytesTotal != realMemBytes {
|
||||
t.Fatalf("MemoryBytesTotal = %d, want %d (apiserver max_mem is already bytes; must not be ×1e6)", n.MemoryBytesTotal, realMemBytes)
|
||||
}
|
||||
}
|
||||
|
|
@ -56,8 +56,10 @@ func TestOrderMap_RowsUseColumnarShape(t *testing.T) {
|
|||
if n.CoresTotal != 8 {
|
||||
t.Errorf("node.CoresTotal = %d, want 8", n.CoresTotal)
|
||||
}
|
||||
if n.MemoryBytesTotal != 8224821248*mbToBytes {
|
||||
t.Errorf("node.MemoryBytesTotal = %d, want %d", n.MemoryBytesTotal, 8224821248*mbToBytes)
|
||||
// max_mem from apiserver is already bytes (worker reports sysinfo bytes);
|
||||
// the client must NOT scale it by mbToBytes (see httpclient.go ListNodes).
|
||||
if n.MemoryBytesTotal != 8224821248 {
|
||||
t.Errorf("node.MemoryBytesTotal = %d, want %d", n.MemoryBytesTotal, 8224821248)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue