ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenBMC自动化测试框架实战:基于Robot Framework的固件测试与CI集成

OpenBMC自动化测试框架实战:基于Robot Framework的固件测试与CI集成 十几年前我们做服务器管理固件测试基本靠手点网页再拿ipmitool敲几条命令觉得能开机、能重启、能看传感器就交差了。等 OpenBMC 普及以后Redfish 接口一多测试矩阵拉出来能到上百条手工点的日子就彻底过不下去了。我去年接手一版 OpenBMC 固件验收就差一点被一个“看起来很简单”的回归用例坑到通宵。从那时起我把自动化测试框架系统地整理了一遍从接口封装、keyword 设计到数据驱动、报告输出一点点搭出了一套能落地、能复用、能直接对接 CI 的框架。这篇文章就是那套框架的完整记录适合正在做 OpenBMC 开发、固件测试或者刚从手工测试往自动化转的工程师参考。先说清楚这套框架不是那种“看起来啥都能测、跑起来全是环境问题”的演示项目而是按照 OpenBMC 的真实测试场景一步步拆出来的。核心思路就一句话把所有能通过 Redfish、IPMI、SOL、DBus 等带外通道验证的功能全部变成可重复执行的自动化用例再把执行结果变成 CI 能认得出来的报告。系列标题里的“1”意味着后面还会接着写多节点并发、压力测试和 CI 集成这篇先打底子。1. OpenBMC 自动化测试到底在测什么1.1 测试域拆解不光是 Redfish 接口调通很多团队一上来就写脚本调 Redfish调通了就以为自动化搞定了其实离“能验收固件”还差得很远。按照我拆过的测试矩阵OpenBMC 自动化测试至少应该覆盖下面七个域Redfish 接口一致性/redfish/v1/根入口是否可访问Systems、Chassis、Managers、UpdateService、SessionService这些标准资源是否存在返回的 JSON 是否符合 DMTF 规范。这一步能挡掉大量“该有的接口 404”的低级问题。IPMI 裸命令兼容性很多老运维还依赖 IPMIipmitool mc info、sdr、chassis status、raw 0x04 0x2D读 SEL 时间这些命令不能因为切了 OpenBMC 就废掉。带外基本管理功能开机、关机、重启、查询电源状态通过 Redfish 的ComputerSystem.Reset动作来验证。这里必须覆盖On、ForceOff、GracefulRestart、ForceRestart等不同动作。传感器与硬件监控从/redfish/v1/Chassis/chassis/Sensors拉取各传感器读数验证 CPU 温度、主板温度、风扇转速、电源功率这些关键项是否存在并且读数在合理范围内。固件升级与回滚这是最容易出问题的模块。上传镜像、轮询 Job 状态、校验版本号、升级失败后能否回滚到上一个 bank每一条都要测。网络与管理通道DHCP、静态 IP、VLAN、NCSI、SSH 登录、HTTPS 端口可达性这些决定了一台服务器能不能被带外管起来。安全与账号策略默认账号密码、密码强度策略、会话超时、TLS 证书、登录失败锁定机制安全项在自动化里很容易被忽略但真出了问题往往是 P0 级事故。这七个域基本覆盖了 OpenBMC 作为“带外管理入口”的核心价值。如果你的测试框架连这七类都没铺开那回归时大概率会漏问题。1.2 自动化边界哪些必须上机哪些别硬搞拆完测试域之后更要命的是搞清楚哪些适合自动化哪些硬自动化只会让自己痛苦。我总结了一个很粗暴的原则凡是能通过网络、命令行、接口触发的都优先自动化凡是必须靠物理操作、故障注入、改变硬件状态的先别硬塞进常规回归流程。能自动化的典型场景Redfish 请求包括 GET、POST、PATCH、DELETE验证状态码和响应体。IPMI 网络命令用ipmitool -H ip -U user -P pass批量执行。SOL 串口日志采集通过 SSH 通道拉取 console 输出验证启动日志中的关键节点。传感器数据周期性读取做阈值和趋势判断。固件升级通过 Redfish UpdateService 上传镜像并跟踪任务状态。不建议在常规自动化里硬啃的真实的硬件掉电、插拔电源、模拟内存故障这类场景自动化的投入产出比很低建议单独做硬件故障注入实验室。需要人工插拔网线或串口线的用例即使脚本能跑到“请插入设备”最后还是得有人站在机架前那你不如保留手工测试步骤。边界明确之后框架的投入方向就很清楚了先把带外能触达的管理通道全部自动化硬件故障类单独排期。2. 框架选型与整体架构2.1 为什么选 Robot Framework 而不是自研脚本OpenBMC 自动化框架的技术选型我见过三种路线Pytest、自研 Python 脚本、Robot Framework。三条路线我都试过最后定了 Robot Framework原因很实际。第一个原因是 OpenBMC 社区官方测试套件本身就是 Robot Framework 写的。GitHub 上 openbmc/openbmc-test-automation 仓库里有大量现成 keyword 和用例包括 Redfish 验证、IPMI 验证、固件升级、日志检查等直接拿来做二开能省掉很多重复造轮子的时间。社区踩过的坑、修过的兼容性问题都沉淀在那些用例里了比自己从零写脚本靠谱。第二个原因是 keyword 的可读性。Robot Framework 的用例本质上是一张行为表格非开发背景的测试工程师也能看懂。比如下面这个用例哪怕没写过代码的人也能猜出它做了什么Verify Power On Via Redfish [Documentation] 通过 Redfish 动作开机 [Tags] smoke power Create Redfish Session Set System Power State On Wait Until Keyword Succeeds 30s 5s Verify System Power State On Delete Redfish Session对比 Pytest逻辑当然也能实现但用例评审的时候让固件开发去看一段 Python 和看一段 keyword 的感受完全不同。keyword 写法在团队协作里的价值被很多人低估了。第三个原因是报告体系。Robot Framework 自带的output.xml、log.html、report.html基本是开箱即用。对接 Jenkins 只要加一个rebot步骤生成 JUnit XMLCI 的失败原因展示、历史趋势图都能直接出来省掉了自己写测试报告的功夫。2.2 框架分层resources、testcases、data、results框架我分成四层每一层职责单一互不越界resources/:关键字库以及外部 Python 库。里面放连接 BMC 的封装、Redfish 请求的封装、IPMI 命令的封装、SOL 日志解析的封装。testcases/:按模块组织的测试套件文件比如redfish_system.robot、ipmi_sensor.robot、firmware_update.robot。data/:测试数据文件包括 Redfish 请求的 JSON 模板、IPMI 命令矩阵、固件升级用的镜像路径、拓扑配置。results/:每次执行生成的报告、日志、抓取到的 BMC journal 和 dump 文件。这套分工看起来简单但能解决测试框架最常见的混乱关键字和用例耦合、测试数据散落在用例里、执行结果无归档。尤其是 data 目录独立出来之后新增一组测试数据不用改任何代码改个 JSON 文件就能扩展出几十条用例。目录结构大致长这样openbmc_atf/ ├── resources/ # 关键字与库 │ ├── common/ # 通用登录、连接、鉴权 │ ├── redfish/ # Redfish 接口封装 │ ├── ipmi/ # IPMI 命令封装 │ ├── sol/ # SOL 串口日志封装 │ └── utils/ # 字符串、时间、文件处理工具 ├── testcases/ # 测试套件 │ ├── suite_system.robot # 电源、Boot 管理 │ ├── suite_redfish.robot # Redfish 接口一致性 │ ├── suite_ipmi.robot # IPMI 兼容性 │ ├── suite_sensor.robot # 传感器数据 │ └── suite_update.robot # 固件升级与回滚 ├── data/ # 测试数据 │ ├── redfish_requests.json │ ├── ipmi_commands.json │ └── firmware/ # 固件镜像 └── results/ ├── report.html ├── log.html └── output.xml2.3 核心链路登录鉴权 业务操作 状态校验整个框架跑起来之后几乎所有用例都遵循同一条链路准备连接完成鉴权执行业务操作校验期待状态清理现场。这个链路听起来简单但每一环都有坑。登录鉴权这块OpenBMC 的 Redfish 默认用 Basic Auth 和 Session 两种方式Session 方式需要先POST /redfish/v1/SessionService/Sessions创建一个 Session拿到返回的X-Auth-Token后续请求头带上它。很多人图省事用 Basic Auth 跑测试结果把 BMC 的登录失败计数器触发得乱七八糟一跑回来全是登录锁定。后来我把 Session 管理封装成一个独立库所有用例统一走Create Openbmc Session这个 keyword用例执行完自动DELETE /redfish/v1/SessionService/Sessions/id彻底解决了 token 泄漏和会话堆积的问题。清理现场这件事也特别重要。比如你测固件升级用例执行完要检查 Image 上传临时目录是否被清理测 SOL 日志用例执行完要断开 SOL 会话不然下次用例连不上 console。我把清理操作统一放进[Teardown]保证就算用例失败Teardown 也会把 BMC 还原到一个干净的状态。3. 环境搭建VSCode Docker Robot Framework 的完整流程3.1 本地开发环境准备先说开发工具。我的主力环境是 Ubuntu 22.04 VSCode配合 Docker 跑测试执行环境。为什么用 Docker因为 Robot Framework 的依赖版本比较敏感尤其robotframework-requests和robotframework-sshlibrary的版本搭配稍有错位就会出现 ssl 告警或者 ssh 连接异常。用 Docker 把依赖锁死团队任何人拉下来就能跑不会出现“在我机器上好的到你那就不行”的问题。Docker 镜像的构建非常简单Dockerfile里装好 Python、pip、Robot Framework、依赖库就行FROM python:3.10-slim RUN pip install --upgrade pip \ pip install \ robotframework6.0.2 \ robotframework-requests0.9.6 \ robotframework-sshlibrary3.8.0 \ robotframework-jsonlibrary0.5 \ requests \ jsonschema \ pyyaml WORKDIR /tests CMD [robot, --outputdir, results, testcases]镜像构建完之后用docker run挂载本地代码目录直接执行测试套件docker build -t openbmc-atf . docker run --rm -v $(pwd):/tests openbmc-atf \ --variable BMC_IP:192.168.1.100 \ --variable BMC_USER:root \ --variable BMC_PASS:0penBmc \ --outputdir results \ testcases/suite_system.robot这里的BMC_IP、BMC_USER、BMC_PASS是全局变量在 Robot Framework 里用${BMC_IP}引用这样测试用例本身不写死具体 IP换一台机器直接改参数就行。3.2 没有真实板卡时用 QEMU 跑 OpenBMC 镜像真实板卡紧张的时候本地用 QEMU 跑一个 OpenBMC 镜像也能应付一部分自动化验证。openbmc 的 yocto 构建产物里有qemuarm、qemux86等镜像启动命令大致是qemu-system-arm \ -machine virt \ -m 1024 \ -drive fileopenbmc-qemuarm.nor,formatraw,ifmtd \ -net nic,modelvirtio \ -net user,hostfwdtcp::2222-:22,hostfwdtcp::2443-:443 \ -nographic启动之后宿主机访问https://127.0.0.1:2443就等价于访问 BMC 的 Redfish 服务SSH 端口映射到127.0.0.1:2222。框架里的BMC_IP就填127.0.0.1端口映射直接用起来。但是 QEMU 有个大坑传感器数据、风扇转速、电源状态这些硬件相关项在虚拟环境里经常会读到 0 或者没有读数针对传感器的断言不能照搬真实板卡的阈值。所以我的建议是QEMU 只用来验证流程正确性比如登录鉴权、接口 schema、Job 状态机这些业务逻辑真实硬件相关的用例必须挂到真实板卡上执行。3.3 网络与证书的坑TLS 自签证书处理OpenBMC 默认 HTTPS 是自签证书Robot Framework 的 RequestsLibrary 做请求时会因为证书校验失败直接报SSLError。处理办法有两个一是在创建 session 时设置verifyFalse二是用robotframework-requests的Create Session关键字里传入verifyFalse。Create Redfish Session [Arguments] ${base_url} ${username} ${password} ${headers} Create Dictionary Content-Typeapplication/json Create Session redfish ${base_url} verifyFalse headers${headers} POST On Session redfish /redfish/v1/SessionService/Sessions ... json{UserName: ${username}, Password: ${password}}但verifyFalse会有个隐藏问题很多库在底层会对 SSL 警告做拦截导致日志里看不到具体报错。解决办法是让 Requests 库把警告打出来在环境里设置PYTHONWARNINGSdefault这样排查问题的时候能看到完整的 ssl 告警记录而不是一个干巴巴的ConnectionError。4. 框架核心实现数据驱动、断言策略与报告输出4.1 数据驱动JSON 文件管理 Redfish 测试数据Redfish 接口测试最大的工作量不在写请求而在维护一堆返回体校验逻辑。我采用数据驱动方式用 JSON 文件维护接口路径和期望结果测试套件读取 JSON 后逐条执行。比如data/redfish_requests.json可以这样组织{ check_root: { method: GET, path: /redfish/v1/, expected_status: 200, expected_json_fields: [Id, Name, RedfishVersion, Systems] }, check_systems: { method: GET, path: /redfish/v1/Systems/system, expected_status: 200, expected_json_fields: [Manufacturer, Model, PowerState] }, check_update_service: { method: GET, path: /redfish/v1/UpdateService, expected_status: 200, expected_json_fields: [ServiceEnabled, FirmwareInventory] } }对应的 Robot Framework 套件核心就是一个循环Read Redfish API Cases [Arguments] ${json_file} ${data} Load JSON From File ${json_file} FOR ${case_name} IN {data.keys()} ${case} Get From Dictionary ${data} ${case_name} ${method} Get From Dictionary ${case} method ${path} Get From Dictionary ${case} path ${expected} Get From Dictionary ${case} expected_status ${resp} Run Keyword If ${method}GET GET On Session ... redfish ${path} expected_status${expected} Verify Response Fields ${resp.json()} ${case[expected_json_fields]} END这样一条 case 对应 JSON 里一条记录加一条测试只是加一行 JSON 的事不用改任何代码。新增接口 schema 变了也只改 JSON 里的字段列表维护成本很可控。4.2 字段级校验与 schema 校验怎么选做接口断言时很多人喜欢把整个返回体Should Be Equal对比这在固件测试里基本没法用因为 OpenBMC 的固件版本、传感器数值、时间戳这些字段没两次是完全一样的。我的做法是分层校验第一层状态码校验。这是最基本的200代表请求到达并被处理401代表鉴权失败404代表路径错误。第二层关键字段存在性校验。判断Id、Name、PowerState这些字段是否在返回体里防止接口被“成功”访问但返回一堆空壳。第三层字段类型与合理范围校验。比如PowerState必须是枚举值里的一种传感器读数必须在物理合理范围之内。schema 校验工具网上有一堆现成的比如jsonschema库但 OpenBMC 某些接口的 schema 本身就和 DMTF 标准有偏差强行用官方 schema 很容易误报。我更推荐维护一个精简版的“关键字段白名单”而不是全量 schema 校验。先保证核心字段稳定再逐步补严。4.3 报告输出从 Robot Framework 到 Jenkins JUnit XMLRobot Framework 默认生成output.xml和log.htmlCI 集成时需要能解析 JUnit XML。做法很简单装一个robotframework-junitlibrary或者直接用rebot命令转换rebot --outputdir results --xunit results/junit.xml results/output.xml转换出的junit.xml可以直接被 Jenkins 的 JUnit 插件消费失败用例会红、通过用例会绿历史趋势图也自动生成。对于测试团队趋势图比单次的通过率重要得多——如果你发现某套件的失败率连续三天上升那一定是固件质量在悄悄劣化尽早介入能省掉后面一堆排查的时间。log.html里我还会开启一个细节日志等级比如红帽 OpenBMC 社区推荐的--loglevel TRACE把 Redfish 请求和响应的完整日志都打进报告排查问题的时候直接翻报告就能看到是哪个接口返回异常不需要再登录 BMC 抓包。4.4 定时回归CI 里的框架编排框架落到 CI 之后我的编排思路是分三层冒烟层每次代码提交后跑20 分钟内完成只跑电源管理、Redfish 根接口、登录鉴权这些最核心的用例目的是快速发现“固件能不能起来”的大问题。回归层每晚全量跑覆盖 Redfish 一致性、IPMI 兼容性、传感器、日志、升级回滚一晚上能跑完。回归报告要归档作为固件发版的质量证据。压力层每周跑一次重点是会话反复创建/删除、传感器高频读取、重复升级用来发现内存泄漏、dbus 句柄泄漏这类慢性问题。Jenkins Pipeline 里冒烟层和回归层只需要改一下 Robot Framework 的测试套件参数压力层我会单独写一个 Python driver 来频繁调用关键字避免 Robot Framework 本身的调度开销成为瓶颈。5. 一个完整用例的落地固件升级与回滚5.1 场景设定与前置条件固件升级回滚是 OpenBMC 自动化里最有代表性也最复杂的场景。先交代一下 OpenBMC 的升级逻辑镜像上传之后BMC 会把它写入一个 bank然后通过重启 BMC 来切换启动 bank。如果新固件起不来BMC 会判断失败并自动回滚到上一个 bank。这个机制保证了远程升级不至于把服务器管理通道彻底搞挂。写自动化用例之前必须先收集当前固件信息和 bank 信息这样才能在升级后判断版本号对不对、bank 有没有切换。通过 Redfish 取版本信息的请求大致是curl -k -u root:0penBmc https://bmc_ip/redfish/v1/UpdateService/FirmwareInventory返回的 JSON 里会包含各个组件的固件版本。自动化用例里我会先把升级前的版本号和 bank 状态记录下来升级完再读取一次对比差异。5.2 升级流程的自动化实现升级动作本身通过 Redfish 的 UpdateService 接口触发。以下是核心流程的 Robot Framework 实现思路Firmware Upgrade And Rollback Test [Documentation] 验证通过 Redfish 升级并自动回滚 [Tags] update regression Create Redfish Session ${before_version} Get Current Firmware Version ${file_path} Get Firmware Image Path ${IMAGE_VERSION_TO_UPGRADE} Upload Image Via UpdateService ${file_path} Wait Until Job Completed 10min BMC Reboot And Wait For Ready 15min ${after_version} Get Current Firmware Version Run Keyword If ${after_version} ${before_version} ... Log Message: 固件未切换触发回滚验证通过 ... ELSE Fail 固件版本异常切换请检查升级策略 Delete Redfish Session这里有个非常关键的细节升级完成后 BMC 自己会重启所以测试脚本必须在 BMC 重启期间“等待并重试”Redfish 接口的可用性而不能直接断言接口断了就报失败。我用Wait Until Keyword Succeeds每隔几秒尝试一次连接直到 BMC 恢复响应为止。5.3 升级回滚的真实案例与踩坑我真实遇到过这么一个问题升级后 BMC 能起来但 Redfish 的 UpdateService 接口一直返回 500。当时第一反应是固件本身有问题后来拉开 BMC 的 journal 日志才发现是升级脚本在 Cleanup 阶段把/tmp/images下的镜像文件删得太早导致 Image Manager 在初始化时找不到文件而报错。这个 bug 只有自动化用例会踩到因为手工测试根本不会清理现场那么干净。这条经验后来被我们写成了固件测试的最佳实践升级完成之后至少等待 30 秒再清理镜像临时文件给 Image Manager 留出初始化时间。再比如 SOL 日志用例看起来很简单就是连上 console 然后重启结果第一次自动化跑就把 console 会话搞挂了。排查下来发现是用例结束时没有断开 SOL 会话下次用例再连就连不上。后来我在 SOL keyword 里增加了强制断连的 Teardown问题就再也没有出现过。这就是框架里“清理比操作更重要”这个原则的来源。6. 常见问题排查与调试技巧6.1 问题速查表把自动化框架跑起来之后大概率会遇到下面表格里的这些问题。我整理了一个速查表遇到直接对号入座。现象可能原因排查与解决Redfish 请求超时BMC 负载高 / 网络不通 / session 表打满检查 BMCjournalctl -f确认网络用curl手动确认接口可访问返回 401 UnauthorizedSession token 过期 / 密码错误重新创建 Session确认 BMC_USER/BMC_PASS 全局变量正确返回 404 路径不存在固件版本 Redfish schema 差异查询/redfish/v1/$metadata确认实际路径传感器读数为空QEMU 环境 / 传感器尚未就绪等待 30 秒后重新读取真实板卡上检查journalctl中 entity-manager 报错固件升级后 job 一直 Staged镜像损坏或升级前置条件不满足检查镜像 md5查看 BMC 侧/var/log或journalctl -fSSH 连接失败BMC sshd 未启动 / key 冲突检查 BMC 网络删除宿主机 known_hosts 后重试Robot Framework 用例日志没有输出--loglevel设置过低增加--loglevel TRACE6.2 BMC 侧日志收集journalctl 是最好用的工具自动化测试框架的排查很多时候问题不在测试代码而在 BMC 固件本身。所以我养成了一个习惯每次用例失败自动把 BMC 的 journal 日志打成一个文件归档到 results 目录。这一步能在排查时省掉大量时间。常用的 BMC 侧调试命令# 查看 BMC 完整日志 ssh rootbmc_ip journalctl -f # 只看 redfish 相关日志 ssh rootbmc_ip journalctl -u redfish # 查看 dbus 服务状态 ssh rootbmc_ip busctl tree # 查看实体管理器的传感器扫描 ssh rootbmc_ip journalctl -u entity-manager -f这些命令不一定每条都写在用例里但排查的时候先用它们定位问题边界能少走很多弯路。真实经验是自动化测试发现的问题一半以上不是测试用例写错而是固件存在隐藏 bug所以日志收集能力比用例数量更重要。6.3 一个具体的排查实录有一次回归跑出来 Redfish 的Systems/system接口偶发返回 500概率不高十次里有两三次。单看测试报告失败用例在同一个地方重启后又能过。这种“偶发失败”最坑人。我把失败时的 BMC journal 拉出来看到了一段关键报错redfish[1234]: ERROR - Failed to get resource: /redfish/v1/Systems/system顺藤摸瓜去看 dbus 调用发现是xyz.openbmc_project.Inventory.Manager偶发超时导致 Redfish 服务拿不到数据。这问题在真实板卡上偶尔出现QEMU 里几乎必现。原因是 QEMU 的磁盘 IO 慢Inventory Manager 初始化没有完成就接受了 Redfish 请求起了一个竞争条件。我把用例改成等待busctl确认服务就绪后再发请求偶发率立刻降为零。7. 框架扩展从单节点到多节点并发的下一步这套框架单节点跑起来没问题之后自然会遇到一个更大的需求多台 BMC 同时执行回归。多节点并发的核心不是把十几个进程放在同一台机器上乱跑而是要把“测试机的调度”和“BMC 的设备管理”解耦。我的做法是引入一个简单的topology.json配置里面维护所有待测 BMC 的 IP、账号、型号、固件版本、归属测试机。执行时测试机从配置里取一台 BMC跑完再取下一台。{ bmc_pool: [ { name: node01, ip: 192.168.1.101, user: root, password: 0penBmc, model: qemuarm, firmware: 2.12.0 }, { name: node02, ip: 192.168.1.102, user: root, password: 0penBmc, model: qemux86, firmware: 2.11.0 } ] }然后每个测试任务都从 pool 里申请一个空闲 BMC跑完释放。这个模型用 Python 的多进程很容易实现本质上只是把单节点框架套了一个“资源调度壳”。下一篇文章我打算专门聊这部分包括并发时 session 管理、BMC 侧资源竞争、以及多型号固件同时覆盖的策略。今天这篇先到框架本身的搭建能把这套基础框架跑通后面不管是接 CI 还是做并发都只是顺水推舟的事。
返回列表