A couple of months ago I wrote about Ansible and how it can enhance network automation. I also did a (very) little multi-vendor lab to show an example of what can be achieved with this tool.
Today I’m gonna write about an interesting project called ntc-ansible which exploits Ansible awesomeness into networks.
NTC-ANSIBLE
It’s an Open Source project launched by Jason Edelman (LinkedIn page, Blog) and his new-founded company Network to Code (it couldn’t have a better name 😀 ). You can find and download it here. It’s mainly composed by 2 module: ntc_show_commands and ntc_config_commands.
ntc_show_commands
The problem is this: most of the network equipments, doesn’t matter the vendor who made them, don’t return structured data like XML or JSON back from show commands but just simple text. This can be painful if you want to automate some task, like gaining devices’ inventory information. A possible solution could be writing a Python module that would connect to your devices using SSH API, execute commands, retrieve output and, finally, parse the output using Regular Expressions. This is OK, but it’s a lenghty and not always simple procedure.
Ntc-ansible want to simplify all the above.
“ntc_show_commans is a multi-vendor module that can automate converting raw text from show commands into structured data, namely JSON.”
How? This module exploits another interesting tool called TextFSM which is a “Python module that implements a template based state machine for parsing semi-formatted text.” Basically, it takes two inputs, a raw file containing a show command output and a well defined template, and it returns a list of records that contains the data parsed from the text.
So, using ntc-ansible’s ntc_show_commands module we can write a simple Ansible playbook to obtain structured data from network devices in an easy way. Then, we can also use those data to do whatever we want (we’ll see an example of this later on this post).
Example:
- ntc_show_command: connection:ssh platform:cisco_nxos command:'show vlan' host:{{ inventory_hostname }} username:{{ username }} password:{{ password }}
ntc_config_commands
This module enables us to write commands to devices that don’t have API. This can be done in two ways: specifying a list of commands or passing a file containing the commands to be executed. This is an useful module and later I’ll show an example of how this can help to do some auto-remediation.
Example:
# write vlan data - ntc_config_command: connection: ssh platform: cisco_nxos commands: - vlan 10 - name vlan_10 - end host: "{{ inventory_hostname }}" username: "{{ username }}" password: "{{ password }}" secret: "{{ secret }}" # write config from file - ntc_config_command: connection: ssh platform: cisco_nxos commands_file: "dynamically_created_config.txt" host: "{{ inventory_hostname }}" username: "{{ username }}" password: "{{ password }}" secret: "{{ secret }}"
Auto-Remediation with NTC-Ansible
The above introduction is far to be enough to let you understand how this tool really works and what it really does. So, in this section we’ll move together across all the steps needed to implement a simple auto-remediation.
Scenario
A common routine task is to take a network node offline gracefully. This can be done prepending AS_PATH in BGP. Let’s imagine something happened into our network requiring us to drain traffic from a device (like a fan problem or something else). We’ll exploit ntc-ansible to write an Ansible playbook to achieve it (this can also be automatically triggered to be fully automated, but let’s focus on the playbook for now).
A common BGP prepending configuration looks like this (actually this is the one I have configured on my virtual lab):
route-map prepend permit 10 set as-path prepend last-as 3 router bgp 10 neighbor 172.16.2.2 remote-as 20 neighbor 172.16.2.2 route-map prepend out neighbor 172.16.3.2 remote-as 30 neighbor 172.16.3.2 route-map prepend out
So, we’ll need to write a template extracting BGP neighbors information (from the show ip bgp summary command). We’ll achieve this using the ntc_show_commands module. Then, we’ll need to generate the proper configuration and to push it to the device using the ntc_config_commands module.
But first, we have to write a template for our show command.
TextFSM Template
The template file consists of two top level sections.
- The Value definitions, which describe the columns of data to extract.
- One or more State definitions, describing the various states of the engine whilst parsing data. A set of rules is defined in order to perform the parsing. The first and mandatory State is Start.
You can find a great guide on how to write one here.
My show ip bgp summary output looks like this:
BGP router identifier 1.1.1.1, local AS number 10
BGP table version is 21, main routing table version 21
7 network entries using 819 bytes of memory
8 path entries using 416 bytes of memory
3/2 BGP path/bestpath attribute entries using 372 bytes of memory
1 BGP AS-PATH entries using 24 bytes of memory
0 BGP route-map cache entries using 0 bytes of memory
0 BGP filter-list cache entries using 0 bytes of memory
BGP using 1631 total bytes of memory
BGP activity 9/2 prefixes, 13/5 paths, scan interval 60 secs
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
172.16.2.2 4 20 297 435 21 0 0 06:48:44 3
172.16.3.2 4 30 409 414 21 0 0 06:45:40 0
I have highlighted the field I’m interested in. These are my Values. Now I need to define RegEx for them.
Value BGP_NEIGH (\d+\.\d+\.\d+\.\d+) Value NEIGH_AS (\d+) Value STATE_PFXRCD (\S+) Start ^${BGP_NEIGH}\s+\S+\s+${NEIGH_AS}.*\s\d+:\d+:\d+\s+${STATE_PFXRCD} -> Record
Regular Expressions on Value lines are used to define what we are interested in, while those inside the State are used to find the defined pattern inside each output line. We’ll save this new template inside the ./ntc_templates directory. This directory contains all the commands supported by the ntc_show_commands module as well as an index file with a list of them. Each line of this file is in this format:
cisco_ios_show_ip_bgp_summary.template, .*, cisco_ios, sh[[ow]] ip bgp sum[[mary]]
The highlited RegEx is used to select the rigth template based on the chosen command inside the playbook.
Playbooks
Using this template I’ll have a table with the selected values. Now I can make a first test with ntc_show_commands
--- - name: GET STRUCTURED DATA BACK FROM CLI DEVICES hosts: cisco_ios connection: local gather_facts: False tasks: - name: TEST TEMPLATE ntc_show_command: connection: ssh platform: cisco_ios command: "show ip bgp summary" host: "{{ inventory_hostname }}" username: "{{ username }}" password: "{{ password }}" register: results - debug: var=results.response
On the same directory I have an inventory file looking like this:
[cisco_ios] 172.16.1.1 username=cisco password=cisco secret=cisco [cisco_ios:vars] as = 10
If you are familiar with Ansible all the fields are self-explanatory. Running this we’ll obtain the following:
As we can see, the command has been correctly executed and data is well parsed in a JSON-like manner. Now we can decide to use these data to do whatever we want.
Our goal is to drain traffic from the device making it less prefered through BGP AS prepending. So, we need to configure AS prependig for every BGP neighbor. Thanks to ntc_show_commands now we know who BGP neighbors are and we can use this knowledge to build the proper configuration.
We write two other task in order to do so:
- name: BUILDING CONFIGURATION lineinfile: dest=./drain.config line="router bgp {{ as }}\nneighbor {{ item['bgp_neigh'] }} route-map prepend out" state=present create=True with_items: - "{{ results.response }}" - name: ADDING ROUTE-MAP lineinfile: dest=./drain.config line="route-map prepend permit 10\nset as-path prepend last-as 3"
The first one builds the actual BGP configuration and store it inside the drain.config file. The state flag, setted to present, and the create flag, setted to True, let the playbook create a new file if it does not exist yet. Let’s run the expanded playbook now:
If the playbook works fine, the drain.config file will appear like this:
router bgp 10
neighbor 172.16.2.2 route-map prepend out
router bgp 10
neighbor 172.16.3.2 route-map prepend out
route-map prepend permit 10
set as-path prepend last-as 3
At this point we write the ntc_config_commands task to configure our device:
- name: PUSH CONFIGURATION ntc_config_command: connection: ssh platform: cisco_ios commands: - router bgp "{{ as }}" commands_file: "./drain.config" host: "{{ inventory_hostname }}" username: "{{ username }}" password: "{{ password }}" secret: "{{ secret }}"
..and now let’s run it again..
As the screen shows, everything has been successful and we can verify it from a show ip bgp on a neighboring device (of course we could do this inside the playbook itself writing another task. Ignore BGP configuration, I’ve just built a sample lab to show ntc-ansible usage).
..and inside red circles we can see the effect of our run.
Conclusions
This post wanted to be a practical introduction to ntc-ansible and a way to let me do some practice with its modules. I think this project to be really interesting and it’s also receiving lot of good feedbacks from people who are already using it in their environments.
If you find interesting too, you can contribute writing more templates. I did it and I’m doing it right now 🙂
4 comments