Tuesday, August 6, 2019

To be or not to be.. using Declarative Intent modules in Ansible?

Declarative Intent modules in Ansible are device specific modules that configure a specific feature on a networking device. Such modules are nxos_bgp, nxos_ntp, iosxr_bgp and several others.

Sounds like a great thing.. there are certain cases that they really help, but when you start digging deeper sometimes you get a not so nice surprise.

Consider the scenario where you want to create several config sections for a device and push them to the device. You have actually two main options

  1. Use multiple Jinja2 templates, create the final config file and push it to the device
  2. Use multiple declarative intent modules, one for each feature you need to configure
Someone might say 'Why should I bother learning Jinja2 and use a template.. Let's go for the easier path using a specific module'. That's what I initially though and started preparing my config using such modules. 

For simple tasks and small playbooks the modules are quite good, but if you start writing more complex ones and use more 'exotic' features, then you'll certainly have problems. That's what I found out:

  • If you want to build a playbook that will create config for several platforms, you will need the respective module for each one of them. Guess what.. There is no parity between platforms for each module. You may find the specific module for nxos, but no module for ios or ios-xr.
  • If you are lucky enough to find the required modules, you realize that one of them supports the vrf option that you need, but the rest don't.
  • If you are still lucky and get to a point where you want to optimize the execution of your playbook, by using  the 'aggregate' option, you realize it's supported only for a few of your modules. For the rest you just wait..
  • If you got to this point you are really lucky and your playbook executes quite well. But then you realize that each module you call, it executes a 'show running-config' on your device. This happens for every module, each time you call it. What if you have a device with long config that takes some time to return? Not so effective, don't you think?
These are some of the problems I got through and so I decided to go with Jinja2 for that playbook. Working with Jinja2 had also some constraints, but the final result was much better.

I'm not saying that declarative intent modules are not useful. Sometimes they certainly help, but don't be fooled, everything comes with a cost..

Sunday, August 4, 2019

Simple list vs dictionary in Ansible (and how easily you can mess)

I've been working on a relatively simple task in Ansible, namely create the VRF configuration for a Cisco router.

I would use a 'for' loop in a Jinja2 template and iterate over a variable that holds my VRF parameters.

Now it depends how you have declared your variable, as a list or as a dictionary? There are valid use-cases to use any method, the point is to understand what you're doing and why.. 


List example
router_vrfs:
  - {vrf_name: "VRF1", vrf_rd: 100, vrf_import_rt: "1:100", vrf_export_rt: "1:100"}
  - {vrf_name: "VRF2", vrf_rd: 200, vrf_import_rt: "1:300", vrf_export_rt: "1:200"}
  - {vrf_name: "VRF3", vrf_rd: 200, vrf_import_rt: "1:300", vrf_export_rt: "1:300"}  


Dictionary example
router_vrfs:
  "VRF1": {vrf_rd: 100, vrf_import_rt: "1:100", vrf_export_rt: "1:100"}
  "VRF2": {vrf_rd: 200, vrf_import_rt: "1:300", vrf_export_rt: "1:200"}
  "VRF3": {vrf_rd: 200, vrf_import_rt: "1:300", vrf_export_rt: "1:300"} 

If you have declared a list, it's an indexed list and you can use router_vrfs[0], router_vrfs[1], router_vrfs[2], or you can use a 'for' loop to access one item after the other as per below

{% for data in router_vrfs %}
 vrf context {{ data.vrf_name }}
   rd {{ router_loopback0 }}:{{ data.vrf_rd }}
   address-family ipv4 unicast
    route-target import {{ data.vrf_import_rt }}
    route-target export {{ data.vrf_export_rt }}
{% endfor %}

If you have declared a dictionary, things are a bit more complex. In this case you actually need to define two variables within your loop and use the 'items()' function on the variable. In the following snippet you also see I'm using a 'sort' filter, because dictionaries are unordered by default and you could have different result every time.

{% for name, data in router_vrfs.items()|sort(false,true) %}
 vrf context {{ name }}
   rd {{ router_loopback0 }}:{{ data.vrf_rd }}
   address-family ipv4 unicast
    route-target import {{ data.vrf_import_rt }}
    route-target export {{ data.vrf_export_rt }}
{% endfor %}


And now the messy part..

What would happen if you declare a dictionary and by mistake use dash (-) in the beginning of each line??

Wrong dictionary example
router_vrfs:
  - "VRF1": {vrf_rd: 100, vrf_import_rt: "1:100", vrf_export_rt: "1:100"}
  - "VRF2": {vrf_rd: 200, vrf_import_rt: "1:300", vrf_export_rt: "1:200"}
  - "VRF3": {vrf_rd: 200, vrf_import_rt: "1:300", vrf_export_rt: "1:300"} 

You have just created a simple list, in which each element is a dictionary! 

Each element contains only one key/value pair, but the damage is done. You understand if you try to use any of the above 'for' loop examples nothing will work. Instead you would need to do something like the following, which is just the wrong way of doing things..

