Ejemplo: Usar una RPC yang personalizada para recuperar información operativa en dispositivos Junos
Puede agregar modelos de datos YANG que definan RPCs personalizadas en dispositivos Junos. La creación de RPCs personalizados le permite definir con precisión los parámetros de entrada y las operaciones, así como los campos de salida y el formato para sus tareas operativas específicas en esos dispositivos. En este ejemplo, se presenta un script de acción y UNA RPC personalizados que recuperan información operativa del dispositivo y muestran la salida de CLI personalizada.
El RPC se agrega al esquema de Junos OS en el dispositivo. Cuando se ejecuta el RPC en la CLI, imprime el nombre y el estado operativo de las interfaces físicas solicitadas.
Requisitos
En este ejemplo, se utilizan los siguientes componentes de hardware y software:
-
Dispositivo que ejecuta la versión 17.3R1 o posterior de Junos OS que admite la carga de modelos de datos YANG personalizados.
Descripción general de la RPC y el script de acción
El módulo YANG de este ejemplo define una RPC personalizada para devolver el nombre y el estado operativo de ciertas interfaces físicas. El módulo rpc-interface-status
YANG se guarda en el archivo rpc-interface-status.yang . El módulo importa los módulos de extensión de Junos OS, que proporcionan las extensiones necesarias para ejecutar RPCs personalizadas en el dispositivo y personalizar la salida de CLI.
El módulo define la get-interface-status
RPC. La <get-interface-status>
etiqueta de solicitud se utiliza para ejecutar de forma remota la RPC en el dispositivo. En la definición de RPC, la junos:command
instrucción define el comando que se utiliza para ejecutar la RPC en la CLI, que en este caso es show intf status
.
Las junos:action-execute
instrucciones and junos:script
definen la secuencia de comandos de acción que se invoca cuando se ejecuta el RPC. En este ejemplo, se utiliza una secuencia de comandos de acción de Python denominada rpc-interface-status.py para recuperar la información requerida por la RPC y devolver los elementos de salida XML tal como se definen en la instrucción RPC output
.
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"; } } ...
A partir de Junos OS versión 17.3, la action-execute
instrucción es una subestación a command
. En versiones anteriores, las action-execute
instrucciones y command
se colocan en el mismo nivel, y la command
instrucción es opcional.
El RPC tiene un parámetro de entrada denominado match
, que determina las interfaces que se deben incluir en la salida. Cuando se ejecuta el RPC, se incluye una cadena que coincide en las interfaces deseadas, por ejemplo, ge-0*. Una cadena vacía ("") coincide con todas las interfaces. La secuencia de comandos de acción define el valor predeterminado como una cadena vacía, de match
modo que si el usuario omite este argumento, la salida incluirá información para todas las interfaces.
input { leaf match { description "Requested interface match condition"; type string; } }
El RPC también define los nodos de salida que debe emitir la secuencia de comandos de acción correspondiente. El nodo raíz es el <interface-status-info>
elemento, que contiene cero o más <status-info>
elementos que encierran los <interface>
nodos y <status>
para una interfaz coincidente. La junos-odl:format interface-status-info-format
instrucción define el formato para el resultado que se muestra en la CLI. Este nodo no se emite en el árbol XML de salida.
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 { ... } } } }
En este ejemplo, se presentan dos versiones de la secuencia de comandos de acción de Python. Los scripts muestran diferentes medios para recuperar la salida del comando operativo, pero ambos scripts emiten una salida RPC idéntica. La primera secuencia de comandos de acción usa el módulo Python subprocess
para ejecutar el show interfaces match-value | display xml
comando y, luego, convierte la salida de cadena en XML. La segunda secuencia de comandos de acción usa Junos PyEZ para ejecutar el equivalente DE RPC del show interfaces match-value
comando. Ambos scripts usan código idéntico para analizar la salida del comando y extraer el nombre y el estado operativo de cada interfaz física. Los scripts construyen el XML para la salida DE LA RPC y, a continuación, imprimen la salida, lo que devuelve la información al dispositivo. El árbol XML debe coincidir exactamente con la jerarquía definida en la RPC.
Los dispositivos Junos definen espacios de nombres dependientes de versiones para muchos de los elementos de la salida operativa, incluido el <interface-information>
elemento. Para hacer que la versión junos OS de RPC sea independiente, el código usa la local-name()
función en las expresiones de XPath para estos elementos. Puede elegir incluir la asignación del espacio de nombres como un argumento para xpath()
y calificar los elementos con el espacio de nombres adecuado.
El módulo que contiene el RPC y el archivo de secuencia de comandos de acción se agregan al dispositivo como parte de un nuevo paquete YANG denominado intf-rpc
.
Módulo YANG
Módulo YANG
El módulo YANG, rpc-interface-status.yang, define el RPC, el comando utilizado para ejecutar el RPC en la CLI y el nombre de la secuencia de comandos de acción que se invocará cuando se ejecuta la RPC. El nombre base del archivo debe coincidir con el nombre del módulo.
/* * 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"; } } } } } } }
Script de acción
La secuencia de comandos de acción correspondiente es rpc-interface-status.py. En este ejemplo, se presentan dos scripts de acción que utilizan medios diferentes para recuperar los datos. Un script usa el módulo Python subprocess
y el otro script usa la biblioteca Junos PyEZ. Ambos scripts emiten la misma salida XML de RPC.
A partir de Junos OS versión 21.2R1 y Junos OS Evolved versión 21.2R1, cuando el dispositivo pasa argumentos de línea de comandos a una secuencia de comandos de Python, prefija un guión único (-) a nombres de argumentos de un solo carácter y prefijo dos guiones (--) a nombres de argumentos de varios caracteres.
- Secuencia de comandos de acción (uso de subproceso)
- Secuencia de comandos de acción (uso de Junos PyEZ)
Secuencia de comandos de acción (uso subprocess
)
La siguiente secuencia de comandos de acción usa el módulo Python subprocess
para ejecutar el comando operativo y recuperar los datos. En este ejemplo, se proporcionan dos versiones de la secuencia de comandos, que manejan adecuadamente los argumentos de línea de comandos de la secuencia de comandos para las diferentes versiones.
Junos OS versión 21.1 y anteriores
#!/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 versión 21.2R1 y posteriores
#!/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()
Secuencia de comandos de acción (uso de Junos PyEZ)
La siguiente secuencia de comandos de acción usa Junos PyEZ para ejecutar el comando operativo y recuperar los datos. En este ejemplo, se proporcionan dos versiones de la secuencia de comandos, que manejan adecuadamente los argumentos de línea de comandos de la secuencia de comandos para las diferentes versiones.
Junos OS versión 21.1 y anteriores
#!/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 versión 21.2R1 y posteriores
#!/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()
Habilitación de la ejecución de scripts de Python
Para permitir que el dispositivo ejecute scripts de Python sin firmar:
Cargar el RPC en el dispositivo
Para agregar el CÓDIGO DE ACCIÓN y EL RPC al esquema de Junos:
Verificación de la RPC
Propósito
Compruebe que el RPC funciona como se esperaba.
Acción
Desde el modo operativo, ejecute el RPC en la CLI mediante la emisión del comando definido por la junos:command
instrucción en la definición de RPC e incluya el argumento de match
entrada. En este ejemplo, el argumento match se usa para coincidir en todas las interfaces que comienzan con 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
También puede ajustar la condición de coincidencia para devolver diferentes conjuntos de interfaces. Por ejemplo:
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
Para devolver el mismo resultado en formato XML, anexe el | display xml
filtro al comando.
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>
Para que coincida con todas las interfaces, omita el match
argumento o establezca el valor del argumento en una cadena vacía ("").
Significado
Cuando ejecuta el RPC, el dispositivo invoca la secuencia de comandos de acción. La secuencia de comandos de acción ejecuta el comando operativo para recuperar la información de la interfaz del dispositivo, analiza la salida de la información deseada e imprime la jerarquía XML para la salida de RPC tal como se define en la instrucción RPC output
. Cuando se ejecuta el RPC en la CLI, el dispositivo usa el formato de CLI definido en la RPC para convertir la salida XML en la salida de CLI mostrada. Para devolver la salida XML original, anexe el | display xml
filtro al comando.
Cuando el RPC se ejecuta de forma remota mediante la etiqueta de solicitud DE RPC, el formato predeterminado de la salida es XML.
Solución de problemas de errores de ejecución de RPC
Problema
Descripción
Cuando ejecuta la RPC, el dispositivo genera el siguiente error:
error: open failed: /var/db/scripts/action/rpc-interface-status.py: Permission denied
Causa
El usuario que invocó la RPC no tiene los permisos necesarios para ejecutar la secuencia de comandos de acción de Python correspondiente.
Solución
Los usuarios solo pueden ejecutar scripts de Python sin firmar en dispositivos Junos cuando los permisos de archivo de la secuencia de comandos incluyen permiso de lectura para la primera clase en la que el usuario se encuentra, en el orden de usuario, grupo u otros.
Compruebe si la secuencia de comandos tiene los permisos necesarios para que ese usuario ejecute la secuencia de comandos y ajuste los permisos, si corresponde. Si actualiza los permisos, también debe actualizar el paquete YANG para que este cambio tenga efecto. Por ejemplo:
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