ACI Endpoints with Cobra SDK

To search the ACI endpoints attached to the fabric ( either via CDP or LLDP ) we utilize specific query objects that return back the value that we would be looking for. These returns in the CobraSDK, will be presented in object format. So understanding classes in Python is required to maximize what you can do with these calls.

In the CobraSDK the object is:

To make sure that this is the case we can utilize the command moquery -c cdpAdjEp at the CLI of the APIC. The result will be similar to the following:
admin@devpod1-apic1a:~> moquery -c cdpAdjEp | more
Total Objects shown: 6

# cdp.AdjEp
index        : 1
cap          : igmp-filter,router,stp-dispute,switch
childAction  :
devId        : devpod1-n6001-1(FOC1744R0MH)
dn           : topology/pod-1/node-201/sys/cdp/inst/if-[eth1/5]/adj-1
duplex       : full

So now we see that the query returned 6 objects ( which means 6 CDP adjacencies across all ports of the fabric ). Now that you have found the object that you are interested in we can explore a little more about it. In the same API library if you click on the class you will get more information about that object.

This diagram is part of every object/class presented in the API. It's purpose is to provide you the user of the API with a reference guide to the details of the object and the relationships that the object has with other objects. If you look at this diagram we can get that the CDP object is a child of the If (interface) object. As you can imagine each interface in the fabric will have this object and goes without saying that CDP is a protocol directly tied to an interface. Also in that relationship are the children objects of CDP

  • AdjStats
  • IntfAddr
  • MgmtAddr

You can utilize the CobraSDK to request that the CDP information from the fabric. The CobraSDK will grab the information from the APIC and place each CDP object in the fabric inside a CDP Object in python. So the first thing to remember is that when you do a request of information via the CobraSDK to APIC and the result could be one or more the result will be presented to you as a list.

So let's take the previous example and expand to now include a query to the CDP object. You will utilize the ClassQuery function to call the CDP object class.

You can utilize the CobraSDK to request that the CDP information from the fabric. The CobraSDK will grab the information from the APIC and place each CDP object in the fabric inside a CDP Object in python. So the first thing to remember is that when you do a request of information via the CobraSDK to APIC and the result could be one or more the result will be presented to you as a list.

So let's take the previous example and expand to now include a query to the CDP object. You will utilize the ClassQuery function to call the CDP object class.


import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
from cobra.mit.access import MoDirectory
from cobra.mit.session import LoginSession

def connect_to_apic():
    apicurl = 'http://%s' % "10.x.x.x"
    modir = MoDirectory(LoginSession(apicurl, "admin", "cisco.123"))
    modir.login()
    return modir

mo_dir = connect_to_apic()

class_query = cobra.mit.access.ClassQuery('cdpAdjEp')
cdpAdjEp_objlist = mo_dir.query(class_query)
    

I have called the return cdpAdjEp_objlist to highlight the fact of what you are getting back from this call. Let's add the following line print type(cdpAdjEp_objlist) and execute so you can observe what is going on.

(aciapp)rmuller-mbp:app rmuller$ python test.py
<<type 'list'>
(aciapp)rmuller-mbp:app rmuller$

As you can see this is a list. We can utilize a for loop to iterate through the list and issue the same command to see what the list is compromised off.


[CUT]
mo_dir = connect_to_apic()

class_query = cobra.mit.access.ClassQuery('cdpAdjEp')
cdpAdjEp_objlist = mo_dir.query(class_query)

for mo in cdpAdjEp_objlist:
    print type(mo)

And the result when executed will be:

(aciapp)rmuller-mbp:app rmuller$ python test.py
<class 'cobra.model.cdp.AdjEp'>
<class 'cobra.model.cdp.AdjEp'>
<class 'cobra.model.cdp.AdjEp'>
<class 'cobra.model.cdp.AdjEp'>
<class 'cobra.model.cdp.AdjEp'>
<class 'cobra.model.cdp.AdjEp'>

So you have 6 CDP Adjacency objects on this list. If we look at the previous API Diagram of the object we can see that it has some properties that we can query directly. As an example let's query to get the platID from that list.


[CUT]
mo_dir = connect_to_apic()

class_query = cobra.mit.access.ClassQuery('cdpAdjEp')
cdpAdjEp_objlist = mo_dir.query(class_query)

for mo in cdpAdjEp_objlist:
    print mo.platId

