Spectrum Virtualize RESTful API

The Spectrum Virtualize Representational State Transfer (REST) model Application Programming Interface (API) consists of command targets that are used to retrieve system information and to create, modify, and delete system resources. These command targets allow command parameters to pass through unedited to the Spectrum Virtualize command line interface, which handles parsing parameter specifications for validity and error reporting. Use Hypertext Transfer Protocol Secure (HTTPS) to successfully communicate with the RESTful API server.

The RESTful API server does not consider transport security (such as SSL), but instead assumes that requests are initiated from a local, secured server. The HTTPS protocol provides privacy through data encryption. The RESTful API provides more security by requiring command authentication, which persists for two hours of activity, or 30 minutes of inactivity, whichever occurs first.

Uniform Resource Locators (URLs) target different node objects on the system. The HTTPS POST method acts on command targets that are specified in the URL. For more information, see RESTful API command targets and characteristics. To make changes or view information about different objects on the system, you must create and send a request to the system. You are required to provide certain elements for the RESTful API server to receive and translate the request into a command, as described in the next section.

Making an HTTPS request

To interact with the system by using the RESTful API, make an HTTPS command request with a valid configuration node URL destination. Include the 7443 port and the keyword rest. Use the following URL format for all requests:
https://system_node_ip:7443/rest/command
Where:
  • system_node_ip is the system IP address, which is the address that is taken by the configuration node of the system.
  • The port number is always 7443 for the Spectrum Virtualize RESTful API.
  • rest is a keyword.
  • command is the target command object (such as auth or lseventlog with any parameters).
    The command specification follows this format:
    command_name,method="POST",headers={'parameter_name': 'parameter_value',
    'parameter_name': 'parameter_value',...}
Why we use POST exclusively: All of the Spectrum Virtualize RESTful API command targets are named after Spectrum Virtualize commands, with names that already reflect Create, Read, Update, and Delete actions. MK commands make (create) resources, LS commands list (read) resources, CH commands change (update) resources, and RM commands remove (delete) resources. Using HTTP methods on top of such commands is redundant.

All commands, including LS commands, accept at least one named parameter. Because the Spectrum Virtualize RESTful API is POST method only, it implements a convention of appending positional parameters to the URI, and packing named parameters into the request body as JavaScript Object Notation (JSON) strings.

Why we do not use GET: Most HTTP servers reject GET requests with body data. To support GET would mean appending named parameters to the URI as a query string. Do not include such arbitrary data in a query string. A valid URI cannot contain whitespace and other reserved characters that might be present in the data. Therefore, you must URL-encode named parameter data before you append it to the URI. In addition to that inconvenience, the key-value structure of named parameter data must be conveyed in some way. The query string would likely be a URL-encoded JSON object. Supporting the GET method means that parameter data represented in your programming language of choice must pass through two separate encoders before the data is sent as part of an HTTP request.

The more elegant method for the command targets was to adopt POST exclusively.

As mentioned, in addition to the URL and the name of the command target, other information is required in the request line and in the body of the HTTP request that regards the action to take on the specified object. In the request line, include the POST HTTP method. Include any required parameters (such as RAID level or IP address) in the body of the request.

Provide any required parameters as valid JSON in the HTTP body, such as shown in the following example:
{'X-Auth-Username': 'superuser'}
The request is routed to port 7443 on the specified destination (which must be the configuration node of the system), where the request is received by the RESTful API server. The server runs the command, collects any resulting output, then creates an HTTP response like the following example:
HTTP/1.1 200 OK
Server: lighttpd/1.4.31
Date: date
Content-type: application/json; charset=UTF-8 
Content-length: content_length
Connection: close
{“attribute”: “value”}
To view API command targets and their characteristics, see RESTful API command targets and characteristics. To see an example of how to get started, see Getting started. See rest_api_http_errors.html for a complete list of HTTP error codes that you might encounter.

RESTful API command targets and characteristics

POST method, authentication requirements, and whether to run on the configuration node stresses the POST method for all commands, including /auth. It also shows that you must use the authentication token that is returned by the /auth command target to authenticate every other command you run. Except for the /auth command target, you run commands against the system IP address so that they are run by the configuration node.

Table 1. POST method, authentication requirements, and whether to run on the configuration node
Command targets Method Authentication required Run in Configuration Node / Cluster
/auth POST No No
All other command targets POST Yes Yes

Supported RESTful API commands for Spectrum Virtualize software shows the command target names of commonly used commands for this release of the RESTful API. Following convention, the svcinfo and svctask executable commands are defaults and do not require listing within your RESTful API command targets.

Descriptions of the command targets and their parameters, and descriptions of other less frequently used commands are available in the CLI command section of the product documentation.

