Beispiel: Verwenden Eines benutzerdefinierten YANG RPC zum Abrufen von Betriebsinformationen auf Junos-Geräten
Sie können YANG-Datenmodelle hinzufügen, die benutzerdefinierte RPCs auf Junos-Geräten definieren. Durch das Erstellen benutzerdefinierter RPCs können Sie die Eingabeparameter und Betriebsabläufe sowie die Ausgabefelder und die Formatierung für Ihre spezifischen Betrieblichen Aufgaben auf diesen Geräten genau definieren. In diesem Beispiel wird ein benutzerdefiniertes RPC- und Aktionsskript dargestellt, das Betriebsinformationen vom Gerät abruft und angepasste CLI-Ausgaben anzeigt.
Der RPC wird dem Junos OS-Schema auf dem Gerät hinzugefügt. Wenn der RPC in der CLI ausgeführt wird, gibt er den Namen und Betriebsstatus für die angeforderten physischen Schnittstellen aus.
Anforderungen
In diesem Beispiel werden die folgenden Hardware- und Softwarekomponenten verwendet:
-
Gerät mit Junos OS Version 17.3R1 oder höher, das das Laden benutzerdefinierter YANG-Datenmodelle unterstützt.
Übersicht über das RPC- und Aktionsskript
Das YANG-Modul in diesem Beispiel definiert einen benutzerdefinierten RPC, um den Namen und Betriebsstatus bestimmter physischer Schnittstellen zurückzugeben. Das YANG-Modul rpc-interface-status
wird in der Datei rpc-interface-status.yang gespeichert. Das Modul importiert die Junos OS-Erweiterungsmodule, die die Erweiterungen bereitstellen, die zum Ausführen benutzerdefinierter RPCs auf dem Gerät und zur Anpassung der CLI-Ausgabe erforderlich sind.
Das Modul definiert den get-interface-status
RPC. Das <get-interface-status>
Anforderungs-Tag wird verwendet, um den RPC per Fernzugriff auf dem Gerät auszuführen. In der RPC-Definition definiert die junos:command
Anweisung den Befehl, der verwendet wird, um den RPC in der CLI auszuführen, die in diesem Fall .show intf status
Die junos:action-execute
Anweisungen definieren junos:script
das Aktionsskript, das aufgerufen wird, wenn der RPC ausgeführt wird. In diesem Beispiel wird ein Python-Aktionsskript namens rpc-interface-status.py verwendet, um die für den RPC erforderlichen Informationen abzurufen und die XML-Ausgabeelemente wie in der RPC-Anweisung output
definiert zurückzugeben.
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"; } } ...
Ab Junos OS Version 17.3 ist die action-execute
Anweisung eine Substatement zu command
. In früheren Versionen werden die action-execute
Anweisungen auf command
der gleichen Ebene platziert, und die command
Anweisung ist optional.
Der RPC hat einen Eingabeparameter namens , match
der bestimmt, welche Schnittstellen in die Ausgabe einbezogen werden sollen. Wenn Sie den RPC ausführen, fügen Sie eine Zeichenfolge ein, die auf den gewünschten Schnittstellen übereinstimmt, z. B. ge-0*. Eine leere Zeichenfolge ("") entspricht auf allen Schnittstellen. Das Aktionsskript definiert den Standardwert für match
als leere Zeichenfolge. Wenn der Benutzer dieses Argument auslässt, enthält die Ausgabe Informationen für alle Schnittstellen.
input { leaf match { description "Requested interface match condition"; type string; } }
Der RPC definiert auch die Ausgabeknoten, die vom entsprechenden Aktionsskript ausgegeben werden müssen. Der Root-Knoten ist das <interface-status-info>
Element, das keine oder mehr <status-info>
Elemente enthält, die die <interface>
Knoten für <status>
eine übereinstimmene Schnittstelle umschließen. Die junos-odl:format interface-status-info-format
Anweisung definiert die Formatierung für die Ausgabe, die in der CLI angezeigt wird. Dieser Knoten wird nicht in der XML-Ausgabestruktur ausgegeben.
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 { ... } } } }
In diesem Beispiel werden zwei Versionen des Python-Aktionsskripts dargestellt. Die Skripte weisen unterschiedliche Mittel zum Abrufen der Betriebsbefehlsausgabe auf, aber beide Skripte geben identische RPC-Ausgabe aus. Das erste Aktionsskript verwendet das Python-Modul subprocess
, um den show interfaces match-value | display xml
Befehl auszuführen, und konvertiert dann die Zeichenfolgenausgabe in XML. Das zweite Aktionsskript verwendet Junos PyEZ , um das RPC-Äquivalent des show interfaces match-value
Befehls auszuführen. Beide Skripte verwenden den gleichen Code, um die Befehlsausgabe zu analysieren und den Namen und Betriebsstatus für jede physische Schnittstelle zu extrahieren. Die Skripte erstellen das XML für die RPC-Ausgabe und drucken dann die Ausgabe, die die Informationen zurück an das Gerät zurückgibt. Die XML-Struktur muss genau mit der im RPC definierten Hierarchie übereinstimmen.
Junos-Geräte definieren versionsabhängige Namespaces für viele Elemente in der Betriebsausgabe, einschließlich des <interface-information>
Elements. Um die RPC Junos OS-Version unabhängig zu machen, verwendet der Code die local-name()
Funktion in den XPath-Ausdrücken für diese Elemente. Sie können die Namespacezuordnung als Argument xpath()
einschließen und die Elemente mit dem entsprechenden Namespace qualifizieren.
Das Modul, das den RPC und die Action-Skriptdatei enthält, wird dem Gerät als Teil eines neuen YANG-Pakets mit dem Namen intf-rpc
.
YANG-Modul
YANG-Modul
Das YANG-Modul rpc-interface-status.yang definiert den RPC, den Befehl zum Ausführen des RPC in der CLI und den Namen des Aktionsskripts, das aufgerufen werden soll, wenn der RPC ausgeführt wird. Der Basisname der Datei muss mit dem Modulnamen übereinstimmen.
/* * 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"; } } } } } } }
Aktionsskript
Das entsprechende Aktionsskript wird rpc-interface-status.py. In diesem Beispiel werden zwei Aktionsskripte dargestellt, die unterschiedliche Mittel zum Abrufen der Daten verwenden. Ein Skript verwendet das Python-Modul subprocess
und das andere Skript die Junos PyEZ-Bibliothek. Beide Skripte geben dieselbe RPC-XML-Ausgabe aus.
Ab Junos OS Version 21.2R1 und Junos OS Evolved Release 21.2R1 setzt das Gerät, wenn Befehlszeilenargumente an ein Python-Aktionsskript weiterleitet, einen einzelnen Bindestrich (-) zu einstelligen Argumentnamen vor und präfixiert zwei Bindestriche (-) zu mehrzeichenigen Argumentnamen.
Aktionsskript (Verwenden subprocess
)
Das folgende Aktionsskript verwendet das Python-Modul subprocess
, um den Betriebsbefehl auszuführen und die Daten abzurufen. In diesem Beispiel werden zwei Versionen des Skripts bereitgestellt, die die Befehlszeilenargumente des Skripts für die verschiedenen Versionen angemessen behandeln.
Junos OS Version 21.1 und früher
#!/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 und höher
#!/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()
Aktionsskript (mit Junos PyEZ)
Das folgende Aktionsskript verwendet Junos PyEZ, um den Betriebsbefehl auszuführen und die Daten abzurufen. In diesem Beispiel werden zwei Versionen des Skripts bereitgestellt, die die Befehlszeilenargumente des Skripts für die verschiedenen Versionen angemessen behandeln.
Junos OS Version 21.1 und früher
#!/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 und höher
#!/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()
Aktivieren der Ausführung von Python-Skripten
So ermöglichen Sie dem Gerät die Ausführung von unsignierten Python-Skripten:
Laden des RPC auf dem Gerät
So fügen Sie das RPC- und Aktionsskript zum Junos-Schema hinzu:
Überprüfung des RPC
Zweck
Stellen Sie sicher, dass der RPC wie erwartet funktioniert.
Aktion
Führen Sie den RPC im Betriebsmodus in der CLI aus, indem Sie den Befehl ausstellen, der durch die junos:command
Anweisung in der RPC-Definition definiert wird, und fügen Sie das match
Eingabeargument ein. In diesem Beispiel wird das Argument match für alle Schnittstellen verwendet, die mit Ge-0 beginnen.
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
Sie können auch die Übereinstimmungsbedingung anpassen, um unterschiedliche Schnittstellensätze zurückzugeben. Zum Beispiel:
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
Um dieselbe Ausgabe im XML-Format zurückzugeben, hängen Sie den | display xml
Filter an den Befehl an.
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>
Wenn Sie auf allen Schnittstellen übereinstimmen möchten, lassen Sie entweder das match
Argument aus oder setzen den Wert des Arguments auf eine leere Zeichenfolge ("").
Bedeutung
Wenn Sie den RPC ausführen, ruft das Gerät das Aktionsskript auf. Das Aktionsskript führt den Betriebsbefehl aus, um die Schnittstelleninformationen vom Gerät abzurufen, analysiert die Ausgabe für die gewünschten Informationen und druckt die XML-Hierarchie für die RPC-Ausgabe, wie in der RPC-Anweisung output
definiert. Wenn Sie den RPC in der CLI ausführen, verwendet das Gerät die im RPC definierte CLI-Formatierung, um die XML-Ausgabe in die angezeigte CLI-Ausgabe zu konvertieren. Um die ursprüngliche XML-Ausgabe zurückzugeben, hängen Sie den | display xml
Filter an den Befehl an.
Wenn der RPC remote mit dem RPC-Anforderungs-Tag ausgeführt wird, ist das Standardformat für die Ausgabe XML.
Fehlerbehebung bei RPC-Ausführungsfehlern
Problem
Beschreibung
Wenn Sie den RPC ausführen, generiert das Gerät den folgenden Fehler:
error: open failed: /var/db/scripts/action/rpc-interface-status.py: Permission denied
Ursache
Der Benutzer, der den RPC aufgerufen hat, verfügt nicht über die erforderlichen Berechtigungen, um das entsprechende Python-Aktionsskript auszuführen.
Lösung
Benutzer können nicht signierte Python-Skripte nur auf Junos-Geräten ausführen, wenn die Dateiberechtigungen des Skripts Leseberechtigungen für die erste Klasse enthalten, in der der Benutzer in der Reihenfolge von Benutzer, Gruppe oder anderen gehört.
Überprüfen Sie, ob das Skript über die erforderlichen Berechtigungen verfügt, damit dieser Benutzer das Skript ausführen kann, und passen Sie die Berechtigungen gegebenenfalls an. Wenn Sie die Berechtigungen aktualisieren, müssen Sie auch das YANG-Paket aktualisieren, damit diese Änderung wirksam wird. Zum Beispiel:
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