Devops for Networking Forum in Santa Clara

Normally, I would be writing this a few weeks ago, but sometimes the world just takes the luxury of time away from you.  In this case, I couldn’t be happier though as I’m about to part of something that I believe is going to be really really amazing.  This event is really a testimony to Brent Salisbury and John Willis’s commitment to community and their relentless pursuit of trying to evolve the whole industry, bringing along as many of the friends they’ve made along the way as possible. 

Given the speaker list, I don’t believe there’s been any event in recent ( or long term!) memory that has such an amazing list of speakers. The most amazing part is that this event was really put together in the last month!!!! 

If you’re in the bay area, you should definitely be there. If you’re not in the area, you should buy a plane ticket as you might not ever get a chance like this again. 

 

DevOps Forum for Networking

From the website

 

previously known as DevOps4Networks is an event started in 2014 by John Willis and Brent Salisbury to begin a discussion on what Devops and Networking will look like over the next five years. The goal is to create a conversation for change similar to what CloudCamp did for Cloud adoption and DevopsDays for Devops.

 

When and Where

You can register here

DevOps Networking Forum 2016

Monday, March 14, 2016 9:00 AM – 5:00 PM (Pacific Time)

Santa Clara Convention Center
5001 Great America Pkwy
Santa ClaraCalifornia 95054
United States
Questions? Contact us at events@linuxfoundation.org

 Who

You can hit the actual speakers page here, but the here’s the short list

  • Kelsey Hightower, Google,
  • Kenneth Duda, Arista
  • Dave Meyer, Brocade
  • Anees Shaikh, Google
  • Chris Young, HPE
  • Leslie Carr, SFMIX
  • Dinesh Dutt, Cumulus
  • Petr Lapukhov, Facebook
  • Matt Oswalt, keepingitclasseless 
  • Scott Lowe, VMware

I’ve also heard that other of a few industry notables who will be wandering the hallways as ONS starts to spin up for the week. 

Yup. What an amazing list and for the low low price of $100, you can join us as well!

OMG

Im absolutely honoured and, to be honest, a little intimidated to be sharing a spot with some of the industry luminaries who have been guiding lights personally for me in the last five years. I’m hoping to be a little education, a little entertaining, and other than that, I’ll be in the front row with a box of popcorn soaking up as much as I can from the rest of the speakers.  

Hope to see you there!

 

@netmanchris

 

Implenting Idempotency using HPE IMC

 

Try saying that five times fast.

 

What if those VLANS already exist?

There’s a concept called idempotency which can be loosely explained as

Make sure it’s like this. If it’s not like this, make it like this. If it’s already like this. Don’t do anything

Essentially, it’s a way to declare the desired configuration state of whatever it is you’re trying to configure. If the configuration state of that server, or switch or router is already in that state, than just leave it alone.

It’s a way to ensure that configuration drift doesn’t happen.

So if there’s some rabbid network administrator with a console cable running around laughing maniacly as they randomly changes things… this will help you keep them in check.

jack photo

 

Idempotent VLANs

So we’re going to look at the last example here where we did the following:

  • grabbed the jinja template for vlans directly from a GIThub repository
  • grabbed the desired vlans file directly from a GIThub repository
  • renderd the Jinja template using the values from the vlan file to get our final config
  • used the pyhpeimc library to push the commands through the executecmd RESTful API
 

Import Libraries

You know the drill here, right? Like in all the other examples, and pretty much every useful python script on the planet, we need to first import the specific libraries that we need to help us achieve whatever outcome it is that we want to perform.

In [2]:
import requests
import yaml
import time
from pyhpeimc.auth import *
from pyhpeimc.plat.device import *
from pyhpeimc.plat.icc import *
from pyhpeimc.plat.vlanm import *
auth = IMCAuth("http://", "10.101.0.203", "8080", "admin", "admin")
#auth = IMCAuth("http://", "kontrolissues.thruhere.net", "8086", "admin", "admin")
 

Download VLANs list from Github

Just like in the last blog post, we’re going to download the VLAN’s directly from the GIThub account. This ensures that we’ve got control versioning in place, as well as all the collaborative multi-user goodness that GIThub gives us. If you’re not already using it for SOMETHING. You should be asking yourself “why”?

In [3]:
desired_vlan_list = yaml.load(requests.get('https://raw.githubusercontent.com/netmanchris/Jinja2-Network-Configurations-Scripts/master/vlans.yaml' ).text)
 

As we’re just starting to play around with this, it’s always good to ensure that what we THINK we’ve got is what we’ve actually got. We’re going to now print out the contents of the GITHub file to make sure we know exactly what VLANs are actually in there.

In [4]:
print (yaml.dump(desired_vlan_list['vlans'], indent = 4))
 
- {vlanId: '1', vlanName: default, vlanStatus: '1'}
- {vlanId: '2', vlanName: TenantABC, vlanStatus: '1'}
- {vlanId: '3', vlanName: management, vlanStatus: '1'}
- {vlanId: '10', vlanName: mgmt, vlanStatus: '1'}

 

Gather just the VLAN IDs

If this was my production network, I’d probably be doing more than just checking the VLAN ID, but for our purposes, I’d like to do a quick and dirty “Does a VLAN with this ID exist or not on the device I’m looking at” check.

I’m not currently doing 802.1x identify based networking usng the VLAN name as the deployment key, so this is going to work just fine for me.

I’m going to do a list comprehension to pull out just the VLAN IDs from the YAML file above and store them in the variable called desired vlans_ids. This will setup the list of things VLAN IDs I want to compare the current state to. Make sense?

In a nutshell, this new list will let us compare the desired VLAN IDs to the existing VLAN IDs fairly easily.

In [14]:
desired_vlan_ids = [vlan['vlanId'] for vlan in desired_vlan_list['vlans']]
desired_vlan_ids
Out[14]:
['1', '2', '3', '10']
 

Get Current VLANs on Target Device

Now that we’ve got the desired list, we need to figure out the existing list of VLANs on the target device. This is a two step process

  • get the device ID of the target device using the get_dev_details function and look at the value in the id key.
  • run the get_dev_vlans function usng the devid from step one as the inut value to designate the target device.
In [15]:
devid = get_dev_details('10.20.10.10', auth.creds, auth.url)['id']
dev_vlan_list = get_dev_vlans(devid, auth.creds, auth.url)
 

What do we have here?

As with the other steps, we’ll stop here and take a look to see exactly what’s currently on the device to make sure that our code is working as desired. In a production environment, we would have to trust that this was all working properly, and make sure that we had all the appropriate tests built into our code to make sure that the trust was well deserved.

In [16]:
print (yaml.dump(dev_vlan_list, indent = 4))
 
