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.