[经验分享] openUBMC 冒烟测试自动化:Nexus 拉包 + Redfish 升级 + IPMI/Redfish 检查
最近在做 openUBMC 机型的冒烟测试,希望把「自动拉包 → 自动升级 → 自动冒烟」这一整套流程固化下来,能挂到 Jenkins 上每天跑一跑,顺便也减少手动点命令的体力活。
这篇发出来主要是记录一下自己的设计思路,给有类似需求的做个参考模板。实现比较朴素,完全基于:
-
bash -
curl -
jq -
ipmitool
没有用到框架,暂时也没有做成通用工具,纯粹是解决我自己这条机型日常 QA 的需求。
1. 背景 & 目标
我当时想解决的是几个具体问题:
-
固件升级流程太手动
-
从 Nexus/Maven 仓库手动找包;
-
下载 ZIP → 解压 → 找 HPM;
-
拿着 HPM 再手工写 Redfish
SimpleUpdate请求; -
升完再手动查 Task/查版本。
-
-
冒烟测试没有统一基线
-
每个人心里都有一套“我习惯看哪些 ipmitool / Redfish”的 checklist;
-
但是没脚本,也没办法挂 CI。
-
-
希望对 CI 友好
-
如果当前 BMC 已经是最新版本,就不用再升级、也可以选择跳过冒烟;
-
偶发的 IPMI 超时/小抖动不能让整条流水线乱红;
-
同时又要保留“真正有问题”的 FAIL 信号。
-
所以最后我做了一套 “总管 + 子脚本 + JSON 配置” 的方案,核心目标:
-
配置集中(一份 JSON 管所有参数);
-
总管统一出口(CI 只关心一个脚本的退出码);
-
尽量幂等(反复跑不会把 BMC 玩坏)。
2. 整体结构 & 调用图谱
整个工程只有几个文件,结构大概是这样:
smoke_test/
├── openubmc_update_config.json # 唯一配置源(模板)
├── openubmc_update.sh # 从 Nexus 拉包 + 解压 + Redfish SimpleUpdate
├── openubmc_smoke_runner.sh # 冒烟总管(升级/IPMI/Redfish 组合)
├── openubmc_smoke_ipmi.sh # 带外 IPMI 冒烟
└── openubmc_smoke_redfish.sh # Redfish 冒烟(系统/CPU/内存/PCIe/网卡/硬盘等)
从 Jenkins 视角看,流程大概长这样:
Jenkins Job
│
│ 1. Clone smoke_test 仓库
│ 2. 渲染 openubmc_update_config.effective.json
│ - 写入 BMC_IP / BMC_USER / BMC_PASS
│ - 写入 group_id / artifact_id / upgrade.mode 等
│
└──▶ 3. 调用:
./openubmc_smoke_runner.sh <MODE> openubmc_update_config.effective.json
│
▼
┌───────────────────────────────┐
│ openubmc_smoke_runner.sh │
│ MODE = all / upgrade-only │
│ / read-only / ... │
└───────────┬───────────────┬──┘
│ │
MODE=upgrade-only │
│ │
┌─────────────▼───────┐ │
│ openubmc_update.sh │ │
└─────────────┬───────┘ │
│ │
MODE=all: 若发生升级 │ │ MODE=read-only:
▼ │ 只跑只读冒烟
┌───────────────┐ │
│ IPMI 冒烟 │◀────────┘
│ openubmc_ │
│ smoke_ipmi.sh│
└───────┬───────┘
▼
┌───────────────┐
│ Redfish 冒烟 │
│ openubmc_ │
│ smoke_redfish│
└───────────────┘
几个 MODE 的语义:
-
upgrade-only:只做升级,不做冒烟; -
read-only:不升级,只做 IPMI + Redfish 冒烟; -
all:先升级,如果版本确实更新了,再跑冒烟。
3. 配置文件:openubmc_update_config.json
所有参数都集中在一个 JSON 里,Jenkins 通过 jq 在构建阶段把 BMC IP、用户密码等写进去。
一个简化示例:
{
"nexus": {
"base_url": "http://<nexus-host>:8081",
"repo": "maven-QA",
"user": "public",
"pass": "******"
},
"artifact": {
"group_id": "openUBMC.S920X20",
"artifact_id": "openUBMC"
},
"paths": {
"local_hpm_root": "/home/smoke_test/bmc/hpm",
"state_dir": "/var/lib/openubmc-updater"
},
"bmc": {
"ip": "192.168.x.x",
"user": "Administrator",
"pass": "******"
},
"image_server": {
"scheme": "SCP",
"user": "arch",
"pass": "******",
"host": "192.168.x.y"
},
"ipmi": {
"host": "192.168.x.x",
"user": "Administrator",
"pass": "******",
"interface": "lanplus",
"cipher_suite": "17"
},
"upgrade": {
"mode": "remote" // remote/local,脚本内部预留
}
}
脚本里约定:
-
升级脚本优先使用
artifact.*/nexus.*; -
IPMI 冒烟优先使用
ipmi.*,为空时回退到bmc.*。
4. 升级脚本:openubmc_update.sh(思路)
升级脚本主要做这几件事(伪流程):
-
从 Maven 仓库拉
maven-metadata.xml:-
过滤奇怪的版本串(比如
66.82.73.68这种中间误上传的); -
选出符合版本规则的最新版本(例如
55.05.01.04.B001)。
-
-
读取本地
state_dir里上一次记录的版本:-
如果当前版本 == 最新版本 → 直接退出,并约定 exit=10;
-
如果版本落后 → 继续升级流程。
-
-
构造 ZIP URL 并下载:
http://<base_url>/repository/<repo>/ <group_id path>/<artifact_id>/<version>/<artifact_id>-<version>.zip -
解压到统一目录:
/home/smoke_test/bmc/hpm/<artifact_id>-<version>/然后在目录里自动搜索
*.hpm,拿到 HPM 绝对路径。 -
使用 Redfish
UpdateService.SimpleUpdate触发升级:-
支持本地
/tmp/xxx.hpm或远程SCP/HTTPS等; -
在例子里用的是 Redfish + 远程镜像服务器。
-
-
轮询 Task 进度:
GET /redfish/v1/TaskService/Tasks/1 - TaskState - Messages.Message - OEM TaskPercentage / PercentComplete -
任务完成后,等待 BMC reset & 再次上线:
-
一直 ping,直到 BMC Reply;
-
再用 Redfish 查询
/UpdateService/FirmwareInventory/ActiveBMC,比对Version与预期版本。
-
-
如果一切正常,写入
state_dir/current_version,下次再跑就知道「我已经是最新版本」了。
退出码约定(这个很关键,给总管脚本和 Jenkins 用):
-
0:本次发生了升级,且版本校验通过; -
10:仓库版本和当前 BMC 一致,本次没升级; -
其它非 0:过程出错(网络、Task 异常、版本不一致等)。
5. 总管脚本:openubmc_smoke_runner.sh
总管主要负责两个事情:
-
检查子脚本是否存在 & 可执行;
-
根据
MODE和升级结果决定要不要跑 IPMI / Redfish 冒烟。
核心逻辑示意:
MODE="${1:-all}"
CONFIG_FILE="${2:-./openubmc_update_config.json}"
BMC_IP_OVERRIDE="${3:-}"
run_update() {
local rc
"${update_script}" "${CONFIG_FILE}" "${BMC_IP_OVERRIDE}"
rc=$?
case "${rc}" in
0)
UPDATE_RESULT="updated"
;;
10)
UPDATE_RESULT="noop" # 已是最新版本
rc=0 # 对总管来说,视作成功
;;
*)
UPDATE_RESULT="error"
;;
esac
return "${rc}"
}
case "${MODE}" in
all)
run_update
up_rc=$?
if [[ ${up_rc} -ne 0 ]]; then
exit "${up_rc}"
fi
if [[ "${UPDATE_RESULT}" == "noop" ]]; then
log "MODE=all:本次固件已是最新版本,跳过 IPMI / Redfish 冒烟。"
exit 0
fi
"${ipmi_script}" "${CONFIG_FILE}" "${BMC_IP_OVERRIDE}"
"${redfish_script}" "${CONFIG_FILE}" "${BMC_IP_OVERRIDE}"
;;
upgrade-only)
run_update
exit $? # 不管是 updated 还是 noop,Jenkins 看的是 0/非 0
;;
read-only)
"${ipmi_script}" "${CONFIG_FILE}" "${BMC_IP_OVERRIDE}"
"${redfish_script}" "${CONFIG_FILE}" "${BMC_IP_OVERRIDE}"
;;
ipmi-only)
"${ipmi_script}" "${CONFIG_FILE}" "${BMC_IP_OVERRIDE}"
;;
redfish-only)
"${redfish_script}" "${CONFIG_FILE}" "${BMC_IP_OVERRIDE}"
;;
*)
echo "用法: $0 {all|upgrade-only|read-only|ipmi-only|redfish-only} [CONFIG_FILE] [BMC_IP_OVERRIDE]"
exit 1
;;
esac
6. IPMI 冒烟脚本:openubmc_smoke_ipmi.sh
这一块我重点做了几个小优化:
-
统一封装了一个带重试的执行器:
-
默认每条命令重试 3 次,每次失败间隔 2s;
-
命令之间自动
sleep 0.5s,防止 IPMI 打太快。
-
-
所有用例分“必选/可选”两类:
-
必选(mandatory):失败会把
FAILED=1,脚本 exit 1; -
可选(optional):失败只打
WARN-OPTIONAL,不影响整体 exit code。
-
-
对
ipmitool fru的特殊行为做了兼容:- 遇到
Error obtaining SDR info但输出里有FRU Device Description时,视为成功,只打一条 WARN,不当作 FAIL。
- 遇到
在这个基础上,分别封了四个 helper:
run_ipmi_cmd # 必选,只看 exit code
run_ipmi_cmd_expect # 必选,看 exit code + 输出关键字
run_ipmi_cmd_optional # 可选,只看 exit code
run_ipmi_cmd_expect_optional # 可选,看 exit code + 输出关键字
用例覆盖大致包括:
-
BMC/Chassis 基本信息:
mc info/mc selftest/chassis status/chassis power status -
网络:
lan print 1(IP/掩码/网关) /channel info 1 -
FRU:
fru/fru print 0 -
SDR/Sensor:
sdr info/sdr list/sdr type Temperature/Voltage/Fan/sensor list -
SEL:
sel info/sel list/sel time get(可选) -
SOL/用户:
sol info/user list 1 -
其它可选:
chassis bootparam get 0/1/2/sdr type Current/pef status/pef info/dcmi power reading
7. Redfish 冒烟脚本:openubmc_smoke_redfish.sh(简单说明)
Redfish 这块的设计和 IPMI 类似:
-
还是只做 GET;
-
分“必选/可选”;
-
用
curl+jq做关键字段检查。
目前主要覆盖这些资源(示意):
-
GET /redfish/v1/Systems/1:主板 FRU 信息; -
GET /redfish/v1/Systems/1/Processors&/Processors/1/Processors/2:CPU信息; -
GET /redfish/v1/Systems/1/Memory&/Memory/<slot>:内存信息; -
GET /redfish/v1/Chassis/1/PCIeDevices:PCIe 信息; -
GET /redfish/v1/Chassis/1/NetworkAdapters:网卡信息; -
GET /redfish/v1/Chassis/1/Thermal:风扇信息; -
GET /redfish/v1/Chassis/1/Drives&/Drives/<disk>:硬盘信息。
8. Jenkins 这边我怎么用
我的 Jenkins 流水线做的事情很简单:
-
固定在一台节点机(比如
node-16-127-smoke-test)跑; -
参数化:
-
BMC_IP,BMC_USER,BMC_PASS -
SMOKE_MODE(all / upgrade-only / read-only) -
GROUP_ID,ARTIFACT_ID
-
-
步骤:
-
Clone
smoke_test仓库; -
找到
openubmc_update_config.json和openubmc_smoke_runner.sh; -
拷贝 JSON →
openubmc_update_config.effective.json; -
用
jq写入 Jenkins 参数; -
直接执行:
./openubmc_smoke_runner.sh "${SMOKE_MODE}" openubmc_update_config.effective.json -
最后把每个 stage 的结果打印成一张 Markdown 表(方便在人肉看 build log 的时候快速浏览)。
-
9. 后面想做的事情:Robot Framework 集成
目前这套东西有个明显短板:
-
虽然日志、关键字检查都已经做了;
-
Jenkins 里也能看出 PASS/FAIL;
-
但测试报告还是非常“原始”:纯 log + 一张简单的 Markdown 表,没有用例级别的结构化结果。
后面有个打算(还没开始动手):
-
把升级 / IPMI / Redfish 每一条逻辑用例做成 Robot Framework 的 Test Case:
-
现有 Bash 脚本可以先保留;
-
由 Robot 去调用这些脚本或者拆成关键字关键调用;
-
-
让 Robot 帮忙生成:
-
HTML 报告(图形化查看每条用例的 PASS/FAIL);
-
JUnit XML(方便和现有的 CI/测试平台对接);
-
-
再慢慢把部分 Bash 逻辑往关键字里迁移,做到:
-
用例更容易增删;
-
日志更结构化;
-
报告更容易给非开发/非 BMC 同学使用。
-
现在这篇帖子里的东西,可以看作 Robot 版之前的 “原型实现 + 行为定稿”: 升级怎么做、冒烟覆盖什么、遇到奇怪的 FRU 行为怎么处理,这些先用 Bash 把经验踩完,后面再慢慢「框架化」。







