示例:使用自定义 YANG RPC 检索 Junos 设备上的操作信息
您可以添加 YANG 数据模型,用于定义 Junos 设备上的自定义 RPC。创建自定义 RPC 使您能够精确定义输入参数和操作以及这些设备上特定操作任务的输出字段和格式。此示例显示自定义 RPC 和操作脚本,用于检索设备的操作信息并显示自定义 CLI 输出。
RPC 被添加到设备上的 Junos OS 模式中。在 CLI 中执行 RPC 时,它会打印请求的物理接口的名称和操作状态。
要求
此示例使用以下硬件和软件组件:
-
运行 Junos OS 17.3R1 或更高版本的设备,支持加载自定义 YANG 数据模型。
RPC 和操作脚本概述
此示例中的 YANG 模块定义自定义 RPC 以返回某些物理接口的名称和操作状态。YANG 模块 rpc-interface-status
保存在 rpc-interface-status.yang 文件中。该模块导入 Junos OS 扩展模块,提供在设备上执行自定义 RPC 和自定义 CLI 输出所需的扩展。
该模块定义 get-interface-status
了 RPC。请求 <get-interface-status>
标记用于在设备上远程执行 RPC。在 RPC 定义中,语 junos:command
句定义用于在 CLI 中执行 RPC 的命令,在此情况下是 show intf status
。
junos:script
和junos:action-execute
语句定义了在执行 RPC 时调用的操作脚本。此示例使用名为 rpc-interface-status.py 的 Python 操作脚本检索 RPC 所需的信息,并按 RPC output
语句中的定义返回 XML 输出元素。
rpc get-interface-status { description "RPC example to retrieve interface status"; junos:command "show intf status" { junos:action-execute { junos:script "rpc-interface-status.py"; } } ...
从 Junos OS 17.3 版开始,该 action-execute
语句是以下部分的子状态 command
。在早期版本中 action-execute
,和 command
语句放置在同一级别,并且语 command
句是可选的。
RPC 有一个输入参数,用于 match
确定在输出中包含的接口。执行 RPC 时,您包括一个在所需接口上匹配的字符串,例如 ge-0*。空字符串 (“”) 在所有接口上匹配。操作脚本将空字符串的默认值定义为 match
空字符串,因此如果用户省略此论点,输出将包含所有接口的信息。
input { leaf match { description "Requested interface match condition"; type string; } }
RPC 还定义了相应操作脚本必须发出的输出节点。根节点是该<interface-status-info>
元素,其中包含包含与接口匹配的<interface>
和<status>
节点包含的零个或更多<status-info>
元素。语junos-odl:format interface-status-info-format
句定义了 CLI 中显示的输出的格式。此节点不会在输出 XML 树中发出。
output { container interface-status-info { list status-info { leaf interface { type string; description "Physical inteface name"; } leaf status { type string; description "Operational status"; } junos-odl:format interface-status-info-format { ... } } } }
此示例介绍了 Python 操作脚本的两个版本。脚本展示检索操作命令输出的方法不同,但两个脚本都发出相同的 RPC 输出。第一个操作脚本使用 Python subprocess
模块执行 show interfaces match-value | display xml
命令,然后将字符串输出转换为 XML。第二个操作脚本使用 Junos PyEZ 执行相当于命令的 show interfaces match-value
RPC。两个脚本都使用相同的代码解析命令输出,并提取每个物理接口的名称和操作状态。这些脚本为 RPC 输出构建 XML,然后打印输出,然后将信息返回设备。XML 树必须与 RPC 中定义的层次结构完全匹配。
Junos 设备为操作输出中的许多元素(包括 <interface-information>
元素)定义了与版本相关的名称空间。为了使 RPC Junos OS 版本独立,代码对 local-name()
这些元素使用 XPath 表达式中的功能。您可以选择将名称空间映射作为参数 xpath()
,并根据适当的名称空间对元素进行资格认证。
包含 RPC 和操作脚本文件的模块作为名为 intf-rpc
的全新 YANG 软件包的一部分添加到设备中。
YANG 模块
YANG 模块
YANG 模块 rpc-interface-status.yang 定义了 RPC、用于在 CLI 中执行 RPC 的命令,以及执行 RPC 时要调用的操作脚本名称。文件的基本名称必须与模块名称匹配。
/* * Copyright (c) 2014 Juniper Networks, Inc. * All rights reserved. */ module rpc-interface-status { namespace "http://yang.juniper.net/examples/rpc-cli"; prefix rpc-cli; import junos-extension-odl { prefix junos-odl; } import junos-extension { prefix junos; } organization "Juniper Networks, Inc."; description "Junos OS YANG module for RPC example"; rpc get-interface-status { description "RPC example to retrieve interface status"; junos:command "show intf status" { junos:action-execute { junos:script "rpc-interface-status.py"; } } input { leaf match { description "Requested interface match condition"; type string; } } output { container interface-status-info { list status-info { leaf interface { type string; description "Physical interface name"; } leaf status { type string; description "Operational status"; } junos-odl:format interface-status-info-format { junos-odl:header "Physical Interface - Status\n"; junos-odl:indent 5; junos-odl:comma; junos-odl:space; junos-odl:line { junos-odl:field "interface"; junos-odl:field "status"; } } } } } } }
操作脚本
对应的操作脚本 rpc-interface-status.py。此示例介绍了两个操作脚本,这些脚本使用不同的方法检索数据。一个脚本使用 Python subprocess
模块,另一个脚本使用 Junos PyEZ 库。两个脚本都发出相同的 RPC XML 输出。
从 Junos OS 版本 21.2R1 和 Junos OS Evolved 版本 21.2R1 开始,当设备将命令行参数传递至 Python 操作脚本时,它将单个连字符 (-) 前缀为单个字符的论证名称,并将两个连字符 (--) 前缀为多字符论证名称。
操作脚本(使用 subprocess
)
以下操作脚本使用 Python subprocess
模块执行操作命令并检索数据。此示例提供了两个版本的脚本,可适当处理不同版本的脚本命令行参数。
Junos OS 版本 21.1 和更低版本
#!/usr/bin/python # Junos OS Release 21.1 and earlier import sys import subprocess from lxml import etree def get_device_info(cmd): """ Execute Junos OS operational command and parse output :param: str cmd: operational command to execute :returns: List containing the XML data for each interface """ # execute Junos OS operational command and retrieve output proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) tmp = proc.stdout.read() root = etree.fromstring(tmp.strip()) xml_items = [] # parse output for required data for intf in root.xpath("/rpc-reply \ /*[local-name()='interface-information'] \ /*[local-name()='physical-interface']"): # retrieve data for the interface name and operational status name = intf.xpath("*[local-name()='name']")[0].text oper_status = intf.xpath("*[local-name()='oper-status']")[0].text # append the XML for each interface to a list xml_item = etree.Element('status-info') interface = etree.SubElement(xml_item, 'interface') interface.text = name status = etree.SubElement(xml_item, 'status') status.text = oper_status xml_items.append(xml_item) return xml_items def generate_xml(cmd): """ Generate the XML tree for the RPC output :param: str cmd: operational command from which to retrieve data :returns: XML tree for the RPC output """ xml = etree.Element('interface-status-info') intf_list_xml = get_device_info(cmd) for intf in intf_list_xml: xml.append(intf) return xml def main(): args = {'match': ""} for arg in args.keys(): if arg in sys.argv: index = sys.argv.index(arg) args[arg] = sys.argv[index+1] # define the operational command from which to retrieve information cli_command = 'show interfaces ' + args['match'] + ' | display xml' cmd = ['cli', '-c', cli_command] # generate the XML for the RPC output rpc_output_xml = generate_xml(cmd) # print RPC output print (etree.tostring(rpc_output_xml, pretty_print=True, encoding='unicode')) if __name__ == '__main__': main()
Junos OS 版本 21.2R1 和更高版本
#!/usr/bin/python3 # Junos OS Release 21.2R1 and later import subprocess import argparse from lxml import etree def get_device_info(cmd): """ Execute Junos OS operational command and parse output :param: str cmd: operational command to execute :returns: List containing the XML data for each interface """ # execute Junos OS operational command and retrieve output proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) tmp = proc.stdout.read() root = etree.fromstring(tmp.strip()) xml_items = [] # parse output for required data for intf in root.xpath("/rpc-reply \ /*[local-name()='interface-information'] \ /*[local-name()='physical-interface']"): # retrieve data for the interface name and operational status name = intf.xpath("*[local-name()='name']")[0].text oper_status = intf.xpath("*[local-name()='oper-status']")[0].text # append the XML for each interface to a list xml_item = etree.Element('status-info') interface = etree.SubElement(xml_item, 'interface') interface.text = name status = etree.SubElement(xml_item, 'status') status.text = oper_status xml_items.append(xml_item) return xml_items def generate_xml(cmd): """ Generate the XML tree for the RPC output :param: str cmd: operational command from which to retrieve data :returns: XML tree for the RPC output """ xml = etree.Element('interface-status-info') intf_list_xml = get_device_info(cmd) for intf in intf_list_xml: xml.append(intf) return xml def main(): parser = argparse.ArgumentParser(description='This is a demo script.') parser.add_argument('--match', required=False, default='') parser.add_argument('--rpc_name', required=True) args = parser.parse_args() # define the operational command from which to retrieve information cli_command = 'show interfaces ' + args.match + ' | display xml' cmd = ['cli', '-c', cli_command] # generate the XML for the RPC output rpc_output_xml = generate_xml(cmd) # print RPC output print (etree.tostring(rpc_output_xml, pretty_print=True, encoding='unicode')) if __name__ == '__main__': main()
操作脚本(使用 Junos PyEZ)
以下操作脚本使用 Junos PyEZ 执行操作命令并检索数据。此示例提供了两个版本的脚本,可适当处理不同版本的脚本命令行参数。
Junos OS 版本 21.1 和更低版本
#!/usr/bin/python # Junos OS Release 21.1 and earlier import sys from jnpr.junos import Device from jnpr.junos.exception import * from lxml import etree def get_device_info(match): """ Execute Junos OS operational command and parse output :param: str match: interface match condition :returns: List containing the XML data for each interface """ # execute Junos OS operational command and retrieve output try: with Device() as dev: if (match == ""): root = dev.rpc.get_interface_information( ) else: root = dev.rpc.get_interface_information(interface_name=match) except Exception: sys.exit() xml_items = [] # parse output for required data for intf in root.xpath("/rpc-reply \ /*[local-name()='interface-information'] \ /*[local-name()='physical-interface']"): # retrieve data for the interface name and operational status name = intf.xpath("*[local-name()='name']")[0].text oper_status = intf.xpath("*[local-name()='oper-status']")[0].text # append the XML for each interface to a list xml_item = etree.Element('status-info') interface = etree.SubElement(xml_item, 'interface') interface.text = name status = etree.SubElement(xml_item, 'status') status.text = oper_status xml_items.append(xml_item) return xml_items def generate_xml(match): """ Generate the XML tree for the RPC output :param: str match: interface match condition :returns: XML tree for the RPC output """ xml = etree.Element('interface-status-info') intf_list_xml = get_device_info(match) for intf in intf_list_xml: xml.append(intf) return xml def main(): args = {'match': ""} for arg in args.keys(): if arg in sys.argv: index = sys.argv.index(arg) args[arg] = sys.argv[index+1] # generate the XML for the RPC output rpc_output_xml = generate_xml(args['match']) # print RPC output print (etree.tostring(rpc_output_xml, pretty_print=True, encoding='unicode')) if __name__ == '__main__': main()
Junos OS 版本 21.2R1 和更高版本
#!/usr/bin/python3 # Junos OS Release 21.2R1 and later import sys import argparse from jnpr.junos import Device from jnpr.junos.exception import * from lxml import etree def get_device_info(match): """ Execute Junos OS operational command and parse output :param: str match: interface match condition :returns: List containing the XML data for each interface """ # execute Junos OS operational command and retrieve output try: with Device() as dev: if (match == ""): root = dev.rpc.get_interface_information( ) else: root = dev.rpc.get_interface_information(interface_name=match) except Exception: sys.exit() xml_items = [] # parse output for required data for intf in root.xpath("/rpc-reply \ /*[local-name()='interface-information'] \ /*[local-name()='physical-interface']"): # retrieve data for the interface name and operational status name = intf.xpath("*[local-name()='name']")[0].text oper_status = intf.xpath("*[local-name()='oper-status']")[0].text # append the XML for each interface to a list xml_item = etree.Element('status-info') interface = etree.SubElement(xml_item, 'interface') interface.text = name status = etree.SubElement(xml_item, 'status') status.text = oper_status xml_items.append(xml_item) return xml_items def generate_xml(match): """ Generate the XML tree for the RPC output :param: str match: interface match condition :returns: XML tree for the RPC output """ xml = etree.Element('interface-status-info') intf_list_xml = get_device_info(match) for intf in intf_list_xml: xml.append(intf) return xml def main(): parser = argparse.ArgumentParser(description='This is a demo script.') parser.add_argument('--match', required=False, default='') parser.add_argument('--rpc_name', required=True) args = parser.parse_args() # generate the XML for the RPC output rpc_output_xml = generate_xml(args.match) # print RPC output print (etree.tostring(rpc_output_xml, pretty_print=True, encoding='unicode')) if __name__ == '__main__': main()
支持执行 Python 脚本
要使设备能够执行未签名的 Python 脚本:
在设备上加载 RPC
要将 RPC 和操作脚本添加到 Junos 模式:
验证 RPC
目的
验证 RPC 是否可根据预期工作。
行动
在操作模式下,在 CLI 中执行 RPC,方法是发出由 RPC 定义中的语句定义的 junos:command
命令,并包括 match
输入参数。在此示例中,匹配参数用于在从 ge-0 开始的所有接口上匹配。
user@host> show intf status match ge-0* Physical Interface - Status ge-0/0/0, up ge-0/0/1, up ge-0/0/2, up ge-0/0/3, up ge-0/0/4, up ge-0/0/5, up ge-0/0/6, up ge-0/0/7, up ge-0/0/8, up ge-0/0/9, up ge-0/1/0, up ge-0/1/1, up ge-0/1/2, up ge-0/1/3, up ge-0/1/4, up ge-0/1/5, up ge-0/1/6, up ge-0/1/7, up ge-0/1/8, up ge-0/1/9, up
您还可以调整匹配条件以返回不同接口集。例如:
user@host> show intf status match *e-0/*/0 Physical Interface - Status ge-0/0/0, up pfe-0/0/0, up ge-0/1/0, up xe-0/2/0, up xe-0/3/0, up
要以 XML 格式返回相同输出,请将过滤器附加 | display xml
到 命令。
user@host> show intf status match *e-0/*/0 | display xml <rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.3R1/junos"> <interface-status-info> <status-info> <interface>ge-0/0/0</interface> <status>up</status> </status-info> <status-info> <interface>pfe-0/0/0</interface> <status>up</status> </status-info> <status-info> <interface>ge-0/1/0</interface> <status>up</status> </status-info> <status-info> <interface>xe-0/2/0</interface> <status>up</status> </status-info> <status-info> <interface>xe-0/3/0</interface> <status>up</status> </status-info> </interface-status-info> <cli> <banner></banner> </cli> </rpc-reply>
要在所有接口上匹配,请省略 match
该参数,或将该参数的值设置为空字符串(“”)。
意义
执行 RPC 时,设备将调用操作脚本。操作脚本执行操作命令,从设备检索接口信息,解析所需信息的输出,并打印 RPC 语句中定义的 RPC output
输出的 XML 层次结构。在 CLI 中执行 RPC 时,设备使用 RPC 中定义的 CLI 格式将 XML 输出转换为显示的 CLI 输出。要返回原始 XML 输出,请将过滤器附加 | display xml
到 命令。
使用 RPC 请求标记远程执行 RPC 时,输出的默认格式为 XML。
RPC 执行错误故障排除
问题
描述
执行 RPC 时,设备会生成以下错误:
error: open failed: /var/db/scripts/action/rpc-interface-status.py: Permission denied
原因
调用 RPC 的用户没有执行相应 Python 操作脚本的必要权限。
解决 方案
用户只能在 Junos 设备上执行未签名的 Python 脚本,因为脚本的文件权限包括用户按用户、组或其他部分所属于的第一类的读取权限。
验证脚本是否具有让用户执行脚本的必要权限,并在适当时调整权限。如果更新了权限,还必须更新 YANG 包,才能使此更改生效。例如:
admin@host> file list ~ detail -rw------- 1 admin wheel 2215 Apr 20 11:36 rpc-interface-status.py
admin@host> file change-permission rpc-interface-status.py permission 644 admin@host> file list ~ detail -rw-r--r-- 1 admin wheel 2215 Apr 20 11:36 rpc-interface-status.py
admin@host> request system yang update intf-rpc action-script /var/tmp/rpc-interface-status.py Scripts syntax validation : START Scripts syntax validation : SUCCESS