Cisco IOS XR has an XML API you can use to retrieve structured data from the device. In this article, I will run through several examples on how you can do this with Python. I will use the backend NAPALM library called pyIOSXR to issue RPCs. I’ll also show some examples on how to convert XML into a Python dictionary and I will finish up with an example where I use an XPATH expression to find something interesting in the XML the IOS XR device returns.

But first I want to highlight something that will make working with the API a little easier and show you how to figure out what information you can retrieve from the CLI.

Viewing the management data APIs from the CLI

We can use the ‘show operational’ command to display the management data APIs from which you can retrieve operational information. For instance, to output the management data APIs for OSPF, you can issue the following command:

iosxr#show operational OSPF
Fri Feb  7 14:03:41.517 UTC
[OSPF]
ProcessTable
  Process/ProcessName=10
    DefaultVRF
      AreaTable
        Area/AreaID=0
          FloodListAreaTable
            FloodList/InterfaceName=Loopback0
              FloodInterfaceName: Loopback0
< output omitted > 

You can also output the data in XML using the following command:

iosxr#show operational OSPF xml

The output that you will see on screen is the following:

<?xml version="1.0"?>
<Response MajorVersion="1" MinorVersion="0" IteratorID="1">
    <Get>
        <Operational>
            <OSPF MajorVersion="8" MinorVersion="29">
                <ProcessTable>
                    <Process>
                        <Naming>
                            <ProcessName>
                                10
                            </ProcessName>
                        </Naming>
                        <DefaultVRF>
                            <AreaTable>
                                <Area>
                                    <Naming>  
<output omitted>                                               

Instead of parsing through all this, you can also to drill all the way down to display exactly what we are looking for:

show operational OSPF processTable process/ProcessName=10 defaultVRF AdjacencyInformation

show operational OSPF ProcessTable process/ProcessName=10 DefaultVRF AreaTable area/AreaID=0  InterfaceBriefTable

Retrieving information from the device

The following example script will connect to the device using pyIOSXR. After setting up a connection to the device, the make_rpc_call function will pass an RPC to the device. The make_rpc_call returns the XML reply as a string.

from pyIOSXR import IOSXR
import getpass

password = getpass.getpass() 

iosxr = IOSXR('10.0.0.1', 'salt123', password)
iosxr.open()
rpc_command = '<Get><Operational><OSPF><ProcessTable></ProcessTable></OSPF></Operational></Get>'
rpc_xml_string = iosxr.make_rpc_call(rpc_command)
iosxr.close()

print(rpc_xml_string)

Though the script neatly displays how to get the RPC reply, it returns a very large string to screen and is not really all that helpful. A quick and easy way to deal with the XML might be using xmltodict:

from pyIOSXR import IOSXR
import xmltodict
import getpass
from pprint import pprint

password = getpass.getpass() 

iosxr = IOSXR('10.0.0.1', 'salt123', password)
iosxr.open()
rpc_command = '<Get><Operational><OSPF><ProcessTable></ProcessTable></OSPF></Operational></Get>'
rpc_xml_string = iosxr.make_rpc_call(rpc_command)
iosxr.close()

rpc_xml_dict = xmltodict.parse(rpc_xml_string)

pprint(rpc_xml_dict['Response']['Get']['Operational']['OSPF']['ProcessTable']['Process']['DefaultVRF']['AdjacencyInformation'])

In the previous example, we use xmltodict.parse() and turn the returned XML into a dictionary. This makes it easy to display only the information we are interested in. Note that instead of xmltodict, another package that might be useful in this case is jxmlease. Using jxmlease in the previous example script could have been done something like this:

# using jxmlease
import  jxmlease
root = jxmlease.parse(rpc_xml_string)
root.prettyprint()

using the previous example, we filter out the information that we display. But to be more efficient and speed things up, we can drill down and ask the IOSXR only what we are interested in. Let’s see how we can narrow down things down to include the NeighborTable from AdjacencyInformation only:

iosxr#show operational OSPF processTable process/ProcessName=10 defaultVRF AdjacencyInformation NeighborTable xml 

This will return the following information:

<?xml version="1.0"?>
<Response MajorVersion="1" MinorVersion="0">
    <Get>
        <Operational>
            <OSPF MajorVersion="8" MinorVersion="29">
                <ProcessTable>
                    <Process>
                        <Naming>
                            <ProcessName>
                                10
                            </ProcessName>
                        </Naming>
                        <DefaultVRF>
                            <AdjacencyInformation>
                                <NeighborTable>
                                    <Neighbor>
<output omitted>

From this output, we can build the following RPC:


<Get><Operational><OSPF><ProcessTable><Process>
    <Naming><ProcessName>10</ProcessName></Naming>
    <DefaultVRF><AdjacencyInformation><NeighborTable></NeighborTable></AdjacencyInformation></DefaultVRF>
</Process></ProcessTable></OSPF></Operational></Get>

To issue that RPC, we can use the following example script:

from pyIOSXR import IOSXR
import getpass
import xmltodict
from pprint import pprint

password = getpass.getpass() 

iosxr = IOSXR('10.0.0.1', 'salt123', password)
iosxr.open()
rpc_command = '''
<Get><Operational><OSPF><ProcessTable><Process>\
<Naming><ProcessName>10</ProcessName></Naming>\
<DefaultVRF><AdjacencyInformation><NeighborTable></NeighborTable></AdjacencyInformation></DefaultVRF>\
</Process></ProcessTable></OSPF></Operational></Get>
'''
rpc_xml_string = iosxr.make_rpc_call(rpc_command)

iosxr.close()

rpc_xml_dict = xmltodict.parse(rpc_xml_string)

pprint(rpc_xml_dict)

Working with the XML return

In this final example, we’ll work with the XML return. We will use findall to extract all OSPF neighbors. After this, we iterate the neighbors to collect some information using find and store the information that we retrieve in a dictionary. Finally, we will print the dictionary to screen:

from pyIOSXR import IOSXR
from lxml import etree as ETREE
import getpass

from pprint import pprint

password = getpass.getpass() 

iosxr = IOSXR('10.0.0.1', 'salt123', password)
iosxr.open()
rpc_command = '''
<Get><Operational><OSPF><ProcessTable><Process>\
<Naming><ProcessName>10</ProcessName></Naming>\
<DefaultVRF><AdjacencyInformation><NeighborTable></NeighborTable></AdjacencyInformation></DefaultVRF>\
</Process></ProcessTable></OSPF></Operational></Get>
'''
rpc_xml_string = iosxr.make_rpc_call(rpc_command)

iosxr.close()

result_tree = ETREE.fromstring(rpc_xml_string)

ospf_neighbors = result_tree.findall('.//NeighborTable/Neighbor')

ospf_d = {}

for neighbor in ospf_neighbors:
    neighbor_id = neighbor.find('./NeighborID').text
    neighbor_interface = neighbor.find('./NeighborInterfaceName').text
    neighbor_uptime = neighbor.find('./NeighborUpTime').text
    ospf_d[neighbor_id] = {
        'interface' : neighbor_interface,
        'uptime' : neighbor_uptime,
    }

pprint(ospf_d)

This gave me the following output:

{'10.0.0.2': {'interface': 'HundredGigE0/0/0/4', 'uptime': '6599559'},
 '10.0.0.3': {'interface': 'HundredGigE0/0/0/5', 'uptime': '2434822'}}

When I was producing these examples, I was running Cisco IOS XR Software, Version 6.6.3.30I and using pyIOSXR version 0.53 that came with napalm 2.5.0.