my cheat sheet ACI API

Home

1 ACI Overview

Physical network of spine/leaf switches in ACI mode ( not NXOS mode) Cluster of controllers (APIC controllers). The controllers operate the datacenter as a SDN, where applicaitons dynamically request resources from the network. ACI is a centralized application-level policy engine for physical, virtual, and cloud infrastructures.

1.1 Application Policy Model.

Uni

  • Tenant
    • Network
    • Policy

Under each tenant you will see:

  • Networking
    • VRFS
    • BRIDGE Domains
    • Subnets
    • External Networks
  • Policy
    • Application Profiles
    • EPGS
    • Contracts
    • Filters

2 APIC

  • a cluster of servers, typically five, minimum three.
  • centralized controller for the SDN
  • Web HTML5 GUI
  • RESTful API
  • XML or JSON for northbound APIs
  • 65 partner 3rd party integrations
  • ACI APP Center extends functionality

3 ACI Object Model

The Object Model is the foundation for EVERYTHING in ACI

Tree structure of related objects, with every object having 1 parent, but a parent having 1 or many children. Scope is important as you could have a relative name "web-egp" in both production tenant as well as dev tenant. You can always use the full DN which shows you a name that includes all levels, from top to bottom. uni/tn-Prod/ap-Healing/epg-Web

Here are some Prefix Properties and their common name

tn - tenant
ctsx - Context/vrf
BD - bridge domain
subnet - subnet
ap - App profile
epg - EPG
cep - Client endpoint
ip - ip address
out - L3 external
flt - filter
brc - contract
subj - contract subject

aci-object-model.png

Figure 1: ACI Object Model

When working with ACI API, you will almost always be using the full DN

3.1 Benefits of ACI Object Model

Using a data model allows Cisco ACI (and others) to provide:

  • structured, computer friendly access to data
  • choice of transport, protocol, encoding
  • model-drive APIs for abstraction and simplification
  • wide standard support while using open source
  • deploys services faster and simpler
  • simplifies app development
  • models manage abstractions of the underlying networking device data structures (configs, state data, …)

These systems use a custom model that may offer the same properties as if it was built using YANG.

  • instead of a YANG model though, ACI models are defined in the Cisco ACI Management Information Model .
  • ACI while not using NETCONF-RESTCONF/YANG, has its own model, and robust API libraries.

4 ACI REST API

Unlike other SDN solutions, ACI APIs open up EVERYTHING to API access, due to the ACI Object Model approach to EVERTHING.

The ACI REST API:

  • accepts and returns HTTP or HTTPS messages
  • encoded in JSON or XML
  • any programming language can be used to generate the messages and JSON or XML documents, as long as they contain the API methods or managed object descriptions
  • also provides a PUSH-based event notification. On changes in the MIT, an event is sent through a web socket

Cisco also provides these open source tools or frameworks:

ACI supports three API approaches.

Actually there are more than 3: ARYA, Cobra, Visore, Moquery to name 4 more.

ACI-APIs.png

Figure 2: ACI APIs

ACI-programmability.png

Figure 3: ACI Programmability Options

ACI REST APIdeals with raw API syntax, but can use any language / method you want. This can get complex. API will target specific class type or mo (managed object)

Rather than using the usual headers, type and accept, you simply call one of these two URI endings when writting the URI

  • .json
  • .xml

As the URL trailing type.. For example:

  • https://{{apic}}/api/aaLogin.json
  • https://{{apic}}/api/aaLogin.xml

That is also how you login to the ACI REST API. The returned page is a token which is automatically included in subsequent request as a cookie

