Genja logo

Junos Operational Tables and Views

video

Junos PyEZ Table and Views Blog.png

by Andre Bowen-Brown

Jan. 24, 2022

Automate network operations with a standardised API


What are Operational Tables and Views?

We already covered operational automation within the Juniper Operational Automation blog of the series. It covered how to use RPC requests to return XML structured data by default. The equivalent of a show command using the CLI on a Junos device. Data was then available to use in a Python native format. We then went on to output the data with styling into a

Tables are:

A "table" is a collection of data for a given XML RPC response, storing each set of data under a unique key name. Each key represents the nodes data and is used to get the data when utilised within a view. Like a database, you have a table name and an id for each entries collection of data.

Views are:

A "view" is a representation of the XML RPC response tree items, mapping the XML element names to a Python structured dictionary.


How to find the list of tables available?

The files are stored in the op/ sub-directory of the junos.jnpr module of the PyEZ library. We need to find the location of the junos.jnpr module to determine the absolute path. Using the interactive mode in Python3, we can use os's os.path.dirname() function to find the name of the directory.


Startup the virtual env

If you don't know how to startup a virtual environment, instructions can be found in my previous blog, Installing Junos PyEZ. I'll also be making a copy of the Github connect_ssh_key.py file, used as a base template for the new script named opertational_tables.py. Place in a new directory called operations. In an attempt to tidy up the folder structure.

cd genja-tutorials/PyEZ/ 
source bin/activate
mkdir operations
cp connect_ssh_key.py operations/operational_table.py
cd operations

Having started up the virtual environment, created the new directory, and made a copy of the connect_ssh_key.py file. We now need to start up an interactive python3 session.


Start python interactively

python3

This will be used as a way to locate the operational tables name in Junos PyEZ.

import jnpr.junos
import os
path = os.path.dirname(jnpr.junos.__file__)
Output>>…  directories absolute path
quit()

Directory path

/home/user/genja-tutorials/Pyez/lib/python3.8/site-packages/jnpr/junos

Change to the directory

Change user to your user name below

cd /home/user/genja-tutorials/Pyez/lib/python3.8/site-packages/jnpr/junos/

We can then grep for the Table names of the op subdirectory *.yml files. The table names are the same as class names used for import.

