Exam Topics Covered:
3.4 Construct API calls to retrieve data from Intersight
3.5 Construct a Python script using the UCS APIs to provision a new UCS server given a template
Cisco Intersight has a modern REST API based on the OpenAPI Specification. The API documentation is located here: https://intersight.com/apidocs
User must generate and download an RSA private key from the Intersight dashboard
An API key ID is also generated from the dashboard
Each request must be signed with the ID + the private key when communicating with Intersight
The Python requests library can't do this on its own. The code to do this is actually quite complex and probably beyond the scope of the exam. An example of what is required to handle the authentication is here: https://github.com/movinalot/intersight-rest-api/blob/master/intersight_auth.py. Another option is to use the SDK which handles the authentication behind the scenes.
The API supports GET, POST, PATCH and DELETE methods. With some help from the intersight-auth module above, the Python requests module can be used to interact with the API. Alternatively, the Intersight SDK can also be utilized.
Below is a simple script leveraging the Python requests module and the intersight_auth module to handle the authentication and generate some REST calls to collect details from Intersight.
import requests
from ucs.intersight_auth import IntersightAuth
api_key_id = "5eaf2f437564612d309e379a/5eaf2f437564612d309e37a7/5ef564117564612d33d47ce1"
key_file = "SecretKey.txt"
auth = IntersightAuth(secret_key_filename=key_file, api_key_id=api_key_id)
base_url = "https://intersight.com/api/v1"
# Blades
resp = requests.get(f"{base_url}/compute/PhysicalSummaries", auth=auth)
# List organizations
resp = requests.get(f"{base_url}/organization/Organizations", auth=auth)
# Service Profiles
resp = requests.get(f"{base_url}/ls/ServiceProfiles", auth=auth)
Intersight also has an SDK which can be downloaded from https://github.com/CiscoUcs/intersight-python.git. The code below provides examples of using the SDK to retrieve data from Intersight. An instance of the IntersightApiClient class is first created, passing in the private key file and the API key.
api_instance = IntersightApiClient(
"https://intersight.com/api/v1",
private_key="SecretKey.txt",
api_key_id = "5eaf2f437564612d309e379a/5eaf2f437564612d309e37a7/5ef564117564612d33d47ce1"
)
Then in order to retrieve information you can create an instance of one of the API classes, for example LsServiceProfileApi, passing the IntersightApiClient instance as an argument.
sp_handle = ls_service_profile_api.LsServiceProfileApi(api_instance)
type(sp_handle)
<class 'intersight.apis.ls_service_profile_api.LsServiceProfileApi'>
This creates an instance of the LsServiceProfileApi class. You can then call one of the available methods of the class to get, update, or delete Service Profiles in Intersight.
sp_handle.ls_service_profiles_get()
{'count': None,
'results': [{'account_moid': '5eaf2f437564612d309e379a',
'ancestors': [],
'assign_state': 'unassigned',
'assoc_state': 'unassociated',
'associated_server': '',
'config_state': 'not-applied',
'create_time': datetime.datetime(2020, 6, 17, 21, 3, 11, 779000, tzinfo=tzutc()),
'device_mo_id': '5eea80b06f72612d31ec82c7',
'dn': 'org-root/ls-test',
'domain_group_moid': '5eaf2f437564612d309e37a2',
'mod_time': datetime.datetime(2020, 6, 17, 21, 3, 11, 824000, tzinfo=tzutc()),
'moid': '5eea850f6176752d3267de4e',
'name': 'test',
'object_type': 'ls.ServiceProfile',
'oper_state': 'unassociated',
'owners': ['5eaf2f437564612d309e379a',
'5eea80b06f72612d31ec82c7'],
'parent': None,
'permission_resources': [{'moid': '5eaf2f466972652d327449da',
'object_type': 'organization.Organization',
'selector': None}],
'registered_device': {'moid': '5eea80b06f72612d31ec82c7',
'object_type': 'asset.DeviceRegistration',
'selector': None},
'rn': '',
'shared_scope': '',
'tags': [],
'version_context': None}]}
To create an object, you would call the POST method of the instance and pass in the appropriate data as a dictionary. Below is an example of creating an organization.
org_handle = organization_organization_api.OrganizationOrganizationApi(api_instance)
payload = {
"Description":"Test Org 2",
"Name": "Test2"
}
org_handle.organization_organizations_post(body=payload)
The code below shows several examples of getting and creating objects using the Intersight API.
from intersight.intersight_api_client import IntersightApiClient
from intersight.apis import fault_instance_api
from intersight.apis import inventory_device_info_api
from intersight.apis import equipment_device_summary_api
from intersight.apis import organization_organization_api
from intersight.apis import ls_service_profile_api
os.chdir(os.path.join(os.getcwd(), 'ucs'))
try:
# Create Intersight API Instance
api_instance = IntersightApiClient(
"https://intersight.com/api/v1",
private_key="SecretKey.txt",
# This file contains the Secret Key that was downloaded when the API Key was created
api_key_id = "5eaf2f437564612d309e379a/5eaf2f437564612d309e37a7/5ee95fa37564612d3344d916"
)
# Get Faults
handle = fault_instance_api.FaultInstanceApi(api_instance)
response = handle.fault_instances_get()
except Exception as err:
print("There was an error!")
print("Status:", str(err.status))
print("Reason:", str(err.reason))
device_handle = inventory_device_info_api.InventoryDeviceInfoApi(api_instance)
devices = device_handle.inventory_device_infos_get()
# Get Organizations (not Organizations in UCSM, these are seen under Settings, Access & Permissions in Intersight)
org_handle = organization_organization_api.OrganizationOrganizationApi(api_instance)
orgs = org_handle.organization_organizations_get()
for org in orgs.to_dict()['results']:
print(org['name'])
# Get Devices
device_handle = equipment_device_summary_api.EquipmentDeviceSummaryApi(api_instance)
devices = chassis_handle.equipment_device_summaries_get()
devices_dict = devices.to_dict()
for item in devices_dict['results']:
print(item['dn'], item['object_type'], item['model'], item['serial'])
# Get Service Profiles
sp_handle = ls_service_profile_api.LsServiceProfileApi(api_instance)
sps = sp_handle.ls_service_profiles_get()
for sp in sps.to_dict()['results']:
print(sp['object_type'], sp['name'])
# Create new organization
payload = {
"Description":"Test Org 2",
"Name": "Test2"
}
org_handle.organization_organizations_post(body=payload)
UCS implements an XML-based API. It is unique from many of the other Cisco product APIs in that all API requests will be POST requests whether information is being retrieved, created, updated, or deleted. In all cases, an XML payload will be sent along with the POST request which will inform UCS what action to take. An XML document will be received in response. A useful utility for working with the UCS API is the Python xmltodict library, which converts XML to a Python dictionary. It is not a native Python library, so it must be first installed with pip install xmltodict
. There is also a useful UCSM SDK which abstracts away having to deal with building and processing XML documents. It can be installed with pip install ucsmsdk
.
Press Ctrl-Option-Q (Mac) or Ctrl-Alt-Q (Windows) to enable the “Record XML” option in the UCS GUI. This will record what you are doing in the GUI and then provide the XML needed for the task. When the task has been completed in the GUI, click Stop XML Recording and it will prompt for a filename. The file will then be downloaded to your system.
Authentication is performed by sending an XML document in the format:
<aaaLogin inName="{username}" inPassword="{password}"/>
The response will be an XML document which will contain a cookie that must be included in all subsequent requests.
url = 'https://10.10.20.113/nuova'
username = 'ucspe'
password = 'ucspe'
login_body = f'<aaaLogin inName="{username}" inPassword="{password}"/>'
headers = {"Content-Type": "application/x-www-form-urlencoded"}
# Log in
resp = requests.post(url=url, headers=headers, data=login_body, verify=False)
Login response:
<aaaLogin cookie="" response="yes" '
'outCookie="1593347478/7bdd8f29-7dbc-42e4-b088-f59ac09e8488" '
'outRefreshPeriod="600" '
'outPriv="aaa,admin,ext-lan-config,ext-lan-policy,ext-lan-qos,ext-lan-security,ext-san-config,ext-san-policy,ext-san-security,fault,operations,pod-config,pod-policy,pod-qos,pod-security,read-only" '
'outDomains="org-root" outChannel="noencssl" outEvtChannel="noencssl" '
'outSessionId="" outVersion="4.1(2c)" outName=""> </aaaLogin>
Converted to JSON:
'{"aaaLogin": {"@cookie": "", "@response": "yes", "@outCookie": '
'"1593347478/7bdd8f29-7dbc-42e4-b088-f59ac09e8488", "@outRefreshPeriod": '
'"600", "@outPriv": '
'"aaa,admin,ext-lan-config,ext-lan-policy,ext-lan-qos,ext-lan-security,ext-san-config,ext-san-policy,ext-san-security,fault,operations,pod-config,pod-policy,pod-qos,pod-security,read-only", '
'"@outDomains": "org-root", "@outChannel": "noencssl", "@outEvtChannel": '
'"noencssl", "@outSessionId": "", "@outVersion": "4.1(2c)", "@outName": ""}}
The @outCookie must be stored and used in all subsequent requests.
A query can be made based on class or Distinguished Name of an object. There are also a number of query filters that can be applied. An XML document must be built and sent in a POST request. The XML document must contain the previously obtained cookie
Below is an example query for the lsServer class. The ls
means Logical Server, meaning Service Profiles. The query will return any configured Service Profiles.
query_sp = "<configResolveClass cookie="1593349985/e8e4793d-a70b-4f4a-bd23-93888d6c854c" inHierarchical="false" classId="lsServer"/>"
resp = requests.post(url=url, headers=headers, data=query_sp, verify=False)
If you have the DN of the object, you can query for it using the configResolveDn query:
query = """<configResolveDn
cookie="1593349985/e8e4793d-a70b-4f4a-bd23-93888d6c854c"
dn="org-root/ls-test" />"""
resp = requests.post(url=url, headers=headers, data=query, verify=False)
Additional query types and filters can be found here: https://www.cisco.com/c/en/us/td/docs/unified_computing/ucs/sw/api/b_ucs_api_book/b_ucs_api_book_chapter_01.html?referring_site=RE&pos=1&page=https://www.cisco.com/c/en/us/td/docs/unified_computing/ucs/sw/api/b_ucs_api_book/b_ucs_api_book_chapter_00.html#r_usingconfigfinddnsbyclassid
Creating a Service Profile is accomplished by creating an XML document containing the parameters of the Service Profile and then posting the document to the XML API. Below is an XML document and post request to create a Service Profile from a Service Profile template called test
. The new Service Profile will be created from the template with the prefix SP
, so the new Service Profile in UCSM should be SP-test
.
sp_xml = f"""
<lsInstantiateNTemplate
dn="org-root/ls-test"
cookie="{cookie}"
inTargetOrg="org-root"
inServerNamePrefixOrEmpty="SP"
inNumberOf="1"
inHierarchical="no">
</lsInstantiateNTemplate>""".strip()
resp = requests.post(url=url, headers=headers, data=sp_xml, verify=False)
Below is a full code example using the Python requests library to create a Service Profile from a template in UCS.
import requests
import xmltodict
url = 'https://10.10.20.113/nuova'
username = 'ucspe'
password = 'ucspe'
login_body = f'<aaaLogin inName="{username}" inPassword="{password}"/>'
headers = {"Content-Type": "application/x-www-form-urlencoded"}
# Log in
resp = requests.post(url=url, headers=headers, data=login_body, verify=False)
# Convert response to dict and eat the cookie
content = xmltodict.parse(resp.content)
cookie = content['aaaLogin']['@outCookie']
# Create the XML to post (This code launches an SP from an SP template called test)
sp_xml = f"""
<lsInstantiateNTemplate
dn="org-root/ls-test"
cookie="{cookie}"
inTargetOrg="org-root"
inServerNamePrefixOrEmpty="SP"
inNumberOf="1"
inHierarchical="no">
</lsInstantiateNTemplate>""".strip()
resp = requests.post(url=url, headers=headers, data=sp_xml, verify=False)
# Note you will get a 200 OK even if it fails. Have to check the returned response for errorCode.
resp_dict = xmltodict.parse(resp.content)
if '@errorCode' not in resp_dict['lsInstantiateNTemplate']:
print(f"Added SP with DN {resp_dict['lsInstantiateNTemplate']['outConfigs']['lsServer']['@dn']}"
f" and ID {resp_dict['lsInstantiateNTemplate']['outConfigs']['lsServer']['@intId']}")
else:
print(resp_dict['lsInstantiateNTemplate']['@errorCode'],
resp_dict['lsInstantiateNTemplate']['@errorDescr'])
Note that some failures report a 200 OK HTTP status code, even though UCS encountered an error. For example, an attempt to create an object that already exists will return HTTP status code 200 with the error reported in the XML response as shown below. Due to this, it is necessary to check the response for an errorCode in the returned XML document.
<configConfMo dn="fabric/server"
cookie="<real_cookie>"
response="yes"
errorCode="103"
invocationResult="unidentified-fail"
errorDescr="can't create; object already exists.">
</configConfMo>
If a query for an object fails because it doesn't exist, UCS returns an XML document with an empty
<outConfig>
field. This is also an HTTP 200 OK status code, but with no errorCode.
<configResolveDn
dn="sys/chassis-1/blade-4711"
cookie="<real_cookie>"
response="yes">
<outConfig>
</outConfig>
</configResolveDn>
Another advantage of UCSM SDK is that it takes care of handling the above errors for you.
The UCSM SDK abstracts some of the lower level functions of forming and processing XML documents and error handling. It can be installed with pip install ucsmsdk
. The documentation for the SDK is available here: https://ucsmsdk.readthedocs.io/en/latest/
The developer must first import and create a UcsHandle object, passing it the UCSM hostname, username and password as arguments. Then the login
method is called to create an authenticated session.
from ucsmsdk.ucshandle import UcsHandle
hostname = '10.10.20.113'
username = 'ucspe'
password = 'ucspe'
handle = UcsHandle(hostname, username, password)
handle.login()
From this point on the UcsHandle object can be used to interact with the API. There are methods of the UcsHandle class that are used to get, create, update, and delete objects in UCS:
Retrieve an object - query_dn, query_classid, query_dns, query_classids
Create an object - add_mo
Update an object - set_mo
Delete an object - delete_mo
Query an object:
blades = handle.query_classid("computeBlade")
This returns a list of ComputeBlade objects:
[<ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x10a0823c8>, <ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x109f4f400>, <ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x109f32518>, <ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x109f35f28>, <ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x109f45d30>, <ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x109f45e80>, <ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x109f450b8>, <ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x109f450f0>, <ucsmsdk.mometa.compute.ComputeBlade.ComputeBlade object at 0x109f24f98>]
Each ComputeBlade object has a number of attributes that you can reference:
blades[0].model
'UCSB-EX-M4-1'
Where to find the classes that can be queried upon. Two options:
Create an object:
from ucsmsdk.mometa.org.OrgOrg import OrgOrg
org = OrgOrg(parent_mo_or_dn="org-root", name="DevNet")
handle.add_mo(org)
handle.commit()
Note that nothing is submitted to the UCSM until the
commit()
method is executed
Below is a functional code example of creating and managing objects using the UCSM SDK.
from ucsmsdk.ucshandle import UcsHandle
hostname = '172.16.4.120'
username = 'ucspe'
password = 'ucspe'
# Logging in
handle = UcsHandle(hostname, username, password)
handle.login()
# Query examples
if handle.is_valid():
blades = handle.query_classid("computeBlade")
for blade in blades:
print(blade)
for blade in blades:
print(blade.dn, blade.num_of_cpus, blade.available_memory)
# Create a new Organization
from ucsmsdk.mometa.org.OrgOrg import OrgOrg
org = OrgOrg(parent_mo_or_dn="org-root", name="DevNet")
handle.add_mo(org)
handle.commit()
# Create a new Service Profile
from ucsmsdk.mometa.ls.LsServer import LsServer
sp = LsServer(parent_mo_or_dn="org-root", name="devnet_sp")
handle.add_mo(sp)
handle.commit()
# Update a Service Profile
existing_sp = handle.query_dn("org-root/ls-devnet_sp")
existing_sp.descr = 'SERVER01'
existing_sp.usr_lbl = 'BIGBADSERVER'
handle.set_mo(existing_sp)
handle.commit()
# Query Objects via ClassID
orgs = handle.query_classid("orgOrg")
# Query with filter (Returns a List containing matched objects)
filter_str = '(name, "DevNet", type="eq")'
org = handle.query_classid(class_id="OrgOrg", filter_str=filter_str)
print(org[0].name)
print(org[0].dn)
# Deleting objects
org = handle.query_dn("org-root/org-DevNet")
handle.remove_mo(org)
handle.commit()
handle.logout()
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-1-de2522095d26> in <module> ----> 1 from ucsmsdk.ucshandle import UcsHandle 2 3 hostname = '172.16.4.120' 4 username = 'ucspe' 5 password = 'ucspe' ModuleNotFoundError: No module named 'ucsmsdk'