Using Python with the REST API

Prev Next

Introduction

VAST administrative functions are available via REST interfaces. This enables you to build your own applications to administer VAST or gather information about VAST. In this article, we provide a very simple example that shows how this can be done. The example is intentionally very simple and focuses only on the query interfaces. You can also change system state by using POST methods, but obviously, greater care needs to be taken.

Usage Overview

Python has simple built-in functions for querying and parsing REST calls, particularly those that return JSON objects. The VAST REST API, therefore, works very well with Python, although any programming language can be used.

In our example, we leverage the built-in Python urllib3 library to make simple HTTP GET requests. We used the lower-level library (as opposed to the requests API) as it gave us the needed access to the certificate controls that will be discussed in a moment.

Essentially, we have to form an HTTP URL that maps to the needed action. The convention in VAST's REST interface is that the REST call is .../api/<action>. You can see these via the Swagger interface, explained in the VAST REST API Overview article. Our example also specifies the userid and password. We will initially ignore certificates. This is done for simplicity, but of course, in a real system, you should not disable certificate validation. 

The key method, then, in our example, is this one. It forms the proper URL, calls VAST via GET, and returns the response as a JSON object.

def get_data(address, user, password, vobj):
  http = urllib3.PoolManager(cert_reqs='CERT_NONE')
  headers = urllib3.make_headers(basic_auth=user + ':' + password )
  r = http.request('GET', 'https://{}/api/{}/'.format(address, vobj),headers=headers)
  return json.loads(r.data.decode('utf-8'))

All of the other methods simply use this method to do all of the heavy lifting. For example, here's the Python method to get all of the nodes in the cluster. This makes two calls to get information, first for the CNodes and then for the DNodes. The resulting JSON objects are combined together into a single response object.

def get_all_nodes(address, user, password):
  output = list()
  for i in ['cnodes', 'dnodes']:
    data = get_data(address, user, password, i)
    output.extend(data)
# print(output)
  return output

Given the response from this method, it's quite easy to just print out interesting portions of information. Here's an example from the code:

data=get_all_nodes(args.address, args.user, args.password)
print ("Node information')
for node in data:
  print(' {} Internal: {} Build: {} State: {}'.format(node['title'],
         node['ip'],
         node['build'], 
         node['state']))

There are far more values returned about each object than are used in the example. If you want to see the entire object, just print it out raw after the get() call completes. In the previous example, you can see the print(output) line that is commented out. 

Example Usage

Here's what a sample interaction with the complete Python script might look like (you'll need to change the IP and password for your environment:

$ python3 vms-example.py  --user admin --password <password> --address 10.100.21.201 --insecure
Node information 
  cnode-1 (10.100.21.201) [smc2_c1n1] Internal: 172.16.3.1 Build: release-2-0-6-87307 State: ACTIVE
  cnode-3 (10.100.22.5) [smc2_c1n3] Internal: 172.16.3.3 Build: release-2-0-6-87307 State: ACTIVE
  cnode-2 (10.100.22.3) [smc2_c1n2] Internal: 172.16.3.2 Build: release-2-0-6-87307 State: ACTIVE
  cnode-4 (10.100.22.7) [smc2_c1cn4] Internal: 172.16.3.4 Build: release-2-0-6-87307 State: ACTIVE
  dnode-101 (10.100.21.103) [smc2_d1n2] Internal: 172.16.3.101 Build: release-2-0-6-87307 State: ACTIVE
  dnode-100 (10.100.21.101) [smc2_d1n1] Internal: 172.16.3.100 Build: release-2-0-6-87307 State: ACTIVE
Alarm information
View information
  /poc_snap1 path: /poc_snap1 alias:  policy: default
  /keystest path: /keystest alias: /keystest2 policy: default
  / path: / alias: /nosquash policy: No_Squash

Certificates

Of course, any real production system should properly validate certificates. VAST by default uses a self-signed certificate for the VMS server, but of course, you can replace that with your own. Refer to the VAST product documentation for how.

Our focus here is on showing how the client can properly check the certificate of the VMS server. In our example code and the example above, we simply ignored certificates. Not a good idea. Here's a revised version of the main get_data function with certificate-related code:

def get_data(address, user, password, vobj):
  if insecure:
    http = urllib3.PoolManager(cert_reqs='CERT_NONE')
  else:
    http = urllib3.PoolManager(ca_certs=certfile, server_hostname=certservername)
  headers = urllib3.make_headers(basic_auth=user + ':' + password )
  r = http.request('GET', 'https://{}/api/{}/'.format(address, vobj),headers=headers)
  return json.loads(r.data.decode('utf-8'))

To support certificates, we introduce three new arguments which main() handles:

  • --certfile - specifies the certificate chain in a PEM file format. This is the standard format Python uses, and most browsers can download a site that supports HTTPS. Including VAST VMS.

  • --certservername - specifies the name in the certificate that we expect. Normally, this defaults to the hostname you specified in the address, but often the hostname you are using, at least for testing, doesn't match the hostname in the certificate. For example, you might use an IP address.

  • --insecure - Passing this tells the client not to check anything related to certificates

Examples Using Certificates

Connect Insecurely

From earlier. This just ignores certificates, but still uses HTTPS.

$ python3 vms-example.py  --user admin --password <password> --address 10.100.21.201 --insecure

Connect securely, assuming VAST is using a CA-issued certificate that is a default authority on your client host (unlikely)

$ python3 vms-example.py  --user admin --password <password> --address 10.100.21.201

Most likely, you'll get an error like this because the certificate signer is not recognized by the client:

urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='10.100.21.201', port=443): Max retries exceeded with url: /api/cnodes/ (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1123)')))

