ARTICLE DETAIL

资讯详情

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

OpenBMC自动化测试框架搭建实战:从BMC连接到用例设计

OpenBMC自动化测试框架搭建实战:从BMC连接到用例设计 对于做过服务器运维或者搞过设备管理的人来说BMC这个词应该不陌生。简单说它就是服务器的“带外管家”哪怕操作系统挂了、机器蓝屏了只要通电和网络正常你就能通过BMC远程开机、关机、看传感器温度、抓日志。而OpenBMC是这套固件的开源实现由OpenBMC社区推动IBM、Intel、Google、Facebook这些大厂都有深度参与。我在实际项目中接触OpenBMC已经有几年时间这次把我的自动化测试框架搭建过程整理出来分几篇聊聊这篇是系列第一篇重点讲框架的整体思路和落地细节。1. 测试框架的整体设计思路1.1 先搞清楚OpenBMC到底要测什么很多人一听到“自动化测试框架”第一反应就是“用代码代替手工点按钮”。但在OpenBMC这个场景里问题要复杂得多。我们要测的对象不是一个普通软件而是一套跑在服务器主板上的嵌入式Linux系统它对外提供的管理能力非常多样。先列一下OpenBMC对外的主要管理通道Redfish REST API这是目前最主流的接口方式基于HTTPS加JSON标准的RESTful风格支持查询硬件信息、设置电源状态、获取传感器数据、管理用户等。IPMI命令老牌协议通过ipmitool这类工具操作很多运维脚本还在依赖它。Web界面OpenBMC自带一套Web管理界面用户登录后可以可视化操作。KVM功能远程控制台可以远程看服务器画面和操作键盘鼠标。如果靠手工去测这些东西每次版本发布都要把一大套测试用例重跑一遍人力成本高得离谱而且特别容易漏掉边角场景。所以自动化测试框架的出发点很直接用程序批量执行这些通道的验证工作跑完出一份报告告诉我们哪些功能是好的哪些回归坏了。1.2 为什么社区和厂商都很重视这套自动化体系做过硬件固件开发的人都知道固件项目有个很头疼的特点对外部环境的依赖特别大。同样一套OpenBMC代码跑在不同厂商的主板上行为可能完全不一样。因为硬件配置不同设备树不一样传感器挂的I2C地址不同FRU信息存储的位置不同甚至GPIO的电平极性都可能不同。这就是热词里“Openbmc硬件移植”反复被提到的原因。在硬件移植的背景下自动化测试框架的价值就更突出了。它的核心目标不是“测出新功能”而是“守住老功能”。比如你这次改了一个I2C驱动的初始化顺序理论上不影响传感器读取但万一呢如果没有自动化回归你可能要花半天时间手工逐条验证传感器数据而自动化框架可以在十几分钟内把几十个传感器全部刷一遍数据差异一目了然。另外OpenBMC的测试还有个特点是“接口多、场景重复”。电源开关机、重启、恢复出厂设置、用户增删改查、日志导出这些操作在开发阶段、验证阶段、生产测试阶段都会反复执行。把这类操作全部沉淀成脚本本质上是把公司最贵的资源——测试工程师的时间从重复劳动里解放出来。我在设计框架时给自己定了几个原则第一接口优先。优先通过Redfish REST API进行测试因为这是目前OpenBMC功能最完整的接口层而且Redfish是DMTF标准未来可扩展性最好。第二用例数据分离。测试脚本和测试数据分开维护换一个机型的时候只改配置文件和期望值不用改代码。第三断点可持续。一个用例失败后不能把后续用例全带崩框架要有隔离机制和日志追溯能力。2. 核心测试对象拆解2.1 电源管理链路从指令下发到电源状态切换电源管理是BMC最核心的功能没有之一。服务器宕机后管理员能不能远程把它拉起来全靠这条链路是否稳定。我拆解的测试点包括冷启动系统处于S5状态关机时通过Redfish发送Power On指令确认主机能正常上电并确认POST上电自检流程能走完。硬重启系统运行时发送Graceful Restart再发送Force Restart验证两种重启方式的差别。强制关机模拟操作系统卡死的情况发送Force Off指令验证BMC能直接把电源切断。AC断电恢复拔掉服务器电源线再插上验证BMC的AC Restore策略Always Off、Always On、Restore Previous State是否正确执行。在自动化实现上这些用例有个共通的难点如何确认电源状态切换真的成功了。你不能只看API返回值因为API返回成功只代表指令被BMC接受了不代表电源真的动作了。所以我在用例里加了双重验证先查询Redfish的Power State字段再通过IPMI的ipmitool power status查一次两者结果必须一致才算通过。2.2 传感器与告警读数准确性和阈值报警传感器是BMC的另一条命脉。服务器里的温度传感器、电压传感器、风扇转速传感器全部通过I2C或SMBus挂到BMC上BMC定时采样并对外提供查询接口。自动化测试里对传感器主要有两类验证一类是读取验证。传感器数量多常见的平台一个BMC下面就有几十个传感器手动去查非常痛苦。自动化脚本会把所有传感器的名称、读数、单位、状态一次性拉出来和基线数据对比同时做基础合理性检查温度传感器读数为0或超过芯片规格的最大值时直接认为异常。另一类是阈值验证。每个传感器都有Upper Critical、Lower Critical、Upper Non-Recoverable等阈值阈值配置错误会导致误报警或者漏报警。用自动化可以主动设置一个越界值来验证报警逻辑但对实机环境来说直接修改物理环境不现实所以更常见的做法是校验阈值配置本身是否和产品规格书一致。2.3 事件日志与故障上报BMC会记录服务器生命周期里产生的所有重要事件比如掉电、温度过高、电压异常、风扇故障等记录在IPMI SELSystem Event Log里同时OpenBMC也会通过Redfish LogService提供日志查询。自动化测试对事件日志的验证点包括日志能否正常生成。比如通过模拟“热插拔一个硬盘”或者“按下前面板按钮”这类动作确认BMC是否产生了对应的SEL事件。日志能否正常清除。Clear Log功能在Redfish里有标准接口用例会先记录当前日志条数执行清除后再确认条数归零。日志在重启后是否持久化。BMC重启后日志不能丢这也是常见回归点很多固件改版后日志存储路径变了导致重启后日志丢失。2.4 用户权限与安全管理OpenBMC支持多用户、多角色管理角色分为Administrator、Operator、ReadOnly等。自动化测试需要验证不同角色创建后能否正常登录。ReadOnly用户是否真正只能读不能写比如尝试调用PATCH接口修改配置时应该被拒绝。密码过期策略和登录失败锁定策略是否生效。这一块经常被忽略但恰恰是产品交付时客户最关注的点。安全测试如果不自动化只靠手工去一遍遍试错效率低不说还容易遗漏权限组合。3. 实操从零搭建一个可复用的测试入口3.1 环境准备与依赖选择我建议直接在测试机上用Python 3.8以上的环境配合pytest作为测试执行框架。OpenBMC对外提供的是REST API所以核心依赖只需要两个requests用来发HTTP请求pytest用来组织用例和执行。再推荐一个工具redfish库它是DMTF官方出的Python包专门封装了Redfish标准操作可以减少很多重复代码。不过我实际用下来发现这个库封装程度偏高出了问题反而不好排查所以我更倾向于直接用requests封装一层自己的客户端。安装依赖pip install pytest requests3.2 封装BMC连接层这一步非常关键。如果每个用例里都直接写requests.post(url, dataxxx)代码会变得非常冗余且难以维护。我习惯的做法是封装一个BMCClient类把登录认证、GET/POST请求、异常处理都放进去。import requests import json import warnings requests.packages.urllib3.disable_warnings() class BMCClient: def __init__(self, host, username, password): self.base_url fhttps://{host} self.session requests.Session() self.session.auth (username, password) self.session.verify False # Redfish标准要求客户端声明自己的版本信息 self.session.headers.update({ Content-Type: application/json, Accept: application/json }) def get(self, path): url self.base_url path resp self.session.get(url, timeout10) resp.raise_for_status() return resp.json() def post(self, path, bodyNone): url self.base_url path resp self.session.post(url, jsonbody or {}, timeout10) return resp def patch(self, path, bodyNone): url self.base_url path resp self.session.patch(url, jsonbody or {}, timeout10) return resp def delete(self, path): url self.base_url path resp self.session.delete(url, timeout10) return resp def close(self): self.session.close()有几个细节值得说明。第一verifyFalse是因为OpenBMC默认自签名证书会导致SSL验证失败测试环境直接关掉校验。但生产环境不能这么干应把BMC的证书导入信任链。第二超时时间一定要设置不设置的话遇到BMC卡死用例会一直挂着整个框架都被拖死。第三raise_for_status()不是所有接口都该调用因为有些接口在业务层面会返回非200的状态码比如权限不足返回403这是预期行为不能把异常直接抛出所以在POST和PATCH方法里我选择把原始response对象返回让用例自己判断。3.3 用pytest写出第一个可执行的用例连接层封装好之后写用例就很顺了。以“查看系统电源状态”为例import pytest pytest.fixture(scopesession) def bmc(): client BMCClient(192.168.1.100, root, 0penBmc) yield client client.close() def test_get_power_state(bmc): resp bmc.get(/redfish/v1/Systems/system) assert resp.status_code 200 data resp.json() assert PowerState in data assert data[PowerState] in [On, Off]这里我用了fixture的session级作用域确保整个测试过程中只创建一次连接BMC的session认证是复用token的频繁重建连接不仅慢还可能触发BMC侧的登录失败锁定策略。但这只是最基础的例子实际项目中我们还要处理一个重要的点用例依赖。比如“执行强制重启”这个用例前提是机器当时处于开机状态。如果前一个用例把机器关机了这个用例就会失败。处理方式有两种用pytest的dependency插件显式声明依赖关系。在每个用例的前置条件里自己检查状态不满足就先通过API把状态调整到需要的状态。我更倾向于第二种。因为依赖插件会让用例的执行顺序变得很僵化不利于后续做用例随机打散执行。每个用例应该尽可能自包含它不关心上一个用例做了什么只负责把自己的前置条件准备好。3.4 把用例改成数据驱动的公共用例集当测试的机器从一台扩展到多台或者从同一个机型扩展到不同机型时你会发现很多用例逻辑是相同的只是IP地址、用户名密码、传感器期望值不同。这时候就体现出“数据驱动”的好处了。我的做法是维护一个config.yaml文件bmc: host: 192.168.1.100 username: root password: 0penBmc sensors: - name: CPU1_TEMP expected_min: 20 expected_max: 90 - name: PCH_TEMP expected_min: 20 expected_max: 80 - name: SYS_FAN1 expected_min: 3000 expected_max: 15000然后写一个通用的传感器校验用例import yaml import pytest pytest.fixture(scopesession) def config(): with open(config.yaml, r) as f: return yaml.safe_load(f) def get_sensor_reading(client, sensor_name): resp client.get(/redfish/v1/Chassis/chassis/Sensors) data resp.json() for sensor in data.get(Sensors, []): if sensor[Name] sensor_name: return sensor[Reading] raise ValueError(fSensor {sensor_name} not found) pytest.mark.parametrize(sensor_name, [CPU1_TEMP, PCH_TEMP, SYS_FAN1]) def test_sensor_reading(bmc, config, sensor_name): expected next( item for item in config[sensors] if item[name] sensor_name ) reading get_sensor_reading(bmc, sensor_name) assert expected[expected_min] reading expected[expected_max]这套体系建好之后新来一个项目你只需要把config.yaml替换一下就知道这台新机器的传感器配置是否符合预期。不同BMC固件版本之间的传感器基线差异也能通过配置一目了然地对比出来。4. 常见问题与排错实录4.1 重启之后连接失效BMC在重启、升级固件或者网络配置变化之后之前建立的HTTPS session会失效。如果你用的是长连接复用这时候再发请求会直接报连接错误或401认证失败。我的处理方式是在客户端里加一个重试机制import time import requests class BMCClient: def __init__(self, host, username, password, retries3): self.retries retries # ... 省略其他初始化 def get(self, path): for attempt in range(self.retries): try: url self.base_url path resp self.session.get(url, timeout10) if resp.status_code 401: # 重新认证并重试 self.session.auth (self.username, self.password) resp self.session.get(url, timeout10) return resp except requests.exceptions.ConnectionError: if attempt self.retries - 1: raise time.sleep(5)这个重试机制在跑长时间回归测试时特别有用因为BMC可能因为看门狗超时或者其他原因自动重启重试能让整个测试任务不被一个偶发的连接问题打断。日志里这一条也要打出来方便排查。4.2 传感器数据不规则波动传感器读数的自动化测试经常会遇到“偶发失败”的问题上一次跑全部通过这一次某个温度传感器读数明显偏离了正常范围。定位之后发现不是BMC固件问题而是那个传感器本身对应的物理硬件确实温度波动大比如CPU在跑负载的时候温度本来就忽高忽低。自动化测试最怕的不是“必现失败”而是“偶发失败”。为了减少误报我在传感器用例里加入了“多次采样取平均”的逻辑def get_sensor_reading_stable(client, sensor_name, samples5, interval1): readings [] for _ in range(samples): reading get_sensor_reading(client, sensor_name) readings.append(reading) time.sleep(interval) return sum(readings) / len(readings)同时阈值判断时给一个容差范围比如期望最大值是90但允许5%的浮动只在超过浮动范围时判定失败。这样一来用例的稳定性高了很多误报率明显下降。4.3 用例等待时间固定导致偶发现象这是刚做自动化时最容易犯的错。比如“发完关机指令后等待30秒然后查询电源状态”看起来很合理对吧实际上一块硬盘有问题的主板POST时间可能比正常主板长一倍30秒根本不够。BMC在POST完成前对Redfish查询接口的响应内容是不同的有的版本直接返回Service Unavailable有的版本返回空数据。我的处理方式是改成“轮询等待直到满足条件或超时”def wait_for_power_state(client, expected_state, timeout120): start time.time() while time.time() - start timeout: resp client.get(/redfish/v1/Systems/system) if resp.status_code 200: data resp.json() if data.get(PowerState) expected_state: return True time.sleep(5) return False这个轮询方法整个框架里到处都在用算是最基础的一个工具函数。时间参数根据需要调整一般电源状态切换我设置120秒BIOS自检等待设置300秒。4.4 日志轮转导致事件丢失有段时间我们的自动化用例偶尔会报错检查SEL事件日志时刚产生的事件查不到。一开始以为是BMC日志存储的bug后来排查发现是OpenBMC的日志系统有轮转机制当日志量达到上限时会删除旧日志腾空间。而我们在跑用例之前没有清理旧日志导致日志量积累到阈值新事件写入时直接把之前的日志覆盖了。所以事件日志类用例的规范动作是测试开始前先清空日志做完操作后立刻查询日志确认新事件在列表里。清空操作在Redfish里的路径是/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLogPOST这个action即可。这个“清理-动作-断言”三步曲在BMC自动化测试里属于基本范式。5. 框架扩展与可持续演进目前这套框架在实机环境上已经跑了快一年累计执行了几千次用例帮我们抓到了不少回归问题。最典型的一次是某个BMC版本将传感器数据上报频率从1秒改成了5秒完全不影响正常功能但我们的传感器用例因为“多次采样取平均”逻辑敏锐地发现了数据更新频率的变化——读数长时间没变化平均值算出来和单次采样完全一致用例就报警了。这种“不是bug但行为有变化”的提示对固件质量管控非常有用。如果你也想在团队里落地类似的框架我建议从最小集开始先封装BMC连接层写电源管理和传感器读取两类用例跑通之后再逐步加日志查询、用户管理、固件升级、BIOS配置等模块。不要一开始就追求大而全那会让框架本身变成维护负担。这套框架后续还可以扩展的方向包括对接CI流水线实现代码提交后自动触发回归、引入Allure报告生成可视化的执行结果、把比对基线从固定配置文件升级为数据库存历史版本便于追踪趋势。这些都是后话等这个系列继续更新时我再展开聊。我在实际使用中的体会是自动化测试框架本身并不难写真正难的是对被测系统的理解深度。框架只是把你的经验固化成代码你对OpenBMC的行为逻辑理解得越透写出来的用例就越有效。遇到问题不要急着改代码先想清楚BMC在这个场景下“应该”怎么表现再去看测试结果和你预期的差距在哪里。这个思辨过程才是自动化测试框架真正值钱的地方。
返回列表