Someone asked me a few days ago if it would be possible to trigger Contrail operations via ansible. After a bit of looking around I did not locate anything already made so wanted to see for myself how doable this is. While keeping in mind that I never played with Python more than modifying a few things in scripts that I already found from others (I’m way better at reverse engineering and improving/changing what is already in place), I decided this would be a good challenge for starting things off.

As this was just my play as hobby from home and I desired to see if I can build something, the current module just does a few basic Virtual Network things:

  • Add a Virtual Network
  • Add to it route-targets, network, policy, subnets
  • Delete the Virtual Network

What this is

As previously mentioned before this one represents my rather primitive attempt at playing with Python from scratch and with finding out what makes an ansible module tick.

After having finished implementing it and testing it on my personal lab setup I noticed by looking at other modules (including Cisco ACI ones) that it would have been more flexible/easier to build my own library+object (let’s call it Contrail) and then a structure such as:

  • Ansible Lib per purpose of operation (VirtualNetwork Operations, Security Groups and so on)
  • These libs importing my Contrail lib, instantiating an object there and passing it the Ansible Module parameters so that they can be used to trigger the operation needed. This would then return the status in the “main()" function of the let’s say in this case vn_ops.py lib.

I believe the concept should have been more: vn_ops.py Ansible Lib –> import Contrail lib (contrail object gets Ansible Module Params and can be called to do different operations) –> return status to main() of vn_ops lib -> send exit_json or fail_json to Ansible.

Also, some of my functions might seem at times to have a strange logic or reinvent the wheel but for just showing that something is possible, it can work out.

[Contrailansible Repo which has the files from below] (https://gitlab.com/playthings/contrailansible) Yaml Playbook File vn_ops.py Contrail Module for Ansible proof of concept

How I started

I knew about Ansible from before but never had hands-on know-how at building a module. The docs from the official Ansible website are nice but as always with such manuals they do come at a price. There is quite some overhead of documentation and a logical schema with what to follow and how to start off is lacking. This can cause a lot of time to go down the drain and for me personally, it does not work out. I prefer to find a sample tutorial made by someone and then after grasping just a basic End-to-End use case to start supplementing it with the official docs and add on top. As such, a very good article I stumbled upon and admired the clarity and directness of it is:

[Ansible Module Tutorial] (https://blog.toast38coza.me/custom-ansible-module-hello-world/)

Module Logic, Structure, Implementation

You will find two files:

  • contrail.yml
  • library/vn_ops.py

By running ansible-playbook contrail.yml in the current directory, it will actually go into “library” find vn_ops.py and use everything defined in there. One particularity I used in the playbook::

   vars:
        contrail_login: &contrail_login
         user: "admin"
         pass: "PUT-YOUR-PASS-HERE"
         contrail_api_url: "10.91.121.62"
         keystone_public: "10.91.121.62"
         keystone_port: "5000"

and then:

 - hosts: localhost
  tasks:
    - name: Test that my module works
      vn_ops:
       <<: *contrail_login
       tenant: "admin"
       vn_name: "TEST-MIHAI"

Basically this creates a reusable structure/alias and each time you add it with "«: *contrail_login” automatically this will get replaced with the contents of contrail_login.

Playbook contents

- hosts: localhost
  vars:
        contrail_login: &contrail_login
         user: "admin"
         pass: "PUT-YOUR-PASS-HERE"
         contrail_api_url: "10.91.121.62"
         keystone_public: "10.91.121.62"
         keystone_port: "5000"

- hosts: localhost
  tasks:
    - name: Test that my module works
      vn_ops:
       <<: *contrail_login
       tenant: "admin"
       vn_name: "TEST-MIHAI"
       network_policy: [ "TEST_NET_and_TEST_NET_2", "TEST" ]
       rt_import: ["target:6000:100", "target:6000:200"]
       rt_export: ["target:5000:100", "target:5000:200"]
       subnet: ["192.168.100.0/24", "192.168.101.0/24"]
       state: present
      register: result
      no_log: false

#    - name: Delete the VN
#      vn_ops:
#       <<: *contrail_login
#       tenant: "admin"
#       vn_name: "TEST-MIHAI"
#       state: absent
#      register: result
    - debug: var=result

Python Module and logic

The python module uses the Contrail vnc_api Library which is just a middle-layer between the user and the REST API calls happening in the background (requests, urlparse libs)..

The vnc_api library comes preinstalled by default on the Contrail Controller. I was a bit lazy and used it directly from there after I installed iPython which I used in a separate console for testing. The official repo has changed now into: Contrail API Client It should appear soon also in pypy and then things will become easier. Alternatively, one can also read on the Contrail API and do directly what vnc_api also does, REST calls, using one of the Python URL libs: Contrail API Contrail VNC_API reference

I basically built a series of functions that do the various operations required although, this being my first playing around with Python when doing stuff from zero, I have overengineering it a bit. The functions that update the route-targets, network policies or subnets do a call to check if the object does not exist already with that property set to the same value and if that is the case, then it does not proceed to alter anything; it also returned the “has_changed” = 0 status to Ansible. If no need to update, then they just call the exit_module method to return the result to ansible. This is where I failed a bit in my initial logic but how as I was already too far ahead, I felt a bit lazy to change. Ansible (the python module that one needs to import into his custom library for reading the passed parameters to the playbook and also returning status “module = AnsibleModule(argument_spec=fields)”) has two built-in functions that handle OK and not OK status:

  • exit_json
  • fail_json

One would normally call various methods, do some operations and then return something like is_error=False, has_changed=True, message to the main() function. Then depending on the bool status of “is_error” you would call either exit_json or fail_json. As my logic has initially been a bit different, I wanted to halt everything and send the status plus message to ansible from directly my update functions. As such I redefined the way to send Ansible the status and message by making my own custom: “exit_module” method. This in turn was just doing print in the expected JSON format of the dictionary values that I saw the original function from the Ansible module was also using (the exit_json & fail_json).

Example to make it visual and clear:

def exit_module(error,has_changed,meta):
    if not error:
        print(json.dumps({ "changed" : has_changed, "meta" : meta }))
        #module.exit_json(changed=has_changed,msg=meta)
    else:
        print(json.dumps({ "failed" : "True", "msg" : meta }))
        #module.fail_json(changed=has_changed,msg=meta)

and calling it like this

exit_module(False, False, "This error " + str(type(exp)) + " happened: " + exp())

For the Ansible “delete” part (state:absent set in the playbook) I just call the vnc_api to remove the virtual network and do not look at the individual constituent elements anymore (they will recursively get deleted).

Extra You will notice that the .py file also has an Examples and Documentation section. Do not be fooled like I was initially through the power of intuition that there one can write however he wants and whatever he wants. Luckily everything is nicely structured and with a prebuilt logic in mind, something that I find quite useful for maintaining consistency. The following two links explain what fields need to be defined, how they should be so that you will not encounter any errors later when trying to run your own playbook: [Module Variables Documentation/Examples] (https://docs.ansible.com/ansible/2.3/dev_guide/developing_modules_documenting.html)

Running it

Just put contrail.yml in the actual directory where you are, modify its parameters. Put the directory “library” in the same place with the file vn_ops.py in it. Start playing, changing, rebuilding the module:)

Building a simple Ansible module Another ansible module tutorial Official Ansible Website -> develop module Contrail.yaml file vn_ops.py library file Contrail API Contrail VNC_API reference