- {vlanId: '1', vlanName: default, vlanStatus: '1'}
- {vlanId: '5', vlanName: DoesntBelong, vlanStatus: '1'}

 

Add Desired VLANs to Target Device

Now that we’ve got the current and desired state of the VLANs on the device. We need to figure out how to make them match.

For the first step, we will need to figure out how to create and any of the missing VLANs and push them to the target device.

Thankfully, there’s a create_dev_vlan function in the pyhpeimc library that allows us to push VLANs to the device directly using an API without having to use the CLI. No CLI commands is a good thing here, right?

This means that we will not have to worry about vendor specific syntax and can focus on what really matters which is the VLAN IDs, names, and descriptions. Everything else is just details.

In [17]:
help (create_dev_vlan)
 
Help on function create_dev_vlan in module pyhpeimc.plat.vlanm:

create_dev_vlan(devid, vlanid, vlan_name, auth, url)
    function takes devid and vlanid vlan_name of specific device and 802.1q VLAN tag and issues a RESTFUL call to add the
    specified VLAN from the target device. VLAN Name MUST be valid on target device.
    :param devid: int or str value of the target device
    :param vlanid:int or str value of target 802.1q VLAN
    :param vlan_name: str value of the target 802.1q VLAN name. MUST be valid name on target device.
    :return:HTTP Status code of 201 with no values.

 

Creating our Add VLANs function

Now that we understand how the create_dev_vlans function works. We’ll create a new function which will take a full list of VLANs in the desired_vlans_list and check if the it already exists in the dev_vlan_ids variable that we created above. If it already exists; we do nothing. If it doesn’t exist, we will add it.

Just for giggles, I also included a small timer which will allow us to see how long it actually takes for this function to run.

In [18]:
def add_vlans():
    start_time = time.time()
    for vlan in desired_vlan_list['vlans']:
        if vlan['vlanId'] in dev_vlan_ids:
            pass
        else:
            print ('adding vlan ' + str(vlan['vlanId']))
            create_dev_vlan(devid, vlan['vlanId'], vlan['vlanName'], auth=auth.creds, url=auth.url)
            
    print("Operation took --- %s seconds ---" % (time.time() - start_time))
 

Adding the VLANs

Now we simply run the function we defined above to add the VLANs to our target device. You can see from the output below that this took a whopping 0.43 seconds to add the missing three VLANs to the device.

In [19]:
dev_vlan_ids = [ vlan['vlanId'] for vlan in (get_dev_vlans(devid, auth.creds, auth.url))]
add_vlans()
get_dev_vlans(devid, auth.creds, auth.url)
 
adding vlan 2
adding vlan 3
adding vlan 10
Operation took --- 0.43477892875671387 seconds ---
Out[19]:
[{'vlanId': '1', 'vlanName': 'default', 'vlanStatus': '1'},
 {'vlanId': '2', 'vlanName': 'TenantABC', 'vlanStatus': '1'},
 {'vlanId': '3', 'vlanName': 'management', 'vlanStatus': '1'},
 {'vlanId': '5', 'vlanName': 'DoesntBelong', 'vlanStatus': '1'},
 {'vlanId': '10', 'vlanName': 'mgmt', 'vlanStatus': '1'}]
 

Let’s do that again

Now we run the same thing again, but this time all the VLANs already exist so there’s no need to add them. The timer function tells us this took an amazing 3.814e-06 seconds. If memory serves, I think that’s 5 pico seconds.

Let’s run it again a few times to see if that stays the same.

In [20]:
dev_vlan_ids = [ vlan['vlanId'] for vlan in (get_dev_vlans(devid, auth.creds, auth.url))]
add_vlans()
 
Operation took --- 3.814697265625e-06 seconds ---
In [23]:
dev_vlan_ids = [ vlan['vlanId'] for vlan in (get_dev_vlans(devid, auth.creds, auth.url))]
add_vlans()
 
Operation took --- 7.152557373046875e-06 seconds ---
In [24]:
dev_vlan_ids = [ vlan['vlanId'] for vlan in (get_dev_vlans(devid, auth.creds, auth.url))]
add_vlans()
 
Operation took --- 3.814697265625e-06 seconds ---
 

Remove Undesired VLANs from Target Device

Now that we’ve added all the VLANs that SHOULD be there, we need to make sure that we get rid of those “undesirables”. we want the state to be exactly what was defined in the GITHub file, no more, no less, right?

We’ll go back to the pyhpeimc library which has a delete_dev_vlans function pre-built for our usage.

This time we’ll do the exact opposite of above. Instead of adding VLANS which aren’t in the list; we’re going to be removing VLANS which aren’t in the list.

In [25]:
help (delete_dev_vlans)
 
Help on function delete_dev_vlans in module pyhpeimc.plat.vlanm:

delete_dev_vlans(devid, vlanid, auth, url)
    function takes devid and vlanid of specific device and 802.1q VLAN tag and issues a RESTFUL call to remove the
    specified VLAN from the target device.
    :param devid: int or str value of the target device
    :param vlanid:
    :return:HTTP Status code of 204 with no values.

In [26]:
def del_vlans():
    start_time = time.time()
    for vlan in get_dev_vlans(devid, auth.creds, auth.url):
        if vlan['vlanId'] not in desired_vlan_ids:
            print ("Deleting vlan " + vlan['vlanId'])
            delete_dev_vlans(devid, vlan['vlanId'], auth.creds, auth.url)
        else:
            print ('Not touching VLAN ' + str(vlan['vlanId']))
    print("Operation took --- %s seconds ---" % (time.time() - start_time))
In [31]:
del_vlans()
 
Operation took --- 5.9604644775390625e-06 seconds ---
Not touching VLAN 1
Not touching VLAN 2
Not touching VLAN 3
Not touching VLAN 10
Operation took --- 0.1680889129638672 seconds ---
 

And again!

Running this the first time took 0.19 seconds. But, since we’ve not got our target device in the desired state. We should now be able to run the command again and see the time come down considerably as, this time, we’re checking the device and finding out there’s nothing to do.

Let’s take a look:

In [29]:
del_vlans()
 
Not touching VLAN 1
Not touching VLAN 2
Not touching VLAN 3
Not touching VLAN 10
Operation took --- 0.07348895072937012 seconds ---
 

Putting it together

Now that we’ve created both functions, let’s run them both at the same time.

In [32]:
add_vlans()
del_vlans()
 
Operation took --- 5.0067901611328125e-06 seconds ---
Not touching VLAN 1
Not touching VLAN 2
Not touching VLAN 3
Not touching VLAN 10
Operation took --- 0.16545391082763672 seconds ---
 

Embracing the possibilities