Table 2. Supported RESTful API commands for Spectrum Virtualize software
Command targets
/addhostclustermember /addhostiogrp /addhostport
/addvdiskaccess /addvdiskcopy /addvolumecopy
/auth /chhost /chnode
/chnodecanister /chrcconsistgrp /chrcrelationship
/chvdisk /expandvdisksize /lscurrentuser
/lseventlog /lsfcconsistgrp /lsfcmap
/lsfcmapcandidate /lsfcmapdependentmaps /lsfcmapprogress
/lshost /lshostcluster /lshostclustermember
/lshostclustervolumemap /lshostiogrp /lshostvdiskmap
/lsiogrp /lsiogrphost /lsmdiskgrp
/lsnode /lsnodecanister /lsnodehw
/lsnodecanisterhw /lsnodecanisterstats /lsnodecanistervpd
/lsnodehw /lsnodestats /lsnodevpd
/lspartnership /lsrcrelationshipprogress /lssystem
/lssystemip /lssystemstats /lsvdisk
/lsvdiskaccess /lsvdiskcopy /lsvdiskfcmapcopies
/lsvdiskfcmappings /lsvdiskhostmap /lsvdisksyncprogress
/mkfcconsistgrp /mkfcmap /mkfcpartnership
/mkhost /mkhostcluster /mkrcconsistgrp
/mkrcrelationship /mkvdisk /mkvdiskhostmap
/mkvolume /mkvolumehostclustermap /movevdisk
/prestartfcconsistgrp /prestartfcmap /rmfcmap
/rmhost /rmhostcluster /rmhostclustermember
/rmhostiogrp /rmhostport /rmvdisk
/rmvdiskaccess /rmvdiskcopy /rmvdiskhostmap
/rmvolume /rmvolumecopy /rmvolumehostclustermap
/startfcmap /startrcconsistgrp /startrcrelationship
/stopfcconsistgrp /stopfcmap /stoprcconsistgrp
/stoprcrelationship    

Authentication overview

Aside from data encryption, the HTTPS server requires authentication of a valid user name and password for each API session. Use two authentication header fields to specify your credentials: X-Auth-Username and X-Auth-Password.

Initial authentication requires that you POST the authentication target (/auth) with the user name and password. The RESTful API server returns a hexadecimal token. A single session lasts a maximum of two active hours or thirty inactive minutes, whichever occurs first. When your session ends due to inactivity, or if you reach the maximum time that is allotted, error code 403 indicates the loss of authorization. Use the /auth command target to reauthenticate with the user name and password.

For example, the following command passes the authentication command to target node IP 192.168.10.109 at port 7443:
https://192.168.10.109:7443/rest/auth, method="POST", 
   headers={'X-Auth-Username': 'superuser', 'X-Auth-Password': 'passw0rd'}
The HTTP request that was sent to the API server looks like:
POST /auth HTTPS/HTTPS_version 
Host: https://192.168.10.109:7443
Content-type: application/json; charset=UTF-8 
Content-length: message_size
X-Auth-Token: 58cfd6acb1676e1cba78b7cb5a9a081d11d1d1cfeb0078083ef225d9c59bf4df

{“attribute”: “value”,“attribute”: “value”}
Where:
  • The first line is the request line with the POST method, API target, protocol (HTTPS), and protocol version (1.1).
  • The second line is the host header, directing the HTTP request to the correct port (7443) and IP address on the system.
  • The third line is the content type header that specifies the content type (application/json; charset=UTF-8).
  • The fourth line is the content length header with the message size.
  • The fifth line is the authentication token header, with the authentication token.
  • A space is left between the headers and the body of the request. Any parameters appear in JSON on the seventh line.
The successful auth command returns a token similar to the following example:
{
"token": "58cfd6acb1676e1cba78b7cb5a9a081d11d1d1cfeb0078083ef225d9c59bf4df"
}

Command target parameters

Command targets interact with different parts of the system. After you target a specific node in the system (usually a configuration node), target an object within that node. See Supported RESTful API commands for Spectrum Virtualize software for command targets and see the CLI commands section of this product documentation for individual command descriptions of parameters that you can specify.

Getting started

The following Python 3 example shows how to complete initial setup to start interacting with the system and running commands. For examples in other languages, see RESTful API usage examples in Perl and rest_api_usage_examples.html.

import ssl
import json
import pprint

import urllib.request
import urllib.error
import urllib.parse

no_verify = ssl.create_default_context()
no_verify.check_hostname = False
no_verify.verify_mode = ssl.CERT_NONE

if getattr(ssl, '_https_verify_certificates', None):
    ssl._https_verify_certificates(False)

