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 即可。

PyEZ 四类协作与批量取数数据流 Python 脚本 批量循环 / 结果汇总 异常捕获与日志 Device NETCONF over SSH :830 facts / rpc / 会话管理 Junos 设备 候选配置 / 运行配置 接口 / 路由 / 状态 调用 RPC XML Config 类 lock / load / diff / commit / unlock 读取运行配置:dev.rpc.get_config() 模式:merge / override / replace Table + View 类 Table:rpc / item / key 定义数据源 View:字段 ↔ XML 标签映射 支持类型转换 {mtu: int} 配置验证输出 diff 差异 / commit 结果 配置文本 / 回滚检查 结构化数据输出 接口状态表 / 路由表 可迭代对象,便于断言

五、批量获取配置、接口状态与路由表

把上述能力组合起来,就能写一个批量采集脚本。核心思路是:设备清单放在列表或 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 权限
ConnectTimeoutError830 端口不通或 NETCONF 未启用检查 system services netconf
接口表为空Table 的 item 路径不匹配打印原始 XML 核对节点名
commit 报错候选配置语法错误或未 lock先 diff() 检查,再 lock/commit