예: 맞춤형 YANG RPC를 사용하여 Junos 디바이스의 운영 정보 검색
Junos 디바이스에서 맞춤형 RPC를 정의하는 YANG 데이터 모델을 추가할 수 있습니다. 맞춤형 RPC를 생성하면 입력 매개변수와 작동, 출력 필드를 정확하게 정의하고 해당 디바이스의 특정 운영 작업에 대한 형식을 정의할 수 있습니다. 이 예에서는 장비에서 운영 정보를 검색하고 사용자 정의된 CLI 출력을 표시하는 맞춤형 RPC 및 작업 스크립트를 제공합니다.
RPC는 디바이스의 Junos OS 스키마에 추가됩니다. CLI에서 RPC가 실행되면 요청된 물리적 인터페이스의 이름과 운영 상태를 인쇄합니다.
요구 사항
이 예에서는 다음과 같은 하드웨어 및 소프트웨어 구성 요소를 사용합니다.
-
Junos OS 릴리스 17.3R1 이상에서 실행되는 디바이스로 사용자 지정 YANG 데이터 모델 로드를 지원합니다.
RPC 및 작업 스크립트 개요
이 예의 YANG 모듈은 특정 물리적 인터페이스의 이름과 운영 상태를 반환하는 사용자 지정 RPC를 정의합니다. YANG 모듈 rpc-interface-status
은 rpc-interface-status.yang 파일에 저장됩니다. 이 모듈은 장비에서 사용자 지정 RPC를 실행하고 CLI 출력을 사용자 지정하는 데 필요한 확장을 제공하는 Junos OS 확장 모듈을 가져옵니다.
모듈은 RPC를 get-interface-status
정의합니다. <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에는 출력에 포함할 인터페이스를 결정하는 1개의 입력 매개 변수가 있습니다 match
. RPC를 실행할 때 원하는 인터페이스(예: ge-0*)에 일치하는 문자열을 포함합니다. 빈 문자열("")이 모든 인터페이스에서 일치합니다. 작업 스크립트는 빈 문자열로 기본값을 match
정의하므로 사용자가 이 인수를 생략하면 출력에는 모든 인터페이스에 대한 정보가 포함됩니다.
input { leaf match { description "Requested interface match condition"; type string; } }
또한 RPC는 해당 작업 스크립트에 의해 방출되어야 하는 출력 노드를 정의합니다. 루트 노드는 일치된 인터페이스를 <interface-status-info>
위해 노드와 <status>
0개 이상의 <status-info>
요소를 <interface>
포함하는 요소입니다. 명령문은 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 를 사용하여 명령과 동등한 RPC를 실행합니다 show interfaces match-value
. 두 스크립트 모두 동일한 코드를 사용하여 명령 출력을 구문 분석하고 각 물리적 인터페이스의 이름 및 운영 상태를 추출합니다. 스크립트는 RPC 출력을 위해 XML을 구성한 다음 출력을 인쇄하여 정보를 장비로 다시 반환합니다. XML 트리는 RPC에 정의된 계층과 정확히 일치해야 합니다.
Junos 장치는 요소를 포함하여 운영 출력의 많은 요소에 대한 릴리스 종속 네임스페이 <interface-information>
스를 정의합니다. RPC Junos OS 릴리스를 독립적으로 만들기 위해 코드는 이러한 요소에 대해 XPath 표현식의 기능을 사용합니다 local-name()
. 네임스페이스 매핑을 인수로 포함시키고 구성 요소를 적절한 네임스페이스로 검증할 xpath()
수 있습니다.
RPC와 작업 스크립트 파일이 포함된 모듈이 새로운 YANG 패키지 intf-rpc
의 일부로 장비에 추가됩니다.
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 Release 21.2R1 및 Junos OS Evolved Release 21.2R1부터 디바이스가 Python 작업 스크립트에 명령줄 인수를 전달하면 단일 하이픈(-)을 단일 문자 인수 이름에 접두사하고 2개의 하이픈(--)을 다중 문자 인수 이름에 접두사합니다.
작업 스크립트(사용 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 로딩
Junos 스키마에 RPC 및 작업 스크립트를 추가하려면 다음을 수행합니다.
RPC 검증
목적
RPC가 예상대로 작동하는지 확인합니다.
작업
운영 모드에서 RPC 정의의 명령어로 junos:command
정의된 명령을 발행하고 입력 인수를 포함함으로써 CLI에서 RPC를 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 output
명령문에 정의된 RPC 출력에 대한 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