So you might be saying “so what?” you just added some vlans to a single switch. With a bit of tweaking, we could easily have the add_vlans() and del_vlans()functions take the IP address of a target device as an input to the function. In this case, we could deploy the VLANS to ALL of the target devices in a specific group, or branch, or campus, or the entire network if we really wanted. That’s the beauty of a little idea.

You can see how the automation of a single small task can quickly save you a lot of time, not to mention the fact that there is no possiblity for human error at the CLI and you will have a predicatable outcome from the centralised YAML file that’s under version control.

Not bad for a network guy, right?

As always, comments or questions are more than welcome. It’s also cool if you just wanted to say “hi”. 

@netmanchris

 

Cleaning up After Ourselves

For those of you following along at home. I have been running this demo a lot lately so I wrote this additional code to get the devices back into the original state. Making it much easier to just run through the whole ipython notebook and perform the same demo in a predicatble manner every time.

I’ve included the code here in case anyone else finds it useful.

In [33]:
create_dev_vlan(devid, '5', 'DoesntBelong', auth.creds, auth.url)
remove_vlans = [ vlan['vlanId'] for vlan in desired_vlan_list['vlans']]
print (remove_vlans)
for i in remove_vlans:
    delete_dev_vlans(devid, i, auth.creds, auth.url)
 
['1', '2', '3', '10']
Unable to delete VLAN.
VLAN does not Exist
Device does not support VLAN function
Vlan deleted
Vlan deleted
Vlan deleted
In [34]:
get_dev_vlans(devid, auth.creds, auth.url)
Out[34]:
[{'vlanId': '1', 'vlanName': 'default', 'vlanStatus': '1'},
 {'vlanId': '5', 'vlanName': 'DoesntBelong', 'vlanStatus': '1'}]
 

Deploying Code to Devices Through your NMS

 
 

note: It’s come to my attention that WordPress is truncating some of my posts so that the right hand side is blocked by the side bar. My apologies for the this. I’ll get it fixed ( or more likely move to GH pages ) as quickly as possible. Thanks for your patience

@netmanchris

 

If you’re luck enough to have an NMS as powerful as HPE IMC then you already have a very capable system which has a ton of APIs that you probably didn’t even know about. IMC isn’t the only NMS which has APIs these days, but it’s the one we’re going to be looking at here.

