Exemplo: Use um RPC YANG personalizado para recuperar informações operacionais em dispositivos Junos
Você pode adicionar modelos de dados YANG que definem RPCs personalizados em dispositivos Junos. Criar RPCs personalizados permite que você defina com precisão os parâmetros e operações de entrada e os campos de saída e a formatação para suas tarefas operacionais específicas nesses dispositivos. Este exemplo apresenta um RPC e um script de ação personalizados que recuperam informações operacionais do dispositivo e exibem a saída CLI personalizada.
O RPC é adicionado ao esquema do Junos OS no dispositivo. Quando o RPC é executado na CLI, ele imprime o nome e o status operacional para as interfaces físicas solicitadas.
Requisitos
Este exemplo usa os seguintes componentes de hardware e software:
-
Dispositivo em execução Junos OS Release 17.3R1 ou posterior que oferece suporte ao carregamento de modelos de dados YANG personalizados.
Visão geral do RPC e script de ação
O módulo YANG neste exemplo define um RPC personalizado para devolver o nome e o status operacional de determinadas interfaces físicas. O módulo rpc-interface-status
YANG é salvo no arquivo rpc-interface-status.yang . O módulo importa os módulos de extensão Junos OS, que fornecem as extensões necessárias para executar RPCs personalizados no dispositivo e personalizar a saída CLI.
O módulo define o get-interface-status
RPC. A <get-interface-status>
tag de solicitação é usada para executar remotamente o RPC no dispositivo. Na definição de RPC, a junos:command
declaração define o comando que é usado para executar o RPC na CLI, que neste caso é show intf status
.
As junos:action-execute
declarações e junos:script
declarações definem o script de ação que é invocado quando o RPC é executado. Este exemplo usa um script de ação python chamado rpc-interface-status.py para recuperar as informações exigidas pelo RPC e devolver os elementos de saída XML conforme definido na declaração de 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 do Junos OS Release 17.3, a action-execute
declaração é um subestações para command
. Em versões anteriores, as declarações e command
declarações action-execute
são colocadas no mesmo nível, e a command
declaração é opcional.
O RPC tem um parâmetro de entrada chamado match
, que determina que as interfaces incluam na saída. Quando você executa o RPC, você inclui uma string que combina com as interfaces desejadas, por exemplo ge-0*. Uma corda vazia ("") combina em todas as interfaces. O script de ação define o valor padrão como uma string vazia, portanto match
, se o usuário omitir esse argumento, a saída incluirá informações para todas as interfaces.
input { leaf match { description "Requested interface match condition"; type string; } }
O RPC também define os nós de saída que devem ser emitidos pelo script de ação correspondente. O nó raiz é o <interface-status-info>
elemento, que contém zero ou mais <status-info>
elementos que incluem o nó e <status>
os <interface>
nós para uma interface combinada. A junos-odl:format interface-status-info-format
declaração define a formatação para a saída exibida na CLI. Este nó não é emitido na árvore XML de saída.
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 { ... } } } }
Este exemplo apresenta duas versões do script de ação python. Os scripts demonstram meios diferentes para recuperar a saída de comando operacional, mas ambos os scripts emitem uma saída RPC idêntica. O primeiro script de ação usa o módulo Python subprocess
para executar o show interfaces match-value | display xml
comando e depois converte a saída de string em XML. O segundo script de ação usa o Junos PyEZ para executar o RPC equivalente ao show interfaces match-value
comando. Ambos os scripts usam código idêntico para analisar a saída de comando e extrair o nome e o status operacional para cada interface física. Os scripts constroem o XML para a saída RPC e depois imprimem a saída, que devolve as informações ao dispositivo. A árvore XML deve corresponder exatamente à hierarquia definida no RPC.
Os dispositivos Junos definem namespaces dependentes de versão para muitos dos elementos na saída operacional, incluindo o <interface-information>
elemento. Para tornar a versão do RPC Junos OS independente, o código usa a local-name()
função nas expressões XPath para esses elementos. Você pode optar por incluir o mapeamento de namespace como um argumento e xpath()
qualificar os elementos com o namespace apropriado.
O módulo que contém o RPC e o arquivo de script de ação são adicionados ao dispositivo como parte de um novo pacote YANG chamado intf-rpc
.
Módulo YANG
Módulo YANG
O módulo YANG, rpc-interface-status.yang, define o RPC, o comando usado para executar o RPC na CLI e o nome do script de ação para invocar quando o RPC é executado. O nome base do arquivo deve combinar com o nome do 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 ação
O script de ação correspondente é rpc-interface-status.py. Este exemplo apresenta dois scripts de ação que usam meios diferentes para recuperar os dados. Um script usa o módulo Python subprocess
e o outro script usa a biblioteca Junos PyEZ. Ambos os scripts emitem a mesma saída RPC XML.
A partir do Junos OS Release 21.2R1 e Junos OS Evolved Release 21.2R1, quando o dispositivo passa argumentos de linha de comando para um script de ação Python, ele prefixa um único hífen (-) para nomes de argumentos de caractere único e prefixa dois hífens (--) para nomes de argumentos de vários caracteres.
Script de ação (usando subprocess
)
O script de ação a seguir usa o módulo Python subprocess
para executar o comando operacional e recuperar os dados. Este exemplo fornece duas versões do script, que lidam adequadamente com os argumentos de linha de comando do script para os diferentes lançamentos.
Junos OS Release 21.1 e anterior
#!/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()
Versão Junos OS 21.2R1 e posterior
#!/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()
Script de ação (usando Junos PyEZ)
O script de ação a seguir usa o Junos PyEZ para executar o comando operacional e recuperar os dados. Este exemplo fornece duas versões do script, que lidam adequadamente com os argumentos de linha de comando do script para os diferentes lançamentos.
Junos OS Release 21.1 e anterior
#!/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()
Versão Junos OS 21.2R1 e posterior
#!/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()
Habilitando a execução de scripts python
Para permitir que o dispositivo execute scripts python não assinados:
Carregando o RPC no dispositivo
Para adicionar o RPC e o script de ação ao esquema Junos:
Verificando o RPC
Propósito
Verifique se o RPC funciona como esperado.
Ação
A partir do modo operacional, execute o RPC na CLI emitindo o comando definido pela junos:command
declaração na definição de RPC e inclua o argumento de match
entrada. Neste exemplo, o argumento de correspondência é usado para combinar em todas as interfaces que começam com a 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
Você também pode ajustar a condição de correspondência para devolver diferentes conjuntos de interfaces. Por exemplo:
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 a mesma saída no formato XML, aplicação do | display xml
filtro ao 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 combinar em todas as interfaces, omite o match
argumento ou defina o valor do argumento para uma corda vazia ("").
Significado
Quando você executa o RPC, o dispositivo invoca o script de ação. O script de ação executa o comando operacional para recuperar as informações da interface do dispositivo, analisa a saída para as informações desejadas e imprime a hierarquia XML para a saída RPC conforme definido na declaração do RPC output
. Quando você executa o RPC no CLI, o dispositivo usa a formatação CLI definida no RPC para converter a saída XML na saída CLI exibida. Para devolver a saída XML original, apense o | display xml
filtro ao comando.
Quando o RPC é executado remotamente usando a tag de solicitação de RPC, o formato padrão para a saída é XML.
Resolução de problemas de erros de execução de RPC
Problema
Descrição
Quando você executa o RPC, o dispositivo gera o seguinte erro:
error: open failed: /var/db/scripts/action/rpc-interface-status.py: Permission denied
Causa
O usuário que invocou o RPC não tem as permissões necessárias para executar o script de ação python correspondente.
Solução
Os usuários só podem executar scripts python não assinados em dispositivos Junos quando as permissões de arquivo do script incluem a permissão de leitura para a primeira classe em que o usuário se encaixa, na ordem do usuário, do grupo ou de outros.
Verifique se o script tem as permissões necessárias para que esse usuário execute o script e ajuste as permissões, se apropriado. Se você atualizar as permissões, você também deve atualizar o pacote YANG para que essa mudança entre em vigor. Por exemplo:
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