Exemple : utiliser un RPC YANG personnalisé pour récupérer des informations opérationnelles sur les équipements Junos
Vous pouvez ajouter des modèles de données YANG qui définissent des RPC personnalisés sur les équipements Junos. La création de RPC personnalisés vous permet de définir avec précision les paramètres et les opérations d’entrée, ainsi que les champs de sortie et la mise en forme de vos tâches opérationnelles spécifiques sur ces équipements. Cet exemple présente un RPC personnalisé et un script d’action qui récupèrent les informations opérationnelles de l’équipement et affichent une sortie CLI personnalisée.
Le RPC est ajouté au schéma Junos OS sur l’équipement. Lorsque le RPC est exécuté dans l’interface de ligne de commande, il imprime le nom et le statut opérationnel des interfaces physiques demandées.
Exigences
Cet exemple utilise les composants matériels et logiciels suivants :
-
Équipement exécutant Junos OS version 17.3R1 ou ultérieure prenant en charge le chargement de modèles de données YANG personnalisés.
Présentation du RPC et du script d’action
Le module YANG de cet exemple définit un RPC personnalisé pour renvoyer le nom et le statut opérationnel de certaines interfaces physiques. Le module rpc-interface-status YANG est enregistré dans le fichier rpc-interface-status.yang . Le module importe les modules d’extension Junos OS, qui fournissent les extensions requises pour exécuter des RPC personnalisés sur l’équipement et personnaliser la sortie CLI.
Le module définit le get-interface-status RPC. La <get-interface-status> balise request permet d’exécuter le RPC à distance sur l’équipement. Dans la définition rpc, l’instruction junos:command définit la commande utilisée pour exécuter le RPC dans l’interface de ligne de commande, qui est show intf statusdans ce cas .
Les junos:action-execute instructions définissent junos:script le script d’action invoqué lors de l’exécution du RPC. Cet exemple utilise un script d’action Python nommé rpc-interface-status.py pour récupérer les informations requises par le RPC et retourner les éléments de sortie XML tels que définis dans l’instruction 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";
}
}
...
À partir de La version 17.3 de Junos OS, l’instruction action-execute est un sous-état de command. Dans les versions antérieures, les action-execute instructions sont command placées au même niveau et l’instruction command est facultative.
Le rpc comporte un paramètre d’entrée nommé match, qui détermine les interfaces à inclure dans la sortie. Lorsque vous exécutez le RPC, vous incluez une chaîne correspondant aux interfaces souhaitées, par exemple ge-0*. Une chaîne vide (« ») s’allume sur toutes les interfaces. Le script d’action définit la valeur match par défaut comme une chaîne vide. Si l’utilisateur omet cet argument, la sortie contient des informations pour toutes les interfaces.
input {
leaf match {
description "Requested interface match condition";
type string;
}
}
Le RPC définit également les nœuds de sortie qui doivent être émis par le script d’action correspondant. Le nœud racine est l’élément<interface-status-info>, qui contient au moins un certain <status-info> nombre d’éléments qui l’enferment et <status> les <interface> nœuds pour une interface correspondant. L’instruction junos-odl:format interface-status-info-format définit la mise en forme de la sortie affichée dans l’interface de ligne de commande. Ce nœud n’est pas émis dans l’arborescence XML de sortie.
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 {
...
}
}
}
}
Cet exemple présente deux versions du script d’action Python. Les scripts présentent des moyens différents de récupérer la sortie de commande opérationnelle, mais les deux scripts émettent une sortie RPC identique. Le premier script d’action utilise le module Python subprocess pour exécuter la show interfaces match-value | display xml commande, puis convertit la sortie de chaîne en XML. Le deuxième script d’action utilise Junos PyEZ pour exécuter l’équivalent RPC de la show interfaces match-value commande. Les deux scripts utilisent le code identique pour analyser la sortie de commande et extraire le nom et l’état opérationnel de chaque interface physique. Les scripts construisent le code XML de la sortie RPC, puis impriment la sortie, qui renvoie les informations à l’équipement. L’arborescence XML doit correspondre exactement à la hiérarchie définie dans le RPC.
Les équipements Junos définissent des espaces de noms dépendant des versions pour un grand nombre d’éléments du résultat opérationnel, y compris l’élément <interface-information> . Afin de rendre la version indépendante du système d’exploitation Rpc Junos, le code utilise la local-name() fonction dans les expressions XPath de ces éléments. Vous pouvez choisir d’inclure le mappage d’espace de noms comme argument et xpath() de qualifier les éléments avec l’espace de noms approprié.
Le module contenant le RPC et le fichier de script d’action sont ajoutés à l’équipement dans le cadre d’un nouveau package YANG nommé intf-rpc.
YANG Module
YANG Module
Le module YANG rpc-interface-status.yang définit le RPC, la commande utilisée pour exécuter le RPC dans l’interface de ligne de commande et le nom du script d’action à invoquer lors de l’exécution du RPC. Le nom de base du fichier doit correspondre au nom du module.
/*
* 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 d’action
Le script d’action correspondant est rpc-interface-status.py. Cet exemple présente deux scripts d’action qui utilisent différents moyens pour récupérer les données. Un script utilise le module Python subprocess et l’autre utilise la bibliothèque PyEZ Junos. Les deux scripts émettent la même sortie XML RPC.
À partir de Junos OS Version 21.2R1 et Junos OS Evolved Version 21.2R1, lorsque l’équipement transmet des arguments de ligne de commande à un script d’action Python, il préfixe un trait d’union unique (-) vers des noms d’arguments à caractère unique et deux traits d’union (--) vers des noms d’arguments multi-caractères.
Script d’action (utilisation subprocess)
Le script d’action suivant utilise le module Python subprocess pour exécuter la commande opérationnelle et récupérer les données. Cet exemple fournit deux versions du script, qui gèrent correctement les arguments de ligne de commande du script pour les différentes versions.
Junos OS version 21.1 et antérieure
#!/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 version 21.2R1 et versions ultérieures
#!/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 d’action (à l’aide de Junos PyEZ)
Le script d’action suivant utilise Junos PyEZ pour exécuter la commande opérationnelle et récupérer les données. Cet exemple fournit deux versions du script, qui gèrent correctement les arguments de ligne de commande du script pour les différentes versions.
Junos OS version 21.1 et antérieure
#!/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 version 21.2R1 et versions ultérieures
#!/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()
Permettre l’exécution de scripts Python
Pour permettre à l’équipement d’exécuter des scripts Python non signés :
Chargement du RPC sur l’unité
Pour ajouter le RPC et le script d’action au schéma Junos :
Vérification du RPC
But
Vérifiez que le RPC fonctionne comme prévu.
Action
Depuis le mode opérationnel, exécutez le RPC dans l’interface de ligne de commande en émettant la commande définie par l’instruction junos:command dans la définition RPC et incluez l’argument d’entrée match . Dans cet exemple, l’argument de correspondance est utilisé pour correspondre à toutes les interfaces qui commencent par 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
Vous pouvez également ajuster la condition de correspondance pour retourner différents ensembles d’interfaces. Par exemple :
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
Pour renvoyer la même sortie au format XML, ajoutez le | display xml filtre à la commande.
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>
Pour correspondre à toutes les interfaces, omettre l’argument match ou définir la valeur de l’argument sur une chaîne vide (« »).
Sens
Lorsque vous exécutez le RPC, l’équipement appelle le script d’action. Le script d’action exécute la commande opérationnelle pour récupérer les informations de l’interface sur l’équipement, analyse la sortie pour les informations souhaitées et imprime la hiérarchie XML pour la sortie RPC telle que définie dans l’instruction RPC output . Lorsque vous exécutez le RPC dans l’interface de ligne de commande, l’équipement utilise la mise en forme CLI définie dans le RPC pour convertir la sortie XML en sortie CLI affichée. Pour renvoyer la sortie XML d’origine, ajoutez le | display xml filtre à la commande.
Lorsque le RPC est exécuté à distance à l’aide de la balise de requête RPC, le format par défaut de la sortie est XML.
Dépannage des erreurs d’exécution RPC
Problème
Description
Lorsque vous exécutez le RPC, l’équipement génère l’erreur suivante :
error: open failed: /var/db/scripts/action/rpc-interface-status.py: Permission denied
Cause
L’utilisateur qui a invoqué le RPC ne dispose pas des droits nécessaires pour exécuter le script d’action Python correspondant.
Solution
Les utilisateurs ne peuvent exécuter des scripts Python non signés sur les équipements Junos que lorsque les autorisations de fichier du script incluent l’autorisation de lecture pour la première classe dans laquelle l’utilisateur se trouve, dans l’ordre de l’utilisateur, du groupe ou d’autres.
Vérifiez si le script dispose des autorisations nécessaires pour l’exécution du script et ajustez les autorisations, le cas échéant. Si vous mettez à jour les autorisations, vous devez également mettre à jour le package YANG pour que cette modification prenne effet. Par exemple :
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