{ 
   "totalCount" : 1
   "imdata": [
      {
	 "aaaLogin": {
	    "attributes": {
	       "token": "p3148fhap8eghjo2ihgpoaeirgypo
	       /ihtpq98wyegloiqhtgekjdhgpoqiunrq
	       "siteFingerprint": "lkjo3ignoqiw",
	       "refreshTimeoutSeconds": 600
      ...

4.1 REST API URL Details

The object-based information model of Cisco ACI makes it a very good fit for REST interfaces: URLs and URIs map directly to distinguished names identifying objects in the MIT, and any data on the MIT can be described as a self-contained structured text tree document encoded in XML or JSON. The objects have parent-child relationships that are identified using distinguished names and properties, which are read and modified by a set of CRUD operations.

  • managed object an instance of an object
  • each managed object has a unique dn (distinguished name)
  • can refer to each object globally
  • can also refer to the relative name
  • relative names identify an object relative to its parent object
  • relative name is appended to dn of parent object
  • DN-s are mapped to URLs

aci-rest-url.png

Figure 4: ACI REST URL

You can query an object in the MIT through

  • its distinguished name
  • on a class of objects,
  • or on the tree level (to discover all members of an object)

For example, to get details of all available ACI spine and leaf switches, send

  • GET https://sandboxapicdc.cisco.com/api/node/class/fabricNode.json

This return the entire class (all fabric node info)

4.2 APIC REST API Inspector

Just access the APIC GUI, then click the "Show API inspector" top right. This lets you see the GUI's equivalent API code. For you to copy and modify. So start with the GUI, do what you need, then look at the auto-generated code that you can copy and use as a starting point for developing your own code.

4.3 Subsequent API REST URL Construct

api-rest-uri.png

Figure 5: API REST URI from CiscoLive 2018

4.4 ACI API REST API authentication

Different from other authentication methods APIC does use tokens for authenticating API calls, but the controller responds with a cookie, and that becomes your token.

To get a token from APIC, you send a POST request with username and password. The token is then returned as a cookie. POSTMAN automatically adds the credentials as POSTMAN environment variables. See the eyeball for the updated token. The POST request has a body, as shown below.

POST https://{{apic}}/api/aaaLogin.json POST https://{{apic}}/api/aaaLogin.json # to get a cookie POST https://{{apic}}/api/aaaLogin.json POST https://{{apic}}/api/aaaLogin.json

With this body:

{
    "aaaUser": {
        "attributes": {
	   "name": {{username}},
	   "pwd": {{password}}
        }
     }
}

After getting a 200 OK, you will get a token in the body of the response.

Because ACI uses cookies for the token, you do NOT have to include this ACI API Token as a header, in any subsequent requests. Unlike DNAC where you have to send the token as a header in every request to DNAC.

But how do I save the cookie when I am using python?

That is also how you login to the ACI REST API. The returned page is a token which is automatically included in subsequent request as a cookie

Here is a response to the login request:

{ 
   "totalCount" : 1
   "imdata": [
      {
	 "aaaLogin": {
	    "attributes": {
	       "token": "p3148fhap8eghjo2ihgpoaeirgypo
	       /ihtpq98wyegloiqhtgekjdhgpoqiunrq
	       "siteFingerprint": "lkjo3ignoqiw",
	       "refreshTimeoutSeconds": 600
      ...

4.5 Create a new Tenant

POST https://{{apic}}/api/node/mo/uni.json

mo is the managed object, under the univserse (top level uni) and .json when asking for the response to be in JSON format.

With this body:

{
    "fvTenant": {
        "attributes": {
	   "dn": "uni/tn-API_Test",
	   "name": "API_Test",
	   "rn": "tn-API_Test",
	   "status": "created"
        }
     "children": []
     }
}

status "created" because you are trying to create anew Tenant.

Another example, to find all tenants (fvTenant) GET /api/node/class/fvTenant/.xml?query-target-filter=and(ne(fvTenant.name,"common"))

5 ACI Toolkit

  • python library
  • simple, easy, subset of full functionality
  • encapsulate common use cases. So you need less code. You just reuse the

code someone else has written. Of course you are at the whim of whether your use case has been written by someone else. See acitoolkit.readthedocs.io

ACI Toolkit

5.1 Installing ACI Toolkit

Cannot use pip, but rather do what pip would do for you. i.e.:

  1. clone from https://github.com/datacenter/acitoolkit git clone https://github.com/datacenter/acitoolkit.git
  2. run setup.py first activate a clean venv, then cd acitoolkit; python3 setup.py install

5.2 Docs

Get the ACI Toolkit documentation from http://acitoolkit.readthedocs.io

from deviceinfo import apic (store the device specific username, password and uri in a separate module that way you can leverage that in multiple python scripts)

5.3 ACI Toolkit three parts:

  1. python Toolkit library
  2. sample scripts
  3. applications'

aci-toolkit.png

Figure 6: ACI Toolkit Three Parts

5.4 ACI Toolkit python library

The steps are

  • import the ACI Toolkit library
  • create a session and login to the APIC
  • Use the toolkit to create, query, or modify objects in ACI
>>> from acitoolkit.acitoolkit import *
>>> from credentials import *
>>> 
>>> URL = "https://10.48.109.10"
>>> LOGIN = "admin"
>>> PASSWORD = "C1sco1234"
>>> 
>>> session = Session(URL, LOGIN, PASSWORD)
>>> session.login()
>>> 
    <Response [200]>
>>> session.logged_in()
True
>>> 
>>> tenant_list = Tenant.get(session)
>>> 
>>> for tenant in tenant_list:
>>>    print(tenant.name)

Coke
Pepsi
BMO
TD

Here is what deviceinfo gets you:

from device_info import apic
print(apic)
{'host': 'https://sandboxapicdc.cisco.com', 'username': 'admin', 'password': 'ciscopsdt', 'port': 443}

Try this combo for sandboxapicdc.cisco.com: admin : ciscopsdt

5.5 Sample scripts

there are many. See them, use them, learn from them, modify them.

5.5.1 Create ACI Objects

Add this snippent to the end of the above aci toolkit example:

import yaml
from pprint import pprint

# Read YAML configuration
yml_file = open("variables.yml").read()
yml_dict = yaml.load(yml_file,  yaml.SafeLoader)

tenant_name = yml_dict["tenant"]
vrf_name = yml_dict["vrf"]
bd_name = yml_dict["bridge_domains"][0]['bd']

# Create ACI objects
tenant = Tenant(tenant_name)
vrf = Context(vrf_name, tenant)
bd = BridgeDomain(bd_name, tenant)

#################################
# while here use this breakpoint() to check out these two values:
print("Explore what is in these 2 variables: tenant.get_url() and ")
print("tenant.get_json()")
breakpoint()

#################################

response = session.push_to_apic(tenant.get_url(), data=tenant.get_json())

print(response)

From Wim Wauters Blog I got this image:

ACI-object-model-simple.png

Figure 7: ACI Object Model

5.6 ACIToolkit git samples

On github.com are sample ACIToolkit scripts. Great place to start. I am summarizing some here:

Here is a typical order of ops when using the ACIToolkit:

from acitoolkit.acitoolkit import *

# create a instance of the Tenant class, call it "tenant"
tenant = Tenant('Coke')

# App profile contains all the EPGs representing the application.
# Create an instance of the AppProfile class, passing it a name, and the
# tenant instance.
app = AppProfile('myapp', tenant)

# most objects in ACI are created this way, i.e.
# pass a name, and the parent object.  The parent object must be a instance
# of this class's parent class.

# Create a instance of the EGP class, call it 'epg',  Again egp's parent
# is an instance of the EPG's parent class, AppProfile.
epg = EPG('myepg', app)

# Now create the lass Context and class BridgeDomain instances.  Both of
# these have a parent class of Tenant.
context = Context('myvrf', tenant)
bd = BridgeDomain('mybd', tenant)

# next we associate the BridgeDomain instance with the Context instance,
# which tells API that this bridge domain exists within this context
bd.add_context(context)

# finally the EPG is associated with the BridgeDomain that we created.
epg.add_bd(bd)

# to send this to the apic, we first establish a session to the apic
# with an instance of the Session class.
session = Session(URL, LOGIN, PASSWORD)
session.login()        # initiates the actual login

# Once logged in we can send the config to the APIC by calling the session
# objects "push_to_apic" method:
resp = session.push_to_apic(tenant.get_url(),
               data=tenant.get_json())

# push_to_apic returns an object.  The object is an instance of the
# Response class from the popular requests library, including all return
# codes.

if resp.ok:
   print("success")

So summarizing the steps are:

from acitoolkit.acitoolkit import *

tenant = Tenant('Coke')
app = AppProfile('myapp', tenant)
epg = EPG('myepg', app)
context = Context('myvrf', tenant)
bd = BridgeDomain('mybd', tenant)
bd.add_context(context)
epg.add_bd(bd)
session = Session(URL, LOGIN, PASSWORD)
session.login()        # initiates the actual login

resp = session.push_to_apic(tenant.get_url(),
               data=tenant.get_json())
if resp.ok:
   print("success")

5.7 ACI Toolkit APIC Login Credentials

The login credentials are retrieved using an instance fo the Credentials class. This class is convenient, and is used by many toolkit apps.

# The credential object is instantiated wiht a string describing the
# type of credentials desirec, and, a description sting

description = 'acitoolkit tutorial app'
creds = Credentials('apic', description)

# retrieving the creds is done by calling the get function:
args = creds.get()

# You can extend the functionality of creds. by *add_argument* function:
creds.add_argument('--delete', action='store_true',
          help='Delete the config  from the APIC')

The apic set of credentials variables consist of the:

  • username,
  • password, and
  • URL of the APIC.

The Credentials class allow the credentials to be provided in a number of formats and is taken in the following priority order

  • Command line options
  • Configuration file called credentials.py
  • Environment variables
  • Interactively querying the user

5.8 More examples using the ACIToolkit

def delete_tenant(self):
    tenant = Tenant('inheritanceautomatedtest')
    tenant.mark_as_deleted()
    apic = Session(APIC_URL, APIC_USERNAME, APIC_PASSWORD)
    apic.login()
    resp = tenant.push_to_apic(apic)
    self.assertTrue(resp.ok)
    time.sleep(4)
    resp = tenant.push_to_apic(apic)
    self.assertTrue(resp.ok)
    time.sleep(2)
    tenants = Tenant.get(apic)
    for tenant in tenants:
    self.assertTrue(tenant.name != 'inheritanceautomatedtest')

Now one to setup a tenant:

def setup_tenant(self, apic):
    tenant = Tenant('inheritanceautomatedtest')
    context = Context('mycontext', tenant)
    l3out = OutsideL3('myl3out', tenant)
    parent_epg = OutsideEPG('parentepg', l3out)
    parent_network = OutsideNetwork('5.1.1.1', parent_epg)
    parent_network.ip = '5.1.1.1/8'
    child_epg = OutsideEPG('childepg', l3out)
    child_network = OutsideNetwork('5.2.1.1', child_epg)
    child_network.ip = '5.2.1.1/16'
    contract = Contract('mycontract', tenant)
    parent_epg.provide(contract)
    entry = FilterEntry('webentry1',
            applyToFrag='no',
            arpOpc='unspecified',
            dFromPort='80',
            dToPort='80',
            etherT='ip',
            prot='tcp',
            sFromPort='1',
            sToPort='65535',
            tcpRules='unspecified',
            parent=contract)
    resp = tenant.push_to_apic(apic)
    self.assertTrue(resp.ok)

To addd a child subnet:

def add_child_subnet(self, apic):
    tenant = Tenant('inheritanceautomatedtest')
    l3out = OutsideL3('myl3out', tenant)
    child_epg = OutsideEPG('childepg', l3out)
    child_network = OutsideNetwork('5.2.1.1', child_epg)
    child_network.ip = '5.2.1.1/16'
    resp = tenant.push_to_apic(apic)
    self.assertTrue(resp.ok)

Add a contract

def add_contract(self, apic):
    tenant = Tenant('inheritanceautomatedtest')
    l3out = OutsideL3('myl3out', tenant)
    parent_epg = OutsideEPG('parentepg', l3out)
    contract = self.get_contract(tenant)
    parent_epg.provide(contract)
    resp = tenant.push_to_apic(apic)
    self.assertTrue(resp.ok)

A simpler setup a tenant. How is this different from the above setup example

def setup_tenant(self, apic):
    tenant = Tenant('inheritanceautomatedtest')
    context = Context('mycontext', tenant)
    l3out = OutsideL3('myl3out', tenant)
    parent_epg = OutsideEPG('parentepg', l3out)
    parent_network = OutsideNetwork('5.1.1.1', parent_epg)
    parent_network.ip = '5.1.1.1/8'
    child_epg = OutsideEPG('childepg', l3out)
    child_network = OutsideNetwork('5.2.1.1', child_epg)
    child_network.ip = '5.2.1.1/16'
    contract = self.get_contract(tenant)
    resp = tenant.push_to_apic(apic)
    self.assertTrue(resp.ok)

ACI-toolkit-errors.png

Figure 8: Swarm

6 ACI SDK (Cobra)

"aka language bindings"

  • Python library that implements a REST API
  • Native bindings for all REST functions
  • 1:1 representation of objects in cobra : MIT
  • provides methods for lookups, and object CRUD
  • full functionality up to lyaer 7, initial fabric builds etc.
  • user:password-based authentication AND
  • cerificate-based authentication/

COBRA is a language wrapper around the API. This simplifies your code syntax. Cobra is extensive and can get complicated. It comes with every release of the APIC, and is always available from the APIC directly (and obviously the correct version to go with the ACI s/w the APIC is running)

Best to look at a python script using the cobra library. This one is from the learning-labs-code-samples directory.

Key parts are:

6.0.1 importing

import cobra.mit.access import cobra.mit.session import cobra.mit.request import cobra.model.pol import cobra.model.fv

Mo = md.lookupByDn('...') Lookup an object using its dn

Mo = md.lookupByClass('...') Look up a specific class

cobra-sdk.png

Figure 9: Cobra SDK Creating Configuration

6.0.2 calling cobra.mit.request.ClassQuery

tenantquery = cobra.mit.request.ClassQuery('fvTenant') tenantquery.propFilter = 'eq(fvTenant.name, "{}")'.format(tenantname)

if apicsession.query(tenantquery): print("\nTenant {} has already been created on the APIC\n".format(tenantname)) exit(1)

6.0.3 Cobra Workflow

  1. identify the object to be manipulated
  2. build the request (crud)
  3. commit change to object

cobra-workflow.png

Figure 10: Cobra Workflow

7 Apic REST pYthon Adapter, arya.py

arya is a tool that will convert APIC object documents from their XML or JSON form into equivalent Python code leveraging the Cobra SDK

  • The GUI creates REST
  • API Inspector shows REST
  • arya.py creates code from REST
  • Auto-generate code to automate task, without heavy lifting
  • download from http://github.com/datacenter/arya

arya can take input from multiple source, including standard input. So, one can copy the JSON or XML extracted from:

  • APIC Visore or
  • API inspector, and

quickly generate the Python source code framework, which can then be modified, tokenized and rapidly turned into functional prototypes.

arya.py.png

Figure 11: Arya

8 Related Libraries on github

There are a LOT of libraries on https://github.com/datacenter

8.1 pyaci

python bindings for Cisco ACI REST API

9 ACIrb (Ruby)

  • Ruby implementation of the Cisco APIC REST API
  • Enables direct manipulation of the MIT through the REST API, using standard Ruby language options

10 moquery

Moquery is a CLI object model query tool, while Visore is an object store browser (GUI)

11 Puppet & Ansible

12 ACI Toolkit for config

From the readthedocs.io tutorial: The configuration object definition is done in this order:

  1. tenant All of the configuration will be created within a single tenant named tutorial. This is done by creating an instance of the Tenant class and passing it a string containing the tenant name.

    tenant = Tenant('tutorial')

  2. application profile The Application Profile contains all of the Endpoint Groups representing the application. The next line of code creates the application profile. It does this by creating an instance of the AppProfile class and passing it a string containing the Application Profile name and the Tenant object that this AppProfile will belong.

    app = AppProfile('myapp', tenant)

    Note that the parent class of AppProfile is Tenant. This is typical. The parent object must be an instance of this class’s parent class according to the acitoolkit object model.

  3. Endpoint Group The Endpoint Group provides the policy based configuration for Endpoints that are members of the Endpoint Group. This is represented by the EPG class. In this case, we create an EPG with the name myepg and pass the AppProfile that we created to be the parent object.

    epg = EPG('myepg', app)

13 Running ACI Toolkit interactively at command line

It seems that we can learn more about the ACI Toolkit by running it interactively as follows:

  1. git clone https://github.com/datacenter/Simple-ACI-Toolkit

    This command will download the necessary libraries to use the ACI Toolkit syntax. Then to run CLI commands from your APIC type:

  2. python acitoolkitcli.py -l admin -p password -u https://APIC_IP

    This will connect you to your APIC so you may run commands that will help you build your application network profiles. We can do things such as switching tenants, creating contexts, creating bridge domains, and creating end point groups (EPGs).

    Here are some examples of the common commands we might use to create these logical objects.

  3. Switch to a tenant configuration mode:
    fabric# switchto tenant <tenant-name>
    fabric-tenant# switchback
    
  1. Create a Context and don’t enforce contracts on it:
    fabric-tenant(config)# [no] context <context-name>
    fabric-tenant(config-ctx)# [no] allow-all
    
  1. Create a bridge domain and assign it to a context:
    fabric-tenant(config)# [no] bridgedomain <bd-name>
    fabric-tenant(config-bd)# [no] context <context-name>
    
  1. Create a subnet under the bridge domain:
    fabric-tenant(config-bd)# [no] ip address <ip-address>/<masklength> [name <subnet-name>]
    

14 Working with ACI Toolkit Objects

14.1 Query APIC for Objects with Class.get() method

tenants = Tenant.get(session) tenants = Tenant.get(session)

14.2 Create new object as instance of class

newtenant = Tenant(“MyTenant”) newtenant = Tenant(“MyTenant”)

14.3 Attach objects with methods

newbd.addcontext(newvrf) newbd.addcontext(newvrf)

14.4 View native ACI object definition

newtenant.getjson() newtenant.getjson()

14.5 Push updates to APIC

session.pushtoapic( newtenant.geturl(), data=newtenant.getjson() ) session.pushtoapic( newtenant.geturl(), data=newtenant.getjson() )

15 ACI Always ON Sandbox

If you go to the always-on sandbox, you will see a set of instructions for the lab, that I am duplicating here:

The server can be connected using login credentials: admin : ciscopsdt at this URL: https://sandboxapicdc.cisco.com

16 Getting Sandbox environment prepped.

17 Since Ansible 2.5 ACI modules are avaible natively.

Ansible includes many network modules by default • Includes Cisco as well as many other vendors • Initial support for ACI released with Ansible 2.4 • Additional modules planned for future release • Stay Tuned! More modules in Ansible 2.5

ansible-aci.png

Figure 12: Anisble ACI modules

Sample playbook

    --- 
tasks:
  - name : Create Tenant
    aci_tenant:
      host: 192.168.1.1
         username: admin
         password: cisco123
         use_ssl: True
         validate_certs: False
         state: present
         tenant: New)tenant
         descriptions: Ne wennant.

 Managing tenants with Ansible
 aci_tenant

        tenant:   

More from Cisco Live DEVNET-200:

---

- name: Example 1 - Managing Tenants
  hosts: apic
  connection: local
  gather_facts: False
  vars:
    tenants:
    - Tenant1 - Tenant2
  tasks:
    - name: Create Tenants
      aci_tenant:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        state: "present"
        validate_certs: False
        tenant: "{{ item }}"
        description: "Tenant Created Using Ansible"
      with_items: "{{ tenants }}"

17.1 Home