版本级自动化冒烟测试设计分享

[经验分享] openUBMC 冒烟测试自动化:Nexus 拉包 + Redfish 升级 + IPMI/Redfish 检查

最近在做 openUBMC 机型的冒烟测试,希望把「自动拉包 → 自动升级 → 自动冒烟」这一整套流程固化下来,能挂到 Jenkins 上每天跑一跑,顺便也减少手动点命令的体力活。

这篇发出来主要是记录一下自己的设计思路,给有类似需求的做个参考模板。实现比较朴素,完全基于:

  • bash

  • curl

  • jq

  • ipmitool

没有用到框架,暂时也没有做成通用工具,纯粹是解决我自己这条机型日常 QA 的需求。


1. 背景 & 目标

我当时想解决的是几个具体问题:

  1. 固件升级流程太手动

    • 从 Nexus/Maven 仓库手动找包;

    • 下载 ZIP → 解压 → 找 HPM;

    • 拿着 HPM 再手工写 Redfish SimpleUpdate 请求;

    • 升完再手动查 Task/查版本。

  2. 冒烟测试没有统一基线

    • 每个人心里都有一套“我习惯看哪些 ipmitool / Redfish”的 checklist;

    • 但是没脚本,也没办法挂 CI。

  3. 希望对 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(思路)

升级脚本主要做这几件事(伪流程):

  1. 从 Maven 仓库拉 maven-metadata.xml

    • 过滤奇怪的版本串(比如 66.82.73.68 这种中间误上传的);

    • 选出符合版本规则的最新版本(例如 55.05.01.04.B001)。

  2. 读取本地 state_dir 里上一次记录的版本:

    • 如果当前版本 == 最新版本 → 直接退出,并约定 exit=10

    • 如果版本落后 → 继续升级流程。

  3. 构造 ZIP URL 并下载:

     http://<base_url>/repository/<repo>/
       <group_id path>/<artifact_id>/<version>/<artifact_id>-<version>.zip
    
  4. 解压到统一目录:

     /home/smoke_test/bmc/hpm/<artifact_id>-<version>/
    

    然后在目录里自动搜索 *.hpm,拿到 HPM 绝对路径。

  5. 使用 Redfish UpdateService.SimpleUpdate 触发升级:

    • 支持本地 /tmp/xxx.hpm 或远程 SCP/HTTPS 等;

    • 在例子里用的是 Redfish + 远程镜像服务器。

  6. 轮询 Task 进度:

     GET /redfish/v1/TaskService/Tasks/1
       - TaskState
       - Messages.Message
       - OEM TaskPercentage / PercentComplete
    
  7. 任务完成后,等待 BMC reset & 再次上线:

    • 一直 ping,直到 BMC Reply;

    • 再用 Redfish 查询 /UpdateService/FirmwareInventory/ActiveBMC,比对 Version 与预期版本。

  8. 如果一切正常,写入 state_dir/current_version,下次再跑就知道「我已经是最新版本」了。

退出码约定(这个很关键,给总管脚本和 Jenkins 用):

  • 0:本次发生了升级,且版本校验通过;

  • 10:仓库版本和当前 BMC 一致,本次没升级;

  • 其它非 0:过程出错(网络、Task 异常、版本不一致等)。


5. 总管脚本:openubmc_smoke_runner.sh

总管主要负责两个事情:

  1. 检查子脚本是否存在 & 可执行

  2. 根据 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

这一块我重点做了几个小优化:

  1. 统一封装了一个带重试的执行器

    • 默认每条命令重试 3 次,每次失败间隔 2s;

    • 命令之间自动 sleep 0.5s,防止 IPMI 打太快。

  2. 所有用例分“必选/可选”两类

    • 必选(mandatory):失败会把 FAILED=1,脚本 exit 1;

    • 可选(optional):失败只打 WARN-OPTIONAL,不影响整体 exit code。

  3. 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 流水线做的事情很简单:

  1. 固定在一台节点机(比如 node-16-127-smoke-test)跑;

  2. 参数化:

    • BMC_IP, BMC_USER, BMC_PASS

    • SMOKE_MODE(all / upgrade-only / read-only)

    • GROUP_ID, ARTIFACT_ID

  3. 步骤:

    • Clone smoke_test 仓库;

    • 找到 openubmc_update_config.jsonopenubmc_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 表,没有用例级别的结构化结果。

后面有个打算(还没开始动手):

  1. 把升级 / IPMI / Redfish 每一条逻辑用例做成 Robot Framework 的 Test Case:

    • 现有 Bash 脚本可以先保留;

    • 由 Robot 去调用这些脚本或者拆成关键字关键调用;

  2. 让 Robot 帮忙生成:

    • HTML 报告(图形化查看每条用例的 PASS/FAIL);

    • JUnit XML(方便和现有的 CI/测试平台对接);

  3. 再慢慢把部分 Bash 逻辑往关键字里迁移,做到:

    • 用例更容易增删;

    • 日志更结构化;

    • 报告更容易给非开发/非 BMC 同学使用。

现在这篇帖子里的东西,可以看作 Robot 版之前的 “原型实现 + 行为定稿”: 升级怎么做、冒烟覆盖什么、遇到奇怪的 FRU 行为怎么处理,这些先用 Bash 把经验踩完,后面再慢慢「框架化」。

10. 附件

1 个赞