Using Requests
ACI
  • Introduction
  • Ansible and ACI
  • Dev/Ops
  • Cisco ACI API
  • Python Basics
  • ReST w/ Python Requests
  • Javascript
  • Web Application
  • Finished
  • References

With the token code completed, we can request data from the ACI fabric. In this portion of the lab you will be able to read some well known data classes available. The first example is to read the LLDP neighbors from the fabric.

In a traditional non-ACI fabric you would need some controller that would be able to retrieve the LLDP neighbors from each of the network devices. ACI operates differently because the ACI APIC controller which is designed to be the view to the world of what is occuring with the ACI Fabric. It is important to note, when programming ACI you interact with the APIC controller only. The APIC controller then interacts with all the devices that it is managining inside the fabric. This single programmatic pane of glass provides huge value to a programmer and simplifies how interacts with the network.

The example of LLDP in particular gives you a great view of this functionality. In a traditional network you would have to first know all the hardware switch entities. Then programmatically interact with each one of them to retrieve the information. In ACI any changes of LLDP neighbors are sent back to the APIC that keeps track and can answer any programmatic query on those objects.

Step 1 - Remove print statement of cookies.

Return to your editor. The code that you wrote to get the authentication token looks like the following once we remove the print statement for the token.


import requests, json

apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

Step 2 - Add LLDP class query URL call

As we mentioned earlier, in ACI there are what we call class queries. While each object has a unique location in the fabric (what we call the DN), there are queries in the system that let you query objects that are all of the same type.

These are described in the URL under the /uni/class URL. For the LLDP object the class query is /node/class/lldpAdjEp.json. Remember, the class name (CN) is a combination of a package and method, lldp:AdjEp that can be found in either the APIC GUI or API documentation.

The first thing you will do is set a variable with the URL of the request query.


import requests, json

apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

request_url = '/node/class/lldpAdjEp.json'

Step 3 - Create request call to ACI APIC controller

With the variable containing the URL to the REST API point, we can make a request call to that URL and get the data. In the command that you will add, a couple of things are happening:

  1. We take the base_url variable and append the request_url. This combination makes the URL we need to reach the ACI APIC controller in the lab.
  2. You take the cookies variable that was built that contains the auth_token and pass it also.
  3. Finally remove the SSL validation with verify=False.

import requests, json, re

apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

request_url = '/node/class/lldpAdjEp.json'

response_data = requests.get(base_url + request_url, cookies=cookies, verify = False )

Step 4 - Convert JSON data into Python dictionary

When you send the request to the REST API the response is a string in JSON format. You have to take that string data and convert it into Python dictionary object. This is done with the command json.loads from the json library we imported.

And then we can print that dictionary so you can see the data. To do so, we need to import pprint.


import requests, json, re, pprintpp

apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

request_url = '/node/class/lldpAdjEp.json'

response_data = requests.get(base_url + request_url, cookies=cookies, verify = False )

structured_data = json.loads(response_data.text)

pprintpp.pprint(structured_data)

Save your script and execute the script from the Linux Terminal, you will get a large output of the dictionary of the adjacency objects known to the ACI fabric. The following is an example of the output (that has been cut for brevity):


python3 req-aci.py


