Cisco ACI – How to connect to the APIC API and get data
By Vince
In this guide I will show Python code that will use HTTP GET requests on the Cisco APIC / ACI system and pull data. This will let you get information from the APIC, and display it in a better way. There are many things missing from the GUI (mostly on the reporting front) that you can create with some basic Python scripting.
There are two formatting types to get data in and out of the APIC: JSON and XML. I will show both of them in this guide so you can decide which one works better in your environment. From a programmers perspective, I like JSON because it is basically a Dict in Python (which is nice for processing). With XML you need to convert it into a usable format using the xmltodict library.
Note: you will need to have pip available to install some modules if your system doesn’t have these by default. Install them with:
pip install requests argparse xmltodict
I have broken this up into 3 different files. I use the from __future__ import so that this will work with python2 and python3. These should be ran from the command line like so:
python json_get.py -i 192.168.1.100 -u USERNAME -p PASSWORD
python xml_get.py -i 192.168.1.100 -u USERNAME -p PASSWORD
# -i is the IP address of your APIC, not a switch.
#Example output:
************
Connecting to :
****************
Auth Token: "12341234123412341234123412341234123412341234"
************************************
Tenant: Tenant1 DN: uni/tn-tenant1
Tenant: infra DN: uni/tn-infra
Tenant: common DN: uni/tn-common
Tenant: common1 DN: uni/tn-common1
Tenant: mgmt DN: uni/tn-mgmt
************************************
Vrf: vrf1
Vrf: default
************************************
BD: LabBD dn: uni/tn-Tenant1/BD-LabBD -- Vrf: uni/tn-Tenant1/BD-LabBD/rsctx dn: ctx-vrf1
************************************
Pod: topology/pod-1
************************************
Node: apic1 Model: APIC-SERVER-M1
Node: apic2 Model: APIC-SERVER-M1
Node: apic3 Model: APIC-SERVER-M1
Node: Leaf1 Model: N9K-C9396PX
Node: Leaf2 Model: N9K-C9396PX
Node: Spine1 Model: N9K-C9336PQ
Node: Spine2 Model: N9K-C9336PQ
************************************
Files:
- apic_token.py - this will login to your apic, you won’t run this directly
- json_get.py - use a http GET request to get some JSON data
- xml_get.py - use a http GET request to get some XML data
File 1: apic_token.py - before we can get any data from the APIC, we have to authenticate and get a token. This token is then sent with each request and it identifies us.
from __future__ import absolute_import, division, print_function
from builtins import *
import json
import requests
def apic_token(args):
apic = args.ip
username = args.user
password = args.password
print("************\nConnecting to : {}\n****************".format(apic))
# Login into APIC and get a token
base_url = 'http://' + apic + '/api/aaaLogin.json'
# create credentials structure
name_pwd = {"aaaUser": {"attributes": {"name": username, "pwd": password}}}
try:
post_response = requests.post(base_url, json=name_pwd, timeout=5)
except requests.exceptions.RequestException as e:
print("Error with http connection Your error is around... \n", e)
exit(1)
# get token from login response structure
auth = post_response.json()
try:
auth_token = auth['imdata'][0]['aaaLogin']['attributes']['token']
# alt: token = response.find("aaaLogin").get("token")
except (KeyError, TypeError):
print("Authenication error or bad response: ", auth)
exit(1)
print("Auth Token: {}\n************************************".format(pretty_json(auth_token)))
# create cookie array from token
return {'APIC-Cookie': auth_token}
File 2 - json_get.py - here we use the token and query the APIC for some information. In this case we are asking for multiple lists of items. We will print out the Tenants, VRF’s, BD’s ( bridge domains), Fabric Pods, and all the devices (called nodes in ACI speak). This is using an HTTP GET request for JSON data.
from __future__ import absolute_import, division, print_function
from builtins import *
import json
import requests
import argparse
from apic_token import apic_token
def json_get(url, cookies):
try:
response = requests.get(url, cookies=cookies)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print("Http error: ", e)
def main(inargs):
cookies = apic_token(inargs)
apic = inargs.ip
base_url = "http://" + apic + "/api/mo/"
classURL = "http://" + apic + "/api/class/"
getTenants = classURL + "fvTenant.json"
getVRFs = classURL + "fvCtx.json"
getBDs = classURL + "fvBD.json"
getPods = classURL + "fabricPod.json"
getNodes = classURL + 'fabricNode.json?order-by=fabricNode.name|asc'
tenants = json_get(getTenants, cookies)
if tenants:
for tenant in tenants['imdata']:
try:
butes = tenant['fvTenant']['attributes']
print("Tenant: {} DN: {}".format(butes['name'], butes['dn']))
except KeyError:
continue
print("************************************")
vrfs = json_get(getVRFs, cookies)
if vrfs:
for vrf in vrfs['imdata']:
try:
butes = vrf['fvCtx']['attributes']
print("Vrf: {}".format(butes['name']))
except KeyError:
continue
print("************************************")
bds = json_get(getBDs, cookies)
if bds:
for bd in bds['imdata']:
try:
butes = bd['fvBD']['attributes']
print("BD: {} dn: {} -- ".format(butes['name'], butes['dn']), end='')
vrf_dn = base_url + butes['dn'] + '.json?query-target=children'
vrf = json_get(vrf_dn, cookies)
# print("Vrf response: ", vrf)
if vrf:
for v in vrf['imdata']:
try:
# print("v: ", v)
butes = v['fvRsCtx']['attributes']
print("Vrf: {} dn: {}".format(butes['dn'], butes['tRn']))
except KeyError:
continue
except KeyError:
continue
print("************************************")
pods = json_get(getPods, cookies)
if pods:
for item in pods['imdata']:
try:
butes = item['fabricPod']['attributes']
print("Pod: {}".format(butes['dn']))
except KeyError:
continue
print("************************************")
nodes = json_get(getNodes, cookies)
if nodes:
for item in nodes['imdata']:
try:
butes = item['fabricNode']['attributes']
print("Node: {} Model: {}".format(butes['name'], butes['model']))
except KeyError:
print("Key Error on this one: ", item)
continue
print("************************************")
exit(0)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='ACI APIC JSON Testing')
parser.add_argument('-i', '--ip', type=str, default='192.168.1.100',
help='APIC ip: x.x.x.x')
parser.add_argument('-p', '--password', type=str, default='password',
help='APIC Password')
parser.add_argument('-u', '--user', type=str, default='admin',
help='APIC Username. "admin" defaulted')
args = parser.parse_args()
main(args)
File 3 - xml_get.py, this does the same thing but requests XML instead. We then convert the response into a python Dict (ordered Dict specifically).
from __future__ import absolute_import, division, print_function
from builtins import *
import requests
import argparse
import xmltodict
from apic_token import apic_token, pretty_json
def xml_get(url, cookies):
try:
response = requests.get(url, cookies=cookies)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print("Http error: ", e)
def main(inargs):
cookies = apic_token(inargs)
apic = inargs.ip
base_url = "http://" + apic + "/api/mo/"
# these are options you can use after the .xml below
# ?query-target={self | children | subtree}
# rsp-subtree={no | children | full}
# query-target-filter=eq(aaaUser.lastName,"Washington")
classURL = "http://" + apic + "/api/class/"
getTenants = classURL + "fvTenant.xml"
getVRFs = classURL + "fvCtx.xml"
getBDs = classURL + "fvBD.xml"
getPods = classURL + "fabricPod.xml"
getNodes = classURL + 'fabricNode.xml?order-by=fabricNode.name|asc'
tenants = xml_get(getTenants, cookies)
if tenants:
tenants = xmltodict.parse(tenants, attr_prefix='')
# reverse: print unparse(mydict, pretty=True)
try:
all_tenants = tenants['imdata']['fvTenant']
for tenant in all_tenants:
print("Tenant: {} DN: {}".format(tenant['name'], tenant['dn']))
except KeyError as e:
print("Keys not found in tenant. ", e)
print("************************************")
vrfs = xml_get(getVRFs, cookies)
if vrfs:
vrfs = xmltodict.parse(vrfs, attr_prefix='')
try:
all_vrfs = vrfs['imdata']['fvCtx']
for vrf in all_vrfs:
print("Vrf: {}".format(vrf['name']))
except KeyError as e:
print("Keys not found in vrf. ", e)
print("************************************")
bds = xml_get(getBDs, cookies)
if bds:
bds = xmltodict.parse(bds, attr_prefix='')
try:
all_bds = bds['imdata']['fvBD']
for bd in all_bds:
print("BD: {} dn: {} -- ".format(bd['name'], bd['dn']), end='')
vrf_dn = base_url + bd['dn'] + '.xml?query-target=children'
vrf_data = xml_get(vrf_dn, cookies)
vrf = xmltodict.parse(vrf_data, attr_prefix='')
vrf = vrf['imdata']['fvRsCtx']
if vrf:
print("Vrf: {} dn: {}".format(vrf['dn'], vrf['tRn']))
except KeyError as e:
print("Keys not found in vrf. ", e)
print("************************************")
pods = xml_get(getPods, cookies)
if pods:
try:
pod = pods['fabricPod']['attributes']['dn']
print("Pod: {}".format(pod))
except KeyError as e:
print("Pod error: ", e)
print("************************************")
nodes = xml_get(getNodes, cookies)
if nodes:
for item in nodes['imdata']['fabricNode']:
try:
print("Node: {} Model: {}".format(item['name'], item['model']))
except KeyError as e:
print("Key Error on node: ", item, e)
continue
exit(0)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='ACI APIC XML Testing')
parser.add_argument('-i', '--ip', type=str, default='192.168.1.100',
help='APIC ip: x.x.x.x')
parser.add_argument('-p', '--password', type=str, default='password',
help='APIC Password')
parser.add_argument('-u', '--user', type=str, default='admin',
help='APIC Username. "admin" defaulted')
args = parser.parse_args()
main(args)