when you execute this you will get:

(aciapp)rmuller-mbp:app rmuller$ python test.py
N6K-C6001-64P
cisco ASR1001
N6K-C6001-64P
N6K-C6001-64P
N6K-C6001-64P
cisco ASR1001

Why don't we get a little more creative and now create a list with a subset of the information that is being extracted from ACI/APIC. Let's just create a nice table in the CLI using tabulate. You will have to install tabulate from PyPi using pip pip install tabulate.


import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
import cobra.mit.access
import cobra.mit.session
from tabulate import tabulate

apicurl = 'http://%s' % "10.x.x.x"
mo_dir = cobra.mit.access.MoDirectory(cobra.mit.session.LoginSession(apicurl, 'admin', 'cisco.123'))
mo_dir.login()

mo_dir = connect_to_apic()

class_query = cobra.mit.access.ClassQuery('cdpAdjEp')
cdpAdjEp_objlist = mo_dir.query(class_query)

cdplist = []

for mo in cdpAdjEp_objlist:
    row = {
        "platform": mo.platId,
        "portID": mo.portId,
    }
    cdplist.append(row)

print tabulate(cdplist, tablefmt='grid', headers="keys")

What was done is that a list was created with cdplist = []. Then we loop through the object list that Cobra got from the APIC. As it loops through the list it's left with the object and we can reference each of the properties. From their we can pull the Platform ID and the Port ID. We then create a dictionary that we has the two values we are interested with the proper key ( the index in the dictionary ) and append to the list.

This list is then be passed to Tabulate (available via pip in pipy) to create a nice table for CLI of these entries. That output will look like:

(aciapp)rmuller-mbp:app rmuller$ python test.py
+---------------+----------------------+
| platform      | portID               |
+===============+======================+
| N6K-C6001-64P | Ethernet1/1          |
+---------------+----------------------+
| N6K-C6001-64P | Ethernet1/2          |
+---------------+----------------------+
| N6K-C6001-64P | Ethernet1/1          |
+---------------+----------------------+
| N6K-C6001-64P | Ethernet1/2          |
+---------------+----------------------+
| cisco ASR1001 | GigabitEthernet0/0/3 |
+---------------+----------------------+
| cisco ASR1001 | GigabitEthernet0/0/3 |
+---------------+----------------------+

When you look at the API reference you can get information on the different properties that are sent back from APIC. The API reference provides tables with information on these properties.

Pulling the children objects

A more common example will be when a query returns the object and also the children of that object. Using the example of CDP we can see from the API library that it has 3 children objects associated with it.

These are of value to us because we could use the management IP address of the neighbor devices. To access these we will need to understand the object concept of both ACI and Python. From the ACI API Library we know that the name of the class is: cdp:MgmtAddr

A quick query to the class from the CLI will return:

admin@devpod1-apic1a:~> moquery -c cdpMgmtAddr
Total Objects shown: 6

# cdp.MgmtAddr
addr         : 10.1.17.121
childAction  :
dn           : topology/pod-1/node-201/sys/cdp/inst/if-[eth1/5]/adj-1/mgmt-[10.1.17.121]
modTs        : never
rn           : mgmt-[10.1.17.121]
status       :
[CUT]
# cdp.MgmtAddr
addr         : 10.1.24.84
childAction  :
dn           : topology/pod-1/node-203/sys/cdp/inst/if-[eth1/5]/adj-1/mgmt-[10.1.24.84]
modTs        : never
rn           : mgmt-[10.1.24.84]
status       :

You can see through the DN the relationship to the parent object. At this point you might be asking the question what happened to the name of the parent object? While we have been quering the class of these objects, how they are named in the MIT ( Managed Information Tree ) is different. And this becomes very important when you want to reference the child objects.

The API Library provides you with this naming convention that you must follow.

The first step is you have to tell Cobra that you want to pull the children objects in conjunction with the object you have been querying ( in this case CDP ). What you will do is add class_query.subtree = 'full' before you execute the python query to get the children objects included in the query.


import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
import cobra.mit.access
import cobra.mit.session
from tabulate import tabulate

apicurl = 'http://%s' % '10.x.x.x'
mo_dir = cobra.mit.access.MoDirectory(cobra.mit.session.LoginSession(apicurl, 'admin', 'cisco.123'))
mo_dir.login()