{u'imdata': [{u'lldpAdjEp': {u'attributes': {u'capability': u'bridge,router',
                             u'chassisIdT': u'mac',
                             u'chassisIdV': u'f4:0f:1b:c1:fd:0d',
                             u'childAction': u'',

To really comprehend the power of this functionality, imagine that this is a large data center ACI fabric with 400 compute leafs attached. Instead of having to programmatically inspect 400 leaf switches, you just queried one single location that provided you the answer you needed.

Step 5 - Convert script to retrieve fabric endpoints

To expand showing you this capability, a single change made to the request_url can generate the list of endpoints that the fabric has learned! Using the exact same code you did for the LLDP neighbors. The class for endpoints is fvCEp (as we discovered earlier). All you have to do is change the URL to /node/class/fvCEp.json


import requests, json, re, pprintpp

apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

request_url = '/node/class/fvCEp.json'

response_data = requests.get(base_url + request_url, cookies=cookies, verify = False )

structured_data = json.loads(response_data.text)

pprintpp.pprint(structured_data)

Save your Python script and when you execute this in your Terminal window, you will get the list of endpoints that the ACI Fabric knows about.


python req-aci.py


{u'imdata': [{u'lldpAdjEp': {u'attributes': {u'capability': u'bridge,router',
                         u'chassisIdT': u'mac',
                         u'chassisIdV': u'f4:0f:1b:c1:fd:0d',
                         u'childAction': u'',

Managing structured data responses

Now that you know how to extract response data from the ACI Fabric, we can do a quick tutorial on how to manage that data and get a better representation of that data.

The response for the ACI endpoints is structured as:


{"imdata":[
    {"fvCEp":
        {"attributes":{
            "mac": value,
            "dn": value,
            "encap": value,

The request is contained inside a dictionary with a key called imdata. Aftewards every object is contained in a list. In this case every dictionary in the list starts with an index fvCEp that is the requested class query object. You will get a dictionary for every endpoint in the ACI fabric.

Since you want to extract specific information from that class query, you will have to build iteration code to parse through the list. One way to accomplish this is to create a list of keys that you wish to extract from the dictionary.

Step 6 - Write iterative code of return class search

The fvCEp object contains a base set of data. That object also contains associations to chidren. For the purpose of this lab we are going to focus on the primary object of fvCEp. Using that object we can determine some intereting information that you can quickly glean.

To accomplish some of this we are going to do what is called an iteration. Iterations are a programing terminology when you go over something with the same code. Therefore, in this case we are taking a long set of data that the APIC returned and processing the different key,value pairs to get the data that we want to show.


import requests, json, re, pprintpp

apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

request_url = '/node/class/fvCEp.json'

response_data = requests.get(base_url + request_url, cookies=cookies, verify = False )

structured_data = json.loads(response_data.text)

data = []

for endpoints in structured_data['imdata']:
    for endpoint_data in endpoints['fvCEp'].items():
        object_dn = endpoint_data[1]["dn"]
        dn_breakdown_re = re.search("tn-(\w*)\/ap-(\w*)\/epg-(\w*)\/cep-(\w*:\w*:\w*:\w*:\w*:\w*)",object_dn)
        if dn_breakdown_re:
            tenant = dn_breakdown_re.group(1)
            app = dn_breakdown_re.group(2)
            epg = dn_breakdown_re.group(3)
            mac = endpoint_data[1]["mac"]
            table_row=[ tenant, app, epg, mac ]
            data.append(table_row)

pprintpp.pprint(data)

Again, save your script and return to your Terminal window. When you execute the script, the output would be as:


python req-aci.py

       
    ['mgmt', 'INB_MGMT', 'MGMT_V336', 'B8:38:61:D7:8D:64'],
    ['mgmt', 'INB_MGMT', 'MGMT_V336', 'B8:38:61:D7:92:EE'],
    ['mgmt', 'INB_MGMT', 'MGMT_V336', '18:E7:28:2E:15:36'],
    ['mgmt', 'INB_MGMT', 'MGMT_V336', '74:26:AC:5A:7F:8E'],
    ['mgmt', 'INB_MGMT', 'MGMT_V336', 'B8:38:61:D7:8B:22'],
[CUT]

Now the data is in a structure that we can manipulate a lot easier. This step isn't something you have to do, as you can access the data and manipulate it directly from the object that is returned from the fabric. This exercise is to show you some tricks that can make your life easier.

Step 7 - Use pretty tables to make the data human readable

The PTable library is used in many Python code to tabulate data in a format that is easy to read by humans. You can get more information on pretty tables from here.

We already installed the library previously into the virtual environment, now we can just import it into the script easily using from prettytable import PrettyTable.


import requests, json, re, pprintpp
from prettytable import PrettyTable

apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

request_url = '/node/class/fvCEp.json'

response_data = requests.get(base_url + request_url, cookies=cookies, verify = False )

structured_data = json.loads(response_data.text)

data = []

for endpoints in structured_data['imdata']:
    for endpoint_data in endpoints['fvCEp'].items():
        object_dn = endpoint_data[1]["dn"]
        dn_breakdown_re = re.search("tn-(\w*)\/ap-(\w*)\/epg-(\w*)\/cep-(\w*:\w*:\w*:\w*:\w*:\w*)",object_dn)
        if dn_breakdown_re:
            tenant = dn_breakdown_re.group(1)
            app = dn_breakdown_re.group(2)
            epg = dn_breakdown_re.group(3)
            mac = endpoint_data[1]["mac"]
            table_row=[ tenant, app, epg, mac ]
            data.append(table_row)

pprintpp.pprint(data)

With that import you can add the Pretty Table code. This code simply goes through the list that was built on the data from APIC and injects it into the pretty table object to print nicer. You can now delete also the pprintpp.pprint(data) code as we are going to style the data we have received from ACI.


import requests, json, re, pprintpp
from prettytable import PrettyTable

apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

request_url = '/node/class/fvCEp.json'

response_data = requests.get(base_url + request_url, cookies=cookies, verify = False )

structured_data = json.loads(response_data.text)

data = []

for endpoints in structured_data['imdata']:
    for endpoint_data in endpoints['fvCEp'].items():
        object_dn = endpoint_data[1]["dn"]
        dn_breakdown_re = re.search("tn-(\w*)\/ap-(\w*)\/epg-(\w*)\/cep-(\w*:\w*:\w*:\w*:\w*:\w*)",object_dn)
        if dn_breakdown_re:
            tenant = dn_breakdown_re.group(1)
            app = dn_breakdown_re.group(2)
            epg = dn_breakdown_re.group(3)
            mac = endpoint_data[1]["mac"]
            table_row=[ tenant, app, epg, mac ]
            data.append(table_row)

table = PrettyTable()
table.field_names = ['Tenant','App Profile','End Point Group','MAC']
for row in data:
    table.add_row(row)

print(table)

Save your code and return to your Terminal window. Execute the script to see the output will be similar to the following. A nice formatted table of the data that was received from the APIC controller.


python req-aci.py

+------------------+-----------------+------------------+-------------------+
|      Tenant      |   App Profile   | End Point Group  |        MAC        |
+------------------+-----------------+------------------+-------------------+
|      infra       |      access     |     default      | FC:5B:39:5B:5B:41 |
|      infra       |      access     |     default      | FC:5B:39:82:C1:59 |
|      infra       |      access     |     default      | FC:5B:39:82:C0:57 |
| msite_p10_tenant |   msite_p10_ap  |  msite_p10_epg   | 00:50:56:04:01:10 |
|  aci_p10_tenant  | aci_p10_ap_mgmt | aci_p10_epg_mgmt | 00:50:56:03:00:10 |
|  aci_p10_tenant  | aci_p10_ap_mgmt | aci_p10_epg_mgmt | 00:50:56:04:00:10 |
|  aci_p10_tenant  | aci_p10_ap_mgmt | aci_p10_epg_mgmt | 00:50:56:01:00:10 |

Sit back and think about this for a second! The power of the ACI fabric has made it possible to give you the list of endpoints in the fabric... The whole fabric! Imagine if you have a 100 switches in your fabric. You can easily query the whole fabric for the list of endpoints!

Due to the compressed time-frames of the lab, we can't go more in-depth to make this more useful, but you should have a foundation on how easy it is to query the fabric for specific data. With that the question that you probably have at this point is what other data can I query from the fabric and what other good things can I build on ths mechanism.

Optional
Using Rich

We wanted to show you another useful python library for CLI output that is called Rich. It has many very interesting capabilities that you can utilize to maximize the interface look of any code you decide to code in Python.

Step 8 - Use Rich to provide progress bar and better tables

Rich is an impressive python library that allows you to provide better output of CLI execution. The library is extensive and has many options. Here you will see two parts of the library that we find useful. The progress bar to provide indication of progress and a better table output. For more information you can check out the Rich Library @ Github.


import requests, json, re, pprintpp
from prettytable import PrettyTable
from rich.progress import track
from rich.console import Console
from rich.table import Table
import time




apic_ip = '10.0.226.41'
apic_username = 'aciproglab04'
apic_password = 'cisco.123'
credentials = {'aaaUser':
                {'attributes':
                    {'name': apic_username, 'pwd': apic_password }
                }
    }

base_url = 'https://%s/api/' % apic_ip
login_url = base_url + '/aaaLogin.json'

json_credentials = json.dumps(credentials)

post_response = requests.post(login_url, data=json_credentials, verify=False)

post_response_json = json.loads(post_response.text)
login_attributes = post_response_json['imdata'][0]['aaaLogin']['attributes']

cookies = {}
cookies['APIC-Cookie'] = login_attributes['token']

request_url = '/node/class/fvCEp.json'

response_data = requests.get(base_url + request_url, cookies=cookies, verify = False )

structured_data = json.loads(response_data.text)

data = []

for endpoints in track( structured_data['imdata'], description="Processing endpoints"):
    for endpoint_data in endpoints['fvCEp'].items():
        object_dn = endpoint_data[1]["dn"]
        dn_breakdown_re = re.search("tn-(\w*)\/ap-(\w*)\/epg-(\w*)\/cep-(\w*:\w*:\w*:\w*:\w*:\w*)",object_dn)
        if dn_breakdown_re:
            tenant = dn_breakdown_re.group(1)
            app = dn_breakdown_re.group(2)
            epg = dn_breakdown_re.group(3)
            mac = endpoint_data[1]["mac"]
            table_row=[ tenant, app, epg, mac ]
            data.append(table_row)
            time.sleep(0.1) # This is to slow down the loop so you can see Rich in action

table = Table(title="Endpoint List")
table.add_column("Tenant", style="cyan", no_wrap=True)
table.add_column("App Profile", style="magenta")
table.add_column("End Point Group", style="green")
table.add_column("MAC Address", style="black")
for row in data:
    table.add_row(row[0],row[1],row[2],row[3])

console = Console()
console.print(table)

And now run the python program the same way.


python req-aci.py