Spectrum Virtualize RESTful API

Spectrum Virtualize Representational State Transfer (REST) 模型「應用程式設計介面 (API)」,是由用於擷取系統資訊以及建立、修改及刪除系統資源的指令目標組成。這些指令目標容許將指令參數未經編輯傳遞至 Spectrum Virtualize 指令行介面,由該介面剖析參數規格以執行有效性及錯誤報告。使用「超文字安全傳送通訊協定 (HTTPS)」可與 RESTful API 伺服器順利通訊。

RESTful API 伺服器不會考量傳輸安全(如 SSL),但會假設要求是從本端安全伺服器起始。HTTPS 通訊協定可透過資料加密提供保密性。RESTful API 可透過要求指令鑑別(持續 2 小時活動或 30 分鐘閒置時間,以先達到者為準)來加強安全。

「統一資源定址器(URL)」會以系統上的不同節點物件作為目標。HTTPS POST 方法則會針對 URL 中指定的指令目標而執行。如需相關資訊,請參閱 RESTful API 指令目標及性質。如果要進行變更或檢視系統上不同物件的相關資訊,您必須建立要求並將其傳送至系統。您需要為 RESTful API 伺服器提供特定元素,才能接收要求並將其轉換為指令(如下一節中所述)。

建立 HTTPS 要求

如果要使用 RESTful API 與系統互動,請建立包含有效配置節點 URL 目的地的 HTTPS 指令要求。請併入 7443 埠及關鍵字 rest。 請針對所有要求使用下列 URL 格式:
https://system_node_ip:7443/rest/command
其中:
  • system_node_ip 是系統 IP 位址,即系統配置節點所採用的位址。
  • 對於 Spectrum Virtualize RESTful API,埠號一律為 7443
  • rest 是關鍵字。
  • command 是目標指令物件(如包含任何參數的 authlseventlog)。
    command 規格會遵循下列格式:
    command_name,method="POST",headers={'parameter_name': 'parameter_value',
    'parameter_name': 'parameter_value',...}
為何僅僅使用 POST: 所有 Spectrum Virtualize RESTful API 指令目標都是以 Spectrum Virtualize 指令命名,其名稱已經反映建立、讀取、更新及刪除動作。MK 指令會創建(建立)資源,LS 指令會列出(讀取)資源,CH 指令會變更(更新)資源,RM 指令會移除(刪除)資源。 在此類指令的頂端使用 HTTP 方法是多餘的。

所有指令(包括 LS 指令)皆至少接受一個具名參數。因為 Spectrum Virtualize RESTful API 僅使用 POST 方法,所以它會實作如下慣例:將位置參數附加至 URI,並將具名參數作為「JavaScript 物件表示法 (JSON)」字串包裝至要求內文。

為何不使用 GET: 大部分 HTTP 伺服器會拒絕包含主體資料的 GET 要求。支援 GET 便意味著要將具名參數作為查詢字串附加至 URI。 請勿在查詢字串中包含此類任意資料。有效的 URI 不得包含空格以及資料中可能存在的其他保留字元。 因此,在將具名參數資料附加至 URI 之前,您必須對其進行 URL 編碼。除了該處不便以外,還必須以某種方式傳送具名參數資料的鍵值結構。查詢字串可能是 URL 編碼的 JSON 物件。 支援 GET 方法便意味著以所選程式設計語言代表的參數資料,在作為 HTTP 要求的一部分進行傳送之前,必須經歷兩個不同的編碼器。

指令目標的較好方法是僅僅採用 POST。

如上所述,除了指令目標的 URL 及名稱以外,要求行及 HTTP 要求內文中還需要有關對指定物件所要採取動作的其他資訊。在要求行中,請併入 POST HTTP 方法。請將任何必要參數(如 RAID 層次或 IP 位址)併入要求內文。

