argus-netconf-exporter/tests/test_ruijie_live_netconf.py

118 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
"""
与真实 Ruijie 设备联调的“活体”测试用例。
说明:
- 连接参数通过环境变量注入(你已经在 .env 中配置):
- RUIJIE_NETCONF_HOST
- RUIJIE_NETCONF_PORT
- RUIJIE_NETCONF_USER
- RUIJIE_NETCONF_PASSWORD
默认行为:
- 若未设置 RUIJIE_NETCONF_PASSWORD或无法建立到指定 host:port 的 TCP 连接,
则使用 pytest.skip() 自动跳过,不影响普通单元测试/CI。
- 仅在本地联调时、显式设置上述环境变量后,此测试才会真正访问设备。
"""
import os
import socket
import pytest
from ncclient import manager
from exporter.netconf_client import build_transceiver_filter, parse_netconf_response
RUIJIE_HOST = os.getenv("RUIJIE_NETCONF_HOST", "127.0.0.1")
RUIJIE_PORT = int(os.getenv("RUIJIE_NETCONF_PORT", "9830"))
RUIJIE_USER = os.getenv("RUIJIE_NETCONF_USER", "ruijie1-admin")
RUIJIE_PASSWORD = os.getenv("RUIJIE_NETCONF_PASSWORD", "")
def _can_connect(host: str, port: int, timeout: float = 2.0) -> bool:
"""快速探测 host:port 是否可连,用于决定是否跳过 live 测试。"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.settimeout(timeout)
sock.connect((host, port))
return True
except OSError:
return False
finally:
sock.close()
@pytest.mark.ruijie_live
def test_ruijie_live_transceiver_rpc_and_parse() -> None:
"""
使用真实 Ruijie 设备验证:
- ncclient 能与设备建立 NETCONF 会话;
- build_transceiver_filter() 构造的 subtree filter 在设备上可用;
- parse_netconf_response(..., vendor='ruijie') 能正确解析设备返回的 XML。
"""
if not RUIJIE_PASSWORD:
pytest.skip("RUIJIE_NETCONF_PASSWORD 未设置,跳过 Ruijie live 测试")
if not _can_connect(RUIJIE_HOST, RUIJIE_PORT):
pytest.skip(f"Ruijie NETCONF {RUIJIE_HOST}:{RUIJIE_PORT} 不可达,跳过 live 测试")
flt = build_transceiver_filter()
with manager.connect(
host=RUIJIE_HOST,
port=RUIJIE_PORT,
username=RUIJIE_USER,
password=RUIJIE_PASSWORD,
hostkey_verify=False,
timeout=30,
allow_agent=False,
look_for_keys=False,
) as m:
reply = m.get(filter=("subtree", flt))
xml_str = str(reply)
# vendor="ruijie" 走厂商感知解析路径
transceivers, channels = parse_netconf_response(
xml_str,
device_name=f"ruijie-{RUIJIE_HOST}",
vendor="ruijie",
)
# 只要返回非空结果,就说明 "连接 + filter + 解析" 在真实设备上可以工作
assert transceivers or channels, "Ruijie 设备未返回任何 transceiver/channel 数据"
# 至少有一个 transceiver 拥有对应的 channel
tx_by_component = {t.component_name: t for t in transceivers}
ch_by_component = {}
for ch in channels:
ch_by_component.setdefault(ch.component_name, []).append(ch)
has_tx_with_channel = any(
comp in tx_by_component and len(ch_list) > 0
for comp, ch_list in ch_by_component.items()
)
assert has_tx_with_channel, (
"Ruijie live 数据中未发现“同时存在 transceiver 与 channel”的组件"
"请检查设备返回的 transceiver/physical-channels 数据是否完整"
)
# 至少有一个 channel 具有 rx 或 tx power 数值
channels_with_power = [
ch
for ch in channels
if ch.rx_power_dbm is not None or ch.tx_power_dbm is not None
]
assert channels_with_power, (
"Ruijie live 数据中未发现带 rx/tx power 的通道,"
"请检查设备是否开启了相关光功率采集"
)
# 额外 sanity 检查:至少有一个端口 label 不以 TRANSCEIVER- 开头,验证清洗逻辑生效
ports = {t.logical_port for t in transceivers} | {c.logical_port for c in channels}
assert any(not p.startswith("TRANSCEIVER-") for p in ports), (
"Ruijie live 数据中未发现清洗后的端口名,"
"请检查 vendor='ruijie' 解析逻辑是否生效"
)