Connect and Verify Certificate, but Do Not Override Server Name

For this example, you'll need to download/create a PEM file that contains the certificate chain for the VAST VMS server. If you are using Firefox, you can download this easily using the download option under the "more information --> view certificate" pop-up you get when you click on the security icon for a secure website. Other browsers have a similar function.

Let's assume we've placed that file in our current directory with the default name: vms-vastdata-conf-chain.pem. This almost works: 

$ python3 vms-example.py  --user admin --password <password> --address 10.100.21.201 --certfile vms-vastdata-conf-chain.pem
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='10.100.21.201', port=443): Max retries exceeded with url: /api/cnodes/ (Caused by SSLError(SSLCertVerificationError("hostname '10.100.21.201' doesn't match either of '*.vastdata.com', 'vms.vastdata.com', '*'")))
Notice the error has changed. The certificate is good but the hostname doesn't match. This is because we used an IP address rather than a name allowed by the certificate. Unless *.vastdata.com is a legal internal name or you've created your own signed certificates, you'll need to override the hostname verification to use an acceptable name from the certificate.

Connect and Override Server Name (most common during testing)

Let's run it again, but this time override the hostname check by providing a fake hostname that matches *.vastdata.com:

$ python3 vms-example.py  --user admin --password <password> --address 10.100.21.201 --certfile vms-vastdata-conf-chain.pem --certservername foo.vastdata.com

And it works. Of course, in your production environment, you may have a proper certificate with a proper hostname.

Summary

We've now shown you the basics of using Python to query VAST using the VAST REST interface. Obviously, our example is trivial, but you can build on it. If you come up with anything really interesting, please let us know if you are willing to share.

Full Example

Here is the complete Python client. You can copy this text into a file or your favorite IDE and then run it. We require only Python3. 

#!/usr/local/bin/python3
# Simple Example using python to call VAST REST APIs
# To install needs python libraries if not already installed
# python3 pip install requests
# To run
# ./vms-example.py
#
# To enhance and learn what the various APIs are and how to use them, leverage the swagger interface to VAST
# https://vasthost/api/
#
#
import argparse
import urllib3
import json
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

#Certificate variables
certfile = None
certservername = None
insecure = False

def parse_args():
 desc = 'Vastdata Get Node IPs'
 parser = argparse.ArgumentParser(description=desc)
 parser.add_argument('--user', required=True, help='--user <name>')
 parser.add_argument('--password', required=True, help='--password <password>')
 parser.add_argument('--address', required=True, help='--address <IP> | <hostname>')
 parser.add_argument('--certfile', required=False, help='--certfile <path> ')
 parser.add_argument('--certservername', required=False, help='--certservername <name in cert subject>')
 parser.add_argument('--insecure', required=False, action="store_true", help='--insecure skips certificate checking')

 return parser.parse_args()


def get_data(address, user, password, vobj):
 if insecure:
  http = urllib3.PoolManager(cert_reqs='CERT_NONE')
 else:
  http = urllib3.PoolManager(ca_certs=certfile, server_hostname=certservername)
  headers = urllib3.make_headers(basic_auth=user + ':' + password )
  r = http.request('GET', 'https://{}/api/{}/'.format(address, vobj),headers=headers)
 return json.loads(r.data.decode('utf-8'))

def get_all_alarms(address, user, password):
 output = list()
 data = get_data(address, user, password, 'alarms')
 output.extend(data)
# print(output)
 return output

def get_all_views(address, user, password):
 output = list()
 data = get_data(address, user, password, 'views')
 output.extend(data)
# print(output)
 return output

#Still work in progress
def are_there_new_alarms(data):
 guids = ""
 for alarm in data:
   guids = guids + alarm['object_guid']
 print(guids)
 return False

def get_all_nodes(address, user, password):
 output = list()
 for i in ['cnodes', 'dnodes']:
   data = get_data(address, user, password, i)
   output.extend(data)
# print(output)
 return output

def main():
 global certfile
 global certservername
 global insecure

 args = parse_args()

 certfile = args.certfile
 certservername = args.certservername

 if args.insecure:
  print("Certificate checking disabled")
  insecure=True
else:
  print("checking certs using certfile={} and servername={}".format(certfile,certservername))

 data = get_all_nodes(args.address, args.user, args.password)

 print('Node information ')
 for node in data:
   print(' {} Internal: {} Build: {} State: {}'.format(node['title'],
     node['ip'], node['build'], node['state']))


 print('Alarm information')
 data = get_all_alarms(args.address, args.user, args.password)
 for alarm in data:
   print(' {} severity: {} message: {}'.format(alarm['severity'], 
     alarm['object_name'], 
     alarm['alarm_message']))

 print('View information')
 data = get_all_views(args.address, args.user, args.password)
 for view in data:
   print(' {} path: {} alias: {} policy: {}'.format(view['title'], 
     view['path'], 
     view['alias'], 
     view['policy']))

# if are_there_new_alarms(data):
 # print('new alarms')
 # else:
 # print('no new alarms')

if __name__ == '__main__':
 main()