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.
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.
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”?
desired_vlan_list = yaml.load(requests.get('https://raw.githubusercontent.com/netmanchris/Jinja2-Network-Configurations-Scripts/master/vlans.yaml' ).text)
Print VLANs
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.
print (yaml.dump(desired_vlan_list['vlans'], indent = 4))
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.
desired_vlan_ids = [vlan['vlanId'] for vlan in desired_vlan_list['vlans']]
desired_vlan_ids
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.
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.
print (yaml.dump(dev_vlan_list, indent = 4))
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.
help (create_dev_vlan)
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.
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.
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)
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.
dev_vlan_ids = [ vlan['vlanId'] for vlan in (get_dev_vlans(devid, auth.creds, auth.url))]
add_vlans()
dev_vlan_ids = [ vlan['vlanId'] for vlan in (get_dev_vlans(devid, auth.creds, auth.url))]
add_vlans()
dev_vlan_ids = [ vlan['vlanId'] for vlan in (get_dev_vlans(devid, auth.creds, auth.url))]
add_vlans()
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.
help (delete_dev_vlans)
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))
del_vlans()
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:
del_vlans()
Putting it together
Now that we’ve created both functions, let’s run them both at the same time.
add_vlans()
del_vlans()
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”.
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.
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)
get_dev_vlans(devid, auth.creds, auth.url)