A peek at network automation: using python, nornir, ntc_templates, and jinja2 to isolate devices in the same vlan
I started a journey to understand and get the most out of network automation with python; and I recently came accross nornir and ntc_templates. In this article, I'm going to share some things I find interesting to do with these tools.
1- Our scenario:
Let’s say we have a network (obviously), with some printers in a dedicated vlan (vlan 200) which spans across multiple switches (or not). Printers (and usually end devices in a typical network) are not supposed to communicate with each other even if they are in the same vlan so we want to isolate them from one another while maintaining their vlan unchanged.
We are going to explore two ways of doing things
o Configure each individual port of this vlan as a protected port
o Configure the printers vlan to be a secondary isolated vlan (see private vlans)
In this post I will share how we can achieve our goal using the first option (protected ports).
2- Our goal
Our goal is to configure every port in our target vlan(or vlans) on some switches while trying to automate the process as much as we can.
The python libraries I intend to use:
- Nornir (https://nornir.readthedocs.io/en/latest/)
- Ntc-templates (https://github.com/networktocode/ntc-templates)
3- Our environment
My lab setup (on a windows 10 machine)
- VMWare Player version 14.1.5 build-10950780
- VMware-VIX-1.17.0-6661328
- GNS3 version 2.2.3 with GNS3VM for vmware with 4vCPU and 4096MB of RAM
- GNS3 images I used for this lab
o Virl ios Layer 2 qemu VM (vios_l2-adventerprisek9-m.SSA.high_iron_20180619.qcow2) with 2048MB memory, 2vCPU and 8 network adapters
o A docker centos8 image (which is the latest version as of 1st feb 2020) built with the following commands inside the gns3vm
§ sudo docker pull centos
§ nano Dockerfile
FROM centos RUN yum clean all RUN yum groupinstall -y “Development tools” RUN yum install -y openssh iputils python36 RUN python3 -m pip install nornir
RUN git clone https://github.com/networktocode/ntc-templates.git
§ sudo docker build -t toto/centostoto:nornir
o I added the following environment variable in the gns3 centos docker template (go to ‘General settings’ tab) NET_TEXTFSM=/ntc-templates/templates
o I also set /code as a persistent directory in the gns3 container template (advanced tab of the template)
Now let’s hope we’ll have fun.
1- Our network topology and initial configuration
I’m using the same centos8 container image to simulate network printers
sw1 configuration
!!! we will need a priv 15 user and scp server to be enabled for NAPALM ip scp server enable username toto privilege 15 secret 0 toto !!! vlan configuration vtp mode transparent vlan 100 name mgmt vlan 200 name printers exit !!! interfaces configuration interface GigabitEthernet0/0 switchport access vlan 100 switchport mode access no shutdown ! interface GigabitEthernet0/1 description Trunk to sw2 switchport access vlan 200 switchport trunk encapsulation dot1q switchport mode trunk no shutdown ! interface GigabitEthernet0/2 switchport access vlan 200 switchport mode access no shutdown ! interface GigabitEthernet1/1 switchport mode access switchport access vlan 200 no shutdown ! interface GigabitEthernet1/2 switchport mode access switchport access vlan 200 shutdown ! interface GigabitEthernet1/3 switchport mode access switchport access vlan 100 no shutdown !!!!! SVIs configuration interface Vlan100 ip address 10.1.100.1 255.255.255.0 no shutdown interface Vlan200 description Printers default gw ip address 10.1.200.1 255.255.255.0 no shutdown ! !!!!! ssh configuration hostname sw1 ip domain-name toto.lab crypto key generate rsa modulus 1024 ip ssh version 2 !!!! lines configuration line con 0 escape-character 3 line vty 0 15 login local escape-character 3 ! ! end
sw2 configuration
!!! we will need a priv 15 user and scp server to be enabled for NAPALM ip scp server enable username toto2 privilege 15 secret 0 toto2 !!! vlan configuration vtp mode transparent vlan 100 name mgmt vlan 200 name printers exit !!! interfaces configuration interface GigabitEthernet0/1 description Trunk to sw1 switchport trunk encapsulation dot1q switchport mode trunk no shutdown interface GigabitEthernet0/2 switchport access vlan 200 switchport mode access no shutdown !!!!! SVIs configuration interface Vlan100 ip address 10.1.100.2 255.255.255.0 no shutdown !!!!! ssh configuration hostname sw2 ip domain-name toto.lab crypto key generate rsa modulus 1024 ip ssh version 2 !!!! lines configuration line con 0 escape-character 3 line vty 0 15 login local escape-character 3 end
o Preliminary verifications
Everything seems ok so far.
1- Our action plan and script
- This is what we’ll try to do with our python script
o We are going to issue the command “show interfaces switchport” on each switch
o Then we will extract the interfaces that satisfy two conditions:
§ Condition1 – The interface must be in static access mode
§ Condition2 – The interface belongs to vlan 200
o And we are finally going to apply some commands to those interfaces. The configuration we are going to apply to each interface is obtained by rendering a jinja2 template
Let’s do that!
o First of all we need to let nornir know about our network devices by providing an inventory in yaml format.
§ mkdir /code/inventory
§ vi /code/inventory/hosts.yaml
--- sw1: hostname: 10.1.100.1 username: toto password: toto platform: ios groups: - switches sw2: hostname: 10.1.100.2 username: toto2 password: toto2 platform: ios groups: - switches
§ We want to group our switches so, we also need a groups.yaml file (vi /code/inventory/groups.yaml)
--- switches: data:
domain: toto.lab
Now Let’s test and see if our inventory is good by initializing a nornir object in a python interactive session.
Our inventory seems good, so far. Now we’ll use netmiko_send_command module to issue “show interfaces switchport” and see what the result looks like for switch1. We are still in our interactive session.
from nornir.plugins.tasks import networking result1=nr.run(task=networking.netmiko_send_command, command_string='show interfaces switchport') result1['sw1'][0].result
type(result1['sw1'][0].result)
As we can see, without ntc_templates, we get all the output of our command as a single string. Trying, with only this string, to figure out which ports are in static access mode and belong to vlan 200, is a headache (at least for me). So TextFSM to the rescue!
Now let’s try with ntc_templates (use_textfsm=True) and see the difference.
result2=nr.run(task=networking.netmiko_send_command, command_string='show interfaces switchport', use_textfsm=True) result2['sw1'][0].result
type(result2['sw1'][0].result)
We get a nice python list of dictionaries, which is simpler to use. Great!
Now that we know how to use ntc_templates (use_textfsm=True), we will create a jinja2 template file in which we will put de configuration commands we would like to apply to every port that satisfy our two conditions
o mkdir /code/templates && vi /code/templates/interfaces_config.j2
{% for iface in interface_list %} interface {{ iface }} switchport protected spanning-tree portfast spanning-tree bpduguard enable {% endfor %}
end
Now everything seems to be in place for our final code
o vi toto.py
from nornir import InitNornir from nornir.plugins.tasks import networking , text from nornir.plugins.functions.text import print_result from nornir.core.filter import F import os nr=InitNornir( core={"num_workers": 2}, inventory={ "plugin": "nornir.plugins.inventory.simple.SimpleInventory", "options": { "host_file": "/code/inventory/hosts.yaml", "group_file": "/code/inventory/groups.yaml", } } ) #we define a group of tasks def interfaces_config(task, vlans_to_find): #Find interesting interfaces to configure r1 = task.run(task=networking.netmiko_send_command, command_string="show interfaces switchport", use_textfsm=True) interesting_interfaces = [ i['interface'] for i in r1.result if ((i['access_vlan'] in vlans_to_find) and (i['admin_mode'] == 'static access'))] if interesting_interfaces: #Generate config for inventoried host r = task.run(task=text.template_file, template='interfaces_config.j2', path='/code/templates', interface_list=interesting_interfaces) #Save config in a host variable task.host["intconfig"] = r.result #Deploy the interface configuration usin napalm task.run(task=networking.napalm_configure, name="Loading config to device", replace=False, configuration=task.host["intconfig"]) res=nr.run(task=interfaces_config, vlans_to_find=['200',]) print_result(res)
We run the script (python3 toto.py) and we cross fingers!
Nothing red on our screen, so it seems good but let’s verify things.
Printer1 can no longer ping printer2 BUT it can still reach printer3. In effect, the protected port feature is only meaningful to the local switch (Link).
It is the reason why our next step is to take a more global approach by implementing Private VLANs. But that is another story, and another article if God permits ??…
Ingénieur Systèmes, Réseaux | IT Associate at Sun King
5 年Great job !!!
Certifications ICSI | CNSS, CCNP/CCNA, ZD
5 年Hi Bro. thank you for this new way of resolving problems. I have read your work and it look like in Trunking configuration of SW1 at interface GigabitEthernet0/1 there as something to ajust (no switchport access vlan 200). My question is that. How will the packet flows in real production, at the end of implementing this solution?
IP MPLS product/protocol Testing (Manual/Automation)
5 年Well written Thierry DADANEMA