4. 使用PyEZ进行设备连接与配置操作
PyEZ(Python Ez)是 Juniper 官方提供的 Python 库,封装了 NETCONF over SSH 的底层细节,让工程师可以用 Python 对象的方式操作 Junos 设备。它的核心价值在于:把“打开 NETCONF 会话、构造 RPC、解析 XML 回复”这一整套繁琐流程,抽象成 Device、Config、Table、View 四个类。掌握这四个类,就掌握了 PyEZ 自动化验证的主干。
一、安装与依赖
PyEZ 通过 pip 安装,包名为 junos-eznc。它依赖 lxml、ncclient、paramiko 等库,建议在虚拟环境中安装,避免污染系统 Python。
python3 -m venv venv
source venv/bin/activate
pip install junos-eznc
python -c "from jnpr.junos import Device; print('PyEZ OK')"
安装完成后,用一行导入验证即可。若导入报错,通常是 lxml 缺少系统编译依赖,在 CentOS 上可先安装 libxml2-devel libxslt-devel。
二、Junos 设备认证与连接
Junos 设备要接受 NETCONF 连接,必须先启用服务:
set system services netconf ssh port 830
PyEZ 的 Device 类支持三种认证方式:密码认证、SSH 密钥认证、以及通过 ssh_config 别名复用已有配置。生产环境推荐密钥认证,避免脚本中硬编码密码。
from jnpr.junos import Device
with Device(host='192.0.2.11', user='auto',
password='Juniper123', port=830) as dev:
print(dev.facts['model'], dev.facts['version'])
with 语句会自动打开和关闭 NETCONF 会话。dev.facts 是设备事实字典,包含型号、Junos 版本、序列号、主机名等,是后续判断和分支的重要依据。连接成功后,dev.connected 为 True;若认证失败会抛出 ConnectAuthError,端口不通则抛出 ConnectTimeoutError,脚本中应分别捕获。
三、Config 类:读取与下发配置
Config 类负责配置的读取、比较和加载。读取运行配置最常用的方式是 dev.rpc.get_config(),返回 lxml 元素;若只想拿文本,可用 etree.tostring 转换。
from lxml import etree
with Device(host='192.0.2.11', user='auto', password='Juniper123') as dev:
cfg = dev.rpc.get_config()
print(etree.tostring(cfg, encoding='unicode')[:200])
下发配置时,Config 支持三种模式:merge(默认,合并)、override(覆盖)、replace(替换指定层级)。批量场景下,通常把配置写成 set 命令文本或 XML,再通过 load 加载:
from jnpr.junos.utils.config import Config
with Device(host='192.0.2.11', user='auto', password='Juniper123') as dev:
cu = Config(dev)
cu.lock()
cu.load('set system host-name R1-Lab', format='set')
cu.commit(comment='batch via PyEZ')
cu.unlock()
注意 lock() 与 unlock() 必须成对出现,否则会阻塞其他管理会话。若只想验证配置差异而不提交,可用 cu.diff() 查看候选配置与运行配置的差异,这是配置验证的关键一步。
四、Table 与 View 类:结构化取数
直接解析 RPC 返回的 XML 很繁琐。Table 和 View 把“取数”和“字段映射”分离:Table 定义数据来源(哪个 RPC、哪个 XPath),View 定义字段与 XML 标签的对应关系。以接口状态为例:
from jnpr.junos.factory import loadyaml
globals().update(loadyaml('''
---
IfTable:
rpc: get-interface-information
item: physical-interface
key: name
view: IfView
IfView:
fields:
name: name
admin: admin-status
oper: oper-status
mtu: {mtu: int}
'''))
with Device(host='192.0.2.11', user='auto', password='Juniper123') as dev:
tbl = IfTable(dev).get()
for item in tbl:
print(item.name, item.admin, item.oper, item.mtu)
YAML 中 rpc 指定调用的 RPC,item 指定每条记录的 XML 节点,key 指定主键,view 引用字段映射。{mtu: int} 表示把 MTU 转成整数,便于后续比较。路由表同理,只需把 rpc 换成 get-route-information,item 换成 route-table/rt,字段映射 rt-destination、rt-nexthop、protocol-name 即可。
五、批量获取配置、接口状态与路由表
把上述能力组合起来,就能写一个批量采集脚本。核心思路是:设备清单放在列表或 YAML 中,循环建立 Device 连接,分别调用配置读取、接口表、路由表,把结果写入字典或 CSV。下面是一个可直接参考的骨架:
from jnpr.junos import Device
from jnpr.junos.utils.config import Config
from lxml import etree
DEVICES = [
{'host': '192.0.2.11', 'user': 'auto', 'password': 'Juniper123'},
{'host': '192.0.2.12', 'user': 'auto', 'password': 'Juniper123'},
]
results = {}
for d in DEVICES:
try:
with Device(**d) as dev:
cfg = etree.tostring(dev.rpc.get_config(), encoding='unicode')
if_tbl = IfTable(dev).get()
rt_tbl = RouteTable(dev).get()
results[d['host']] = {
'config_len': len(cfg),
'interfaces': [(i.name, i.oper) for i in if_tbl],
'routes': [(r.dest, r.nexthop) for r in rt_tbl],
}
except Exception as e:
results[d['host']] = {'error': str(e)}
for host, r in results.items():
print(host, r.get('error') or f"{len(r['interfaces'])} if, {len(r['routes'])} rt")
运行后,若某台设备返回 error,说明连接或认证失败,应优先检查 NETCONF 服务是否开启、端口是否被 ACL 拦截、账号是否有 junos-read 权限。若接口表为空,通常是 item 路径写错,可用 dev.rpc.get_interface_information() 打印原始 XML 核对节点名。
timeout 参数(如 Device(..., timeout=30)),避免单台设备无响应拖垮整个采集任务;同时把每台设备的采集结果独立保存,便于失败重试。六、结果判读与常见问题
采集完成后,判断是否达成目标主要看三点:一是每台设备是否都返回了配置、接口、路由三类数据;二是接口的 oper 字段是否为 up,与预期一致;三是路由表中是否存在关键前缀及其下一跳。若使用 Config.diff() 做配置验证,则关注差异是否为空——为空表示设备配置与模板一致,非空则需逐条核对。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| ConnectAuthError | 账号密码错误或权限不足 | 核对账号,确认具备 read 权限 |
| ConnectTimeoutError | 830 端口不通或 NETCONF 未启用 | 检查 system services netconf |
| 接口表为空 | Table 的 item 路径不匹配 | 打印原始 XML 核对节点名 |
| commit 报错 | 候选配置语法错误或未 lock | 先 diff() 检查,再 lock/commit |