Ansible – Infrastructure as Code Part 3 (Let’s do something interesting!)

We’ve collected our state data in Part 2.

Now let’s do something interesting with our data. I’ve switched out the role I will be using. So this will be a long blog post on how to set up and finally make something pretty with your data!

 

The Setup

What you’ll need –

The reason I’m using a different module for this, is that it will automatically grab a snapshot and format it into JSON. It has other features, like compare that I won’t get into. But for now, this will be good enough to start manipulating data.

The Play Book

---
- hosts: localhost
connection: local
gather_facts: no

- name: Interface Snapshot
hosts: switches
gather_facts: no
connection: network_cli
roles:
- ansible-pyats

tasks:
- name: Gather Snapshot
include_role:
name: ansible-pyats
tasks_from: snapshot_command

vars:
command: show interfaces
file: "snapshots/{{ inventory_hostname }}_interface_snapshot.json"

This play book is assigned to the switches in the inventory. It calls the role that we downloaded from GitHub with the task snapshot_command. I’m telling it to snapshot the show interfaces command. 

Step 1 – Grab the state of the interfaces using your playbook and output to JSON.

Step 2 – Import the data into Jupyter Notebook and convert it into useable information using Pandas.

Now you may be asking why Pandas and why Jupyter? I have a blog post here on why it’s easier to work with. On the note about Pandas, it’s a great tool for quickly putting information into a structure that easier to analyze. Need to do math based on Date time? Need to do some quick counting on cell values? Or maybe convert row data to columns? Very quick an easy to do in Pandas. So let’s get started.

Now this may seem a little denser than normal. But this is a direct export from Jupyter. This is using Python and the two modules to make the magic happen. If you want to try it out, you can copy the code below into Jupyter and use this JSON document. 

import pandas as pd #import pandas for data manipulation
import plotly.express as px #For a quick pretty graph at the end

df
= pd.read_json('/Users/**/Automation/ansible/snapshots/SW1_interface_snapshot.json') #Import the JSON document df.loc['arp_timeout':'bandwidth' , 'FastEthernet0/1':'FastEthernet0/13'] #grab the first 5 columns and top 3 rows
  FastEthernet0/1 FastEthernet0/10 FastEthernet0/11 FastEthernet0/12 FastEthernet0/13
arp_timeout 04:00:00 04:00:00 04:00:00 04:00:00 04:00:00
arp_type arpa arpa arpa arpa arpa
bandwidth 100000 10000 10000 10000 10000
df2 = df.loc[ ['line_protocol', 'last_input'] , : ] #export the desired values
df2.loc['line_protocol':'last_input', 'FastEthernet0/1':'FastEthernet0/13']
  FastEthernet0/1 FastEthernet0/10 FastEthernet0/11 FastEthernet0/12 FastEthernet0/13
line_protocol up down down down down
last_input 00:00:01 never never never never
df2 = df2.transpose() #flip the columns and rows; by default the interfaces are the columns
df2.head(3)
  line_protocol last_input
FastEthernet0/1 up 00:00:01
FastEthernet0/10 down never
FastEthernet0/11 down never
df2.loc[(df2.last_input == "never"), 'last_input']='23:59:59' #convert never to time value
df2.head(3)
  line_protocol last_input
FastEthernet0/1 up 00:00:01
FastEthernet0/10 down 23:59:59
FastEthernet0/11 down 23:59:59
df2["last_input"]= pd.to_datetime(df2["last_input"]) #convert time values to datetime; panda adds todays date
df2.head(3)
  line_protocol last_input
FastEthernet0/1 up 2020-06-18 00:00:01
FastEthernet0/10 down 2020-06-18 23:59:59
FastEthernet0/11 down 2020-06-18 23:59:59
df_value_counts = df2['line_protocol'].value_counts() #grab value counts and put into new dataframe
df_value_counts = df_value_counts.reset_index() #reset the index
df_value_counts.columns = ['State', 'Count'] #set column values
df_value_counts #display values
  State Count
0 down 26
1 up 6
fig = px.bar(df_value_counts,              # dataframe
       x="State",         # x will be the 'State' column of the dataframe
       y="Count",   # y will be the 'Count' column of the dataframe
       color="State", # color gets assigned to the State axis
       title=f"Interface State",
       labels={"State": "Up/Down","Count": "Count"}, # the axis names
       color_discrete_sequence=["red", "green"], # the colors used
       height=500,
       width=800) 