class HostString(str):
    """
        Comment: Special subclass of string, for storing arbitrary host-related
                 attributes (such as auth tokens) without losing any string behavior
    """
    def __new__(cls, *args, **kwds):
        return super(HostString, cls).__new__(cls, *args, **kwds)

class RESTUtil(object):
    show_default=False
    default_headers = {}
    port = 80

    def __init__(self, show=None, catch=True):
        self.hosts = {}
        self.curr_host = None
        self.catch=catch
        self.show_default=show if show != None else self.show_default

    @property
    def host(self):
        return self.curr_host

    @host.setter
    def host(self, hostname):
        """
            Comment: Retrieve the HostString object of a known host from its
                     host name or string definition. Even if the host definition 
                     is provided, we still need to key into self.hosts in case the 
                     client classes are storing things on their HostString objects.
        """
        try:
            if hostname in self.hosts:
                self.curr_host = self.hosts[hostname]
            else:
                self.curr_host = [h for h in self.hosts.values() if h == hostname][0]
            return self.curr_host
        except IndexError:
            raise KeyError("Unrecognized host/name %s" % hostname)

    def add_host(self, hostdef, hostname=None):
        hostname = hostname if hostname is None else hostdef
        self.hosts[hostname] = HostString(hostdef)
        if self.curr_host == None:
            self.curr_host = self.hosts[hostname]
        return hostname

    def command(self, protocol, postfix, method='POST', headers=None, show=None, **cmd_kwds):
        """
            Comment: A fairly generic RESTful API request builder. 
                     See subclasses for examples of use.
        """
        if show == None:
            show = self.show_default
        headers = {} if headers == None else headers
        url = '%s://%s:%s/%s' % (
            protocol,
            self.curr_host,
            self.port,
            postfix
        )
        request = urllib.request.Request(
            url,
            headers =dict(self.default_headers, **headers),
            data=bytes(json.dumps(cmd_kwds), encoding="utf-8") if cmd_kwds else None)
        request.get_method = lambda: method
        if show:
            self.request_pprint(request)
        try:
            cmd_out = urllib.request.urlopen(request, context=no_verify).read().decode('utf-8')
        except urllib.error.HTTPError as e:
            self.exception_pprint(e)
            if not self.catch:
                raise Exception("RESTful API command failed.")
            return
        try:
            cmd_out = json.loads(cmd_out)
        except ValueError:
            pass
        if show:
            print("\nCommand Output:")
            pprint.pprint(cmd_out)
            print("")
        return cmd_out

    @staticmethod
    def request_pprint(request):
        """
            Comment: Request info print function 
                     (for self.command with show=True)
        """
        print(request.get_method(), request.get_full_url(), 'HTTP/1.1')
        print('Host:', request.host)
        for key, value in request.headers.items():
            print(key.upper() + ':', str(value))
        if request.data != None:
            print()
            pprint.pprint(request.data)

    @staticmethod
    def exception_pprint(http_error):
        """
            Comment: HTTPError info print function
        """
        print(http_error.code, '--', http_error.reason)
        print(http_error.fp.read())
        print("")

class SVCREST(RESTUtil):
    """
        Comment: RESTful wrapper for the SVC CLI
    """

    def __init__(self, host, *args, **kwds):
        self.debug = kwds.pop('debug', False)
        super().__init__(*args, **kwds)
        self.add_host(host)

    @property
    def default_headers(self):
        return {'X-Auth-Token': getattr(self.curr_host, 'token', 'badtoken'),
                'Content-Type': 'application/json'}

    @property
    def port(self):
        return getattr(self, '_port', None) or ('7665' if self.debug else '7443')

    @property
    def protocol(self):
        return getattr(self, '_protocol', None) or ('http' if self.debug else 'https')

    def command(self, cmd, *args, method="POST", headers=None, show=None, **cmd_kwds):
        postfix = '/'.join(
            ['rest'] + [cmd] + [urllib.parse.quote(str(a)) for a in args]
        )
        return super().command(
            self.protocol,
            postfix,
            method=method,
            headers=headers,
            show=show,
            **cmd_kwds
        )

    def authenticate(self, username='superuser', password='passw0rd', show=None):
        cmd_out = self.command(
            'auth', show=show, method="POST", headers={'X-Auth-Username': username, 'X-Auth-Password': password}
        )
        if cmd_out:
            self.curr_host.token = cmd_out['token']
    """
       Comment:  First, set your cluster ipaddress.  
          It's assumed superuser/passw0rd (6 lines above) is the crednetial.
          After the authenticate call, you can issue any command in 
                s.command('') that is an svcinfo or svctask cmmand)
   """
s = SVCREST('192.168.10.109')
s.authenticate()
print(s.command('lssystem'))