Example: Use a Custom YANG RPC to Retrieve Operational Information from Junos Devices
You can add YANG data models that define custom RPCs on Junos devices. Creating custom RPCs enables you to precisely define the input parameters and operations and the output fields and formatting for your specific operational tasks on those devices. This example presents a custom RPC and action script that retrieve operational information from the device and display customized CLI output.
The example adds the RPC to the Junos OS schema on the device. When you execute the RPC in the CLI, it prints the name and operational status for the requested physical interfaces.
Requirements
This example uses the following hardware and software components:
-
Device running Junos OS or device running Junos OS Evolved
-
Device supports loading custom YANG data models
Overview of the RPC and Action Script
The YANG module in this example defines a custom RPC to return the name and operational
status of certain physical interfaces. The YANG module rpc-interface-status
is saved in the rpc-interface-status.yang file. The module imports the
Junos OS extension modules, which provide the extensions required to execute custom RPCs on
the device and to customize the CLI output.
The module defines the get-interface-status RPC. The
<get-interface-status> request tag is used to remotely execute the
RPC on the device. In the RPC definition, the junos:command statement
defines the command that is used to execute the RPC in the CLI, which in this case is
show intf status.
The junos:action-execute and junos:script statements define the
action script that is invoked when you execute the RPC. This example uses a Python action
script named rpc-interface-status.py to retrieve the information
required by the RPC and return the XML output elements as defined in the RPC
output statement.
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";
}
}
...The RPC has one input parameter named match, which determines the interfaces to
include in the output. When you execute the RPC, you include a string that matches on the
required interfaces, for example ge-0*. An empty string ("") matches on all interfaces. The
action script defines the default value for match as an empty string, so if
the user omits this argument, the output includes information for all interfaces.
input {
leaf match {
description "Requested interface match condition";
type string;
}
}The RPC also defines the output nodes that the corresponding action script must emit. The root
node is the <interface-status-info> element, which contains zero or
more <status-info> elements that enclose the
<interface> and <status> nodes for a matched
interface. The junos-odl:format interface-status-info-format statement
defines the formatting for the CLI text output. The output XML tree does not include this
node.
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 {
...
}
}
}
}This example presents two versions of the Python action script. The scripts demonstrate
different means to retrieve the operational command output, but both scripts emit identical
RPC output. The first action script uses the Python subprocess module to
execute the show interfaces match-value | display xml
command and then converts the string output into XML. The second action script uses Junos PyEZ to execute the RPC equivalent of the show
interfaces match-value command. Both scripts use identical
code to parse the command output and extract the name and operational status for each
physical interface. The scripts construct the XML for the RPC output and then print the
output, which returns the information back to the device. The XML tree must exactly match
the hierarchy defined in the RPC.
Junos devices define release-dependent namespaces for many of the elements in the
operational output, including the <interface-information> element.
In order to make the RPC release independent, the code uses the
local-name() function in the XPath expressions for these elements. You
might choose to include the namespace mapping as an argument to xpath()
and qualify the elements with the appropriate namespace.
You add the module containing the RPC and the action script file to the device as part of a new
YANG package named intf-rpc.
YANG Module
YANG Module
The YANG module, rpc-interface-status.yang, defines: the RPC, the command that you use to execute the RPC in the CLI, and the name of the action script to invoke when you execute the RPC. The base name of the file must match the module name.
/*
* Copyright (c) 2024 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";
}
}
}
}
}
}
}
Action Script
The corresponding action script is rpc-interface-status.py. This example
presents two action scripts that use different means to retrieve the data. One script uses
the Python subprocess module. The other script uses the Junos PyEZ library.
Both scripts emit the same RPC XML output.
Starting in Junos OS Release 21.2R1 and Junos OS Evolved Release 21.2R1, when the device passes command-line arguments to a Python action script, it prefixes a single hyphen (-) to single-character argument names and prefixes two hyphens (--) to multi-character argument names.
Action Script (Using subprocess)
The following action script uses the Python subprocess module to execute
the operational command and retrieve the data. This example provides two versions of the
script, which appropriately handle the script's command-line arguments for the different
releases.
Junos OS Release 21.1 and earlier
#!/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 Release 21.2R1 and later
#!/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()
Action Script (Using Junos PyEZ)
The following action script uses Junos PyEZ to execute the operational command and retrieve the data. This example provides two versions of the script, which appropriately handle the script's command-line arguments for the different releases.
Junos OS Release 21.1 and earlier
#!/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 Release 21.2R1 and later
#!/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()Enable the Execution of Python Scripts
To enable the device to execute unsigned Python scripts:
Load the RPC on the Device
To add the RPC and action script to the Junos schema:
Verify the RPC
Purpose
Verify that the RPC works as expected.
Action
From operational mode, execute the RPC in the CLI by issuing the command defined by the
junos:command statement in the RPC definition, and include the
match input argument. In this example, the match argument is used to
match on all interfaces that start with 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
You can also adjust the match condition to return different sets of interfaces. For example:
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
To return the same output in XML format, append the | display xml filter
to the command.
user@host> show intf status match *e-0/*/0 | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/24.4R1/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>
To match on all interfaces, either omit the match argument or set the
value of the argument to an empty string ("").
Meaning
When you execute the RPC, the device invokes the action script. The action script
executes the operational command to retrieve the interface information from the device and
parses the output for the required information. The script then prints the XML hierarchy
for the RPC output as defined in the RPC output statement. When you
execute the RPC in the CLI, the device uses the CLI formatting defined in the RPC to
convert the XML output into the displayed CLI output. To return the original XML output,
append the | display xml filter to the command.
When you execute the RPC remotely using the RPC request tag, the default format for the output is XML.
Troubleshoot RPC Execution Errors
Problem
Description
When you execute the RPC, the device generates the following error:
error: open failed: /var/db/scripts/action/rpc-interface-status.py: Permission denied
Cause
The user who invoked the RPC does not have the necessary permissions to execute the corresponding Python action script.
Solution
Users can only execute unsigned Python scripts on Junos devices when the script's file permissions include read permission for the first class that the user falls within, in the order of user, group, or others.
Verify whether the script has the necessary permissions for that user to execute the script, and adjust the permissions, if appropriate. If you update the permissions, you must also update the YANG package in order for this change to take effect. For example:
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
Change History Table
Feature support is determined by the platform and release you are using. Use Feature Explorer to determine if a feature is supported on your platform.