Using Jinja Templates on CVP

Why use Jinja?

Jinja2 is a user-friendly template engine for Python. It is easy to learn and use, and also fast – as a result, a lot of developers use it these days. It is easy to model since its syntax is quite similar to Python; debugging is easy, in fact quite similar to Python’s debugging capabilities.

To install Jinja, download Jinja2 from https://pypi.python.org/pypi/Jinja2 and install it in the /cvp/pythonlab/Lib folder.

Usage of Jinja2 on CVP

In CVP, we have the facility of creating dynamic configlets which can generate device specific configuration. In our example deployment, we will create a dynamic configlet to use Jinja2 module to help create device templates. In other words, the python script we create acts as a configlet builder.

When a new device is first brought up and connected to a management network that also hosts CVP, that device, as you may already know, comes up in the “undefined container”. You can now simply move the device into a predefined container that is specific to the device. For instance, you may want to move a 7050S into a container called “TOR-Switches”.

Now that you have the device in the right container, it is time for you to load device specific configuration.  This is where Jinja templates can be very helpful along with configlet builders. You may have different types of Arista devices in your data center, each performing a different role. Hence, creating and using templates specific to them is important. In our example I have created two templates “as_template.j2” and “sw_template.j2”. The former caters to the needs of Aggregate Spine switches while the latter for TOR switches.

Excerpt from sw_template.j2:

hostname {{ hostname }}
!
interface Loopback0
description IPV4 Management Loopback
ip address {{ loopback0 }}/32
!
interface Loopback60
description IPV6 Management Loopback
ipv6 address {{ loopback60 }}/128
!
{% for key,value in  bgpdata.items() %}
{% for item in value %}
interface {{ item[3] }}
description Connection_to_{{item[5]}}
no switchport
ip address  {{ item[1] }}/30
ipv6 address {{ item[2] }}/64
no shutdown
{% endfor %}

{{…}} are used for expressions to send values to the template.

Our python script (found here) will determine and send {{<var>}} values to be rendered in the templates. For example, based on the current provisioning switch, the script will determine the value of {{loopback0}} variable and renders it into the template.

Digging deep into the example.py script

Data for the script is acquired from three CSV files “mgmt_applicator.csv”, “bgpfile.csv” and “loopback.csv”. These files go into the same directory you are running the script from. The default directory the CVP configlets are run from is /cvp/tomcat/bin.

So how will the script know against what variable the data should be acquired from? With help from Cheyne Womble and his team, we figured out that this is where the CVPGlobalVariables instance can help. It is basically a class instance from which you can extract the ‘mac_address’ value, which you can then pass to a RestClient.

RestClient offers support for GET/POST/PUT/DELETE functions in the domain of web services. You can pass API requests and get responses back via http. In our case, the netElementId we used was the mac_address of the switch we grabbed earlier. We then used the RestClient to capture all the basic info in the form of a dictionary (dev_info), which is then indexed to capture the serial number of the device.

def netid_to_serialnum(net_id):
    query='http://localhost:8080/web/provisioning/getNetElementById.do?netElementId=%s' % mac
    client= RestClient(query,'GET')
    if client.connect():
        dev_info = json.loads(client.getResponse())
        return dev_info['serialNumber']
    return None

mac = CVPGlobalVariables.getValue(GlobalVariableNames.CVP_MAC)
serialnum = netid_to_serialnum(mac)

Using ‘serialnum’ as a unique identifier per device, we then are able to create bgpdata and loopbackdata dictionaries from the CSV files mentioned earlier. Thus, we now have all the information ready to be rendered into templates.

Rendering information into templates

To render information into a template, we would first create an object called ‘template’ as shown below:

template = env.get_template('sw_ab_template.j2')

Now that we have compiled the object, we can render a context or in other words pass variables to it using the render() method as shown below, thus enabling us to print the configuration template for that specific device.

print (template.render({'bgpdata': bgpdata,
                        'hostname': hostname,
                        'loopback0': loopback0,
                        'loopback60': loopback60}))

Notes to remember

  • To install Jinja2 manually, below steps may help:
    • Download the prerequisite libraries for Jinja2 called MarkupSafe from https://pypi.python.org/pypi/MarkupSafe and extract them to the /cvp/pythonlab/Lib/markupsafe folder.
    • Download  Jinja2 from https://pypi.python.org/pypi/Jinja2 and install it in  /cvp/pythonlab/Lib/jinja2 folder.
    • Make sure the above two folders are set with right ownerships/permissions. In my case, I chown’ed them to user CVP.
      chown cvp:cvp -r <directory>
  • The CSV files, and the templates folder are all placed in /cvp/tomcat/bin folder. This is the default folder for the configlets run by CVP.