Python.VxLAN.Cumulus

In the previous post, I used Ansible to automate the Vxlan creation on Cumulus leaf switches. Now let’s go one step further and do it with a programming language like python. I know it sounds like it will be difficult but believe me, python is not that hard. In fact, it is pretty easy to work with and you’ll find a lot of documentation and examples if you do a search on the internet. I myself am a newbie working with python.

The scope of this project is to replicate the configuration on the leaf switches that were done using Ansible in the previous post ( https://latebits.com/2020/02/07/ansible-vxlan-cumulus/ ), but this time using the Cumulus switches API and Python. Of course, the Real scope is to learn a little bit of Python. ๐Ÿ™‚

The topology of the network remains the same:

The project is made of 2 files:

  • Variables file: var.py
  • Script: cumulus_api_VlanVxlanEvpn.py

The variable file contains a list of VLANs, the number of spine switches a leaf switch is attached to, clagid which in my case is 1 but it can be a list and in this case, the script needs to be changed a little bit. Also the id of the device which is used for ip’s, AS number.

#clagid/vPC,mlag , this can be a list but script needs to be changed
clagid = 1

#number of spine switches
spinenumber = 2

#id of the device
# id=1 10.0.0.1 , id=2 10.0.0.2
id = [ 1, 2 , 3]

# vlans
vlans = [ 100, 200 ]

The python script file contains a function for each task that is needed.

import requests
from var import vlans,id,clagid,spinenumber
#suppressing ssl warnings
requests.packages.urllib3.disable_warnings()


# http headers
httpheaders = {'Content-Type': 'application/json',
           'Authorization': 'Basic Y3VtdWx1czpDdW11bHVzTGludXgh' }


def post(com,dev_id):
      command = com
      api_url = "https://10.0.0.%i:8080/nclu/v1/rpc" % (dev_id)
      payload = '{"cmd": %s}' % (command)
      request = requests.post(api_url,headers=httpheaders,data = payload,verify=False)
#      r_url = request.text
#      print("reply is:%s"%r_url)


def create_vlan(b):
    for m in vlans:
      c = vlans.index(m) + 20
      n = hex(c).lstrip("0x")
      command = '"add vlan %i"' % (m)
      post(command,b)
      command = '"add vlan %i ip address 192.168.%i.24%i/24"' % (m,m,b)
      post(command,b)
      command = '"add bond bond%i bridge trunk vlans %i"' % (clagid,m)
      post(command,b)
      command = '"add vlan %i ip address-virtual 00:00:5e:00:01:%s 192.168.%i.254/24"'   % (m,n,m)
      post(command,b)
      print("vlan %i has been created on device %i" ) % (m,b)

def create_vxlan(b):
      for m in vlans:
            command = '"add vxlan vni%i vxlan id %i "' % (m,m)
            post(command,b)
            command = '"add vxlan vni%i bridge access %i"' % (m,m)
            post(command,b)
            command = '"add vxlan vni%i bridge learning off"' % (m)
            post(command,b)
            command = '"add vxlan vni%i stp bpduguard"' % (m)
            post(command,b)
            command = '"add vxlan vni%i stp portbpdufilter"' % (m)
            post(command,b)
            command = '"add vxlan vni%i vxlan local-tunnelip 10.1.1.%i"' % (m,b)
            post(command,b)
            command = '"add vxlan vni%i bridge arp-nd-suppress on"' % (m)
            post(command,b)
            print("vxlan %i has been created on device %i" ) % (m,b)


def create_evpn(b):
          command = '"add loopback lo ip address 10.1.1.%i/32 "' % (b)
          post(command,b)
          command = '"add bgp autonomous-system 6500%i"' % (b)
          post(command,b)
          command = '"add bgp neighbor swp1-%i interface remote-as external"' % (spinenumber)
          post(command,b)
          command = '"add bgp l2vpn evpn neighbor swp1-%i activate"' % (spinenumber)
          post(command,b)
          command = '"add bgp l2vpn evpn advertise-all-vni"'
          post(command,b)
          command = '"add bgp network 10.1.1.%i/32"' % (b)
          post(command,b)
          print("BGP eVPN  is done on device %i " ) % (b)

def create_VTEPanycast(b):
       y = b+1
       if b%2 == 0:
          command = '"add loopback lo clag vxlan-anycast-ip 10.10.10.%i"' % (b)
          post(command,b)
          command = '"add bgp network 10.10.10.%i/32 "' % (b)
          post(command,b)
       else:
          command = '"add loopback lo clag vxlan-anycast-ip 10.10.10.%i"' % (y)
          post(command,b)
          command = '"add bgp network 10.10.10.%i/32 "' % (y)
          post(command,b)
       print("VTEP anycast  is done on device %i " ) % (b)

def create_clagRole(b):
       if b%2 == 0:
          command = '"add clag peer sys-mac 44:38:39:FF:01:01 interface swp5-6 secondary"'
          post(command,b)
       else:
          command = '"add clag peer sys-mac 44:38:39:FF:01:01 interface swp5-6 primary"'
          post(command,b)


for dev in id:
    command = '"add clag port bond bond%i interface swp3 clag-id %i"' % (clagid,clagid)
    post(command,dev)
    create_clagRole(dev)
    create_vlan(dev)
    create_vxlan(dev)
    create_evpn(dev)
    create_VTEPanycast(dev)
    command = '"commit"'
    post(command,dev)
    print("config commited on device %i") % (dev)

For making HTTP API calls to the Cumulus switches I am using the “requests” python module. To send the config i am using the HTTP method POST. And I also need 2 HTTP headers: Content-Type and Authorization. The Authorization header contains a base64 string that represents the user: pass.

The API of the Cumulus switches is pretty simple. You need to send your data to the same URL, for example:
https://10.0.0.1:8080/nclu/v1/rpc
And as the payload/data you send the Cumulus NCLU commands without “net”. Just like in Ansible.
Example using cURL:

curl -X POST -k -H "Authorization: Basic Y3VtdWx1czpDdW11bHVzTGludXgh" -H "Content-Type: application/json" -d '{"cmd": "add vlan 299 ip address 172.16.19.1/24"}' https://10.0.0.1:8080/nclu/v1/rpc

Here you’ll find more information on the Cumulus API:
https://docs.cumulusnetworks.com/cumulus-linux/System-Configuration/HTTP-API/

As I have to do an HTTP POST for every command, I have created a function called “post” that I’ll be using to send each command to the switches.
Next, I have a function for each task: creating the VLANs, creating VxLANs, creating the evpn, the VTEP anycast address, and the clag Role.
All these functions are then used/called for each device that I want to configure. So using the id list, I am calling all these functions to configure each device.
But I also can comment on the functions I don’t need. As an example when I want to create a new vlan and its corresponding vxlan , I don’t need all the functions. What I need are the VLAN function and the vxlan one. So I can do something like this in the last “for” statement:

for dev in id:
   # command = '"add clag port bond bond%i interface swp3 clag-id %i"' % (clagid,clagid)
   # post(command,dev)
   # create_clagRole(dev)
    create_vlan(dev)
    create_vxlan(dev)
   # create_evpn(dev)
   # create_VTEPanycast(dev)
    command = '"commit"'
    post(command,dev)
    print("config commited on device %i") % (dev)

Also, you might want to create something only on specific switches. Then you’ll need to modify the id list in var.py file.
If you want to add a new VLAN and VxLAN, you only need to add it to the VLANs list.

And that’s it. Pretty simple, isn’t it?!

You can find the project files here:
https://github.com/czirakim/Python.VxLAN.Cumulus

Update (03.03.2020):

I’ve added a function(conf_pending) to capture the config that it’s committed and save it in a file for each device. The file will also have the date/time when it was captured in its name. This way it would be easy to go back in time and see what/how you have done. So the files would look something like this:

Switch_1_2020.03.03-15.23.txt
Switch_2_2020.03.03-15.23.txt
Switch_3_2020.03.03-15.23.txt

The function looks like this:

def conf_pending(b):
     command = '"pending"'
     post(command,b)
     result = post(command,b)
     ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
     final = ansi_escape.sub('', result)
     dev_name = 'Switch_%i_' % (b)
     file_name = dev_name + str(datetime.datetime.now().strftime("%Y.%m.%d-%H.%M")) + '.txt'
     with open (file_name,'w') as file:
            file.write(final)

What I am doing in the above function is, take the output of the “net pending” command, removing weird characters (using re.compile), and then writing everything in a txt file. In the file name, I am using the id of the device and the date and time when it was captured.

I’ve also updated the git repository.

About the author

Mihai is a Senior Network Engineer with more than 15 years of experience