fig.show()
Interface State Graph

 

Ansible – Infrastructure as Code Part 2 (State Data)

Now that you have backups running successfully. Let’s talk about abstracting data states!

The Why?

It’s pretty important to differentiate between state and config data. Configuration will only get you half the picture and in many cases less than half. How are relationships built on your network? Do you know who the primary neighbors for your devices are? That’s state information, many people keep it in their visio. But the critical piece here is you need that information to build programmability. That’s where state abstraction comes into play.

We will be using Ansible to gather this information and with the help of the PyATS library do some quick state abstraction and turn it into something useful.

State Abstraction –

What you’ll need –

  • Python
  • Ansible
  • PyATS – Ansible Galaxy Module

The Play –

---
- name: pyATS testing
  hosts: cisco
  gather_facts: no
  connection: network_cli
  roles:
    - parse_genie

  tasks:
  - name: Run the show ip interface command
    ios_command:
      commands:
        - show ip interface brief
    register: interface_output

  - name: Set fact with Genie filter plugin
    set_fact:
      pyats_showipinterface: "{{ interface_output['stdout'][0] | parse_genie(command='show ip interface brief', os='ios') }}"

  - name: Debug interface parse
    debug:
      var: pyats_showipinterface

  - name: Convert parse data to YAML
    template:
      src: templates/show_ip_interface_brief.j2
      dest: "reports/{{ inventory_hostname }}"

This play book logs into each device and runs the “show ip interface brief” command. It then parses the output into a JSON structure using PyATS Genie. In the final step it calls a Jinja template and converts to a structured YAML format.

You’ll need to install Parse Genie. The directions are available on Ansible Galaxy. I did rename my folder for easier reference. Normally the role is “(user).(module)”.

Here’s the Jinja template –

---
interfaces:
{% for key1 in pyats_showipinterface %}
{% for nested_key in pyats_showipinterface[key1] %}
- name: {{ nested_key }}
- IP address: {{ pyats_showipinterface[key1][nested_key]["ip_address"] }}
- Status: {{ pyats_showipinterface[key1][nested_key]["status"] }}
{% endfor %}
{% endfor %}

Jinja is a Python templating engine. If you need anything done at speed, you’ll need to hand your data to Jinja. Could I simplify this and just call the nested key? Yes, but this also shows you how to parse through multiple nested keys. Very handy if say you have a Route Table -> VRFs -> Routes. You may need to parse multiple key value structures.

You can take data that’s human readable-

R1#sh ip int bri
Interface              IP-Address      OK? Method Status                Protocol
GigabitEthernet1       unassigned      YES NVRAM  up                    up      
GigabitEthernet1.13    155.1.13.1      YES NVRAM  up                    up      
GigabitEthernet1.100   169.254.100.1   YES NVRAM  up                    up      
GigabitEthernet1.146   155.1.146.1     YES NVRAM  up                    up      
GigabitEthernet2       unassigned      YES NVRAM  administratively down down    
GigabitEthernet3       192.168.3.61    YES NVRAM  up                    up      
Loopback0              150.1.1.1       YES NVRAM  up                    up      
Tunnel0                155.1.0.1       YES NVRAM  up                    up

And present it in a format that a machine can consume and is human readable –

---
interfaces:
- name: GigabitEthernet1
  - IP address: unassigned
  - Status: up
- name: GigabitEthernet1.13
  - IP address: 155.1.13.1
  - Status: up
- name: GigabitEthernet1.100
  - IP address: 169.254.100.1
  - Status: up
- name: GigabitEthernet1.146
  - IP address: 155.1.146.1
  - Status: up
- name: GigabitEthernet2
  - IP address: unassigned
  - Status: administratively down
- name: GigabitEthernet3
  - IP address: 192.168.3.61
  - Status: up
- name: Loopback0
  - IP address: 150.1.1.1
  - Status: up
- name: Tunnel0
  - IP address: 155.1.0.1
  - Status: up

Conclusion –

Ansible with the power of modules and Jinja allow you to take your current deployment and put it into a consumable format. This is just a fraction of what’s possible. I only picked interfaces since it’s a common and easy item to pull state info from. But the possibilities are endless. Take a look at the Genie parsers for yourself and consider what you can accomplish!

https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/parsers

Scroll to Top