This introductory article is the first part in a mini series covering how to get started with the Nutanix v4 REST APIs and SDKs. In this part, we’ll setup our first requests using a custom API client written with Python 3.
Part 2, coming soon, will cover making the same requests but using the new Nutanix v4 API SDKs. Please follow @NutanixNation on Twitter so you don’t miss the publication announcements!
Demo Environment
The demo environment used throughout this article is as follows.
- Prism Central version: pc.2022.6 (Prism Central will be referred to as “PC”)
- AOS version: 6.0.2.4
- Postman version: 9.15.6
- Python version: 3.10
- Client details: Arch Linux (rolling release, although any OS capable of running Python >= 3.6 will be fine)
Requests
For this demo we’ll work with three of the currently available EA (Early Access) v4 APIs:
- Images, from the “vmm” namespace
- Tasks, from the “prism” namespace
- Clusters, from the “clustermgmt” namespace
It is important to note these APIs are currently released as Early Access (EA) and are not yet considered GA.
Building the requests
Each of the three available APIs used in this article are published under v4.0.a1 of the Nutanix v4 REST APIs. This means all our requests will be sent to URLs constructed similar to the following:
http://{{pc_ip}}:9440/api/{{namespace}}/{{version}}/{{api}}
The first example we’ll look at is retrieving a list of AHV images. Images have many uses but are typically used as base disk images during VM deployments. The complete URL to retrieve a list of all images registered on a specific PC instance is as follows:
https://{{pc_ip}}:9440/api/vmm/v4.0.a1/images
Listing registered clusters and getting specific task details would be as follows (respectively):
https://{{pc_ip}}:9440/api/clusters/v4.0.a1/clusters
https://{{pc_ip}}:9440/api/prism/v4.0.a1/config/tasks/ZXJnb24=:{{task_extId}}
Important note: When requesting details about a specific task, the task extId must always be prefixed with ZXJnb24=:
The following details apply to all requests so far:
- HTTP method: GET
- Authentication: HTTP Basic auth (username & password)
- Headers: Content-Type: application/json (this is a mandatory header)
The Script
With all required prerequisite information available, a usable demo Python script can be built fairly quickly. The demo script functions as follows:
- The Prism Central IP address or FQDN and username are mandatory command-line parameters
- The user’s password is accepted by the Python
getpass
function - Authentication headers are created using the Python
b64encode
function - Request headers are created as a Python dictionary
- A request is sent to the specified Prism Central IP address, asking for a list of all visible images
- If the request is successful, an image count is displayed
- If the request fails, general exception information is displayed
The complete script is as follows:
"""
Use the Nutanix v4 REST APIs to request and parse a list of all available images
"""
import requests
import urllib3
import getpass
import argparse
from base64 import b64encode
"""
suppress warnings about insecure connections
please consider the security implications before doing this in a production environment
"""
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
"""
setup the command line parameters
for this example only two parameters are required
- the Prism Central IP address or FQDN
- the Prism Central username; the script will prompt for the user's password
so that it never needs to be stored in plain text
"""
parser = argparse.ArgumentParser()
parser.add_argument("pc_ip", help="Prism Central IP address or FQDN")
parser.add_argument("username", help="Prism Central username")
args = parser.parse_args()
# get the cluster password
cluster_password = getpass.getpass(
prompt="Please enter your Prism Central \
password: ",
stream=None,
)
pc_ip = args.pc_ip
username = args.username
# make sure the user enters a password
if not cluster_password:
while not cluster_password:
print(
"Password cannot be empty. Please enter a password or Ctrl-C/Ctrl-D to exit."
)
cluster_password = getpass.getpass(
prompt="Please enter your Prism Central password: ", stream=None
)
try:
"""
setup the HTTP Basic Authorization header based on the
supplied username and password
"""
encoded_credentials = b64encode(
bytes(f"{username}:{cluster_password}", encoding="ascii")
).decode("ascii")
auth_header = f"Basic {encoded_credentials}"
# setup the URL that will be used for the API request
url = f"https://{pc_ip}:9440/api/vmm/v4.0.a1/images"
"""
setup the request headers
note the use of {auth_header} i.e. the Basic Authorization
credentials we setup earlier
"""
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"{auth_header}",
"cache-control": "no-cache",
}
# submit the request
try:
response = requests.request(
"GET", url, headers=headers, verify=False, timeout=10
)
if response.ok:
# show a total count of images found
print(
f'Total images found: {response.json()["metadata"]["totalAvailableResults"]}'
)
else:
print(f"An error occurred while connecting to {pc_ip}.")
"""
the following line can be uncommented to show
detailed error information
"""
print(response.text)
except Exception as ex:
print(
f"An {type(ex).__name__} exception occurred while \
connecting to {pc_ip}.\nArgument: {ex.args}."
)
# catching all exceptions like this should be generally be avoided
except Exception as e:
print(f"{e}")
Investigating the response
A request to list all images will return a response similar to what is shown below. This example shows 14 available images; to keep the response readable only the first image has been left in the list.
{
"$dataItemDiscriminator": "List<vmm.v4.images.Image>",
"data": [
{
"sizeBytes": 8589934592,
"$sourceItemDiscriminator": "vmm.v4.images.UrlSource",
"source": {
"url": "https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-2003.qcow2",
"allowInsecure": false,
"$reserved": {
"$fqObjectType": "vmm.v4.r0.a1.images.UrlSource"
},
"$objectType": "vmm.v4.images.UrlSource"
},
"$reserved": {
"$fqObjectType": "vmm.v4.r0.a1.images.Image"
},
"$objectType": "vmm.v4.images.Image",
"extId": "61c631c6-08d0-45c1-ac1a-d8bde49dd5c7",
"name": "CentOS-7.8-2003",
"type": "DISK_IMAGE"
}
],
"metadata": {
"flags": [
{
"name": "hasError",
"value": false,
"$reserved": {
"$fqObjectType": "common.v1.r0.a3.config.Flag"
},
"$objectType": "common.v1.config.Flag"
},
{
"name": "isPaginated",
"value": true,
"$reserved": {
"$fqObjectType": "common.v1.r0.a3.config.Flag"
},
"$objectType": "common.v1.config.Flag"
}
],
"links": [
{
"href": "https://10.42.250.40:9440/api/vmm/v4.0.a1/images?limit=3&$page=0&$limit=100",
"rel": "last",
"$reserved": {
"$fqObjectType": "common.v1.r0.a3.response.ApiLink"
},
"$objectType": "common.v1.response.ApiLink"
},
{
"href": "https://10.42.250.40:9440/api/vmm/v4.0.a1/images?limit=3&$page=0&$limit=100",
"rel": "self",
"$reserved": {
"$fqObjectType": "common.v1.r0.a3.response.ApiLink"
},
"$objectType": "common.v1.response.ApiLink"
},
{
"href": "https://10.42.250.40:9440/api/vmm/v4.0.a1/images?limit=3&$page=0&$limit=100",
"rel": "first",
"$reserved": {
"$fqObjectType": "common.v1.r0.a3.response.ApiLink"
},
"$objectType": "common.v1.response.ApiLink"
}
],
"totalAvailableResults": 14,
"$reserved": {
"$fqObjectType": "common.v1.r0.a3.response.ApiResponseMetadata"
},
"$objectType": "common.v1.response.ApiResponseMetadata"
},
"$reserved": {
"$fqObjectType": "vmm.v4.r0.a1.images.ImageListApiResponse"
},
"$objectType": "vmm.v4.images.ImageListApiResponse"
}
The key data points/properties in this response are:
- The
data
list includes details of all visible images - For each image within the
data
list, the following information is available:- Image size:
sizeBytes
- The type of image e.g. created from URL:
$sourceItemDescriminator
- Image source details, including the source URL:
source
- The image’s unique identifier:
extId
- Name and type:
name
andtype
, respectively
- Image size:
- The
metadata.flags
object shows key request data, including:- Whether or not any errors were returned:
hasError
- If the request has been paginated i.e. multiple pages were returned:
isPaginated
- Whether or not any errors were returned:
- The total number of results:
metadata.totalAvailableResults
- If required, the total available results could also be extracted by examining the length of the
data
list
- If required, the total available results could also be extracted by examining the length of the
Wrapping Up
In this example the Nutanix v4 REST APIs are easily consumed by standard Python libraries and functions, although the same approach can be applied to any language capable of sending a REST API request. Similar to previous Nutanix REST API versions, the use of standard JSON-formatted responses allows easy parsing using any capable scripting or development language.
In the next article we’ll carry out similar actions but using the new Nutanix v4 SDKs.
Thanks for reading and have a great day! 🙂