請在 HTTP 內文中提供任何必要參數作為有效的 JSON,如下列範例中所示:
{'X-Auth-Username': 'superuser'}
要求會遞送至指定目的地(必須是系統的配置節點)上的埠 7443,RESTful API 伺服器將在其中接收要求。伺服器會執行指令、收集任何產生的輸出,然後建立類似下列範例的 HTTP 回應:
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”}
如果要檢視 API 指令目標及其性質,請參閱 RESTful API 指令目標及性質。如果要查看如何開始使用的範例,請參閱開始使用。如需您可能會遇到的 HTTP 錯誤碼完整清單,請參閱 RESTful API HTTP 錯誤訊息

RESTful API 指令目標及性質

表 1 強調了所有指令(包括 /auth)的 POST 方法。它還顯示您必須使用 /auth 指令目標傳回的鑑別記號,來鑑別您執行的所有其他指令。除了 /auth 指令目標以外,您會針對系統 IP 位址執行指令,以讓配置節點執行這些指令。

表 1. POST 方法、鑑別需求以及是否在配置節點中執行
指令目標 方法 需要鑑別 在配置節點/叢集中執行
/auth POST
所有其他指令目標 POST

表 2 顯示此 RESTful API 版本的常用指令的指令目標名稱。按照慣例,svcinfosvctask 可執行指令是預設值,因此不需要在 RESTful API 指令目標內列出。

在產品說明文件的 CLI 指令小節中,將會提供這些指令目標及其參數的說明,以及其他不太常用的指令之說明。

表 2. Spectrum Virtualize 軟體的受支援 RESTful API 指令
指令目標
/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    

鑑別概觀

除了資料加密以外,HTTPS 伺服器還需要鑑別每個 API 階段作業的有效使用者名稱及密碼。請使用下列兩個鑑別標頭欄位來指定認證:X-Auth-UsernameX-Auth-Password

起始鑑別要求您利用使用者名稱及密碼,對鑑別目標 (/auth) 執行 POST。RESTful API 伺服器會傳回十六進位記號。單一階段作業最長會持續 2 小時作用中或 30 分鐘閒置時間,以先達到者為準。在階段作業因閒置而結束或您達到分配的時間上限時,錯誤碼 403 會指出喪失授權。請使用 /auth 指令目標,利用使用者名稱及密碼來重新鑑別。

例如,下列指令會將鑑別指令傳遞至埠 7443 上的目標節點 IP 192.168.10.109:
https://192.168.10.109:7443/rest/auth, method="POST", 
   headers={'X-Auth-Username': 'superuser', 'X-Auth-Password': 'passw0rd'}
傳送至 API 伺服器的 HTTP 要求如下所示:
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”}
其中:
  • 第一行是包含 POST 方法、API 目標、通訊協定 (HTTPS) 及通訊協定版本 (1.1) 的要求行。
  • 第二行是將 HTTP 要求導向至系統上正確的埠 (7443) 及 IP 位址的主機標頭。
  • 第三行是指定內容類型 (application/json; charset=UTF-8) 的內容類型標頭。
  • 第四行是包含訊息大小的內容長度標頭。
  • 第五行是包含鑑別記號的鑑別記號標頭。
  • 標頭與要求內文之間留有空格。任何參數皆會出現在第七行上的 JSON 中。
順利執行的 auth 指令會傳回類似下列範例的記號:
{
"token": "58cfd6acb1676e1cba78b7cb5a9a081d11d1d1cfeb0078083ef225d9c59bf4df"
}

指令目標參數

指令目標可與系統的不同組件互動。以系統中的特定節點(通常為配置節點)作為目標之後,請再以該節點內的某個物件作為目標。如需瞭解指令目標,請參閱表 2;如需您可以指定之參數的個別指令說明,請參閱本產品說明文件的 CLI 指令小節。

開始使用

下列 Python 3 範例顯示如何完成起始設定,以開始與系統互動並執行指令。如需其他語言的範例,請參閱使用 Perl 的 RESTful API 用法範例使用 CURL 的用法範例

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'))