my cheat sheet ACI API
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
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 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 aremapped
toURLs
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
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.:
- clone from https://github.com/datacenter/acitoolkit
git clone https://github.com/datacenter/acitoolkit.git
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:
- python Toolkit library
- sample scripts
- applications'
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
, ormodify
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:
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)
6 ACI SDK (Cobra)
"aka language bindings"
- Python library that implements a REST API
- Native bindings for all REST functions
1
:1 representation ofobjects 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
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
- identify the object to be manipulated
- build the request (crud)
- commit change to object
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.
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:
- 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')
- 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 creatingan instance of the AppProfile class
and passing it a string containing theApplication Profile name
and theTenant 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.
- 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:
- 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:
python acitoolkitcli.py -l admin -p password -u
https://APIC_IPThis 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.
- Switch to a tenant configuration mode:
fabric# switchto tenant <tenant-name> fabric-tenant# switchback
- Create a Context and don’t enforce contracts on it:
fabric-tenant(config)# [no] context <context-name> fabric-tenant(config-ctx)# [no] allow-all
- 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>
- 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.
- git clone https://github.com/CiscoDevNet/aci-learning-labs-code-samples
- cd aci-learning-labs-code-samples
- python3.7 -m venv aci-learning-labs
- source aci-learning-labs/bin/activate
- pip install -r requirements.txt
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
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 }}"