mo_dir = connect_to_apic()

class_query = cobra.mit.access.ClassQuery('cdpAdjEp')
class_query.subtree = 'full'
cdpAdjEp_objlist = mo_dir.query(class_query)

Add the function printtree that was given to me by Paul Lesiak to show the object and the children. This function will go recursively through the object list returned by APIC. This gives you better visibility into the tree.


import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
import cobra.mit.access
import cobra.mit.session
from tabulate import tabulate

apicurl = 'http://%s' % '10.x.x.x'
mo_dir = cobra.mit.access.MoDirectory(cobra.mit.session.LoginSession(apicurl, 'admin', 'cisco.123'))
mo_dir.login()

def printtree(mos, indent=0):
    for mo in mos:
        print ' ' * indent, str(mo.meta.className)
        printtree(mo.children, indent=indent+2)

cq = cobra.mit.access.ClassQuery('cdpAdjEp')
cq.subtree = 'full'
cdpAdjEp_objlist = mo_dir.query(cq)

printtree(cdpAdjEp_objlist)

When you execute this you can see the tree for each of the returned objects.

(aciapp)rmuller-mbp:app rmuller$ python test.py
 cobra.model.cdp.AdjEp
   cobra.model.cdp.IntfAddr
   cobra.model.cdp.AdjStats
   cobra.model.cdp.MgmtAddr
[CUT]
 cobra.model.cdp.AdjEp
   cobra.model.cdp.IntfAddr
   cobra.model.cdp.AdjStats
   cobra.model.cdp.MgmtAddr

So this gives you a pretty good glimpse of the CDP adjacency object and the three children objects that we had seen in the API reference. Now we can return to the original code to expand the inclusion of these objects information.


import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
import cobra.mit.access
import cobra.mit.session
from tabulate import tabulate

apicurl = 'http://%s' % '10.x.x.x'
mo_dir = cobra.mit.access.MoDirectory(cobra.mit.session.LoginSession(apicurl, 'admin', 'cisco.123'))
mo_dir.login()

cq = cobra.mit.access.ClassQuery('cdpAdjEp')
cq.subtree = 'full'
cdpAdjEp_objlist = mo_dir.query(cq)

print cdpAdjEp_objlist[0].mgmt
print list(cdpAdjEp_objlist[0].mgmt)[0].addr

The output of this will show:

(aciapp)rmuller-mbp:app rmuller$ python test.py
<cobra.internal.base.moimpl._ClassContainer object at 0x10a9de210>
10.1.17.121

Armed with this piece of information we can now query the sub child of the returned object and extract the Management IP address of the neighbor.


import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
import cobra.mit.access
import cobra.mit.session
from tabulate import tabulate

apicurl = 'http://%s' % '10.x.x.x'
mo_dir = cobra.mit.access.MoDirectory(cobra.mit.session.LoginSession(apicurl, 'admin', 'cisco.123'))
mo_dir.login()

cq = cobra.mit.access.ClassQuery('cdpAdjEp')
cq.subtree = 'full'
cdpAdjEp_objlist = mo_dir.query(cq)

cdplist = []

for mo in cdpAdjEp_objlist:
    row = {
        "platform": mo.platId,
        "portID": mo.portId,
        "mgmtIP": list(mo.mgmt)[0].addr
    }
    cdplist.append(row)

print tabulate(cdplist, tablefmt='grid', headers="keys")

That produces the output as:

(aciapp)rmuller-mbp:app rmuller$ python test.py
+---------------+----------------------+-------------+
| platform      | portID               | mgmtIP      |
+===============+======================+=============+
| N6K-C6001-64P | Ethernet1/2          | 10.1.17.121 |
+---------------+----------------------+-------------+
| N6K-C6001-64P | Ethernet1/1          | 10.1.17.121 |
+---------------+----------------------+-------------+
| N6K-C6001-64P | Ethernet1/1          | 10.1.24.84  |
+---------------+----------------------+-------------+
| N6K-C6001-64P | Ethernet1/2          | 10.1.24.84  |
+---------------+----------------------+-------------+
| cisco ASR1001 | GigabitEthernet0/0/3 | 10.0.240.5  |
+---------------+----------------------+-------------+
| cisco ASR1001 | GigabitEthernet0/0/3 | 10.0.240.2  |
+---------------+----------------------+-------------+