Diff`ing the Network (jsondiff) - Part 2

Part 2 of the diff`ing the network series. Learn how to compare Python dictionaries using jsondiff.
Diff`ing the Network (jsondiff) - Part 2
Photo by Max Duzij / Unsplash

Welcome back to this series on Diff`ing the network. In part 1 we looked at difflib for performing network differentials when dealing with text-based, i.e unstructured data. If you missed it or need to recap see below.

Diff`ing the Network (difflib) - Part 1
A look at the different tools that we can use to perform a differential comparison—ranging from difflib for text block comparison to the more advanced diffsync.

We will now look at how to perform differential comparisons with Python dictionaries using jsondiff.

jsondiff

jsondiff is a tool that allows you to:

Diff JSON and JSON-like structures in Python.

In other words, we can compare 2 outputs, be it JSON or a Python dictionary. The differences are then returned as a Python dictionary, which we can then work with in a programmatic way to perform further actions.

Let's look at an example...

First, we install jsondiff. As always we also install rich.print to brighten things up a little! Learn more here.

$ pip3 install jsondiff rich

Next we collect the ARP cache using PyATS's Genie Parse (course here).

from genie import testbed

testbed = testbed.load("./testbeds/002_internal_testbed.yml")
device = testbed.devices["spine1-nxos"]
device.connect(log_stdout=False)

arp1 = device.parse("show ip arp")
arp2 = device.parse("show ip arp")

The 2 outputs are then compared, like so:

from jsondiff import diff

arp_diff = diff(arp1, arp2)

If we look at the results we can see the differences (shown below). The differences include the age for each of the neighbour entries. But we also see delete: ['Ethernet1/1'].

from rich import print as rprint

rprint(arp_diff)
===
{
    'interfaces': {
        'Ethernet1/2': {'ipv4': {'neighbors': {'10.1.2.2': {'age': '00:17:47'}}}},
        'Ethernet1/3': {'ipv4': {'neighbors': {'10.1.3.2': {'age': '00:04:05'}}}},
        'Ethernet1/4': {'ipv4': {'neighbors': {'10.1.4.2': {'age': '00:03:18'}}}},
        'Ethernet1/5': {'ipv4': {'neighbors': {'10.1.5.2': {'age': '00:10:04'}}}},
        'Ethernet1/6': {'ipv4': {'neighbors': {'10.1.6.2': {'age': '00:03:09'}}}},
        delete: ['Ethernet1/1']
    },
    'statistics': {'entries_total': 5}
}

This is all well and good, but what are the ARP details for the entries that are no longer present in arp2. To see this information we use syntax=symmetric, which includes the data that has changed from the starting data point e.g arp1. For example:

arp_diff_symmetric = diff(arp1, arp2, syntax="symmetric")

rprint(arp_diff_symmetric)
===
{
    'interfaces': {
        'Ethernet1/2': {'ipv4': {'neighbors': {'10.1.2.2': {'age': ['00:16:15', '00:17:47']}}}},
        'Ethernet1/3': {'ipv4': {'neighbors': {'10.1.3.2': {'age': ['00:02:33', '00:04:05']}}}},
        'Ethernet1/4': {'ipv4': {'neighbors': {'10.1.4.2': {'age': ['00:01:47', '00:03:18']}}}},
        'Ethernet1/5': {'ipv4': {'neighbors': {'10.1.5.2': {'age': ['00:08:33', '00:10:04']}}}},
        'Ethernet1/6': {'ipv4': {'neighbors': {'10.1.6.2': {'age': ['00:01:37', '00:03:09']}}}},
        delete: {
            'Ethernet1/1': {
                'ipv4': {
                    'neighbors': {
                        '10.1.1.2': {
                            'ip': '10.1.1.2',
                            'link_layer_address': '5000.0009.0000',
                            'physical_interface': 'Ethernet1/1',
                            'origin': 'dynamic',
                            'age': '00:00:09'
                        }
                    }
                }
            }
        }
    },
    'statistics': {'entries_total': [6, 5]}
}

To access the missing ARP entry we perform a lookup against delete using jd.delete (shown below).

import jsondiff as jd

deleted_arps_symmetric = arp_diff['interfaces'].get(jd.delete)

rprint(deleted_arps_symmetric)
===
{
    'Ethernet1/1': {
        'ipv4': {
            'neighbors': {
                '10.1.1.2': {'ip': '10.1.1.2', 'link_layer_address': '5000.0009.0000', 'physical_interface': 'Ethernet1/1', 'origin': 'dynamic', 'age': '00:00:09'}
            }
        }
    }
}

Now we have the details of our missing ARP entry as a Python dictionary, we can use it to perform further actions. Such as use the IP to collect the device details from Netbox (via pynetbox).

Other examples could include extracting certain keys and values to build an HTML report, trigger a Slack message to notify different teams within the business, or create troubleshooting scripts that support teams can run to quickly pinpoint issues.

Subscribe to our newsletter and stay updated.

Don't miss anything. Get all the latest posts delivered straight to your inbox.
Great! Check your inbox and click the link to confirm your subscription.
Error! Please enter a valid email address!