grep "Table:" op/*.yml

Result

Below is just a snippet of the tables. You'll have more in your result.

grep operational tables.png



Bash Alternative - (find)

Linux shell.png

A quicker way of finding the files is using the Bash command find. find is capable of finding directories or files anywhere on your Unix-like OS.

find ~/genja-tutorials/pyez -path *op/*.yml -exec grep "Table:" {}
 > ~/genja-tutorials/PyEZ/tutorials/operations/Operational_Tables.txt \;
  • find ~/genja-tutorials/PyEZ: tells find to use ~/genja-tutorials/PyEZ as the parent directory (top-level)
  • - path: doesn't treat the / as a special character allowing you to enter a partial directory and filename
  • *: is a special glob character in Linux, used to match any number of characters
  • - exec: allows you to execute another bash command if the result is true. In this instance, we will use the grep command to find the string containing "Table:" in the filename held in the curly braces "{}". The exec command must end with the terminator \; or ";"
  • >: redirects the output and creates a new file within the specified directory

The results show the pathnames relative to the current working directory and the predefined operational tables. All in one command!

Sometimes a bash command can be quicker than using Python3 when working with the native file system.



Import the modules based on the table name

The import statement is a combination of the .yml file name minus the extension and the table name held within the named file, from within the directory jnpr/junos/op/ i.e.,

from jnpr.junos.op.arp import ArpTable

Open up the new file in the operational folder called operation_table.py to start building the script. We'll start by importing the module for ArpTable placed at the top with the rest of the other imports.

from jnpr.junos.op.arp import ArpTable

When creating an object of the selected table class, it requires a Device instance to be passed as an argument. So, prior to creating the *Table object, you need to initiate the Device instance inserted beneath the command print("Are we.."

arp_table = ArpTable(dev)

Once the object has been created you can use the methods associated with the table to start gathering information. Let's start by populating the ArpTable using the get method. If we print the instance of the arp table after the get method, we can see how many IP addresses it gathered from the arp table.

arp_table.get()
print(arp_table)
from jnpr.junos import Device
from getpass import getpass
import jnpr.junos.exception
from jnpr.junos.op.arp import ArpTable


ipadd = input('Enter hostname or IP address: ')
key_password = getpass('Enter key password: ')


try:
    with Device(host=ipadd, password=key_password) as dev:
        print('Are we connected?', dev.connected)
        arp_table = ArpTable(dev)
        arp_table.get()
        print(arp_table)
except jnpr.junos.exception.ConnectAuthError as autherr:
    print('Check key password', autherr)

The output displays the number of items equal to the number of IP addresses found in the ARP table. If you don't want the entire ARP table, there is the option of passing an argument (arg) or keyword argument (kwarg). In the case of show arp on the command line, we could specify no-resolve as an argument or hostname=10.0.3.2 as a keyword argument. We know hostname is a keyword argument because if you'd just entered hostname on the cli and tried to hit enter, you would get a prompt asking for a value.


How to create a python function

We will create a python function to break up the script and filter the arp_table.get() command as mentioned a moment ago based on hostname, which would effectively be an IP address.

Let's call the new function get_arp_info. The function is placed just below the password variable and before the try-except statement. Python functions need to be created before being called into use.

def get_arp_info(ip_addr, dev):
    """ Get the mac address from the arp table for the given 
    IP address. 
    """
    arp_table = ArpTable(dev)
    print(arp_table.get(hostname=ip_addr))
    print(arp_table.items())
    items = len(arp_table.keys())
    for ip in range(items):
        if arp_table[ip]['ip_address'] == ip_addr:
            print("\n\nFound the ip address", arp_table[ip]['ip_address'])
            return arp_table[ip]['mac_address'] 
    return

We can remove the three statements we added to the try-except clause as the function now contains them.

  • def get_arp_info(ip_addr, dev) - the function name get_arp_info is expecting two arguments. ip_addr,which would be entered by the user, and dev taken from the Device class instance
  • print(arp_table.get(hostname=ip_addr)) - it will populate the arp_table with the entries matching the given IP address and print out how many items found. It should just be one, providing there are no duplicates
  • for ip in range(items) - loops through ar - sanity check of times equal to the total number of items in the arp_table
  • if arp_table[ip]['ip_address'] == ip_addr - sanity checks the IP address
  • return arp_table[ip]['mac_address'] - return terminates the function and returns the mac address of the filtered IP address to be used in the next function


How to call the function

Before showing you the complete code, two fundamental pieces are missing. The input of the IP address and a variable named mac_addr within the try-except statement after the print statement.

ip_addr = input is required to get the IP address from the user, rather than hardcoding it.

ip_addr = input("Enter an IP address to locate: ")        
mac_addr = get_arp_info(ip_addr, dev)
  • ip_addr = input() - will prompt the user to enter an IP address stored as a variable named ip_addr
  • mac_addr - is a variable used to store the returned d statement should e below.

Complete code

The complete code with the ip_addr input and mac_addr statement should resemble the code below.

from jnpr.junos import Device
from getpass import getpass
import jnpr.junos.exception
from jnpr.junos.op.arp import ArpTable


ipadd = input('Enter hostname or IP address: ')
key_password = getpass('Enter key password: ')

def get_arp_info(ip_addr, dev):
    """ Get the mac address from the arp table for the given 
    IP address. 
    """
    arp_table = ArpTable(dev)
    print(arp_table.get(hostname=ip_addr))
    print(arp_table.items())
    items = len(arp_table.keys())
    for ip in range(items):
        if arp_table[ip]['ip_address'] == ip_addr:
            print("Found the ip address", arp_table[ip]['ip_address'])
            return arp_table[ip]['mac_address'] 
    return

try:
    with Device(host=ipadd, password=key_password) as dev:
        print('Are we connected?', dev.connected)
        ip_addr = input("Enter an IP address to locate: ")
        mac_addr = get_port_arp(ip_addr, dev)
except jnpr.junos.exception.ConnectAuthError as autherr:
    print('Check key password', autherr)


OutcomeSave the configuration and run the script. Input the IP address of choice when prompted. It should find if it exists on the network. Bear in mind this is using a standalone device. If the network contains multiple devices, the script may need to be tweaked.

get_arp_result.png


Taking it a step further - get the physical port

To take it a step further, we could use the ethernet switching table to get the physical interface of the IP address. Hence, this was the reason for returning the mac address from the get_arp_info function and creating a variable. Filtering of the ethernet switching table can be done but requires the mac address.

We'll create another function called get_els_ether_table. The reason for the ELS (Enhanced Layer Switching) at the beginning is a gentle reminder of the type of Junos code we are using. By now, most devices should be using the ELS style of configuration. Otherwise, you are using an old version of Junos for your device.

First, we need to import the ElsEthernetSwitchingTable class.

from jnpr.junos.op.elsethernetswitchingtable import ElsEthernetSwitchingTable

Then

Add the following after the get_arp_info function.

def get_els_ether_table(mac_addr, dev):
    """ Get the physical interface name from the ethernet table of the given 
    IP address. 
    """
    ether = ElsEthernetSwitchingTable(dev)
    ether.get(mac_addr)
    print("\n\n\nFound the interface.", ether[mac_addr]['logical_interface'],
    end="-" * 70)
    print()


Amend try-except clause

We also need to add a little extra within the try-except clause. We'll be passing the mac_addr variable to the function as an argument. It's placed just below the mac_addr variable.

if mac_addr:
    get_els_ether_table(mac_addr, dev)
else:
    print("Not found")


Fully updated code

You can copy the code below or from my GitHub repository operational_table.py.

from jnpr.junos import Device
from getpass import getpass
import jnpr.junos.exception
from jnpr.junos.op.arp import ArpTable
from jnpr.junos.op.elsethernetswitchingtable import ElsEthernetSwitchingTable


ipadd = input('Enter hostname or IP address: ')
key_password = getpass('Enter key password: ')


def get_arp_info(ip_addr, dev):
    """ Get the mac address from the arp table for the given 
    IP address. 
    """
    arp_table = ArpTable(dev)
    print(arp_table.get(hostname=ip_addr))
    print(arp_table.items())
    items = len(arp_table.keys())
    for ip in range(items):
        if arp_table[ip]['ip_address'] == ip_addr:
            print("\n\nFound the ip address:", arp_table[ip]['ip_address'])
            return arp_table[ip]['mac_address'] 
    return


def get_els_ether_table(mac_addr, dev):
    """ Get the physical interface name from the ethernet table of the given 
    IP address. 
    """
    ether = ElsEthernetSwitchingTable(dev)
    ether.get(mac_addr)
    print("\n\nFound the interface.", ether[mac_addr]['logical_interface'],
    "\n\n", end="-" * 70)
    print()


try:
    with Device(host=ipadd, password=key_password) as dev:
        print('Are we connected?', dev.connected)
        ip_addr = input("Enter an IP address to locate: ")
        mac_addr = get_port_arp(ip_addr, dev)
        if mac_addr:
            get_els_ether_table(mac_addr, dev)
        else:
            print("Not found")
except jnpr.junos.exception.ConnectAuthError as autherr:
    print('Check key password', autherr)

And there we have it

els_get_port_result.png

The initial extra effort and time to create the script can reduce the time of future checks. Imagine trying this on a larger scale! It would seem like a tedious task when doing it manually. However, amending the script for this would certainly make life easier.