{% for dict in router_vrfs %}
  {% for key,value in dict.items() %}


So, beware how you declare variables!

Thursday, June 27, 2019

How to upload an existing project to Gitlab

1. Visit Gitlab site and create a new project following the online instructions

2. Install git on your local computer

3. Configure git username & email
git config --global user.name "username"
git config --global user.email "myemail@example.com"

4. Initialize git in your project directory and upload files
cd your_project_dir
git init
git remote add origin https://gitlab.com/myusername/myproject.git
git add .
git commit -m "Initial version"
git push -u origin master

In case it fails with a rejection you may need to execute "git pull" first and then "git push" as per above.

Wednesday, March 13, 2019

Executing an ansible playbook from within PHP

Ansible seems to be the perfect tool to create a device inventory and keep track of all the devices in the network. Having this in mind, I decided to write a web application in PHP with a Postgres database that would hold the data of each device. Below you can see the web app.


Starting with the basics I used the "ios_facts" function to get the data and insert into the database. The Ansible playbook was executed manually, it went through the devices connecting to one after the other and database was updated with the new data.

That worked very well until I decided to trigger the execution of the Ansible playbook from within PHP, in order to create a more dynamic inventory or execute a playbook against a certain network device. Also I wanted to get the output from the execution of the playbook and display it to the web user.

Since PHP runs as a www-data user the privileges for executing anything are rather limited. This is what I had to do in order to make it work.

1. Create a user www-data in Postgress and grand "connect" privileges to my database
2. Grand 'insert', 'update' & 'select' privileges to the www-data user for the table I was interested in
3. Put the ansible playbook in the directory where the PHP application files existed
4. Use the PHP command passthru to execute the playbook and get the output back to the web application as per below
<?php 
passthru("/usr/bin/ansible-playbook -i myinventory mytest.yml"); 
?>
5. Create an "ansible.cfg" file in the directory of the playbook to disable host key checking
****ansible.cfg****
[defaults]
host_key_checking = False
6. Modify write permissions of the application directory, to allow Ansible write on the disk

And this is the output that I get on my browser after executing the script. This is just a Javascript alert, but you get the point..




Please keep in mind my application is running in an internal lab network and the security of the application is not an issue. The above process took place just to make things work in an internal lab environment. You shouldn't take such actions in a production environment where the security of the application and the network itself is critical

Tuesday, February 26, 2019

Split a long string on a specific delimiter in Ansible playbook

I've been trying to split a long string in an Ansible playbook and there seems to be an easy way to do that.

You have to use the "split" function and specify the delimiter. Depending on which part of the "exploded" string you need, you can use positive or negative values as a parameter.

For example, when trying to get the IOS image from a router you may get something like the following:

"ansible_net_image": "unix:/opt/unetlab/addons/iol/bin/i86bin-linux-l3-adventerprisek9-15.4"

How would you get just the last part of the string in order to store it in a database? Using the split function you specify the delimiter ("/" in this case) and the substring location "-1", counting from the end of the string. 

{{ ansible_net_image.split('/')[-1] }}

In another case how would you get the first part of the string?

{{ ansible_net_image.split('/')[0] }}

Using ansible to write directly to postgresql

Lately I've been playing with ansible to create an inventory for network equipment. Actually I want to maintain an inventory and keep it updated.

The concept is to connect to a network device, "get facts" and write the results directly to a database. In this case I've been using postgresql primarily for its "inet" variable type.

Anyway, this is how I did it. I used the shell option in an Ansible task and passed data from "facts" directly to psql command.


- name: Writing facts to database
shell: "psql -U george -d inventory -c 'UPDATE device SET (osversion,osimage,model,hostname,serialnum) = ($${{ansible_net_version}}$$,$${{ ansible_net_image.split('/')[-1] }}$$,$${{ ansible_net_model }}$$,$${{ ansible_net_hostname }}$$,$${{ansible_net_serialnum}}$$) WHERE ipaddress = $${{ inventory_hostname }}$$'"


The best way to make it work seems to be by using "$$" around variables

Monday, August 21, 2017

EVE-NG - A GNS3 alternative for your network simulations

A few months ago I had been searching for GNS3 alternatives. Although GNS3 has helped me a lot the last few years, I needed another, more flexible tool, to run my network simulations. After quite a bit of testing, I decided to abandon GNS3 for EVE-NG.



Actually EVE-NG is the new generation of an older tool, which you may have heard of, known as UnetLab. I had tried UnetLab once in the past, but my experience wasn't that good, so I continued with GNS3. But now things have changed..

The major advantage of EVE-NG against GNS3 is that EVE-NG doesn't need client application to be running on your computer. You can install EVE-NG on a remote server and you'll be able to access it from anywhere, just using your browser. Of course you can still install it on your laptop or desktop computer without any issue.

It's a very powerful platform which supports many different vendor appliances, which, in my opinion, certainly deserves your time to give it a try.