Practical Network Automation, Part 1: Building Blocks

This series provides concise, practical direction for the network engineer trying to get a handle on network configuration automation at scale.

The automation architecture that we build in this series will be scalable, easy to use, efficient, and predictable. You can even integrate it with other tools in your ecosystem.

Building Blocks

This post will help you understand the foundational elements required for any good automation system. In later posts, we’ll build structure around it. For now, we just need to understand why these elements are important and how they work together.

The building blocks of this architecture:

  • Structured data
  • Templating engine
  • A few bits of python for:
    • Rendering full configurations with data and templates
    • Pushing rendered configuration to devices
  • Version control
  • Graphical user portal
  • Device inventory system

We’ll eventually dig into all of these. For now, this post will cover structured data, templating, and intro to Python.

Structured Data

If you’re brand new to network automation, you may be generating configurations by hand. This typically involves manually editing a configuration file by inserting relevant variables to the device (vlans, interfaces, ip addresses, etc.). Stop that. Let’s instead build a data set that is used by a template to automatically generate this configuration for you.

Perhaps you need to populate the vlan section of a Cisco configuration:

vlan 100
  name users
vlan 200
  name iot
vlan 300
  name wifi

That’s fine, but it’s Cisco specific. What if I need to configure the same vlans on a Juniper switch?

Let’s abstract away from this vendor specific syntax and instead take a structured data approach (we’ll use YAML). This will allow us to define vlans however we wish and use the data for any vendor operating system:

vlans:
 - id: 100
   name: users
 - id: 200
   name: iot
 - id: 300
   name: wifi

That’s nice and readable. I, the user, no longer need to care about vendor syntax. It’s also programmatic – I can iterate through the data using scripts and templates. I could also generate this data with a graphical front end. Lots of options to explore later!

This abstracted data set is part of a more complete data set. We’ll need data for all sections of the configuration that contain variables. We’ll keep using the vlans data set as a bare bones example for now, but we’ll build a complete set later.

Templating Engine

Let’s say we need to convert our data into meaningful configurations for at least two vendors: Cisco and Juniper. Why not toss in Arista too?

Instead of inserting the variables from the data set by hand, let’s use Jinja2. It will automatically process the data set and render our desired configurations.

Cisco:
{% for vlan in vlans %}
vlan {{vlan.id}}
  name {{vlan.name}}
{% endfor %}

Juniper:
{% for vlan in vlans %}
set vlans {{ vlan.name }} vlan-id {{ vlan.id }}
{% endfor %}

Arista (identical to Cisco, I know):
{% for vlan in vlans %}
vlan {{ vlan.id }}
  name {{ vlan.name }}
{% endfor %}

Did you notice that we only wrote the template configuration for a single vlan? The jinja2 template will iterate through the data set and create configurations for every vlan automatically.

Try it out using this handy web conversion tool: textfsm.nornir.tech. Click “Jinja2” at the top, paste the yaml into the leftmost pane and the jinja2 templates in the middle pane. The rightmost pane will show your resulting configuration. This tool is great for practicing and testing your templates.

Templates and structured data are key to the automation strategy we’re using. By the end of this series, you’ll be able to generate configurations for any number of switches simultaneously.

Some Python

Knowing a little bit of python can go a long way. It’s not hard to debug and the ceiling of possibilities is only limited by your imagination. You won’t need much imagination here, though. We just need to render structured data through templates.

Prerequisite python packages:

pip install Jinja2
pip install pyyaml

data.yml

vlans:
 - id: 100
   name: users
 - id: 200
   name: iot
 - id: 300
   name: wifi

templates.j2

Cisco:
{% for vlan in vlans %}
vlan {{vlan.id}}
  name {{vlan.name}}
{% endfor %}

Juniper:
{% for vlan in vlans %}
set vlans {{ vlan.name }} vlan-id {{ vlan.id }}
{% endfor %}

Arista:
{% for vlan in vlans %}
vlan {{ vlan.id }}
  name {{ vlan.name }}
{% endfor %}

render.py

import yaml
from jinja2 import Environment, FileSystemLoader
from pprint import pprint

with open("data.yml") as f:
    data = yaml.safe_load(f)

env = Environment(loader = FileSystemLoader(''), trim_blocks=True, lstrip_blocks=True)
templates = env.get_template('templates.j2')

output = templates.render(data)
print(output)

and finally, the rendered output:

Cisco:
vlan 100
  name users
vlan 200
  name iot
vlan 300
  name wifi

Juniper:
set vlans users vlan-id 100
set vlans iot vlan-id 200
set vlans wifi vlan-id 300

Arista:
vlan 100
  name users
vlan 200
  name iot
vlan 300
  name wifi

Now we’re getting somewhere useful. You could easily spend much time here and build elaborate yaml file heirarchies to store your variables, but our solution won’t need that.

No need for us to spend anymore time here – this is only an example to demonstrate the underlying concepts to our much more scalable solution.

Part 2 will cover the handling of pushing configurations to devices with the nornir python package.