本文记录在 Ubuntu Server 测试控制台上搭建 Redfish 事件接收服务(HTTPS + Flask)的完整过程,用于接收 BMC 主动推送的 Redfish 事件,并自动解析测试用例要求的 16 个关键字段。
一、背景
在 Redfish 接口测试中,BMC 会通过订阅(Subscription)机制,将事件主动 POST 到我们指定的 Destination 地址。因此需要在一台"测试控制台"上搭建一个 HTTPS 事件接收服务,用来:
-
接收 BMC 推送的 Redfish 事件;
-
将事件原文写入日志文件,便于事后分析;
-
实时解析并打印测试用例要求的 16 个关键字段。
关键要求 :BMC 要求事件接收地址必须是合法的 HTTPS 地址,所以服务必须启用 SSL 证书(测试环境使用自签名证书即可)。
二、环境说明
| 项目 | 说明 |
|---|---|
| 操作系统 | Ubuntu Server 20.04 / 22.04 / 24.04 LTS |
| 测试控制台 IP | 192.168.2.100(示例,请按实际替换) |
| Python | 系统预装 Python 3 |
| 依赖 | Flask、OpenSSL |
| 监听端口 | 8443 |
目录规划
在测试控制台的家目录下准备以下文件(本文以 ~/redfish-event-listener/ 为例):
redfish-event-listener/
├── event_listener.py # 事件接收服务脚本
├── cert.pem # 自签名证书
├── server.key # 证书私钥
└── logs/ # 日志目录(脚本自动创建)
三、安装 Python 环境
Ubuntu Server 默认已预装 Python 3,先确认版本:
python3 --version
预期输出:
Python 3.10.x # 或 3.12.x,视 Ubuntu 版本而定
如未安装(极少见),执行:
sudo apt update
sudo apt install -y python3 python3-pip python3-venv
四、安装 Flask 和 OpenSSL 依赖
方案 A:使用虚拟环境(推荐,避免污染系统 Python)
Ubuntu 22.04 及之后版本因 PEP 668 限制,直接用 pip3 install 安装系统级包会报错 externally-managed-environment,推荐用虚拟环境:
cd ~/redfish-event-listener
python3 -m venv venv
source venv/bin/activate
pip install flask
之后每次启动服务前,需要先 source venv/bin/activate 激活虚拟环境。
方案 B:使用系统包管理器安装
sudo apt update
sudo apt install -y python3-flask openssl
这种方式不需要虚拟环境,系统会统一管理 Flask 的版本。
方案 C:强制 pip 安装(不推荐,仅供快速试验)
pip3 install flask --break-system-packages
⚠️ 使用
--break-system-packages会绕过系统包管理保护,建议只在临时验证时使用。
五、生成自签名 SSL 证书
BMC 要求事件接收地址为合法的 HTTPS 地址。在测试控制台上生成自签名证书:
mkdir -p ~/redfish-event-listener
cd ~/redfish-event-listener
openssl req -x509 -newkey rsa:2048 \
-keyout server.key \
-out cert.pem \
-days 365 \
-nodes \
-subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Test/CN=192.168.2.100"
⚠️ 注意 :其中
CN必须设置为测试控制台的真实 IP 地址 。上例假设控制台 IP 为192.168.2.100,请根据实际情况替换。
生成完成后,cert.pem 与 server.key 应与 event_listener.py 位于同一目录下。
六、编写事件接收服务脚本
创建 event_listener.py:
bash
#!/usr/bin/env python3
# Redfish 事件接收服务
# - 监听 HTTPS 端口,接收 BMC 推送的 Redfish 事件
# - 将事件写入 logs/events.log,并打印到控制台
# - 自动解析测试用例要求的 16 个关键字段
import os
import ssl
import json
import datetime
from flask import Flask, request
# 基础路径(基于脚本所在目录)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CERT_FILE = os.path.join(BASE_DIR, "cert.pem")
KEY_FILE = os.path.join(BASE_DIR, "server.key")
LOG_DIR = os.path.join(BASE_DIR, "logs")
LOG_FILE = os.path.join(LOG_DIR, "events.log")
os.makedirs(LOG_DIR, exist_ok=True)
app = Flask(__name__)
def log_event(event_data: dict):
"""将事件写入日志文件并打印到控制台"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
separator = "=" * 80
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(f"\n{separator}\n")
f.write(f"[接收时间] {timestamp}\n")
f.write(json.dumps(event_data, indent=2, ensure_ascii=False))
f.write(f"\n{separator}\n")
print(f"\n{separator}")
print(f"[接收时间] {timestamp}")
print(json.dumps(event_data, indent=2, ensure_ascii=False))
print(separator)
def extract_event_fields(event_data: dict):
"""提取并展示测试用例要求的 16 个关键字段"""
print("\n-- 关键字段验证 --")
events = event_data.get("Events", [])
if not events:
print("[警告] 事件体中没有 Events 数组")
return
for idx, event in enumerate(events):
print(f"\n[事件 #{idx + 1}]")
print(f" EventType : {event.get('EventType', 'N/A')}")
print(f" EventId : {event.get('EventId', 'N/A')}")
print(f" EventTimestamp : {event.get('EventTimestamp', 'N/A')}")
print(f" Severity : {event.get('Severity', 'N/A')}")
print(f" Message : {event.get('Message', 'N/A')}")
oem = event.get("Oem", {}).get("Public", {})
print(f" Oem.Public.alarmStatus : {oem.get('alarmStatus', 'N/A')}")
print(f" Oem.Public.specificProblemID : {oem.get('specificProblemID', 'N/A')}")
print(f" Oem.Public.specificProblem : {oem.get('specificProblem', 'N/A')}")
print(f" Oem.Public.neUID : {oem.get('neUID', 'N/A')}")
print(f" Oem.Public.neName : {oem.get('neName', 'N/A')}")
print(f" Oem.Public.neType : {oem.get('neType', 'N/A')}")
print(f" Oem.Public.objectUID : {oem.get('objectUID', 'N/A')}")
print(f" Oem.Public.objectName : {oem.get('objectName', 'N/A')}")
print(f" Oem.Public.objectType : {oem.get('objectType', 'N/A')}")
print(f" Oem.Public.locationInfo : {oem.get('locationInfo', 'N/A')}")
print(f" Oem.Public.Addinfo : {oem.get('Addinfo', 'N/A')}")
@app.route("/event-receiver", methods=["POST"])
def receive_event():
"""接收 BMC 推送的 Redfish 事件"""
try:
event_data = request.get_json(force=True)
if event_data is None:
event_data = {}
log_event(event_data)
extract_event_fields(event_data)
# 按 Redfish 规范返回 204 No Content
return "", 204
except Exception as e:
print(f"[错误] 解析事件失败: {e}")
return "", 400
def main():
# 检查证书文件是否存在
if not os.path.exists(CERT_FILE):
print(f"[错误] 找不到证书文件: {CERT_FILE}")
print("请确保证书 cert.pem 与 event_listener.py 在同一目录下。")
return
if not os.path.exists(KEY_FILE):
print(f"[错误] 找不到私钥文件: {KEY_FILE}")
print("请确保私钥 server.key 与 event_listener.py 在同一目录下。")
return
# 配置 SSL
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE)
print("=" * 60)
print("Redfish 事件接收服务已启动")
print("监听地址: https://0.0.0.0:8443/event-receiver")
print(f"证书文件: {CERT_FILE}")
print(f"私钥文件: {KEY_FILE}")
print(f"日志文件: {LOG_FILE}")
print("=" * 60)
app.run(
host="0.0.0.0",
port=8443,
ssl_context=context,
debug=False,
threaded=True
)
if __name__ == "__main__":
main()
脚本要点说明
-
路径自适配:所有文件路径均基于脚本所在目录计算,脚本放哪都能跑。
-
日志自动落盘 :
logs/目录不存在时自动创建,事件以====分隔追加写入events.log。 -
返回 204 :Redfish 事件推送要求服务端返回
204 No Content,脚本已按要求处理。 -
多线程 :
threaded=True,避免 BMC 并发推送时阻塞。
七、启动事件接收服务
cd ~/redfish-event-listener
# 如果使用虚拟环境(方案 A),先激活
source venv/bin/activate
python3 event_listener.py
服务启动后输出:
============================================================
Redfish 事件接收服务已启动
监听地址: https://0.0.0.0:8443/event-receiver
证书文件: /home/ubuntu/redfish-event-listener/cert.pem
私钥文件: /home/ubuntu/redfish-event-listener/server.key
日志文件: /home/ubuntu/redfish-event-listener/logs/events.log
============================================================
八、开放防火墙端口
Ubuntu Server 默认使用 UFW 管理防火墙(很多镜像默认状态为 inactive)。先查看状态:
sudo ufw status
如状态为 inactive,需要先启用(注意:启用时务必先放行 SSH,避免把自己锁在外面):
sudo ufw allow ssh
sudo ufw enable
然后放行 8443 端口:
sudo ufw allow 8443/tcp
sudo ufw reload
再次确认:
sudo ufw status
应能看到 8443/tcp ALLOW 的规则。
如服务器用的是
iptables或云厂商安全组,也需要在对应位置放行 8443/tcp。
九、本地验证服务连通性
在测试控制台上另开一个终端,用 curl 模拟推送一条事件:
curl -k -X POST https://127.0.0.1:8443/event-receiver \
-H "Content-Type: application/json" \
-d '{"Events": [{"EventType": "Alert", "Message": "test"}]}'
预期结果:
-
事件接收服务终端打印出测试事件内容与关键字段验证结果;
-
curl返回 HTTP 204; -
logs/events.log中新增一条记录。

十、关键提醒(避坑指南)
-
Destination 必须是真实可达的地址
在 BMC 上创建事件订阅时,
Destination必须填测试控制台真实可达的 HTTPS 地址,例如:https://192.168.2.100:8443/event-receiverIP 与端口需和本服务一致,且证书
CN要与之对应。 -
防火墙必须放行端口
Ubuntu 默认 UFW inactive,很多人忘了这一步;若启用了云服务器,还要在云厂商安全组同步放行。BMC 是主动连接方,控制台侧 8443 不通则订阅永远收不到事件。
-
PEP 668 限制(Ubuntu 22.04+)
直接
pip3 install flask会报externally-managed-environment错误,请使用本文提供的虚拟环境或apt install python3-flask方案。 -
证书与脚本必须同目录
cert.pem、server.key与event_listener.py放同一目录,否则脚本启动时会直接报错退出。 -
端口冲突
若 8443 已被占用,可修改脚本中的
port,同时同步修改订阅Destination和防火墙规则。 -
自签名证书的 CN 与 SAN
现代 TLS 客户端校验更严格,若 BMC 对证书校验较严,可在生成证书时额外指定
subjectAltName(SAN)为控制台 IP,兼容性更好。例如:openssl req -x509 -newkey rsa:2048 \ -keyout server.key -out cert.pem -days 365 -nodes \ -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Test/CN=192.168.2.100" \ -addext "subjectAltName=IP:192.168.2.100"
十一、总结
整个流程可以概括为 5 步:
- 确认 Python3 环境 → 2. 安装 Flask/OpenSSL → 3. 生成自签名证书 → 4. 部署事件接收脚本 → 5. 放行防火墙并用 curl 自测。
在 Ubuntu Server 上特别需要注意 PEP 668 安装限制 和 UFW 默认未启用 这两个坑,其余步骤与常规 Linux 发行版基本一致。验证通过后,即可在 BMC 侧创建 EventDestination 订阅,把事件推送到本服务,开始联调 Redfish 告警上报功能。
如果这篇文章对你有帮助,欢迎点赞收藏 👍,有问题也欢迎在评论区交流~