We’ve spent the last few posts ( herehere, and here running through creating some network configurations through the Jinja2 templating language.

There are at least a couple of immediate benefits to this approach:

  • Consistency in the configuration between devices
  • Accuracy in the commands going into your devices

But the one large draw back is that you’ve still got to cut and paste that configuration into your device somehow, which is not the ideal scenario. We’re trying to get away from touching our devices.

In this post, we’re going to look at taking the rendered configuration and pushing it directly to the desired device through HPE IMC’s RESTful API, refered to as the eAPI in documentation.

Although there used to be a charge for this, HPE recently made some changes and the RESTful API is now included in both the Standard and Enterprise editions of the NMS.

In [2]:
import requests
import yaml
import githubuser
from pyhpeimc.auth import *
from pyhpeimc.plat.device import *
from pyhpeimc.plat.icc import *
from pyhpeimc.plat.vlanm import *
from jinja2 import Environment, FileSystemLoader, Template
 

Loading the templates and values from Git

We’ve gone through this before, so I’m not going to spend much time here going over this. In a nutshell, we’re loading the comware_template and the variables we’d like to use to fill in the template. Again, make sure you’re using the raw URL from Github and not the normal URL or you will end up with the whole HTML structure and not just the content you’re looking for.

In [3]:
comware_template = requests.get('https://raw.githubusercontent.com/netmanchris/Jinja2-Network-Configurations-Scripts/master/comware_vlan.j2').text
gitauth = githubuser.gitcreds() #you didn't think I was going to give you my password did you?
simple = yaml.load(requests.get('https://raw.githubusercontent.com/netmanchris/Jinja2-Network-Configurations-Scripts/master/vlans.yaml', auth=gitauth).text)
cw_template = Template(comware_template)
 

Rendering the template

Here we’re going to take a quick look at the rendered combination of the comware_tempalte and the variables to make sure this is what we want to send during the final push to the device. Automation is great, but it’s going to be a long time before it can replace a human being with knowledge of the environment. Trust… but verify.

In [4]:
my_template = cw_template.render(simple=simple)
print (my_template)
 
#vlan config
vlan 1
    name default
    description default
vlan 2
    name TenantABC
    description TenantABC
vlan 3
    name management
    description management
vlan 10
    name mgmt
    description mgmt

 

Options, Options, Options…

We now have a decision to make. There are a couple of different APIs available to us to push VLANs to the device.

For this example, we’re going to use the executecmd API that allows us to send a series of commands to the device through the HPE IMC REST API.

vlan api

As you can see from the REST documentation, you need to send a JSON object which is a list of the commands that you would type in from the command prompt of the switch.

So there are a couple of things we need to prepare the rendered jinja template into a format that can be sent to the API.

  1. We need to add the command “system-view” to the beginning of the command list.

    system-view on HPE Comware devices is equivalent to the enable + conf t commands using the IOS syntax you’re probably used to

  2. We need to split the giant string that rendering the jinja template gave us into a python list with one command per list item. Thankfully, we can use the python split method to help us through this. We can use the carriage return symbol to identify the end of each line. python identifies the carriage return by the \n characters which is what we’re going to use as the input to the split method.

  3. Once we’ve got those two things done, we simply add the two together and voila!

In [5]:
cmd_list = ['system-view']
cmd_list = cmd_list + my_template.split('\n')
 

Trust but verify

Are you seeing a trend here? If we’re ever going to learn to trust automation, we need to get comfortable that our expectations are met at each step of the journey, so we’re going to take a look at the new cmd_list variable and make sure that

  • it’s a list
  • the first elemend of the list is system-view
  • the rest of the list is one command per element
  • all the commands are in the right order
In [6]:
cmd_list[0:10]
Out[6]:
['system-view',
 '#vlan config',
 'vlan 1',
 '    name default',
 '    description default',
 'vlan 2',
 '    name TenantABC',
 '    description TenantABC',
 'vlan 3',
 '    name management']
 

Sending the commands

So far, other than splitting on the \n, this isn’t much different than what we’ve covered in the other blog posts. Now is where we connect the list of commands we’ve created to the device they are destined for.

The first thing we’re going to do is to create an authentication object that we can use to feed into the requests commands upon sending to the REST API.

In [7]:
auth = IMCAuth("http://", "10.101.0.203", "8080", "admin", "admin")
 
 

Getting the Device ID

The input for the run_dev_cmd is the device ID. This is an internal number that IMC uses to idenitfy that specific device. Thankfully, we’ve also got an RESTful function to get that based on the IP address of the device. To make things a little bit easier on us, we will grab the results of the get_dev_details API and assign the device ID directly to a variable called devid. Once we’ve got the device ID back, this gives us what we need to move on to the next steps.

In [8]:
devid = get_dev_details('10.20.10.10', auth.creds, auth.url)['id']
devid
Out[8]:
'221'
 

Sending the Commands to the target Device

We will now use the run_dev_cmd function from the pyhpeimc library to send the commands directly to the device. You can see that we are using the devidvariable assigned above as the input for the target device. We’re also using the cmd_list variable that containts the list of all the commands that we wish to send to the device.

We’re going to look for the contents of the success response. Which, if we’re lucky, should be true.

In [9]:
run_dev_cmd(devid, cmd_list, auth.creds, auth.url)['success']
Out[9]:
'true'
 

Double Checking the VLANs

Now that we’ve sent the VLANs to the device, the last thing we should be doing is to double check that nothing went wrong in the sending. We’ll use the exact same run_dev_cmd function, but this time, we’ll be sending the display vlan command and looking at the content of the return instead of the success.

In [10]:
cmd_list = ['system-view', 'display vlan']
print (run_dev_cmd(devid, cmd_list, auth.creds, auth.url)['content'])
 
 1(default), 2-3, 5, 10
 

Getting better, right?

So we’ve come a long way in a short time. We’ve

And in this post, we learned how to leverage the first three to deploy configurations directly from code to our devices.

The good part

For those who have done some scripting to device before, you’ll have noticed that using an API provided by an NMS such as HPEIMC makes life much easier. We didn’t have to worry about username and passwords for the individual devces, nor having to worry about deciding what protocol we need to use to connect to the device. The great part about using the NMS as a proxy is that all the credential and protocl negotiations are all handled by the NMS itself, allowing us to get on to the trouble of worrying about what we want to send to our devices and not concerning with how they actually get there.

This is a big step forward, but there are still a couple of small problems that we need to address

Configuration Drift

If you look closely, we’ve actually got an extra VLAN in there. VLAN 5 has been configured on the device, but it’s not in our list of desired_vlans where we have declared exactly which VLANs should be on the target device. This is what is sometimes known as configuration drift. Some people may say

Hey, It’s just an extra VLAN right? That won’t hurt us!

Sorry to respectfully disagree, but this attitude is exactly what causes us issues. This is the death of a thousand cuts. It’s JUST one VLAN, JUST one switch running a differnet version of code, JUST one router that has some unused sub-interfaces on it.

IT’S JUST ONE MORE THING THAT WILL BITE YOU WHEN YOU’RE TROUBLESHOOTING AN ISSUE.

These JUST things are what we sometimes call technical debt. If you can figure out out.

Vendor Syntax

The other problem with this example is that we are bound to a specific vendor’s syntax. If you attmept to run the system-view command on a Juniper/Cisco/Brocade/Extreme/ARISTA device, it’s going to error out. Right? This coule easily be addressed by some conditional logic which figures out which kind of a box it is first and then leverages a specific Jinja template for that vendor, but you can see how this becomes a slippery slope rather quickly.

In the next post, we’re going to look at a way to address both of these issues.

Stay Tuned!

@netmanchris

P.S. As always, comments and questions are more than welcome.

In [ ]:
 

GIT and Jinja – Like Peanut butter and Pickles!

Thanks to @mierdin for point this out. It looks like the wordpress format is causing some strange word-wrap issues. For a better view please click here to see the full post without presentation issues. 

 

Using GITHub to build our Network Configs

As I wrote in this post, one of my goals for this year is to be able to compltely automate the build of my lab environment programatically.

In the last couple of jinja posts, I wrote about the basics of Jinja2 templates and how they can be applied to building network configurations.

In this post, I’m going to take the next step and move those files from my local hard drive out to…

 

duh duh dahhhhhhhhhh

The cloud.

The cloud

 

Before we get started…

We’re going to go over some basics on the tools we’re using to make sure everyone’s on the same page. cool?

What’s GIT?

Git is a widely-used source code management system for software development. It is a distributed revision control system with an emphasis on speed, data integrity, and support for distributed, non-linear workflows. wikipedia

Huh?

GIT is a piece of software that allows you to track changes to files over time.

So what’s GITHub?

“Where software is built Powerful collaboration, code review, and code management for open source and private projects. Public projects are always free. “Github.com

GITHub is like facebook for developers. It’s a place where you can sync your local GIT repository to a central location, and then sync that central location to other local repositories.

Different people can connect to the same repository allowing multiple people to work on the same project.

What’s a repository?

A repository is essentially a collection of files that make up a project. You could think of it like a folder or directory. That analogy is not exact as it’s possible for a repository to have multiple sub-folders or directories, but it’s close enough for our purposes.

Is GIT only for Code?

GIT was definitely designed for software developers to as a versioning control system while developing software, but you can use it for tracking changes to things other than

You could use it for anything text format that you want to track changes to over time. For example

  • grocery lists
  • contact list
  • tracking your weight

There are a lot of interesting uses for GIT, one of those that we’re going to use today is looking at storing our Jinja2 templates on a public GIT repository and loading them directly into our python script as part of the code.

 

Import Required Libraries

Unles you’ve already got them, you’ll need to  pip install jinj2  and  pip install requests these two libraries before loading them into your running environment.

In [1]:
import requests
import yaml
import githubuser
from jinja2 import Environment, FileSystemLoader, Template
 

Loading Templates from GITHub

Like with most things in python, if it’s useful enough, chances are there’s probably someone else who already put a library together for that. In our case, we’re going to use the python request library to handle loading files directly from our Github repository.

 

The first thing we’ll do is load the HPE comware switch template from that we used in this post. If you wanted to take a look at this directly on github, it can be found here. All we have to do is to copy and paste the URL from our browser directly into the first input of the requests.get function.

note: The requests function will return a whole object that has various attributes. the ” .text ” at the end of this tells the function to just give us the contents of the file, not of the other information, like the HTTP status_code.

Simple, right?

In [75]:
comware_template = requests.get('https://github.com/netmanchris/Jinja2-Network-Configurations-Scripts/blob/master/simple_comware.j2').text
 

Looking at the output

So now that we’ve loaded the contents of the simple_comware.j2 template directly from the Github site into the comware_template variable. Let’s take a look to make sure that we have what we need.

In [76]:
print (comware_template)
 
<!DOCTYPE html>
<html lang="en" class="">
  <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#">
    <meta charset='utf-8'>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="Content-Language" content="en">
    <meta name="viewport" content="width=1020">
    
    
    <title>Jinja2-Network-Configurations-Scripts/simple_comware.j2 at master · netmanchris/Jinja2-Network-Configurations-Scripts · GitHub</title>
    <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
    <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
   
...
 

Hmmmmm. That’s not right?

The requests library is reaching out and grabbing whatever we put into that first variable. If we look at the print contents, we can see the first line is<!DOCTYPE html> . So it looks like we’re grabbing the rendered webpage, not just the contents of the file. Thankfully, looking at the GITHub website, there’s an option to look at any of your files in raw mode. So let’s grab that URL and try this again, ok?

In [77]:
comware_template = requests.get('https://raw.githubusercontent.com/netmanchris/Jinja2-Network-Configurations-Scripts/master/simple_comware.j2').text
In [78]:
print (comware_template)
 
#sysname config
sysname {{ simple['hostname'] }}
#vlan config
{% for vlan in simple['vlans'] -%}
vlan {{ vlan['id'] }}
    name {{ vlan['name'] }}
    description {{ vlan['description'] }}
{% endfor %}#snmp_config
snmp-agent
snmp-agent community read {{ simple['snmp']['read'] }}
snmp-agent community write {{ simple['snmp']['write'] }}
snmp-agent sys-info contact {{ simple['snmp']['syscontact']  }}
snmp-agent sys-info location {{ simple['snmp']['syslocation'] }}
snmp-agent sys-info version all
 

Ahhhh… That’s better.

 

Loading Network Specific Values from GITHub

Now we’re going to load our network specific values which were stored in the YAML file in this post. But this time, we’re going to load them directly from a private github repository.

The free GITHub accounts allow you to have public repositories, which means everyone can see what you’re doing, but if you have a paid version, you can get private repositories for as little as five dollars a month.

The private repositories are secured and can only be accessed by someone with a GIThub username and password who has explicitly been given access to this repository.

I would say that it’s probably a bad idea for us to keep any secure information like usernames, passwords, or SNMP strings in a online repository. But for my purposes, I don’t have anythng of value in this lab environment so I’m not too worried about it.

note: Before you put any sensitive data into an online repository of any kind, be sure to check with your companies data policies to see if you’re breaking any corporate rules.

 

Creating an Auth Object

First, I’m going to create an auth object, which is basically a single object that represents the username and password for my github account. In my case, I’ve got a file on my local hard drive that will automatically create the auth object for me.

In case you’re interested, the file is called githubuser.py and contains the following code. 

 

from requests.auth import HTTPBasicAuth

def gitcreds(): auth = HTTPBasicAuth('netmanchris', 'my_secret_password') return auth

In [79]:
auth = githubuser.gitcreds() #you didn't think I was going to give you my password did you?
 

Loading simple.yaml

We’ll now load the simple.yaml file like we did in this post but instead of opening it from a local file, we’re going to load it directly from the raw version of the file on github. I’d give you the link but it’s in a private repository, so you won’t be able to access it anyways.

Thigs I want to point out

  • yaml.load: takes the response and processes the yaml content directly into a python data structure ( dictionary )
  • .text: takes the “.text” attribute from the requests object which is the content of the page.
  • auth = auth: takes the auth object we created above and passes it as the username and password during the HTTP request.

Make sense?

In [80]:
simple = yaml.load(requests.get('https://raw.githubusercontent.com/netmanchris/PrivateRepo/master/simple_config.yaml', auth=auth).text)
In [81]:
simple
Out[81]:
{'hostname': 'testswitch',
 'ip': '10.101.0.221',
 'snmp': {'read': 'supersecret',
  'syscontact': 'admin.lab.local',
  'syslocation': 'lab',
  'trap': [{'target': '10.101.0.200'},
   {'target': '10.101.0.201'},
   {'target': '10.101.0.202'}],
  'write': 'macdonald'},
 'vlans': [{'description': 'management vlan',
   'id': '10',
   'name': 'management'},
  {'description': 'users vlan', 'id': '15', 'name': 'users'},
  {'description': 'phones vlan', 'id': '16', 'name': 'phones'},
  {'description': 'servers vlan', 'id': '20', 'name': 'servers vlan'}]}
 

Putting it all together

So looking at our list

  • download simple_comware.j2 template from Github public repo: **Check!**
  • download simple.yaml values file from Github private repo: **Check!**
  • rendered templates: **Nope**

So I guess we know what comes next, right?

 

Rendering the final config

We use the Template function to create a jinja2 template object and then we use the simple variable we created during the yaml section as input into the cw_template object.

In [82]:
cw_template = Template(comware_template)
type(cw_template)
Out[82]:
jinja2.environment.Template
In [83]:
print (cw_template.render(simple=simple))
 
#sysname config
sysname testswitch
#vlan config
vlan 10
    name management
    description management vlan
vlan 15
    name users
    description users vlan
vlan 16
    name phones
    description phones vlan
vlan 20
    name servers vlan
    description servers vlan
#snmp_config
snmp-agent
snmp-agent community read supersecret
snmp-agent community write macdonald
snmp-agent sys-info contact admin.lab.local
snmp-agent sys-info location lab
snmp-agent sys-info version all
 

Writing the Config to Disk

So far we’ve only been rendering and printing configurations, but it would be kinda nice to be able to have these on disk so that we can open them in our favorite editor before we cut and paste them into a telnet session to our network device.

The next two commands simply write the rendered template to disk with the filename comware.cfg and then we open and print the file to screen just to make sure it worked.

In [84]:
with open('comware.cfg', "w") as file:
    file.write(cw_template.render(simple=simple))
In [85]:
with open('comware.cfg') as file:
    print (file.read())
 
#sysname config
sysname testswitch
#vlan config
vlan 10
    name management
    description management vlan
vlan 15
    name users
    description users vlan
vlan 16
    name phones
    description phones vlan
vlan 20
    name servers vlan
    description servers vlan
#snmp_config
snmp-agent
snmp-agent community read supersecret
snmp-agent community write macdonald
snmp-agent sys-info contact admin.lab.local
snmp-agent sys-info location lab
snmp-agent sys-info version all
 

What’s next?

So far, we’ve come pretty far. We’ve written a couple of jinja templates, we’ve figure out how to store those files in a centralized control versioning system, but we’re still cut’ing and past’ing those configurations ourselves which is not ideal.

In the next post, we’ll look at using APIs to push the configuraiton directly to a configuraiton management tool.

Questions or comments? Feel free to post below!

@netmanchris

OpenSwitch in an OVA

 

First, disclaimer: I’m an HPE employee. Hewlett Packard Enterprise is a major contributor to the OpenSwitch project. Just thought you should know in case you think that affects my opinion here.

If you need more info on the OpenSwitch project, you can check out the other post in this series here and here

Network Engineers Don’t Like Learning New Things

Got your attention, didn’t I?  After the first couple of posts on OpenSwitch and a lot of discussions about this cool new project at some recent events, there was one piece of feedback that came back fairly consistently from the traditional engineers. OpenSwitch is hard to get running because there’s so many new things to learn.

When released in November of last year, the initial demonstration environment was actually pretty simple and streamlined to get up and running, as long as you’re a developer.  

The process involved the standard set of dev tools:

  • Virtual Box
  • Vagrant
  • DockerToolbox
  • Docker

 For anyone involved in a development environment, these tools are like an old hoody on a cold winter day. Welcome and familiar. 

But for the majority of network engineers who are far more comfortable with a console cable and a telnet session, it appears that the barrier to entry was just too high for people to start getting their hands dirty. 

 

I was able to bring this feedback to the OpenSwitch engineering team and I’m happy to bring the news that OpenSwich is now available in a OVA format that you can run natively on VirtualBox. 

 

Read the Docs

I’m going to go the long way around to get this up and running, but I’ve heard that the OVA file may be prebuilt and available on the OpenSwitch website in the near future. *I’ll try and come back to edit this post with a direct link if that happens*

The build process for OpenSwitch is actually well documented here.  Depending on the OS you’re running, there are some specific dependencies that are well documented. I won’t cover those since they are already there, but make sure you do check the docs carefully when you’re creating you’re build system as it won’t work unless you follow them.

Getting the Code

Since we’re going to be simply creating an OVA image, we don’t need the entire OPS GIT repo, we only need the ops-build portion. The first thing we’re going to do is to get to a terminal window on your linux ubuntu 14.04 host, create a directory called opsova and then GIT clone the ops-build repository using the following command. This command will copy the contents of the ops-build directory on GITHUB into a local directory called ops-build on your local machine.

git clone https://git.openswitch.net/openswitch/ops-build
 

 Selecting the Build

Now that we’ve cloned the necessary code to our local machines. We’re going to select the type of OpenSwitch build that we’d like to create. If you were pushing this to a supported white box switch you would use the following commands

make configure genericx86-64

But since we’re going to be creating an OVA so that we can import directly into Oracle Virtual box (Because it’s free!) we’re going to configure the appliance build

make configure appliance
 

Creating the OVA

Now for the final-ish step of the build. We’re going to run the make command to actually create the OVA file.

Warning: If you’re doing this in a VM. You want to give it lots of CPU for this step or it could take quite a long time. Remember burning CD’s on a 1x speed burner? Yeah… it feels like that. 

make
 

Running the OVA

Now that we’ve successfully created the OVA, the next step is to move it out of the VM to the host machine where you have Oracle VirtualBox installed.. This, of course, is assuming that you followed my example and you were doing this in a Ubuntu VM rather than a bare metal machine. From here, we follow a typical deployment and import the OVA using the following steps.

Finding the OVA

Once the make process, finishes ( there may be a couple of warnings, but it should build successfully  ), you will navigate to the ./images folder where you will find a symbolic link to OVA file. Following the symbolic link, the actual OVA was located in ./ops-build/build/tmp/deploy/images/appliance.  

Screen Shot 2016 01 19 at 1 14 15 PM

Now you need to get it off of your VM host and move it over to the machine where you are running VirtualBox.  ( I’m assuming you are comfortable with moving files between two machines and I’m not covering that here. Please feel free to point out in the comments if I’ve made a false assumption ).


Importting the OVA into Virtual Box

 

Now that we’ve moved this over to the host machine where you’re running VirtualBox, you simply choose File\import Appliance and navigate to the directory where you stored the OVA click next a couple of times and you should be good to go.

Screen Shot 2016 01 19 at 1 18 25 PM

 

Logging into OpenSwitch

In the last part of this post, we’re going to login to the OPS image.  The default username for the appliance build is root with no password.  Simply type in the username and you should be in the system

If you want to jump ahead of the next post, you would now type vtysh at the command prompt to pop into the quagga network shell which is where us network types will find ourselves most at home.

 

 

Screen Shot 2016 01 19 at 1 24 48 PM

What’s Next

In the next post, I’ll be looking at some basic configuration tasks, like adding an IP address and establishing basic network connectivity. If you have any issues getting this running, please feel free to post in the comments below, or even better, get involved in the OPS community by using the mailing list or the IRC Channels ( You can find information on all the ways to participate in the OPS community here

 

@netmanchris

More Jinja – Working with YAM as an Input

 

Jinja2 Simple YAML Example


We’re now going to take a look at grabbing a file from the hard drive written in YAML syntax. YAML is arguably the most human readable data serialization format which makes it really easy for coders and non-coders alike to work with.

We’re going to build on the last Jinja2 example. Instead of creating the templates and variables directly in python. We’re going to load them instead from files on our computer.

This may seem like a small detail, but this allows us to deconstruct the building of our configurations, meaning that different people can be responsible for different components of the configuration. As with anything, if you can break a complex process down into several smalller less complex tasks, the whole thing starts to feel easier.

Loading Libraries

We’ll start by loading the required libraries

In [2]:
import yaml
from jinja2 import Environment, FileSystemLoader, Template
 

Set the Environment

Essentially, this set’s the path which will define the directory where the templates will be loaded from. In this case, I’m setting it to load from the same directory.

In [3]:
ENV = Environment(loader=FileSystemLoader('./'))
 

Printing the YAML File

In this step we’re going to use the with open() as file: way of opening the file and just printing it out so you can have a look at what the YAML syntax actually looks like. The advantage of using the with open as… way of opening the file is that there’s no need to go back and close the file when you’re done. May not sound like a big deal, but trust me, files don’t like to be orphaned in an open pyton variable somewhere.

If you’re comfortable with configuring a network device by hand, you can PROBABLY figure out exactly what these variables are doing. The hostname istestswitch, the SNMP configuration are in the snmp section. The read string is supersecret, the vlans are in the vlans section, etc…

Really easy to understand exactly what’s going on here, right?

In [15]:
with open('simple_config.yaml') as file:
    print (file.read())
 
hostname: testswitch

ip: 10.101.0.221


snmp:
    read: supersecret
    write: macdonald
    syscontact: admin.lab.local
    syslocation: lab
    trap:
    - {target: 10.101.0.200}
    - {target: 10.101.0.201}
    - {target: 10.101.0.202}



vlans:
- {description: management vlan, id: '10', name: management}
- {description: users vlan, id: '15', name: users}
- {description: phones vlan, id: '16', name: phones}
- {description: servers vlan, id: '20', name: servers vlan }
 

Loading the YAML File

In this next step, we’ll use the same with open as… way of opening the file, except rather than just reading the file this time, we’re going to use the yaml.loadmethod to load the contents of that file into a python variable called simple.

The nice thing is that the YAML library takes care of all the hard work for us. Automation is supposed to make things easier, right?

In [12]:
with open("simple_config.yaml") as simple:
    simple =  yaml.load(simple)
 

Looking at the simple variable

So now that we’ve loaded the contents of simple.yaml into the simple variable, let’s take a look at what python sees. We’ll run the type() command first to see what kind of object simple has been created as, and then we’ll print out the contents of simple so you can see how the YAML library has transformed it from the text in the YAML file to something which is easier to work with in python.

In [16]:
type(simple)
Out[16]:
dict
In [11]:
simple
Out[11]:
{'hostname': 'testswitch',
 'ip': '10.101.0.221',
 'snmp': {'read': 'supersecret',
  'syscontact': 'admin.lab.local',
  'syslocation': 'lab',
  'trap': [{'target': '10.101.0.200'},
   {'target': '10.101.0.201'},
   {'target': '10.101.0.202'}],
  'write': 'macdonald'},
 'vlans': [{'description': 'management vlan',
   'id': '10',
   'name': 'management'},
  {'description': 'users vlan', 'id': '15', 'name': 'users'},
  {'description': 'phones vlan', 'id': '16', 'name': 'phones'},
  {'description': 'servers vlan', 'id': '20', 'name': 'servers vlan'}]}
 

Templates


Printing the simple_cisco template

In this step we’re going to take a look at the simple_cisco.j2 template that I’ve created. This is a very simple configuration just to show the power of jinja for making your network configurations.

If you look closely, you’ll see a couple of {% for…%} tags in here. This is a control structure called an interator, commonly known as a For loop. Essentially it’s saying, take the list of things and do this one action to each of the things in the list.

One thing to note here is that j2 is not a jinja2 specific file extention, but just something that I, and many others I’m sure, use to designate their template files.

In [28]:
with open('simple_cisco.j2') as file:
    print (file.read())
 
#hostname config
hostname {{ simple['hostname'] }}
#vlan config
{% for vlan in simple['vlans'] -%}
vlan {{ vlan['id'] }}
    name {{ vlan['name'] }}
    description {{ vlan['description'] }}
{% endfor %}#snmp config
snmp-server community {{ simple['snmp']['read'] }} RO
snmp-server community {{ simple['snmp']['write'] }} RW
snmp-server ifindex persist
snmp-server location {{ simple['snmp']['syslocation'] }}
snmp-server contact {{ simple['snmp']['syscontact'] }}
{% for trap in simple['snmp']['trap'] -%}
snmp-server host {{ trap['target'] }}  public
{% endfor %}
 

Rendering the simple_cisco template

In this last step of working with the simple_cisco template, we’re going to now pass this through the jinja rendering engine. Because we’ve got dynamic parts in the template, we’re going to have to supply a source for the variables to fill the dynamic part in. If you look below, we’re saying that anytime you see the wordsimple in the template, you should look in the variable simple we created above and see if there’s the appropriate information there to fill it in.

In simpler terms, we’re going to take the simple.yaml file we loaded above as the input values into this template.

We then render the template and…

In [29]:
template = ENV.get_template("simple_cisco.j2")
print (template.render(simple=simple))
 
#hostname config
hostname testswitch
#vlan config
vlan 10
    name management
    description management vlan
vlan 15
    name users
    description users vlan
vlan 16
    name phones
    description phones vlan
vlan 20
    name servers vlan
    description servers vlan
#snmp config
snmp-server community supersecret RO
snmp-server community macdonald RW
snmp-server ifindex persist
snmp-server location lab
snmp-server contact admin.lab.local
snmp-server host 10.101.0.200  public
snmp-server host 10.101.0.201  public
snmp-server host 10.101.0.202  public

 

Look familiar right? Minimal typing. Alll the VLANS are there, etc… and the best thing is if I run this a thousand times, it will always come out the same way.

 

But I have a multi-vendor network!!!!

This is the true power of jinja for me. I happen to run multiple vendors in my lab, but I’d like to have the ability to drive all of the configurations from a central location to make sure that they all have the same vlans, usernames and passwords, snmp strings, etc…

So now let’s take a look at running another vendor’s template using the same simple.yaml file as the input source.

 

Printing the simple_comware template

In this step we’re going to take a look at the simple_comware.j2 template that I’ve created. This is a very simple configuration just to show the power of jinja for making your network configurations.

You’ll notice that it’s very close to the simple_cisco.j2 file shown above. The real difference is the parts outside of the jinja2 variables. HPE Networking’s comware devices use the keyword sysname instead of hostname. They use the keyword snmp-agent instead of snmp-server. Minor differences in the syntax, but the actual values are exactly the same for both devices.

In [30]:
with open('simple_comware.j2') as file:
    print (file.read())
 
#sysname config
sysname {{ simple['hostname'] }}
#vlan config
{% for vlan in simple['vlans'] -%}
vlan {{ vlan['id'] }}
    name {{ vlan['name'] }}
    description {{ vlan['description'] }}
{% endfor %}#snmp_config
snmp-agent
snmp-agent community read {{ simple['snmp']['read'] }}
snmp-agent community write {{ simple['snmp']['write'] }}
snmp-agent sys-info contact {{ simple['snmp']['syscontact']  }}
snmp-agent sys-info location {{ simple['snmp']['syslocation'] }}
snmp-agent sys-info version all
 

Rendering the simple_comware template

In this step, we’re going to render and print the template using the same simple variable that we used for the simple_cisco.j2 template above. Because of the difference in the simple_comware.j2 templates, it will render with the proper syntax for the HPE devices.

In [31]:
template = ENV.get_template("simple_comware.j2")
print (template.render(simple=simple))
 
#sysname config
sysname testswitch
#vlan config
vlan 10
    name management
    description management vlan
vlan 15
    name users
    description users vlan
vlan 16
    name phones
    description phones vlan
vlan 20
    name servers vlan
    description servers vlan
#snmp_config
snmp-agent
snmp-agent community read supersecret
snmp-agent community write macdonald
snmp-agent sys-info contact admin.lab.local
snmp-agent sys-info location lab
snmp-agent sys-info version all
 

Wrap Up

I’m sure you’ll agree this is a much better way of creating your configurations than grabbing a console cable and typing them all out by hand, right? But there’s still a lot of room for improvement! Currently, we’re going to render the template and then cut and paste them into a console cable. So although we’ve made some gains… it could be better.

We’ll take a look at that next time.

@netmanchris

 

Intro to Jinja2

 
 

What is Jinja2

Jinja2 is a templating language that was originally used as part of the Flask python web framework. From the Jinja2 website

Jinja2 is a full featured template engine for Python. It has full unicode support, an optional integrated sandboxed execution environment, widely used and BSD licensed

It was originally developed to help automatically generate HTML dynamically as part of the flask framework, more on that in another post, but it can also easily be used to help us generate our configuration files for our infrastructure devices.

This is going to be a very simple introduction to a few of the basic concepts of that jinja uses which, hopefully, will help to understand how Jinja can be used as a first step down the road of gaining automation skills.

We’ll take a look at a developing some intuition on how Jinja2 can be used to create basic network infrastructure device configurations. This is definitly not the modern method of interfacing directly into the control/data/management plane of devices using APIs, but it’s definitely a step in the right direction on understanding how a bit of code can help make your life better.

Prereqs

I’m assuming you’ve already got python installed on your system. You’re also going to need to run the pip install jinja2 command from a terminal window to get the latest version of jinja2 which should work just fine here.

 

Learning by Example

In this section we’ll start with a small example on how to create a few VLANs using the typical syntax from a modern networking OS. In this case, I used the HPE Comware syntax, but it would be easy enough to create this using a Cisco or Juniper configuration and you’re encouraged to try to get this working with your own network vendor.

 

Import required libraries

First We’ll import the required modules from the Jinja2 library. This is pretty much stolen directly from the jinja2 docs.

In [1]:
from jinja2 import Environment, FileSystemLoader, Template
 

Creating the VLANS

For this example, we’re going to create a python list of dicts which contains six different VLANS as listed in the tabel below.

A python dictionary is just a key-value pair, where the value for a specific key in the dictionary can be accessed using the key name.

  • Name: Name of the VLAN
  • Description: Descrition of the VLAN
  • VLAN ID: Dot1q VLAN ID.
Name Description VLAN ID
Management Management VLAN 10
Users Users VLAN 15
Phones Phones VLAN 16
Servers Servers VLAN 20
Mobility Mobility VLAN 30
Guest Guest VLAN 40
In [5]:
vlans_list = [{'name': 'management', 'description': 'management vlan', 'id': '10'},
         {'name': 'users', 'description': 'users vlan', 'id': '15'},
         {'name': 'phones', 'description': 'phones vlan', 'id': '16'},
         {'name': 'servers', 'description': 'servers vlan', 'id': '20'},
         {'name': 'mobility', 'description': 'mobility vlan', 'id': '30'},
         {'name': 'guest', 'description': 'guest vlan', 'id': '40'},
         {'name': 'rob', 'description': 'guest vlan', 'id': '45'}
         ]
 

VLAN Jinja2 Template

In this step, we’re going to create a variable called text_file which will contain the content of a jinja2 template. This is a basic python string object which means, at this point, it’s just a bunch of text.

In normal circunstances, we would actually be reading this template from a file located on the hard drive, but for our purposes today, we’ll just put the templatein by hand.

What makes Jinja2 powerful is the control structures that allow it to perform programatic operations. In this example, we’re creating a For loop.

Following the code we will each vlan in the vlans object we created above and then render the template using the ‘id’ key for the first variable, the ‘name’ key for the second variable, and the ‘description’ key for the last variable.

Hopfully, this makes sense, but if not, just hold on and it should become clear before the end.

In [6]:
text_file = ('''
#vlan config
{% for vlan in vlans -%}
vlan {{ vlan['id'] }}
 name {{ vlan['name'] }}
 description {{ vlan['description'] }}
{% endfor %}''')
 

If I was to write the same as a traditional python iterator it would look something like this. You can see how they are related I hope?

In [7]:
for vlan in vlans_list:
    print ('''vlan ''' +vlan['id']+
           '''\n name '''+vlan['name']+
           '''\n description '''+vlan['description'])
 
vlan 10
    name management
    description management vlan
vlan 15
    name users
    description users vlan
vlan 16
    name phones
    description phones vlan
vlan 20
    name servers
    description servers vlan
vlan 30
    name mobility
    description mobility vlan
vlan 40
    name guest
    description guest vlan
vlan 45
    name rob
    description guest vlan
 

That’s a lot of work typing isn’t it?

You could ask

That’s more typing than I would do by hand? Why would I use this?

Great question. The point of automating anything is to cut down on the repetitive effort it takes to accomplish a given goal. In this case, we can simply count the number of key strokes it would take to create a single new VLAN on a switch.

In [6]:
count_chars = "vlan', 'name', 'description"
keystrokes = len(count_chars)
print (keystrokes)
 
27
 

Now let’s pretend we had to type that 10 times

In [7]:
keystrokes * 10
Out[7]:
270
 

Or maybe we had to create 100 VLANs.

In [10]:
keystrokes * 100
Out[10]:
2700

Ir maybe we had to create the full 4094 VLANs available

In [11]:
keystrokes * 4094
Out[11]:
110538
 

Not sure about you, but if I don’t have to type 110,000 keystrokes, my fingers will love me at the end of the day. Not to mention the fact that it’s also repeated perfectly every single time, not a single typo in there.

 

Create the Template Object

Now that we’ve created the text_file string object, we need to transform it into a jinj2 template which will allow us to then render it. We will create a new object called vlan_template and assign an instance of the Template class using the text_file contents as the input.

In [12]:
vlan_template = Template(text_file)
 

Make the Magic Happen

We will now use the render method on the vlan_template that we created above. We have a single argument to pass into the function. In this case we are passing the vlans_list list of dictionaries we create above in to the function as the vlans variable.

In [13]:
vlan_template.render(vlans=vlans_list)
Out[13]:
'\n#vlan config\nvlan 10\n    name management\n    description management vlan\nvlan 15\n    name users\n    description users vlan\nvlan 16\n    name phones\n    description phones vlan\nvlan 20\n    name servers\n    description servers vlan\nvlan 30\n    name mobility\n    description mobility vlan\nvlan 40\n    name guest\n    description guest vlan\n'
 

Hmmm What happened there?

That doesn’t look like a configuration file does it? The output of this file is actually a python string object. In python, we need someway to represent a carriage return (enter-key) and the \n just happens to have that honour.

Instead of running the template rendering directly, we can instead capture the output into a string object which we will then pass to the print command.

In [14]:
rendered_template = vlan_template.render(vlans=vlans_list)
In [15]:
print (rendered_template)
 
#vlan config
vlan 10
    name management
    description management vlan
vlan 15
    name users
    description users vlan
vlan 16
    name phones
    description phones vlan
vlan 20
    name servers
    description servers vlan
vlan 30
    name mobility
    description mobility vlan
vlan 40
    name guest
    description guest vlan

 

Clear?

Hopefully, this has shown you a bit of how a basic jinja control structure, like a For loop, can be used to cut down on a lot of key strokes, increase the accuracy of the configurations and help to streamline the operations.

In the next post, I’ll look at loading YAML files directly into python and using their contents as input into some more advanced jinja2 templates.