Skip to main content

We've Moved!

Product Documentation has moved to docs.hitachivantara.com
Hitachi Vantara Knowledge

Sample coding

Sample client code written in Python is provided as reference information.

Overview of sample coding

This section provides sample client coding written in Python, as reference information for calling and using the REST API. The sample client coding includes basic code constructs required for client programs, including those for getting information about, creating, and changing the attributes of objects. Each sample of client coding shows an operation example such as allocating a volume or creating a copy pair. You can apply the code constructs included in the sample client coding to other use cases.
Code constructs of sample coding

The following operations are performed by the sample coding:

  • Allocate a volume

    In one of the samples, an HDP volume is created from an HDP pool that has already been created, and the volume is made accessible by allocating it to a host.

  • Operate a ShadowImage pair

    In one of the samples, volumes that have already been created are used to create a ShadowImage pair. After the pair is split, the status of the pair is verified.

  • Register remote storage system information

    In one of the code samples, information about the remote storage systems is registered to the REST API server of the local storage system. This is a prerequisite operation for a remote copy operation.

  • Operate a TrueCopy pair

    In one of the code samples, a TrueCopy pair is created by using volumes that have already been created, and the pair status is checked. A resource group is locked while the pair is being created.

  • Upload files (specify the transfer destinations of audit log files)

    Specify settings so that the audit log files of storage systems are transferred to the syslog server. This sample coding includes the procedure for uploading the client certificates to a storage system.

  • Download a file (back up encryption keys)

    Back up the encryption keys by using a REST API client. This sample coding includes the procedure for downloading an encryption backup file to a client. The supported storage systems for this sample coding are VSP E series, VSP G350, G370, G700, G900, VSP F350, F370, F700, F900.

  • Re-create a parity group (encrypt data)

    Delete an existing parity group, and then create a parity group for which encryption is enabled. This operation is performed as part of the procedure for encrypting existing data without changing the drive configuration. For details on the entire procedure, see the description of the flow of data encryption operations. The supported storage systems for this sample coding are VSP E series, VSP G350, G370, G700, G900, VSP F350, F370, F700, F900.

Code constructs such as the following are included in the previously listed operations:

  1. Processing that is run before the REST API is called
    • Generating request bodies in JSON format
    • Creating form data
    • Specifying request headers (for the default HTTP headers or for the custom HTTP headers)
    • Setting user authentication information (for authentication by using a user ID and a password, or for authentication by using session-based authentication)
    • Getting the URLs of the resources (when object IDs are not specified or when object IDs that are obtained from the operation results are specified, or when fixed object IDs such as those for single instances are specified)
  2. Processing to call the REST API
    • Getting information about the version of the REST API by performing a GET operation
    • Getting the action template by performing a GET operation
    • Changing object attributes by performing a PATCH operation
    • Running actions that use the action template by performing a POST operation
    • Running actions for objects by using the POST operation
    • Running actions for services by performing a POST operation
    • Creating objects by performing a POST operation
    • Running actions for objects by using the POST operation
    • Operations that require sessions to be generated on multiple devices (remote copy operation)
    • Deleting objects by performing a DELETE operation
  3. Processing that is run after the REST API is called
    • Outputting obtained information
    • Getting job execution results
    • Getting the URLs of the resources to which the operation results have been applied
    • Getting error codes
    • Outputting error messages

The following table shows an example of using each code construct included in the samples. For details about each code, see the description of each sample coding.

Code constructs

Example of using sample coding

Corresponding sample coding

Generating a request body in JSON format

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Volume allocation

Operate a ShadowImage pair

Create an HDP volume.

Volume allocation

Create a ShadowImage pair.

Operate a ShadowImage pair

Creating form data

Upload the root certificate of the syslog server to which audit log files are to be transferred.

Upload the client certificate of the syslog server to which audit log files are to be transferred.

Upload files (specify the transfer destinations of audit log files)

Specifying request headers (for the default HTTP headers)

Define headers.

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Operate a TrueCopy pair

Specifying request headers (for the custom HTTP headers)

Define headers.

Operate a ShadowImage pair

Setting user authentication information (for authentication by using a user ID and a password)

Generate a session.

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Operate a TrueCopy pair

Setting user authentication information (for authentication by using session-based authentication)

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Volume allocation

Operate a ShadowImage pair

Create an HDP volume.

Volume allocation

Split a ShadowImage pair.

Operate a ShadowImage pair

Getting the URLs of the resources (when object IDs are not specified)

Create an HDP volume. (BlockStorageAPI.ldevs)

Volume allocation

Create a host group. (BlockStorageAPI.host_groups)

Volume allocation

Create a ShadowImage pair. (BlockStorageAPI.local_copy_pairs)

Operate a ShadowImage pair

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Create a host group. (BlockStorageAPI.affected_resource)

Volume allocation

Create a ShadowImage pair. (BlockStorageAPI.affected_resource)

Operate a ShadowImage pair

Getting the URLs of the resources (when fixed object IDs such as those for single instances are specified)

Specify the syslog server to which audit log files are to be transferred. (BlockStorageAPI.auditlog_syslog)

Send a test message to the syslog server to which audit log files are to be transferred. (BlockStorageAPI.auditlog_syslog_send_test)

Upload files (specify the transfer destinations of audit log files)

Download the encryption key backup file. (BlockStorageAPI.encryption_key_file_backup)

Download a file (back up encryption keys)

Getting information about the version of the REST API by performing the GET operation

Check the version of the REST API. (BlockStorageAPI.api_version)

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Operate a TrueCopy pair

Getting an object by performing the GET operation

Get HDP volume information. (BlockStorageAPI.ldev)

Volume allocation

Get information about a ShadowImage pair.

Operate a ShadowImage pair

Getting the action template by performing the GET operation

Split a ShadowImage pair. (BlockStorageAPI.split_local_copy _pair_template)

Operate a ShadowImage pair

Getting information about the job status by performing the GET operation

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Define the function for getting status changes for asynchronous processing.

Operate a TrueCopy pair

Changing the object attribute by performing the PATCH operation

Change the host mode.

Volume allocation

Running actions that use the action template by performing a POST operation

Split a ShadowImage pair. (BlockStorageAPI.split_local_copy_pair)

Operate a ShadowImage pair

Running actions for objects by using the POST operation

Send a test message to the syslog server to which audit log files are to be transferred.

Upload files (specify the transfer destinations of audit log files)

Running actions for services by performing a POST operation

Lock resources.

Unlock resources.

Operate a TrueCopy pair

Creating objects by performing a POST operation

Create an HDP volume. (BlockStorageAPI.ldevs)

Register the WWN of the host. (BlockStorageAPI.host_wwns)

Create a host group. (BlockStorageAPI.host_groups)

Set an LU path. (BlockStorageAPI.luns)

Volume allocation

Create a ShadowImage pair. (BlockStorageAPI.local_copy_pairs)

Operate a ShadowImage pair

Running actions for objects by using the POST operation

Upload the root certificate of the syslog server to which audit log files are to be transferred.

Upload the client certificate of the syslog server to which audit log files are to be transferred.

Upload files (specify the transfer destinations of audit log files)

Operations that require sessions to be generated on multiple devices (remote copy operation)

Create a TrueCopy pair.

Operate a TrueCopy pair

Deleting objects by performing a DELETE operation

Discard the session.

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Operate a TrueCopy pair

Outputting the obtained information

Get HDP volume information.

Volume allocation

Get information about a ShadowImage pair.

Operate a ShadowImage pair

Getting the job execution results

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing. (BlockStorageAPI.job)

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Define the function for getting status changes for asynchronous processing.

Operate a TrueCopy pair

Getting the URLs of the resources to which the operation results have been applied

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Define the function for getting status changes for asynchronous processing.

Operate a TrueCopy pair

Getting error codes

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Define the function for getting status changes for asynchronous processing.

Operate a TrueCopy pair

Outputting error messages

Output error messages.

Volume allocation

Operate a ShadowImage pair

Register remote storage system information

Operate a TrueCopy pair

Structure of sample coding

The following operations are shared by the samples. These operations are implemented as functions, which are called and executed by the main operation. For details on each function, see the section explaining the functions used in the sample coding.

  • Generating a URL (BlockStorageAPI class)

    Creates a URL to issue an HTTP request to the storage system.

  • Issuing an HTTP request and verify the status of the asynchronous processing (invoke_async_command)

    Issues an HTTP request, verifies whether the execution results of the asynchronous processing have been applied, and then returns the result.

  • Getting status changes for asynchronous processing (wait_until_jobstatus_is_changed)

    Verifies that jobs have moved to the status specified in the parameter, and then returns the result.

Sample coding files

The following describes files that are provided as sample coding and their content:

  • provisioning.py

    This file contains sample coding for volume allocation.

  • local_copy.py

    This file contains sample coding for ShadowImage pair operations.

  • register_remote_storage.py

    This file contains sample coding for registering remote storage system information.

  • synchronous_remote_copy.py

    This file contains sample coding for TrueCopy pair operations.

  • auditlog_syslog_server_setting.py

    This file contains sample coding for uploading files (specifying the transfer destinations of audit log files).

  • backup_encryption_keys.py

    This file contains sample coding for downloading a file (backing up encryption keys).

  • recreate_parity_group.py

    This file contains sample coding for re-creating a parity group (encrypting data).

  • block_storage_api.py

    This file defines the BlockStorageAPI class.

  • rest_server_param.py

    This file defines information about the server where the REST API server is installed.

  • storage_param.py

    This file defines information about the local storage system.

  • remote_copy_param.py

    This file defines information about the remote and the local storage systems.

You can download the sample coding file from the following URL:

  • For VSP 5000 series storage systems:

    http://SVP-IP-address/download/samplecode.zip

  • For VSP E series, VSP G350, G370, G700, G900, VSP F350, F370, F700, F900 storage systems:

    http://GUM-IP-address/download/restapi/samplecode.zip

Operation environment for sample coding

The samples are coded in the script language Python. Python code has excellent readability. Python is a versatile programming language widely used by Web service developers. Please download Python from the official Python website (https://www.python.org/) and configure the operation environment.

The code samples provided in this chapter use the standard library (json, sys, http.client, time, and traceback). Along with the standard library, you are also going to use the Requests library, which is a third-party library. Please download it from the web page for downloading the Requests library.

Note that the operations of the code samples described in this chapter have been checked in an environment of Python 3.7.5 and Requests 2.13.0.

TipTo prevent errors that occur when the server certificate used for SSL communication between the REST API client and the storage system is a self-signed certificate, the sample coding specifies that verification of the server certificate is skipped when a request is issued.

Sample coding for volume allocation

This section explains the sample coding for volume allocation.
Sample coding operation flow for volume allocation

The following table shows the sample coding operation flow for volume allocation and the corresponding code constructs.

Step

Sample coding operation flow

Code constructs

1

Import necessary libraries and set parameters.

-

2

Define headers.

Specifying request headers (for the default HTTP headers)

3

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Getting information about the job status by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Getting the job execution results

Getting the URLs of the resources to which the operation results have been applied

Getting error codes

4

Check the version of the REST API.

Getting information about the version of the REST API by performing a GET operation

5

Generate a session.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using a user ID and a password)

Creating objects by performing a POST operation

6

Create an HDP volume.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Creating objects by performing a POST operation

7

Create a host group.

Getting the URLs of the resources (when object IDs are not specified)

Generating a request body in JSON format

Creating objects by performing a POST operation

Getting the URLs of the resources (when object IDs that are automatically obtained from the operation results are specified)

8

Change the host mode.

Generating a request body in JSON format

Changing the object attribute by performing a PATCH operation

9

Register the WWN of the host.

Getting the URLs of the resources (when object IDs are not specified)

Generating a request body in JSON format

Creating objects by performing a POST operation

10

Set an LU path.

Getting the URLs of the resources (when object IDs are not specified)

Generating a request body in JSON format

Creating objects by performing a POST operation

11

Get HDP volume information.

Getting an object by performing the GET operation

Setting user authentication information (for authentication by using session-based authentication)

Outputting the obtained information

12

Output error messages.

Outputting error messages

13

Discard the session.

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Deleting objects by performing a DELETE operation

Expected system configuration

This sample coding assumes the system configuration is as shown in the following figure.

GUID-B368983F-645B-44EA-89CE-6409D4F92E88-low.gif

The following table shows the values specified for the parameters in the sample coding. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

USER_CREDENTIAL

("user1", "pass1")

This is the authentication information to be used for authentication in the storage system. The coding sample shows a setting example when the user ID is user1, and the password is pass1. The user needs the Storage Administrator (Provisioning) role.

POOL_ID

8

ID of the created HDP pool which will be used to create the HDP volume

BYTE_CAPACITY

1T

Capacity of the HDP volume to be created

PORT_ID

["CL1-A"]

The array of names of the Fibre Channel port that is used for I/O with the host

HOST_GRP_NAME

WindowsHost

The host group name to be created in order to associate the host and the port

HOST_MODE

WIN

The host mode to be specified for the host group

HOST_WWN

aaaabbbbcccc0123

The WWN of the host

FIRST_WAIT_TIME

1

The first interval (seconds) for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

MAX_RETRY_COUNT

6

The maximum number of retries for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

Contents of the sample coding

This subsection explains the sample coding.

  1. Import necessary libraries and set parameters.

    Before starting the volume allocation processing, the sample coding imports the required libraries or classes. In addition to the common libraries, the sample coding also imports the BlockStorageAPI class that defines the function that generates URLs.

    # coding:utf-8
    
    """
    provisioning
    
    This program requires API version 1.9.0 or newer.
    """
    
    import requests
    import json
    import sys
    import http.client
    import time
    import traceback
    import rest_server_param
    import storage_param
    
    
    from block_storage_api import BlockStorageAPI
    

    Set parameters to be used in the sample coding.

    # #################Initialize parameters################# #
    # Change the following parameters to fit your environment
    
    # A POOL ID for creating a volume
    POOL_ID = 8
    
    # The DP volume capacity to create
    BYTE_CAPACITY = "1T"
    
    # A port name to add a LUN path
    PORT_ID = ["CL1-A"]
    
    # A host group name to create
    # You can assign any host group name
    HOST_GRP_NAME = "WindowsHost"
    
    # A Host mode for the created host group
    # Please refer to the manual and set an appropriate mode
    HOST_MODE = "WIN"
    
    # A World Wide Name of the host (HBA) to allocate the volume
    HOST_WWN = "aaaabbbbcccc0123"
    
    
    # This parameter defines the first interval to access
    # an asynchronous job. (Unit: Second)
    FIRST_WAIT_TIME = 1
    
    # This parameter defines the maximum retry time
    # to confirm job status.
    MAX_RETRY_COUNT = 6
    
    # An user id and password of the target storage
    USER_CREDENTIAL = ("user1", "pass1")
    
    ###########################################################
    
  2. Define headers.

    Define the HTTP request header. Because the REST API only supports JSON format data, the sample coding defines header information so that data is handled in JSON format.

    # ###You don't have to change the following parameters### #
    block_storage_api = BlockStorageAPI(
        rest_server_param.REST_SERVER_IP_ADDR,
        rest_server_param.REST_SERVER_PORT,
        storage_param.STORAGE_MODEL,
        storage_param.SERIAL_NUMBER)
    
    headers = {"content-type": "application/json",
               "accept": "application/json"}
    
    REQUIRED_MAJOR_VERSION = 1
    REQUIRED_MINOR_VERSION = 9
    
    session_id = 0
    
    ###########################################################
    
  3. Define the function for issuing an HTTP request and for verifying the status of asynchronous processing (the invoke_async_command function).

    Define the function that issues an HTTP request and verifies the status of asynchronous processing. Call and use this function from the main volume allocation operation. For details on this function, see the section explaining the functions used in the sample coding.

    TipTo prevent errors that occur when the server certificate used for SSL communication between the REST API client and the storage system is a self-signed certificate, the sample coding specifies verify=False in the request message to skip verification of the server certificate.
    """
    Check whether the asynchronous command was finished.
    @param job_id the job ID to identify
           the asynchronous command
    @return r the response data
    """
    
    
    def check_update(job_id):
        url = block_storage_api.job(str(job_id))
        r = requests.get(url, headers=headers, verify=False)
        return r
    
    """
    Execute the HTTP request (POST or PATCH)
    @param method_type HTTP request method (POST or PATCH)
    @param url URL to execute HTTP method
    @param body The information of a resource
    @return job_result.json()["affectedResources"][0]
             URL of an affected resource
    """
    
    
    def invoke_async_command(method_type, url, body):
        if method_type == "patch":
            r = requests.patch(url, headers=headers,
                             data=json.dumps(body), verify=False)
        elif method_type == "post":
            r = requests.post(
                url,
                headers=headers,
                data=json.dumps(body),
                verify=False)
        if r.status_code != http.client.ACCEPTED:
            raise requests.HTTPError(r)
        print("Request was accepted. JOB URL : " +
              r.json()["self"])
    
        status = "Initializing"
        job_result = None
        retry_count = 1
        wait_time = FIRST_WAIT_TIME
        while status != "Completed":
            if retry_count > MAX_RETRY_COUNT:
                raise Exception("Timeout Error! "
                                "Operation was not completed.")
            time.sleep(wait_time)
            job_result = check_update(r.json()["jobId"])
            status = job_result.json()["status"]
            double_time = wait_time * 2
            if double_time < 120:
                wait_time = double_time
            else:
                wait_time = 120
            retry_count += 1
        if job_result.json()["state"] == "Failed":
            error_obj = job_result.json()["error"]
            if "errorCode" in error_obj:
                if "SSB1" in error_obj["errorCode"]:
                    print("Error! SSB code : ",
                          error_obj["errorCode"]["SSB1"],
                          ", ", error_obj["errorCode"]["SSB2"])
                elif "errorCode" in error_obj["errorCode"]:
                    print("Error! error code : ",
                          error_obj["errorCode"]["errorCode"])
            raise Exception("Job Error!", job_result.text)
    
        print("Async job was succeeded. affected resource : " +
              job_result.json()["affectedResources"][0])
        return job_result.json()["affectedResources"][0]
    
  4. Check the version of the REST API.

    Get information about the version of the REST API to make sure that the version is supported.

    """
    Check whether this API version allows the REST
     Server to execute this program
    
    @param api_version api version of this REST Server
    @param required_major_version the lowest number of
           the major version that this program requires
    @param required_minor_version the lowest number of
           the minor version that this program requires
    
    """
    
    
    def check_api_version(api_version, required_major_version,
                          required_minor_version):
        version = api_version.split(".")
        major_version = int(version[0])
        minor_version = int(version[1])
        if not ((major_version == required_major_version and
                 minor_version >= required_minor_version) or
                major_version >= required_major_version + 1):
            sys.exit("This program requires API Version " +
                     str(required_major_version) + "." +
                     str(required_minor_version) +
                     "." + "x or newer.\n")
    
    try:
        # step1 Check the API version #
        print("Check the API version")
        url = block_storage_api.api_version()
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(
            r.json()["apiVersion"],
            REQUIRED_MAJOR_VERSION,
            REQUIRED_MINOR_VERSION)
    
  5. Generate a session.

    Generate sessions by using the REST API server.

        # step2 Generate a session #
        print("Generate a session")
        url = block_storage_api.generate_session()
        r = requests.post(url, headers=headers, auth=USER_CREDENTIAL,
                          verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        token = r.json()["token"]
        auth = "Session " + token
        session_id = r.json()["sessionId"]
    

    When a session is generated, a session ID and a token are returned. When running the API, specify the token for the Authentication header as the required authentication information for the subsequent operations. Use the session ID to discard the session after a set of operations is completed.

  6. Create an HDP volume.

    Specify the pool ID, and the volume capacity, and then create the HDP volume.

        # step3 Add an LDEV #
        print("Add an LDEV")
        url = block_storage_api.ldevs()
        headers["Authorization"] = auth
        body = {
            "poolId": POOL_ID,
            "byteFormatCapacity": BYTE_CAPACITY,
            "isParallelExecutionEnabled": True
        }
        ldev_id = invoke_async_command("post", url, body).split("/")[-1]
    
    The invoke_async_command function issues the request to create the HDP volume, checks the execution status of the jobs that were run asynchronously, and then returns the URL of the created HDP volume as the execution result.
  7. Create a host group.

    To allocate the created HDP volume to the host, create a host group. The sample coding only specifies the port number to be used by the host group and the host group name. Specification of the host group number is omitted. In this case, a host group number is automatically assigned.

        # step4 Add a host group #
        print("Add a host group")
        url = block_storage_api.host_groups()
        body = {
            "portId": PORT_ID[0],
            "hostGroupName": HOST_GRP_NAME
        }
        affected_resource_path = invoke_async_command("post",
                                                      url, body)
    
    Get the URL of the created host group and the assigned host group number.
        url = block_storage_api.affected_resource(
            affected_resource_path)
    
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
        host_group_number = r.json()["hostGroupNumber"]
    
  8. Change the host mode.

    Change the host mode of the created host group according to the platform of the host to which the volume will be allocated. To issue a request to change the host mode, use the URL of the host group that was obtained in the step when the host group was created.

        # step5 Modify the host group #
        print("Modify the host group")
        body = {
            "hostMode": HOST_MODE
        }
        invoke_async_command("patch", url, body)
    
  9. Register the WWN of the host.

    Register the host to which the HDP volume will be allocated in the host group that you created. Specify the WWN of the HBA of the host to be registered, the port number of the host group, and the assigned host group number. Use the host group number that was obtained when the host group was created.

        # step6 Add an HBA WWN #
        print("Add an HBA WWN")
        url = block_storage_api.host_wwns()
        body = {
            "hostWwn": HOST_WWN,
            "portId": PORT_ID[0],
            "hostGroupNumber": host_group_number
        }
        invoke_async_command("post", url, body)
    
  10. Set an LU path.

    Set the LU path by associating the created volume with the host group. The sample coding specifies the LDEV number of the created HDP volume, the port number to be used by the host group, and the host group number. Specification of the LUN is omitted. In this case, a LUN is automatically assigned.

        # step7 Add a LUN path #
        print("Add a LUN path")
        url = block_storage_api.luns()
        body = {
            "ldevId": ldev_id,
            "portIds": PORT_ID,
            "hostGroupNumber": host_group_number
        }
        invoke_async_command("post", url, body)
    
    The LU path is set, and the HDP volume is now accessible from the host.
  11. Get HDP volume information.

    To check whether the operations up to this step have been correctly applied to the resource, specify the LDEV number that was obtained when the HDP volume was created, and then obtain HDP volume information. From the collected information, the sample coding outputs the LDEV number, the ID of the pool from which the volume was created, the capacity of the HDP volume, and the assigned port.

        # step8 Print the LDEV #
        print("Print the LDEV")
        url = block_storage_api.ldev(ldev_id)
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
        print("LDEV ID : " + str(r.json()["ldevId"]))
        print("POOL ID : " + str(r.json()["poolId"]))
        print("CAPACITY : " +
              str(r.json()["byteFormatCapacity"]))
        print("PORT : " + str(r.json()["ports"]))
        print()
    
  12. Output error messages.

    In the sample coding, processing for communication errors, HTTP request errors, and job execution errors is described. If a communication error occurs, an error message is output. If an HTTP request error occurs, the error code, the error message, and the response body are output. If a job execution error occurs, all of the contents included in the job execution result are output.

    except requests.ConnectionError:
        sys.stderr.write("Connection Error!\n")
        sys.stderr.write(traceback.format_exc())
    except requests.HTTPError as he:
        sys.stderr.write("HTTP Error! status code : ")
        sys.stderr.write(str(he.args[0].status_code) + "\n")
        sys.stderr.write(he.args[0].text + "\n")
    except Exception as e:
        sys.stderr.write(traceback.format_exc())
        for msg in e.args:
            sys.stderr.write(str(msg) + "\n")
    
  13. Discard the session.

    After a set of operations is completed, discard the session. Specify the session ID that was obtained when the session was generated. The "finally" statement in the sample coding makes sure that the session will be discarded even if an error occurs while the API is running. After the session is discarded, the processing ends.

    finally:
        # ----step10 Discard the session----#
        print("Discard the session")
        url = block_storage_api.discard_session(session_id)
        r = requests.delete(url, headers=headers, verify=False)
        try:
            if r.status_code != http.client.OK:
                raise requests.HTTPError(r)
        except requests.HTTPError as he:
            sys.stderr.write("HTTP Error! status code : ")
            sys.stderr.write(str(he.args[0].status_code) + "\n")
            sys.stderr.write(he.args[0].text + "\n")
    
        print("Operation was completed.")
        sys.exit()
    

Sample coding for ShadowImage pair operations

This section explains the sample coding for the ShadowImage pair operations.
Sample coding operation flow for ShadowImage pair operations

The following table shows the sample coding operation flow for ShadowImage pair operations and the corresponding code constructs.

Step

Sample coding operation flow

Code constructs

1

Import necessary libraries and set parameters.

-

2

Define headers.

Specifying request headers (for the default HTTP headers)

Specifying request headers (for the custom HTTP headers)

3

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Getting information about the job status by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Getting the job execution results

Getting the URLs of the resources to which the operation results have been applied

Getting error codes

4

Check the version of the REST API.

Getting information about the version of the REST API by performing a GET operation

5

Generate a session.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using a user ID and a password)

Creating objects by performing a POST operation

6

Create a ShadowImage pair.

Getting the URLs of the resources (when object IDs are not specified)

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Generating a request body in JSON format

Creating objects by performing a POST operation

7

Split a ShadowImage pair.

Getting the action template by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Running actions that use the action template by using a POST operation

8

Get information about a ShadowImage pair.

Getting an object by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Outputting the obtained information

9

Output error messages.

Outputting error messages

10

Discard the session.

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Deleting objects by performing a DELETE operation

Expected system configuration

This sample coding assumes the system configuration is as shown in the following figure.

GUID-7CE540C5-9B44-463B-9C6E-DF6A895A6AE4-low.gif

The following table shows the values specified for the parameters in the sample coding. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

USER_CREDENTIAL

("user1", "pass1")

This is the authentication information to be used for authentication in the storage system. The coding sample shows a setting example when the user ID is user1, and the password is pass1. The user needs the Storage Administrator (Provisioning) role and the Storage Administrator (Local Copy) role.

COPY_GROUP_NAME

SI_347

The copy group name to be used for creating a ShadowImage pair. In the sample coding, a new copy group is created when creating a pair.

COPY_PAIR_NAME

p_347-348

The copy pair name of the ShadowImage pair to be created

PVOL_LDEV_ID

347

The LDEV number of the already created volume to be used as the primary volume

SVOL_LDEV_ID

348

The LDEV number of the already created volume to be used as the secondary volume

FIRST_WAIT_TIME

1

The first interval for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

MAX_RETRY_COUNT

10

The maximum number of retries for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

Contents of the sample coding

This subsection explains the sample coding.

  1. Import necessary libraries and set parameters.

    Before performing operations for the ShadowImage pair, the sample coding imports the required libraries or classes. In addition to the common libraries, the sample coding also imports the BlockStorageAPI class that defines the function that generates URLs.

    # coding:utf-8
    
    """
    local_copy
    
    This program requires API version 1.9.0 or newer.
    """
    
    import traceback
    import requests
    import json
    import sys
    import http.client
    import time
    import rest_server_param
    import storage_param
    
    from block_storage_api import BlockStorageAPI
    

    Set parameters to be used in the sample coding.

    # #################Initialize parameters################# #
    # Change the following parameters to fit your environment
    
    # A copy group name
    COPY_GROUP_NAME = "SI_347"
    
    # A copy pair name
    COPY_PAIR_NAME = "p_347-348"
    
    # A primary volume ID
    # Specify already created and allocated volume ID by decimal
    PVOL_LDEV_ID = 347
    
    # A secondary volume ID which has the exactly same size
    # as the primary volume
    # Specify already created and allocated volume ID by decimal
    SVOL_LDEV_ID = 348
    
    
    # This parameter defines the first interval to access
    # an asynchronous job. (Unit: Second)
    FIRST_WAIT_TIME = 1
    
    # This parameter defines the maximum retry time
    # to confirm job status.
    MAX_RETRY_COUNT = 10
    
    # An user id and password of the target storage
    USER_CREDENTIAL = ("user1", "pass1")
    
    ###########################################################
    
  2. Define headers.

    Define the HTTP request header. Because the REST API only supports JSON format data, the sample coding defines header information so that data is handled in JSON format. In addition, for asynchronous processing, the sample coding specifies the settings of the Response-Job-Status header so that responses are returned after waiting for the completion of the jobs.

    # ###You don't have to change the following parameters### #
    block_storage_api = BlockStorageAPI(
        rest_server_param.REST_SERVER_IP_ADDR,
        rest_server_param.REST_SERVER_PORT,
        storage_param.STORAGE_MODEL,
        storage_param.SERIAL_NUMBER)
    
    headers = {"content-type": "application/json",
               "accept": "application/json",
               "Response-Job-Status": "Completed"}
    
    REQUIRED_MAJOR_VERSION = 1
    REQUIRED_MINOR_VERSION = 9
    
    session_id = 0
    
    ###########################################################
    
  3. Define the function for issuing an HTTP request and for verifying the status of asynchronous processing (the invoke_async_command function).

    Define the function that issues an HTTP request and verifies the status of asynchronous processing. Call and use this function from the main ShadowImage pair operation. For details on this function, see the section explaining the of functions used in the sample coding.

    TipTo prevent errors that occur when the server certificate used for SSL communication between the REST API client and the storage system is a self-signed certificate, the sample coding specifies verify=False in the request message to skip verification of the server certificate.
    """
    Check whether the asynchronous command was finished.
    @param job_id The job ID to identify
                  the asynchronous command
    @return r.json() The JSON data that contains response data
    
    """
    
    
    def check_update(job_id):
        url = block_storage_api.job(str(job_id))
        r = requests.get(url, headers=headers, verify=False)
        return r
    
    
    """
    Execute the HTTP request (POST or PATCH)
    @param method_type HTTP request method (POST or PATCH)
    @param url URL to execute HTTP method
    @param body The information of a resource
    @return job_result.json()["affectedResources"][0]
             URL of an affected resource
    """
    
    
    def invoke_async_command(method_type, url, body):
        if method_type == "patch":
            r = requests.patch(url, headers=headers,
                             data=json.dumps(body), verify=False)
        elif method_type == "post":
            r = requests.post(
                url,
                headers=headers,
                data=json.dumps(body),
                verify=False)
        if r.status_code != http.client.ACCEPTED:
            raise requests.HTTPError(r)
        print("Request was accepted. JOB URL : " +
              r.json()["self"])
        status = "Initializing"
        job_result = None
        retry_count = 1
        wait_time = FIRST_WAIT_TIME
        while status != "Completed":
            if retry_count > MAX_RETRY_COUNT:
                raise Exception("Timeout Error! "
                                "Operation was not completed.")
            time.sleep(wait_time)
            job_result = check_update(r.json()["jobId"])
            status = job_result.json()["status"]
            double_time = wait_time * 2
            if double_time < 120:
                wait_time = double_time
            else:
                wait_time = 120
            retry_count += 1
        if job_result.json()["state"] == "Failed":
            error_obj = job_result.json()["error"]
            if "errorCode" in error_obj:
                if "SSB1" in error_obj["errorCode"]:
                    print("Error! SSB code : ",
                          error_obj["errorCode"]["SSB1"],
                          ", ", error_obj["errorCode"]["SSB2"])
                elif "errorCode" in error_obj["errorCode"]:
                    print("Error! error code : ",
                          error_obj["errorCode"]["errorCode"])
            raise Exception("Job Error!", job_result.text)
        print("Async job was succeeded. affected resource : " +
              job_result.json()["affectedResources"][0])
        return job_result.json()["affectedResources"][0]
    
  4. Check the version of the REST API.

    Get information about the version of the REST API to make sure that the version is supported.

    """
    Check whether this API version allows the REST
     Server to execute this program
    
    @param api_version api version of this REST Server
    @param required_major_version the lowest number of
           the major version that this program requires
    @param required_minor_version the lowest number of
           the minor version that this program requires
    
    """
    
    
    def check_api_version(api_version, required_major_version,
                          required_minor_version):
        version = api_version.split(".")
        major_version = int(version[0])
        minor_version = int(version[1])
        if not ((major_version == required_major_version and
                 minor_version >= required_minor_version) or
                major_version >= required_major_version + 1):
            sys.exit(
                "This program requires API Version " +
                str(required_major_version) +
                "." +
                str(required_minor_version) +
                "." +
                "x or newer.\n")
    
    try:
        # step1 Check the API version #
        print("Check the API version")
        url = block_storage_api.api_version()
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(
            r.json()["apiVersion"],
            REQUIRED_MAJOR_VERSION,
            REQUIRED_MINOR_VERSION)
    
  5. Generate a session.

    Generate sessions by using the REST API server.

        # step2 Generate a session #
        print("Generate a session")
        url = block_storage_api.generate_session()
        r = requests.post(url, headers=headers,
                          auth=USER_CREDENTIAL, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        token = r.json()["token"]
        auth = "Session " + token
        session_id = r.json()["sessionId"]
    

    When a session is generated, a session ID and a token are returned. When running the API, specify the token for the Authentication header as the required authentication information for the subsequent operations. Use the session ID to discard the session after a set of operations is completed.

  6. Create a ShadowImage pair.

    Use the already created volumes to create a ShadowImage pair. Also create a new copy group. Specify the copy group name, copy pair name, and the LDEV number of the volume to be used that are defined in advance in the parameters. In addition, specify the copy pair type, MU number, and whether to create a copy group, and then issue a request to create a ShadowImage pair. The block_storage_api function is used to generate the URL.

        # step3 Create a local copy pair #
        print("Create a local copy pair")
        url = block_storage_api.local_copy_pairs()
        body = {
            "copyGroupName": COPY_GROUP_NAME,
            "copyPairName": COPY_PAIR_NAME,
            "replicationType": "SI",
            "pvolLdevId": PVOL_LDEV_ID,
            "pvolMuNumber": 0,
            "svolLdevId": SVOL_LDEV_ID,
            "isNewGroupCreation": True,
        }
        headers["Authorization"] = auth
        affected_resource = invoke_async_command("post",
                                                 url, body)
        pair_url = block_storage_api.affected_resource(
            affected_resource)
    

    The invoke_async_command function issues the request to create a ShadowImage pair under the specified conditions, checks the execution status of the jobs that were run asynchronously, and then returns the URL of the created pair as the execution result.

  7. Split the ShadowImage pair.

    In the sample coding, a ShadowImage pair is split by using the action template. First, get the action template for splitting a ShadowImage pair by using the URL of the pair that was obtained when the ShadowImage pair was created.

        # step4 Split the local copy pair #
        print("Split the local copy pair")
        url = block_storage_api.split_local_copy_pair_template(
            pair_url)
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        print("Action template(split):")
        print(r.text)
    

    Set values in the obtained template, and then issue a request to split the created ShadowImage pair.

        body = r.json()
        body["parameters"]["copyPace"] = 3
        split_url = block_storage_api.split_local_copy_pair(
            pair_url)
        invoke_async_command("post", split_url, body)
    
  8. Get information about the ShadowImage pair.

    Get information about the pair by using the URL of the pair that was obtained when the ShadowImage pair was created. In the sample coding, the following items are output: the copy group name, copy pair name, LDEV number and pair volume status for the P-VOL, and the LDEV number and pair volume status for the S-VOL.

        # step5 Print the pair status #
        print("Print the pair status")
        r = requests.get(pair_url,
                         headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
        print("COPY GROUP NAME : " + r.json()["copyGroupName"])
        print("COPY PAIR NAME : " + r.json()["copyPairName"])
        print("P-VOL LDEV ID : " + str(r.json()["pvolLdevId"]))
        print("S-VOL LDEV ID : " + str(r.json()["svolLdevId"]))
        print("P-VOL STATUS : " + r.json()["pvolStatus"])
        print("S-VOL STATUS : " + r.json()["svolStatus"])
        print("LOCAL CLONE COPY PAIR ID : " +
              r.json()["localCloneCopypairId"])
        print()
    
  9. Output error messages.

    In the sample coding, processing for communication errors, HTTP request errors, and job execution errors is described. If a communication error occurs, an error message is output. If an HTTP request error occurs, the error code, the error message, and the response body are output. If a job execution error occurs, all of the contents included in the job execution result are output.

    except requests.ConnectionError:
        sys.stderr.write("Connection Error!\n")
        sys.stderr.write(traceback.format_exc())
    except requests.HTTPError as he:
        sys.stderr.write("HTTP Error! status code : ")
        sys.stderr.write(str(he.args[0].status_code) + "\n")
        sys.stderr.write(he.args[0].text + "\n")
    except Exception as e:
        sys.stderr.write(traceback.format_exc())
        for msg in e.args:
            sys.stderr.write(str(msg) + "\n")
    
  10. Discard the session.

    After a set of operations is completed, discard the session. Specify the session ID that was obtained when the session was generated. The "finally" statement in the sample coding makes sure that the session will be discarded even if an error occurs while the API is running. After the session is discarded, the processing ends.

    finally:
        # step6 Discard the session #
        print("Discard the session")
        url = block_storage_api.discard_session(session_id)
        r = requests.delete(url, headers=headers, verify=False)
        try:
            if r.status_code != http.client.OK:
                raise requests.HTTPError(r)
        except requests.HTTPError as he:
            sys.stderr.write("HTTP Error! status code : ")
            sys.stderr.write(str(he.args[0].status_code) + "\n")
            sys.stderr.write(he.args[0].text + "\n")
    
        print("Operation was completed.")
        sys.exit()
    

Sample coding for registering remote storage system information

This section explains the sample coding for registering remote storage system information.
Sample coding operation flow for registering remote storage system information

The following table shows the sample coding operation flow for registering remote storage system information and the corresponding code constructs.

Step

Sample coding operation flow

Code constructs

1

Import necessary libraries and set parameters.

-

2

Define headers.

Specifying request headers (for the default HTTP headers)

3

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Getting information about the job status by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Getting the job execution results

Getting the URLs of the resources to which the operation results have been applied

Getting error codes

4

Check the version of the REST API.

Getting information about the version of the REST API by performing a GET operation

5

Generate a session.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using a user ID and a password)

Creating objects by performing a POST operation

6

Register information about storage systems.

Getting the URLs of the resources (when object IDs are not specified)

Generating a request body in JSON format

Operations that require sessions to be generated on multiple devices (remote copy operation)

Creating objects by performing a POST operation

7

Get information about storage systems.

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Setting user authentication information (for authentication by using session-based authentication)

Getting an object by performing a GET operation

Outputting the obtained information

8

Output error messages.

Outputting error messages

9

Discard the session.

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Deleting objects by performing a DELETE operation

Expected system configuration

This sample coding assumes the system configuration is as shown in the following figure.

GUID-403F9D59-F012-4A19-975F-ECFF77665089-low.gif

The following table shows the values specified for the parameters in the sample coding. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

LOCAL_USER _CREDENTIAL

("local_user", "local_pass")

This is the authentication information to be used for authentication in the local storage system. The coding sample shows a setting example when the user ID is local_user, and the password is local_pass. The user needs the Storage Administrator (Initial Configuration) role.

REMOTE_USER _CREDENTIAL

("remote_user", "remote_pass")

This is the authentication information to be used for authentication in the remote storage system. The coding sample shows a setting example when the user ID is remote_user, and the password is remote_pass. The user needs the Storage Administrator (Initial Configuration) role.

FIRST_WAIT_TIME

1

The first interval for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

MAX_RETRY_COUNT

10

The maximum number of retries for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

The following table shows the parameters and values defined in the remote_copy_param.py file, which can be used in coding samples as common variables for the information about the local and the remote storage systems. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

LOCAL_REST_SERVER_IP_ADDR

192.0.2.100

The IP address of the REST API server of the local storage system

LOCAL_PORT

443

The SSL communication port for the REST API server of the local storage system

LOCAL_STORAGE_MODEL

VSP G900

The model name of the local storage system

LOCAL_SERIAL_NUMBER

410000

The serial number of the local storage system

REMOTE_REST_SERVER_IP_ADDR

192.0.2.200

The IP address of the REST API server of the remote storage system

REMOTE_PORT

443

The SSL communication port for the REST API server of the remote storage system

REMOTE_STORAGE_MODEL

VSP G900

The model name of the remote storage system

REMOTE_SERIAL_NUMBER

420000

The serial number of the remote storage system

Contents of the sample coding

This subsection explains the sample coding.

  1. Import necessary libraries and set parameters.

    Before starting the registration processing of remote storage system information, the sample coding imports the required libraries or classes. In addition to the common libraries, the sample coding also imports the BlockStorageAPI class that defines the function that generates URLs.

    # coding:utf-8
    
    """
    register_remote_storage
    
    This program requires API version 1.9.0 or newer.
    """
    
    import traceback
    import requests
    import json
    import sys
    import http.client
    import time
    import remote_copy_param
    
    from block_storage_api import BlockStorageAPI
    

    Set parameters to be used in the sample coding.

    # #################Initialize parameters################# #
    # Change the following parameters to fit your environment
    
    # This parameter defines the first interval to access
    # an asynchronous job. (Unit: Second)
    FIRST_WAIT_TIME = 1
    
    # This parameter defines the maximum retry time
    # to confirm job status.
    MAX_RETRY_COUNT = 10
    
    # An user id and password of the local storage
    LOCAL_USER_CREDENTIAL = ("local_user", "local_pass")
    
    # An user id and password of the remote storage
    REMOTE_USER_CREDENTIAL = ("remote_user", "remote_pass")
    
    ###########################################################
    
  2. Define headers.

    Define the HTTP request header. Because the REST API only supports JSON format data, the sample coding defines header information so that data is handled in JSON format.

    # ###You don't have to change the following parameters### #
    local_storage_api = BlockStorageAPI(
        remote_copy_param.LOCAL_REST_SERVER_IP_ADDR,
        remote_copy_param.LOCAL_PORT,
        remote_copy_param.LOCAL_STORAGE_MODEL,
        remote_copy_param.LOCAL_SERIAL_NUMBER)
    
    remote_storage_api = BlockStorageAPI(
        remote_copy_param.REMOTE_REST_SERVER_IP_ADDR,
        remote_copy_param.REMOTE_PORT,
        remote_copy_param.REMOTE_STORAGE_MODEL,
        remote_copy_param.REMOTE_SERIAL_NUMBER)
    
    local_headers = {"content-type": "application/json",
                     "accept": "application/json",
                     "Response-Job-Status": "Completed"}
    
    remote_headers = {"content-type": "application/json",
                      "accept": "application/json",
                      "Response-Job-Status": "Completed"}
    
    REQUIRED_MAJOR_VERSION = 1
    REQUIRED_MINOR_VERSION = 9
    
    local_session_id = 0
    remote_session_id = 0
    
    ###########################################################
    
  3. Define the function for issuing an HTTP request and for verifying the status of asynchronous processing (the invoke_async_command function).

    Define the function that issues an HTTP request and verifies the status of asynchronous processing. Call and use this function from the main registration operation of the remote storage system information. For details on this function, see the section explaining the functions used in the sample coding.

    TipTo prevent errors that occur when the server certificate used for SSL communication between the REST API client and the storage system is a self-signed certificate, the sample coding specifies verify=False in the request message to skip verification of the server certificate.
    """
    Check whether the asynchronous command was finished.
    
    @param storage_api storage_api
    @param job_id the job ID to identify
    the asynchronous command
    @param headers the array of the http headers
    @return r the response data
    """
    
    
    def check_update(storage_api, job_id, headers):
        url = storage_api.job(str(job_id))
        r = requests.get(url, headers=headers, verify=False)
        return r
    
    """
    Execute the HTTP request (POST or PATCH)
    
    @param storage_api storage_api
    @param method_type HTTP request method (POST or PATCH)
    @param headers the array of the http headers
    @param url URL to execute HTTP method
    @param body The information of a resource
    @return job_result.json()["affectedResources"][0]
             URL of an affected resource
    """
    
    
    def invoke_async_command(storage_api, method_type, headers,
                             url, body):
        if method_type == "patch":
            r = requests.patch(url, headers=headers,
                             data=json.dumps(body), verify=False)
        elif method_type == "post":
            r = requests.post(
                url,
                headers=headers,
                data=json.dumps(body),
                verify=False)
        if r.status_code != http.client.ACCEPTED:
            raise requests.HTTPError(r)
        print("Request was accepted. JOB URL : " +
              r.json()["self"])
        status = "Initializing"
        job_result = None
        retry_count = 1
        wait_time = FIRST_WAIT_TIME
        while status != "Completed":
            if retry_count > MAX_RETRY_COUNT:
                raise Exception("Timeout Error! "
                                "Operation was not completed.")
            time.sleep(wait_time)
            job_result = check_update(storage_api,
                                      r.json()["jobId"], headers)
            status = job_result.json()["status"]
            double_time = wait_time * 2
            if double_time < 120:
                wait_time = double_time
            else:
                wait_time = 120
            retry_count += 1
        if job_result.json()["state"] == "Failed":
            error_obj = job_result.json()["error"]
            if "errorCode" in error_obj:
                if "SSB1" in error_obj["errorCode"]:
                    print("Error! SSB code : ",
                          error_obj["errorCode"]["SSB1"],
                          ", ", error_obj["errorCode"]["SSB2"])
                elif "errorCode" in error_obj["errorCode"]:
                    print("Error! error code : ",
                          error_obj["errorCode"]["errorCode"])
            raise Exception("Job Error!", job_result.text)
        print("Async job was succeeded. affected resource : " +
              job_result.json()["affectedResources"][0])
        return job_result.json()["affectedResources"][0]
    
  4. Check the version of the REST API.

    Get information about the version of the REST API for both the local and the remote storage systems by using the REST API server of each system to make sure that the version is supported.

    """
    Check whether this API version allows the REST
     Server to execute this program
    
    @param api version api_version of this REST Server
    @param required_major_version the lowest number of
    the major version that this program requires
    @param required_minor_version the lowest number of
    the minor version that this program requires
    
    """
    
    
    def check_api_version(api_version, required_major_version,
                          required_minor_version):
        version = api_version.split(".")
        major_version = int(version[0])
        minor_version = int(version[1])
        if not ((major_version == required_major_version and
                 minor_version >= required_minor_version) or
                major_version >= required_major_version + 1):
            sys.exit("This program requires API Version " +
                     str(required_major_version) + "." +
                     str(required_minor_version) +
                     "." + "x or newer.\n")
    
    try:
        # step1 Check the API version of the local REST API #
        print("Check the API version of the local REST API")
        url = local_storage_api.api_version()
        r = requests.get(url, headers=local_headers,
                         verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(r.json()["apiVersion"],
                          REQUIRED_MAJOR_VERSION,
                          REQUIRED_MINOR_VERSION)
    
        # step1 Check the API version of the remote REST API #
        print("Check the API version of the remote REST API")
        url = remote_storage_api.api_version()
        r = requests.get(url, headers=remote_headers,
                         verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(r.json()["apiVersion"],
                          REQUIRED_MAJOR_VERSION,
                          REQUIRED_MINOR_VERSION)
    
  5. Generate a session.

    Generate a session in both the local and the remote storage systems by using the REST API server of each system.

        # step2 Generate a local session #
        print("Generate a local session")
        url = local_storage_api.generate_session()
        r = requests.post(
            url,
            headers=local_headers,
            auth=LOCAL_USER_CREDENTIAL,
            verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        local_token = r.json()["token"]
        local_auth = "Session " + local_token
        local_session_id = r.json()["sessionId"]
    
        # step2 Generate a remote session #
        print("Generate a remote session")
        url = remote_storage_api.generate_session()
        r = requests.post(url, headers=remote_headers,
                          auth=REMOTE_USER_CREDENTIAL,
                          verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        remote_token = r.json()["token"]
        remote_auth = "Session " + remote_token
        remote_session_id = r.json()["sessionId"]
        remote_headers["Authorization"] = remote_auth
    

    When a session is generated, a session ID and a token are returned. When running the API, specify the token for the Authentication header as the required authentication information for the subsequent operations. Use the session ID to discard the session after a set of operations is completed.

  6. Register information about the remote storage system

    Register information about the remote storage system on the REST API server of the local storage system. For the request body, specify information about the remote storage system.

        # step3 Register a remote storage device #
        print("Register a remote storage device")
        url = local_storage_api.remote_storage()
        body = {
            "storageDeviceId": remote_storage_api.
            get_storage_id(),
            "restServerIp":
                remote_copy_param.REMOTE_REST_SERVER_IP_ADDR,
            "restServerPort": remote_copy_param.REMOTE_PORT
        }
        local_headers["Authorization"] = local_auth
        local_headers["Remote-Authorization"] = remote_auth
        affected_resource_path = invoke_async_command(
            local_storage_api, "post",
            local_headers, url, body)
    

    The invoke_async_command function issues a request for registering information about the remote storage system, verifies the execution status of the jobs that were run asynchronously, and then returns the URL of the registered storage system as the execution result.

  7. Get information about the registered remote storage system.

    To confirm that information about the remote storage system is correctly registered, get the registered storage information by using the REST API server of the local storage system.

        # step4 Print the remote storage device #
        print("Print the remote storage device")
        url = local_storage_api.affected_resource(
            affected_resource_path)
        r = requests.get(url, headers=local_headers,
                         verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        print("STORAGE DEVICE ID : " +
              str(r.json()["storageDeviceId"]))
        print("DKC TYPE : " + str(r.json()["dkcType"]))
        print("REST SERVER IP : " + str(r.json()["restServerIp"]))
        print("REST SERVER PORT : " +
              str(r.json()["restServerPort"]))
        print("MODEL : " + str(r.json()["model"]))
        print("SERIAL NUMBER : " +
              str(r.json()["serialNumber"]))
    

    In the sample coding, the following items are obtained and output: storage device ID, storage system type, IP address of the REST API server for the remote storage system, port number of the REST API server for the remote storage system, model name, and serial number.

  8. Output error messages.

    In the sample coding, processing for communication errors, HTTP request errors, and job execution errors is described. If a communication error occurs, an error message is output. If an HTTP request error occurs, the error code, the error message, and the response body are output. If a job execution error occurs, all of the contents included in the job execution result are output.

    except requests.ConnectionError:
        sys.stderr.write("Connection Error!\n")
        sys.stderr.write(traceback.format_exc())
    except requests.HTTPError as he:
        sys.stderr.write("HTTP Error! status code : ")
        sys.stderr.write(str(he.args[0].status_code) + "\n")
        sys.stderr.write(he.args[0].text + "\n")
    except Exception as e:
        sys.stderr.write(traceback.format_exc())
        for msg in e.args:
            sys.stderr.write(str(msg) + "\n")
    
  9. Discard the session.

    After a set of operations is completed, discard the session by using the REST API server of both the local and the remote storage systems. Specify the session ID that was obtained when the session was created. The "finally" statement in the sample coding makes sure that the session will be discarded even if an error occurs while the API is running. After the session is discarded, the processing ends.

    finally:
        # step5 Discard the local session #
        print("Discard the local session")
        url = local_storage_api.discard_session(
            local_session_id)
        r = requests.delete(url, headers=local_headers,
                            verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
        # step5 Discard the remote session #
        print("Discard the remote session")
        url = remote_storage_api.discard_session(
            remote_session_id)
        r = requests.delete(url, headers=remote_headers,
                            verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
        print("Operation was completed.")
        sys.exit()
    

Sample coding for TrueCopy pair operations

This section explains the sample coding for the TrueCopy pair operations.
Sample coding operation flow for TrueCopy pair operations

The following table shows the sample coding operation flow for TrueCopy pair operations and the corresponding code constructs.

Step

Sample coding operation flow

Code constructs

1

Import necessary libraries and set parameters.

-

2

Define headers.

Specifying request headers (for the default HTTP headers)

3

Define the function for getting status changes for asynchronous processing.

Getting information about the job status by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Getting the job execution results

Getting error codes

4

Check the version of the REST API.

Getting information about the version of the REST API by performing a GET operation

5

Generate a session.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using a user ID and a password)

Creating objects by performing a POST operation

6

Lock resources.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using session-based authentication)

Running actions for services by performing a POST operation

7

Create a TrueCopy pair.

Getting the URLs of the resources (when object IDs are not specified)

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Operations that require sessions to be generated on multiple devices (remote copy operation)

Creating objects by performing a POST operation

8

Unlock resources.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using session-based authentication)

Running actions for services by performing a POST operation

9

Confirm that the pair has been created.

-

10

Get information about a TrueCopy pair.

Getting an object by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Getting the URLs of the resources to which the operation results have been applied

Getting error codes

11

Output error messages.

Outputting error messages

12

Discard the session.

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Setting user authentication information (for authentication by using session-based authentication)

Deleting objects by performing a DELETE operation

Expected system configuration

This sample coding assumes the system configuration is as shown in the following figure.

GUID-22619B9B-4EFB-4203-9BA9-E8F6FE256F12-low.gif

The following table shows the values specified for the parameters in the sample coding. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

LOCAL_USER _CREDENTIAL

("local_copy_user", "local_copy_pass")

This is the authentication information to be used for authentication in the local storage system. The coding sample shows a setting example when the user ID is local_copy_user, and the password is local_copy_pass. The user needs the Storage Administrator (Provisioning) role and the Storage Administrator (Remote Copy) role.

REMOTE_USER _CREDENTIAL

("remote_copy_user", "remote_copy_pass")

This is the authentication information to be used for authentication in the remote storage system. The coding sample shows a setting example when the user ID is remote_copy_user, and the password is remote_copy_pass. The user needs the Storage Administrator (Provisioning) role and the Storage Administrator (Remote Copy) role.

COPY_GROUP_NAME

"TC_GROUP"

The copy group name for a TrueCopy pair to be created

COPY_PAIR_NAME

"p_347-348"

The copy pair name for a TrueCopy pair to be created

PVOL_LDEV_ID

347

The number of the already created LDEV to be used for the primary volume

SVOL_LDEV_ID

348

The number of the already created LDEV to be used for the secondary volume

FIRST_WAIT_TIME

1

The first interval for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

MAX_RETRY_COUNT

10

The maximum number of retries for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

The following table shows the parameters and values defined in the remote_copy_param.py file, which can be used in coding samples as common variables for the information about the local and the remote storage systems. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

LOCAL_REST_SERVER_IP_ADDR

192.0.2.100

The IP address of the REST API server of the local storage system

LOCAL_PORT

443

The SSL communication port for the REST API server of the local storage system

LOCAL_STORAGE_MODEL

VSP G900

The model name of the local storage system

LOCAL_SERIAL_NUMBER

410000

The serial number of the local storage system

REMOTE_REST_SERVER_IP_ADDR

192.0.2.200

The IP address of the REST API server of the remote storage system

REMOTE_PORT

443

The SSL communication port for the REST API server of the remote storage system

REMOTE_STORAGE_MODEL

VSP G900

The model name of the remote storage system

REMOTE_SERIAL_NUMBER

420000

The serial number of the remote storage system

Contents of the sample coding

This subsection explains the sample coding.

  1. Import necessary libraries and set parameters.

    Before starting operations for the TrueCopy pair, the sample coding imports the required libraries or classes. In addition to the common libraries, the sample coding also imports the BlockStorageAPI class that defines the function that generates URLs.

    # coding:utf-8
    
    """
    synchronous_remote_copy
    
    This program requires API version 1.9.0 or newer.
    """
    
    import traceback
    import requests
    import json
    import sys
    import http.client
    import time
    import remote_copy_param
    
    from block_storage_api import BlockStorageAPI
    

    Set parameters to be used in the sample coding.

    # #################Initialize parameters################# #
    # Change the following parameters to fit your environment
    
    # A copy group name
    COPY_GROUP_NAME = "TC_GROUP"
    
    # A copy pair name
    COPY_PAIR_NAME = "p_347-348"
    
    # A primary volume ID
    # Specify already created and allocated volume ID by decimal
    PVOL_LDEV_ID = 347
    
    # A secondary volume ID which has the exactly same size
    # as the primary volume
    # Specify already created and allocated volume ID by decimal
    SVOL_LDEV_ID = 348
    
    # This parameter defines the first interval to access
    # an asynchronous job. (Unit: Second)
    FIRST_WAIT_TIME = 1
    
    # This parameter defines the maximum retry time
    # to confirm job status.
    MAX_RETRY_COUNT = 10
    
    # An user id and password of the local storage
    LOCAL_USER_CREDENTIAL = ("local_copy_user",
                             "local_copy_pass")
    
    # An user id and password of the remote storage
    REMOTE_USER_CREDENTIAL = ("remote_copy_user",
                              "remote_copy_pass")
    
    ###########################################################
    
  2. Define headers.

    Define the HTTP request header. Because the REST API only supports JSON format data, the sample coding defines header information so that data is handled in JSON format.

    # ###You don't have to change the following parameters### #
    local_storage_api = BlockStorageAPI(
        remote_copy_param.LOCAL_REST_SERVER_IP_ADDR,
        remote_copy_param.LOCAL_PORT,
        remote_copy_param.LOCAL_STORAGE_MODEL,
        remote_copy_param.LOCAL_SERIAL_NUMBER)
    
    remote_storage_api = BlockStorageAPI(
        remote_copy_param.REMOTE_REST_SERVER_IP_ADDR,
        remote_copy_param.REMOTE_PORT,
        remote_copy_param.REMOTE_STORAGE_MODEL,
        remote_copy_param.REMOTE_SERIAL_NUMBER)
    
    local_headers = {"content-type": "application/json",
                     "accept": "application/json",
                     "Response-Job-Status": "Completed"}
    
    remote_headers = {"content-type": "application/json",
                      "accept": "application/json",
                      "Response-Job-Status": "Completed"}
    
    REQUIRED_MAJOR_VERSION = 1
    REQUIRED_MINOR_VERSION = 9
    
    local_session_id = 0
    remote_session_id = 0
    
    ###########################################################
    
  3. Define the function for getting status changes for asynchronous processing. (wait_until_jobstatus_is_changed function)

    Define the function for getting status changes for asynchronous processing. Call and use this function from the main TrueCopy pair operation. For details on this function, see the description of functions used in the sample coding.

    TipTo prevent errors that occur when the server certificate used for SSL communication between the REST API client and the storage system is a self-signed certificate, the sample coding specifies verify=False in the request message to skip verification of the server certificate.

    """
    Check whether the asynchronous command was finished.
    
    @param storage_api storage_api
    @param job_id the job ID to identify
          the asynchronous command
    @param headers the array of the http headers
    @return r the response data
    """
    
    
    def check_update(storage_api, job_id, headers):
        url = storage_api.job(str(job_id))
        r = requests.get(url, headers=headers, verify=False)
        return r
    
    """
    Wait until the job status is changed
    
    @param storage_api storage_api
    @param headers the array of the http headers
    @param job_id the job ID to identify
           the asynchronous command
    @param changed_status job status after waiting
    @param is_retry_count_enabled if true, wait
           until MAX_RETRY_COUNT. if false, wait forever
           until job status is changed.
    @return job_result.json()["affectedResources"][0]
             URL of an affected resource
    """
    
    
    def wait_until_jobstatus_is_changed(
            storage_api,
            headers,
            job_id,
            changed_status,
            is_retry_count_enabled):
        status = "Initializing"
        retry_count = 1
        wait_time = FIRST_WAIT_TIME
        while status != changed_status:
            if status == "Completed":
                print("Status was already changed" +
                      "to Completed.")
                break
            if is_retry_count_enabled and \
                    retry_count > MAX_RETRY_COUNT:
                raise Exception("Timeout Error! "
                                "Operation was not completed.")
            time.sleep(wait_time)
            job_result = check_update(storage_api,
                                      job_id, headers)
            status = job_result.json()["status"]
            double_time = wait_time * 2
            if double_time < 120:
                wait_time = double_time
            else:
                wait_time = 120
            retry_count += 1
        if job_result.json()["state"] == "Failed":
            error_obj = job_result.json()["error"]
            if "errorCode" in error_obj:
                if "SSB1" in error_obj["errorCode"]:
                    print("Error! SSB code : ",
                          error_obj["errorCode"]["SSB1"],
                          ", ", error_obj["errorCode"]["SSB2"])
                elif "errorCode" in error_obj["errorCode"]:
                    print("Error! error code : ",
                          error_obj["errorCode"]["errorCode"])
            raise Exception("Job Error!", job_result.text)
        print("Async job was succeeded. affected resource : " +
              job_result.json()["affectedResources"][0])
        return job_result.json()["affectedResources"][0]
    
    
  4. Check the version of the REST API.

    Get information about the version of the REST API for both the local and the remote storage systems by using the REST API server of each system to make sure that the version is supported.

    """
    Check whether this API version allows the REST
     Server to execute this program
    
    @param api_version api version of this REST Server
    @param required_major_version the lowest number of
           the major version that this program requires
    @param required_minor_version the lowest number of
           the minor version that this program requires
    
    """
    
    
    def check_api_version(api_version, required_major_version,
                          required_minor_version):
        version = api_version.split(".")
        major_version = int(version[0])
        minor_version = int(version[1])
        if not ((major_version == required_major_version and
                 minor_version >= required_minor_version) or
                major_version >= required_major_version + 1):
            sys.exit(
                "This program requires API Version " +
                str(required_major_version) +
                "." +
                str(required_minor_version) +
                "." +
                "x or newer.\n")
    
    try:
        # step1 Check the API version of the local REST API #
        print("Check the API version of the local REST API")
        url = local_storage_api.api_version()
        r = requests.get(url, headers=local_headers,
                         verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(r.json()["apiVersion"],
                          REQUIRED_MAJOR_VERSION,
                          REQUIRED_MINOR_VERSION)
    
        # step1 Check the API version of the remote REST API #
        print("Check the API version of the remote REST API")
        url = remote_storage_api.api_version()
        r = requests.get(url, headers=remote_headers,
                         verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(r.json()["apiVersion"],
                          REQUIRED_MAJOR_VERSION,
                          REQUIRED_MINOR_VERSION)
    
  5. Generate a session.

    Generate a session in both the local and the remote storage systems by using the REST API server of each system.

        # step2 Generate a local session #
        print("Generate a local session")
        url = local_storage_api.generate_session()
        r = requests.post(
            url,
            headers=local_headers,
            auth=LOCAL_USER_CREDENTIAL,
            verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        local_token = r.json()["token"]
        local_auth = "Session " + local_token
        local_session_id = r.json()["sessionId"]
    
        # step2 Generate a remote session #
        print("Generate a remote session")
        url = remote_storage_api.generate_session()
        r = requests.post(
            url,
            headers=remote_headers,
            auth=REMOTE_USER_CREDENTIAL,
            verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        remote_token = r.json()["token"]
        remote_auth = "Session " + remote_token
        remote_session_id = r.json()["sessionId"]
    

    When a session is generated, a session ID and a token are returned. When running the API, specify the token for the Authentication header as the required authentication information for the subsequent operations. Use the session ID to discard the session after a set of operations is completed.

  6. Lock resources.

    Obtain a lock to prevent other users from performing operations on the target volume. For the local storage system, lock the resource group to which the LDEV for the primary volume belongs. For the remote storage system, lock the resource group to which the LDEV for the secondary volume belongs.

        try:
            # step3 Lock the local resource group #
            print("Lock the local resource group")
            url = local_storage_api.lock()
            local_headers["Authorization"] = local_auth
            r = requests.post(url, headers=local_headers,
                             verify=False)
            if r.status_code != http.client.ACCEPTED:
                raise requests.HTTPError(r)
            print("Request was accepted. JOB URL : " +
                  r.json()["self"])
            wait_until_jobstatus_is_changed(
                local_storage_api,
                local_headers,
                r.json()["jobId"],
                "Completed",
                True)
    
            # step3 Lock the remote resource group #
            print("Lock the remote resource group")
            remote_headers["Authorization"] = remote_auth
            url = remote_storage_api.lock()
            r = requests.post(url, headers=remote_headers,
                             verify=False)
            if r.status_code != http.client.ACCEPTED:
                raise requests.HTTPError(r)
            print("Request was accepted. JOB URL : " +
                  r.json()["self"])
            wait_until_jobstatus_is_changed(
                remote_storage_api,
                remote_headers,
                r.json()["jobId"],
                "Completed",
                True)
    

    The wait_until_jobstatus_is_changed function checks the execution status of the jobs that were run asynchronously, and waits until the job status changes to the specified status. In the sample coding, the job execution status is confirmed to have changed to "Completed" and to have been locked.

  7. Create a TrueCopy pair.

    Use the already created LDEV to create a TrueCopy pair. Also create a new copy group. Specify the copy group name, copy pair name, and the LDEV number of the volume to be used, which are defined in advance in the parameters. In addition, specify items such as the copy pair type, whether to create a copy group, and the fence level, and then issue a request for creating a TrueCopy pair. The block_storage_api function is used to generate the URL.

            # step4 Create a remote copy pair #
            print("Create a remote copy pair")
            url = local_storage_api.remote_copy_pairs()
            body = {
                "copyGroupName": COPY_GROUP_NAME,
                "copyPairName": COPY_PAIR_NAME,
                "replicationType": "TC",
                "remoteStorageDeviceId": remote_storage_api.
                get_storage_id(),
                "pvolLdevId": PVOL_LDEV_ID,
                "svolLdevId": SVOL_LDEV_ID,
                "isNewGroupCreation": "true",
                "fenceLevel": "data",
            }
            local_headers["Remote-Authorization"] = remote_auth
            r = requests.post(
                url,
                headers=local_headers,
                data=json.dumps(body),
                verify=False)
            if r.status_code != http.client.ACCEPTED:
                raise requests.HTTPError(r)
            print("Create remote copy pair request " +
                  "was accepted. JOB URL : " + r.json()["self"])
            wait_until_jobstatus_is_changed(
                local_storage_api,
                local_headers,
                r.json()["jobId"],
                "StorageAccepted",
                False)
            jobid = r.json()["jobId"]
    
            print("Status changed to StorageAccepted")
    

    The wait_until_jobstatus_is_changed function checks the execution status of the jobs that were run asynchronously, and waits until the job status changes to the specified status. In the sample coding, it is confirmed that the job execution status has changed to "StorageAccepted" and the request for creating a TrueCopy pair has been received by the storage system.

  8. Unlock resources.

    After having confirmed that the storage system received processing for creating the pair, cancel the obtained lock. The "finally" statement in the sample coding makes sure that the lock will be canceled even if an error occurs while the API is running.

        finally:
            # step5 Unlock the local resource group #
            print("Unlock the local resource group")
            url = local_storage_api.unlock()
            r = requests.post(url, headers=local_headers,
                             verify=False)
            if r.status_code != http.client.ACCEPTED:
                raise requests.HTTPError(r)
            print("Request was accepted. JOB URL : " +
                  r.json()["self"])
            wait_until_jobstatus_is_changed(
                local_storage_api, local_headers,
                r.json()["jobId"], "Completed", True)
    
            # step5 Unlock the remote resource group #
            print("Unlock the remote resource group")
            url = remote_storage_api.unlock()
            r = requests.post(url, headers=remote_headers,
                             verify=False)
            if r.status_code != http.client.ACCEPTED:
                raise requests.HTTPError(r)
            print("Request was accepted. JOB URL : " +
                  r.json()["self"])
            wait_until_jobstatus_is_changed(
                remote_storage_api,
                remote_headers,
                r.json()["jobId"],
                "Completed",
                True)
    
  9. Confirm that the pair has been created.

    Confirm that processing for creating the pair is completed in the storage system. Use the wait_until_jobstatus_is_changed function to confirm that the job execution status has changed to "Completed".

        # step6 Wait until the operation is complete #
        affected_resource_path = wait_until_jobstatus_is_changed(
            local_storage_api, local_headers,
            jobid, "Completed", False)
    
  10. Get information about a TrueCopy pair.

    To confirm that the pair has been correctly created, get information about the pair by using the URL of the pair that was obtained when the TrueCopy pair was created. In the sample coding, the following items are output: the copy group name, copy pair name, pair type, LDEV numbers for the P-VOL and S-VOL, pair volume status, and the storage device ID.

        # step7 Print the remote copy pair #
        print("Print the remote copy pair")
        url = local_storage_api.affected_resource(
            affected_resource_path)
        r = requests.get(url, headers=local_headers,
                         verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        print("COPY GROUP NAME : " +
              str(r.json()["copyGroupName"]))
        print("COPY PAIR NAME : " +
              str(r.json()["copyPairName"]))
        print("REPLICATION TYPE : " +
              str(r.json()["replicationType"]))
        print("PVOL LDEV ID : " + str(r.json()["pvolLdevId"]))
        print("SVOL LDEV ID : " + str(r.json()["svolLdevId"]))
        print("PVOL STATUS : " + str(r.json()["pvolStatus"]))
        print("SVOL STATUS : " + str(r.json()["svolStatus"]))
        print("PVOL STORAGE DEVICE ID : "
              + str(r.json()["pvolStorageDeviceId"]))
        print("SVOL STORAGE DEVICE ID : "
              + str(r.json()["svolStorageDeviceId"]))
        print("REMOTE MIRROR COPY PAIR ID : "
              + str(r.json()["remoteMirrorCopyPairId"]))
        print()
    
  11. Output error messages.

    In the sample coding, processing for communication errors, HTTP request errors, and job execution errors is described. If a communication error occurs, an error message is output. If an HTTP request error occurs, the error code, the error message, and the response body are output. If a job execution error occurs, all of the contents included in the job execution result are output.

    except requests.ConnectionError:
        sys.stderr.write("Connection Error!\n")
        sys.stderr.write(traceback.format_exc())
    except requests.HTTPError as he:
        sys.stderr.write("HTTP Error! status code : ")
        sys.stderr.write(str(he.args[0].status_code) + "\n")
        sys.stderr.write(he.args[0].text + "\n")
    except Exception as e:
        sys.stderr.write(traceback.format_exc())
        for msg in e.args:
            sys.stderr.write(str(msg) + "\n")
    
  12. Discard the session.

    After a set of operations is completed, discard the session by using the REST API server of both the local and the remote storage systems. Specify the session ID that was obtained when the session was created. The "finally" statement in the sample coding makes sure that the session will be discarded even if an error occurs while the API is running. After the session is discarded, the processing ends.

    finally:
        # step8 Discard the local session #
        print("Discard the local session")
        url = local_storage_api. \
            discard_session(local_session_id)
        r = requests.delete(url, headers=local_headers,
                            verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
        # step8 Discard the remote session #
        print("Discard the remote session")
        url = remote_storage_api.discard_session(
            remote_session_id)
        r = requests.delete(url,
                            headers=remote_headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
        print("Operation was completed.")
        sys.exit()
    

Sample coding for uploading files (specifying the transfer destinations of audit log files)

This section provides sample code for specifying the transfer destinations of the audit log files of storage systems. This sample coding includes the procedure for uploading the client certificates to a storage system.
Operation flow of the sample code for specifying the transfer destinations of the audit log files of storage systems

The following is the operation flow of the sample code for specifying the transfer destinations of the audit log files of storage systems and the corresponding code constructs.

Step

Sample coding operation flow

Code constructs

1

Import necessary libraries and set parameters.

-

2

Define headers.

Specifying request headers (for the default HTTP headers)

3

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Getting information about the job status by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Getting the job execution results

Getting the URLs of the resources to which the operation results have been applied

Getting error codes

4

Check the version of the REST API.

Getting information about the version of the REST API by performing a GET operation

5

Generate a session.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using a user ID and a password)

Creating objects by performing a POST operation

6

Upload the root certificate of the syslog server to which audit log files are to be transferred.

Getting the URLs of the resources (when object IDs are not specified)

Creating form data

Running actions for objects by using the POST operation

7

Upload the client certificate of the syslog server to which audit log files are to be transferred.

Getting the URLs of the resources (when object IDs are not specified)

Creating form data

Running actions for objects by using the POST operation

8

Specify the syslog server to which audit log files are to be transferred.

Getting the URLs of the resources (when fixed object IDs such as those for single instances are specified)

Getting the URLs of the resources (for objects of a single instance)

Generating a request body in JSON format

Changing the attribute of an object by using the PATCH operation

9

Send a test message to the syslog server to which audit log files are to be transferred.

Getting the URLs of the resources (when fixed object IDs such as those for single instances are specified)

Running actions for objects by using the POST operation

10

Obtain the configuration information of the transfer destinations of audit log files

Getting the URLs of the resources (when fixed object IDs such as those for single instances are specified)

Obtaining an object by using the GET operation (when obtaining a specific object)

Outputting obtained information

11

Output error messages.

Outputting error messages

12

Discard the session.

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Deleting objects by performing a DELETE operation

Expected system configuration

The sample code assumes the system configuration in the following figure. Note that the transfer destination is a single syslog server.

GUID-899ECF84-7EDC-4344-BE31-6CBE101C9077-low.gif

The following table shows the values specified for the parameters in the sample coding. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

USER_CREDENTIAL

("user1", "pass1")

Authentication information used for authentication by the storage system. In the sample code, the user ID is user1 and the password is pass1. The user must have the Audit Log Administrator (View & Modify) role.

ROOT_CERT_FILE_PATH

"D:\\cert\\"

The path where the root certificate file of the syslog server (the file to be uploaded to the storage system) is stored.

The sample code specifies for SSL communication to be used between the storage system and the syslog server. Prepare the root certificate of the syslog server in advance.

ROOT_CERT_FILE_NAME

"root.crt"

The name of the root certificate file of the syslog server (the file to be uploaded to the storage system).

CLIENT_CERT_FILE_PATH

"D:\\cert\\"

The path where the client certificate file of the syslog server (the file to be uploaded to the storage system) is stored.

The sample code specifies for SSL communication to be used between the storage system and the syslog server. Prepare the client certificate of the syslog server in advance.

CLIENT_CERT_FILE_NAME

"client.pfx"

The name of the client certificate file of the syslog server (the file to be uploaded to the storage system).

TRANSFER_PROTOCOL

"TLS"

The protocol to be used when transferring the audit logs to the syslog server.

LOCATION_NAME

"STORAGE_SYSTEM_1"

The name that identifies the storage system from which the audit logs are to be transferred.

RETRY_INTERVAL

5

The interval (in seconds) between retries when communication with the syslog server fails.

PRIMARY_SYSLOG_SERVER_IP_ADDR

"192.0.1.101"

The IP address of the syslog server.

PRIMARY_SYSLOG_SERVER_PORT

"12345"

The port number used by the syslog server.

CLIENT_CERT_FILE_PASSWORD

"certFilePass"

The password for the client certificate file.

FIRST_WAIT_TIME

1

The first interval (in seconds) for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

MAX_RETRY_COUNT

6

The maximum number of retries for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

Contents of the sample coding

This subsection explains the sample coding.

  1. Import necessary libraries and set parameters.

    Before starting the volume allocation processing, the sample coding imports the required libraries or classes. In addition to the common libraries, the sample coding also imports the BlockStorageAPI class that defines the function that generates URLs.

    # coding:utf-8
    
    """
    auditlog_syslog_server setting
    
    This program requires API version 1.9.0 or newer.
    """
    
    import requests
    import json
    import sys
    import http.client
    import time
    import traceback
    import rest_server_param
    import storage_param
    
    from block_storage_api import BlockStorageAPI
    

    Set parameters to be used in the sample coding.

    # #################Initialize parameters################# #
    # Change the following parameters to fit your environment
    
    # A path of root certificate
    ROOT_CERT_FILE_PATH = "D:\\cert\\"
    
    # A root certificate name
    ROOT_CERT_FILE_NAME = "root.crt"
    
    # A path of client certificate
    CLIENT_CERT_FILE_PATH = "D:\\cert\\"
    
    # A client certificate name
    CLIENT_CERT_FILE_NAME = "client.pfx"
    
    # A transfer protocol
    TRANSFER_PROTOCOL = "TLS"
    
    # A location name
    LOCATION_NAME = "STORAGE_SYSTEM_1"
    
    # A retry interval
    RETRY_INTERVAL = 5
    
    # A primary syslog server IP address
    PRIMARY_SYSLOG_SERVER_IP_ADDR = "192.0.1.101"
    
    # A primary syslog server port number
    PRIMARY_SYSLOG_SERVER_PORT = "12345"
    
    # A password of the client certificate
    CLIENT_CERT_FILE_PASSWORD = "certFilePass"
    
    # This parameter defines the first interval to access
    # an asynchronous job. (Unit: Second)
    FIRST_WAIT_TIME = 1
    
    # This parameter defines the maximum retry time
    # to confirm job status.
    MAX_RETRY_COUNT = 6
    
    # An user id and password of the target storage
    USER_CREDENTIAL = ("user1", "pass1")
    
    ###########################################################
  2. Define headers.

    Define the HTTP request header. In addition to the JSON format, which is the standard format for the REST API, define the header information so that the form data format, which used in by the API function for uploading the file, can also be handled.

    # ###You don't have to change the following parameters### #
    block_storage_api = BlockStorageAPI(
        rest_server_param.REST_SERVER_IP_ADDR,
        rest_server_param.REST_SERVER_PORT,
        storage_param.STORAGE_MODEL,
        storage_param.SERIAL_NUMBER)
    
    headers = {"content-type": "application/json",
               "accept": "application/json"}
    file_upload_headers = {"accept": "application/json",
                           "Expect": ""}
    
    REQUIRED_MAJOR_VERSION = 1
    REQUIRED_MINOR_VERSION = 9
    
    session_id = 0
    
    ###########################################################
  3. Define the function for issuing an HTTP request and for verifying the status of asynchronous processing (the invoke_async_command function).

    Define the function that issues an HTTP request and verifies the status of asynchronous processing. Call and use this function from the main volume allocation operation. For details on this function, see the section explaining the functions used in the sample coding.

    TipTo prevent errors that occur when the server certificate used for SSL communication between the REST API client and the storage system is a self-signed certificate, the sample coding specifies verify=False in the request message to skip verification of the server certificate.

    """
    Check whether the asynchronous command was finished.
    @param job_id the job ID to identify
           the asynchronous command
    @return r the response data
    """
    
    
    def check_update(job_id):
        url = block_storage_api.job(str(job_id))
        r = requests.get(url, headers=headers, verify=False)
        return r
    
    """
    Execute the HTTP request (POST or PATCH)
    @param method_type HTTP request method (POST or PATCH)
    @param url URL to execute HTTP method
    @param body The information of a resource
    @return job_result.json()["affectedResources"][0]
             URL of an affected resource
    """
    
    
    def invoke_async_command(method_type, url, body):
        if method_type == "patch":
            r = requests.patch(url, headers=headers,
                             data=json.dumps(body), verify=False)
        elif method_type == "post":
            if body is None:
                r = requests.post(
                    url,
                    headers=headers,
                    verify=False)
            else:
                r = requests.post(
                    url,
                    headers=headers,
                    data=json.dumps(body),
                    verify=False)
        if r.status_code != http.client.ACCEPTED:
            raise requests.HTTPError(r)
        print("Request was accepted. JOB URL : " +
              r.json()["self"])
    
        if "affectedResources" in job_result.json():
            print("Async job was succeeded." +
                  " affected resource : " +
                  job_result.json()["affectedResources"][0])
            return job_result.json()["affectedResources"][0]
        else:
            print("Async job was succeeded.")
            return None
    
  4. Check the version of the REST API.

    Get information about the version of the REST API to make sure that the version is supported.

    """
    Check whether this API version allows the REST
     Server to execute this program
    
    @param api_version api version of this REST Server
    @param required_major_version the lowest number of
           the major version that this program requires
    @param required_minor_version the lowest number of
           the minor version that this program requires
    
    """
    
    
    def check_api_version(api_version, required_major_version,
                          required_minor_version):
        version = api_version.split(".")
        major_version = int(version[0])
        minor_version = int(version[1])
        if not ((major_version == required_major_version and
                 minor_version >= required_minor_version) or
                major_version >= required_major_version + 1):
            sys.exit("This program requires API Version " +
                     str(required_major_version) + "." +
                     str(required_minor_version) +
                     "." + "x or newer.\n")
    
    try:
        # step1 Check the API version #
        print("Check the API version")
        url = block_storage_api.api_version()
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(
            r.json()["apiVersion"],
            REQUIRED_MAJOR_VERSION,
            REQUIRED_MINOR_VERSION)
    
  5. Generate a session.

    Generate sessions by using the REST API server.

        # step2 Generate a session #
        print("Generate a session")
        url = block_storage_api.generate_session()
        r = requests.post(url, headers=headers, auth=USER_CREDENTIAL,
                          verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        token = r.json()["token"]
        auth = "Session " + token
        session_id = r.json()["sessionId"]
        headers["Authorization"] = auth
        file_upload_headers["Authorization"] = auth
    

    When a session is generated, a session ID and a token are returned. When running the API, specify the token for the Authentication header as the required authentication information for the subsequent operations. Use the session ID to discard the session after a set of operations is completed.

  6. Upload the root certificate of the syslog server to which the audit log files are to be transferred.

    As preparation for transferring the audit log files to the syslog server by using SSL communication, upload the root certificate of the syslog server to the storage system.

        # step3 Upload a root certificate #
        print("Upload a root certificate")
        url = block_storage_api.file_upload()
        files = {"file": (ROOT_CERT_FILE_NAME,
            open(ROOT_CERT_FILE_PATH + ROOT_CERT_FILE_NAME, "rb"),
            "application/octet-stream")}
        r = requests.post(url, headers=file_upload_headers,
            data={"fileType": "AuditSyslogPrimaryRootCertFile"},
            files=files, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
  7. Upload the client certificate of the syslog server to which audit log files are to be transferred.

    As preparation for transferring the audit log files to the syslog server by using SSL communication, upload the client certificate of the syslog server to the storage system.

        # step4 Upload a client certificate #
        print("Upload a client certificate")
        files = {"file": (CLIENT_CERT_FILE_NAME,
            open(CLIENT_CERT_FILE_PATH + CLIENT_CERT_FILE_NAME,
            "rb"), "application/octet-stream")}
        r = requests.post(url, headers=file_upload_headers,
            data={"fileType":
            "AuditSyslogPrimaryClientCertFile"},
            files=files, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
    
  8. Specify the syslog server to which audit log files are to be transferred.

    Specify the settings for transferring audit log files to the syslog server. Because the sample code specifies for SSL communication to be used for communication with the syslog server, in addition to information about the syslog server to which audit log files are to be transferred, the sample code also specifies information about the certificate for SSL communication.

        # step5 Modify the syslog server #
        print("Modify the syslog server")
        url = block_storage_api.auditlog_syslog()
        body = {
            "transferProtocol": TRANSFER_PROTOCOL,
            "locationName": LOCATION_NAME,
            "retries": True,
            "retryInterval": RETRY_INTERVAL,
            "primarySyslogServer": {
                "isEnabled": True,
                "ipAddress": PRIMARY_SYSLOG_SERVER_IP_ADDR,
                "port": PRIMARY_SYSLOG_SERVER_PORT,
                "clientCertFileName": CLIENT_CERT_FILE_NAME,
                "clientCertFilePassword": CLIENT_CERT_FILE_PASSWORD,
                "rootCertFileName": ROOT_CERT_FILE_NAME
            },
            "secondarySyslogServer": {
                "isEnabled": False
            }
        }
        invoke_async_command("patch", url, body)
    

    The invoke_async_command function issues a request to specify the transfer destination of audit log files and checks the execution status of jobs that are executed asynchronously. The function then returns, as the execution result, the URL of information about the transfer destination of audit log files that is specified for the storage system.

  9. Send a test message to the syslog server to which audit log files are to be transferred.

    Send a test message to check whether the syslog server that is set as the transfer destination of audit log files can correctly receive audit log data.

        # step6 Send a test message to the syslog server #
        print("Send a test message to the syslog server")
        url = block_storage_api.auditlog_syslog_send_test()
        invoke_async_command("post", url, None)
    
  10. Obtain the configuration information of the transfer destinations of audit log files.

    To make sure that the operations in the previous steps (operations to set information on the storage system) have been successful, obtain and output the configuration information of the transfer destinations of audit logs. The sample code obtains and outputs the following information: the protocol to be used, the identifier of the storage system from which audit log files are to be transferred, retry settings and the interval for retries when communication fails, and the IP address and port number of the syslog server.

        # step7 Get the syslog server #
        print("get the syslog server")
        url = block_storage_api.auditlog_syslog()
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        print("TRANSFER PROTOCOL : " +
              str(r.json()["transferProtocol"]))
        print("LOCATION NAME : " +
              str(r.json()["locationName"]))
        print("RETRIES : " +
              str(r.json()["retries"]))
        print("RETRY INTERVAL : " +
              str(r.json()["retryInterval"]))
        print("PRIMARY SYSLOG SERVER")
        print("IP ADDRESS : " +
              str(r.json()["primarySyslogServer"]["ipAddress"]))
        print("PORT : " +
              str(r.json()["primarySyslogServer"]["port"]))
    
  11. Output error messages.

    In the sample coding, processing for communication errors, HTTP request errors, and job execution errors is described. If a communication error occurs, an error message is output. If an HTTP request error occurs, the error code, the error message, and the response body are output. If a job execution error occurs, all of the contents included in the job execution result are output.

    except requests.ConnectionError:
        sys.stderr.write("Connection Error!\n")
        sys.stderr.write(traceback.format_exc())
    except requests.HTTPError as he:
        sys.stderr.write("HTTP Error! status code : ")
        sys.stderr.write(str(he.args[0].status_code) + "\n")
        sys.stderr.write(he.args[0].text + "\n")
    except Exception as e:
        sys.stderr.write(traceback.format_exc())
        for msg in e.args:
            sys.stderr.write(str(msg) + "\n")
    
  12. Discard the session.

    After a set of operations is completed, discard the session. Specify the session ID that was obtained when the session was generated. The "finally" statement in the sample coding makes sure that the session will be discarded even if an error occurs while the API is running. After the session is discarded, the processing ends.

    finally:
        # step8 Discard the session #
        print("Discard the session")
        url = block_storage_api.discard_session(session_id)
        r = requests.delete(url, headers=headers, verify=False)
        try:
            if r.status_code != http.client.OK:
                raise requests.HTTPError(r)
        except requests.HTTPError as he:
            sys.stderr.write("HTTP Error! status code : ")
            sys.stderr.write(str(he.args[0].status_code) + "\n")
            sys.stderr.write(he.args[0].text + "\n")
    
        print("Operation was completed.")
        sys.exit()
    

Sample coding for downloading a file (backing up encryption keys)

This section provides the sample coding for backing up encryption keys. This sample coding includes the procedure for downloading an encryption key backup file to a client.
Sample coding operation flow for backing up encryption keys

The following table shows the sample coding operation flow for backing up encryption keys and the corresponding code constructs.

Step

Sample coding operation flow

Code constructs

1

Import necessary libraries and set parameters.

-

2

Define headers.

Specifying request headers (for the default HTTP headers)

3

Check the version of the REST API.

Getting information about the version of the REST API by performing a GET operation

4

Download the encryption key backup file.

Getting the URLs of the resources (when fixed object IDs such as those for single instances are specified)

Running actions for objects by using the POST operation

5

Output error messages.

Outputting error messages

Values to be specified for the parameters in the sample coding

The following table shows the values specified for the parameters in the sample coding. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

USER_CREDENTIAL

("user1","pass1")

This information is used for authentication by the storage system. In the sample coding, the user ID is user1, and the password is pass1. The user must have the Security Administrator (View & Modify) role.

BACKUP_PASSWORD

backuppassword

The password for the encryption key backup file

ENCRYPTION_KEY_BACKUP_FILE_PATH

D:\encryption\\

The path where the encryption key backup file is stored

ENCRYPTION_KEY_BACKUP_FILE_NAME

backupfile.ekf

The name of the encryption key backup file

Contents of the sample coding

This subsection explains the sample coding.

  1. Import necessary libraries and set parameters.

    Before starting the volume allocation processing, the sample coding imports the required libraries or classes. In addition to the common libraries, the sample coding also imports the BlockStorageAPI class that defines the function that generates URLs.

    # coding:utf-8
    
    """
    backup_encryption_keys
    
    This program requires API version 1.12.0 or newer.
    """
    
    import requests
    import json
    import sys
    import http.client
    import traceback
    import rest_server_param
    import storage_param
    
    from block_storage_api import BlockStorageAPI
    

    Set parameters to be used in the sample coding.

    # #################Initialize parameters################# #
    # Change the following parameters to fit your environment
    
    # An user id and password of the target storage
    USER_CREDENTIAL = ("user1", "pass1")
    
    # A backup password
    BACKUP_PASSWORD = "backuppassword"
    
    # A path of encryption key backup file
    ENCRYPTION_KEY_BACKUP_FILE_PATH = "D:\encryption\\"
    
    # A encryption key backup file name
    ENCRYPTION_KEY_BACKUP_FILE_NAME = "backupfile.ekf"
    
    ###########################################################
    
  2. Define headers.

    Define the HTTP request header. In addition to the JSON format, which is the standard format for the REST API, define the header information so that the stream data format, which is used in the API function for file downloading, can also be handled.

    # ###You don't have to change the following parameters### #
    block_storage_api = BlockStorageAPI(
        rest_server_param.REST_SERVER_IP_ADDR,
        rest_server_param.REST_SERVER_PORT,
        storage_param.STORAGE_MODEL,
        storage_param.SERIAL_NUMBER)
    
    headers = {"content-type": "application/json",
               "accept": "application/json"}
    
    file_download_headers = {"content-type": "application/json",
                             "accept": "application/octet-stream"}
    
    REQUIRED_MAJOR_VERSION = 1
    REQUIRED_MINOR_VERSION = 12
    
    ###########################################################
    
  3. Check the version of the REST API.

    Get information about the version of the REST API to make sure that the version is supported.

    """
    Check whether this API version allows the REST
     Server to execute this program
    
    @param api_version api version of this REST Server
    @param required_major_version the lowest number of
           the major version that this program requires
    @param required_minor_version the lowest number of
           the minor version that this program requires
    
    """
    
    
    def check_api_version(api_version, required_major_version,
                          required_minor_version):
        version = api_version.split(".")
        major_version = int(version[0])
        minor_version = int(version[1])
        if not ((major_version == required_major_version and
                 minor_version >= required_minor_version) or
                major_version >= required_major_version + 1):
            sys.exit("This program requires API Version " +
                     str(required_major_version) + "." +
                     str(required_minor_version) +
                     "." + "x or newer.\n")
    
    try:
        # step1 Check the API version #
        print("Check the API version")
        url = block_storage_api.api_version()
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(
            r.json()["apiVersion"],
            REQUIRED_MAJOR_VERSION,
            REQUIRED_MINOR_VERSION)
    
    
  4. Download the encryption key backup file.

    Download the encryption key backup file to a specified path.

        # step2 Get the encryption keys backup #
        print("Get the encryption keys backup")
        url = block_storage_api.encryption_key_file_backup()
        body = {
            "parameters": {
                "password": BACKUP_PASSWORD
            }
        }
        r = requests.post(url, headers=file_download_headers,
                          auth=USER_CREDENTIAL, data=json.dumps(body),
                          verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        with open(ENCRYPTION_KEY_BACKUP_FILE_PATH + ENCRYPTION_KEY_BACKUP_FILE_NAME, 'wb') as saveFile:
            saveFile.write(r.content)
    
  5. Output error messages.

    In the sample coding, processing for communication errors, HTTP request errors, and job execution errors is described. If a communication error occurs, an error message is output. If an HTTP request error occurs, the error code, the error message, and the response body are output. If a job execution error occurs, all of the contents included in the job execution result are output.

    except requests.ConnectionError:
        sys.stderr.write("Connection Error!\n")
        sys.stderr.write(traceback.format_exc())
    except requests.HTTPError as he:
        sys.stderr.write("HTTP Error! status code : ")
        sys.stderr.write(str(he.args[0].status_code) + "\n")
        sys.stderr.write(he.args[0].text + "\n")
    except Exception as e:
        sys.stderr.write(traceback.format_exc())
        for msg in e.args:
            sys.stderr.write(str(msg) + "\n")
    finally:
        print("Operation was completed.")
        sys.exit()
    
    

Sample coding for re-creating a parity group (data encryption)

This section provides the sample coding for re-creating a parity group (encrypting data).
Note

This operation is performed as part of the procedure for encrypting existing data without changing the drive configuration. For details on the entire procedure, see the description of the flow of data encryption operations.

Before deleting a parity group, be sure to back up the data in the parity group to a volume in another parity group.

Sample coding operation flow for re-creating a parity group

The following table shows the sample coding operation flow for re-creating a parity group and the corresponding code constructs.

Step

Sample coding operation flow

Code constructs

1

Import necessary libraries and set parameters.

-

2

Define headers.

Specifying request headers (for the default HTTP headers)

3

Define functions for issuing an HTTP request and for verifying the status of asynchronous processing.

Getting information about the job status by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Getting the job execution results

Getting the URLs of the resources to which the operation results have been applied

Getting error codes

4

Check the version of the REST API.

Getting information about the version of the REST API by performing a GET operation

5

Generate a session.

Getting the URLs of the resources (when object IDs are not specified)

Setting user authentication information (for authentication by using a user ID and a password)

Creating objects by performing a POST operation

6

Get information about the parity group.

Getting objects by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Outputting the acquired information

7

Get information about drives.

Getting objects by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Outputting the acquired information

8

Delete the parity group.

Getting the URLs of the resources (when no object ID is specified)

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Deleting objects by performing a DELETE operation

9

Create a parity group.

Getting the URLs of the resources (when no object ID is specified)

Setting user authentication information (for authentication by using session-based authentication)

Generating a request body in JSON format

Creating objects by performing a POST operation

10

Get information about the parity group.

Getting objects by performing a GET operation

Setting user authentication information (for authentication by using session-based authentication)

Outputting the acquired information

11

Output error messages.

Outputting error messages

12

Discard the session.

Getting the URLs of the resources (when object IDs that are obtained from the operation results are specified)

Deleting objects by performing a DELETE operation

Expected system configuration

This sample coding assumes the system configuration is as shown in the following figure.

GUID-0F0D5316-9F9F-4443-85A4-C4BE94DC7D1C-low.gif

The following table shows the values specified for the parameters in the sample coding. If necessary, change the settings to match the system environment and requirements.

Parameter

Value

Description

USER_CREDENTIAL

("user1", "pass1")

This is the authentication information to be used for authentication in the storage system. The coding sample shows a setting example when the user ID is user1, and the password is pass1. The user needs the Storage Administrator (Provisioning) role.

PARITY_GROUP_ID

1-1

The ID of the parity group to be recreated

FIRST_WAIT_TIME

1

The first interval (seconds) for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

MAX_RETRY_COUNT

6

The maximum number of retries for collecting the execution result of asynchronous processing. Normally, you do not need to change this value.

Contents of the sample coding

This subsection explains the sample coding.

  1. Import necessary libraries and set parameters.

    Before starting the volume allocation processing, the sample coding imports the required libraries or classes. In addition to the common libraries, the sample coding also imports the BlockStorageAPI class that defines the function that generates URLs.

    # coding:utf-8
    
    """
    recreate_parity_group
    
    This program requires API version 1.12.0 or newer.
    """
    
    import requests
    import json
    import sys
    import http.client
    import time
    import traceback
    import rest_server_param
    import storage_param
    
    from block_storage_api import BlockStorageAPI
    

    Set parameters to be used in the sample coding.

    # #################Initialize parameters################# #
    # Change the following parameters to fit your environment
    
    # This parameter defines the first interval to access
    # an asynchronous job. (Unit: Second)
    FIRST_WAIT_TIME = 1
    
    # This parameter defines the maximum retry time
    # to confirm job status.
    MAX_RETRY_COUNT = 6
    
    # An user id and password of the target storage
    USER_CREDENTIAL = ("user1", "pass1")
    
    # A parity group id
    PARITY_GROUP_ID = "1-1"
    
    ###########################################################
    
  2. Define headers.

    Define the HTTP request header. Because the REST API only supports JSON format data, the sample coding defines header information so that data is handled in JSON format.

    # ###You don't have to change the following parameters### #
    block_storage_api = BlockStorageAPI(
        rest_server_param.REST_SERVER_IP_ADDR,
        rest_server_param.REST_SERVER_PORT,
        storage_param.STORAGE_MODEL,
        storage_param.SERIAL_NUMBER)
    
    headers = {"content-type": "application/json",
               "accept": "application/json",
               "Response-Job-Status": "Completed"}
    
    REQUIRED_MAJOR_VERSION = 1
    REQUIRED_MINOR_VERSION = 12
    
    ###########################################################
    
  3. Define the function for issuing an HTTP request and for verifying the status of asynchronous processing (the invoke_async_command function).

    Define the function that issues an HTTP request and verifies the status of asynchronous processing. Call and use this function from the main volume allocation operation. For details on this function, see the section explaining the functions used in the sample coding.

    TipTo prevent errors that occur if the server certificate used for SSL communication between the REST API client and the storage system is a self-signed certificate, the sample coding skips the verification of the server certificate by specifying verify=False when a request is issued.
    """
    Check whether the asynchronous command was finished.
    @param job_id the job ID to identify
           the asynchronous command
    @return r the response data
    """
    
    
    def check_update(job_id):
        url = block_storage_api.job(str(job_id))
        r = requests.get(url, headers=headers, verify=False)
        return r
    
    
    """
    Execute the HTTP request (POST, PUT or DELETE)
    @param method_type HTTP request method (POST, PUT or DELETE)
    @param url URL to execute HTTP method
    @param body The information of a resource
    @return job_result.json()["affectedResources"][0]
             URL of an affected resource
    """
    
    
    def invoke_async_command(method_type, url, body):
        if method_type == "put":
            if body is None:
                r = requests.put(url, headers=headers, verify=False)
            else:
                r = requests.put(url, headers=headers,
                                 data=json.dumps(body), verify=False)
        elif method_type == "post":
            r = requests.post(
                url,
                headers=headers,
                data=json.dumps(body),
                verify=False)
        elif method_type == "delete":
            r = requests.delete(
                url,
                headers=headers,
                verify=False)
        if r.status_code != http.client.ACCEPTED:
            raise requests.HTTPError(r)
        print("Request was accepted. JOB URL : " +
              r.json()["self"])
        status = "Initializing"
        job_result = None
        retry_count = 1
        wait_time = FIRST_WAIT_TIME
        while status != "Completed":
            if retry_count > MAX_RETRY_COUNT:
                raise Exception("Timeout Error! "
                                "Operation was not completed.")
            time.sleep(wait_time)
            job_result = check_update(r.json()["jobId"])
            status = job_result.json()["status"]
            double_time = wait_time * 2
            if double_time < 120:
                wait_time = double_time
            else:
                wait_time = 120
            retry_count += 1
        if job_result.json()["state"] == "Failed":
            error_obj = job_result.json()["error"]
            if "errorCode" in error_obj:
                if "SSB1" in error_obj["errorCode"]:
                    print("Error! SSB code : ",
                          error_obj["errorCode"]["SSB1"],
                          ", ", error_obj["errorCode"]["SSB2"])
                elif "errorCode" in error_obj["errorCode"]:
                    print("Error! error code : ",
                          error_obj["errorCode"]["errorCode"])
            raise Exception("Job Error!", job_result.text)
        if "affectedResources" in job_result.json():
            print("Async job was succeeded. affected resource : " +
                  job_result.json()["affectedResources"][0])
            return job_result.json()["affectedResources"][0]
        else:
            print("Async job was succeeded.")
            return None
    
    
  4. Check the version of the REST API.

    Get information about the version of the REST API to make sure that the version is supported.

    """
    Check whether this API version allows the REST
     Server to execute this program
    
    @param api_version api version of this REST Server
    @param required_major_version the lowest number of
           the major version that this program requires
    @param required_minor_version the lowest number of
           the minor version that this program requires
    
    """
    
    
    def check_api_version(api_version, required_major_version,
                          required_minor_version):
        version = api_version.split(".")
        major_version = int(version[0])
        minor_version = int(version[1])
        if not ((major_version == required_major_version and
                 minor_version >= required_minor_version) or
                major_version >= required_major_version + 1):
            sys.exit("This program requires API Version " +
                     str(required_major_version) + "." +
                     str(required_minor_version) +
                     "." + "x or newer.\n")
    
    try:
        # step1 Check the API version #
        print("Check the API version")
        url = block_storage_api.api_version()
        r = requests.get(url, headers=headers, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        check_api_version(
            r.json()["apiVersion"],
            REQUIRED_MAJOR_VERSION,
            REQUIRED_MINOR_VERSION)
    
  5. Generate a session.

    Generate sessions by using the REST API server.

        # step2 Generate a session #
        print("Generate a session")
        url = block_storage_api.generate_session()
        r = requests.post(url, headers=headers,
                          auth=USER_CREDENTIAL, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        token = r.json()["token"]
        auth = "Session " + token
        session_id = r.json()["sessionId"]
        headers["Authorization"] = auth
    

    When you generate a session, a session ID and a token are returned. When running the API, specify the token for the Authentication header as the required authentication information for the subsequent operations. Use the session ID to discard the session after a set of operations is completed.

  6. Get information about the parity group.

    Get information about the parity group by specifying its parity group number. From the acquired information, the sample coding outputs the following information: number of LDEVs in the parity group, usage rate of the parity group, available capacity, RAID level, RAID type, CLPR number, code indicating the drive type of the drives belonging to the parity group, drive type of the drives that belong to the parity group, rotation speed of the drives belonging to the parity group, value of the encryption setting of the parity group, total logical capacity of the parity group, total physical capacity of the parity group, and value of the accelerated compression setting of the parity group.

        # step3 Get the parity group #
        print("Get the parity group")
        url = block_storage_api.parity_group(PARITY_GROUP_ID)
        r = requests.get(url, headers=headers,
                         auth=USER_CREDENTIAL, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        
        raidType = r.json()["raidType"]
        isCopyBackModeEnabled = r.json()["isEncryptionEnabled"]
        isAcceleratedCompressionEnabled = r.json()["isAcceleratedCompressionEnabled"]
        clprId = r.json()["clprId"]
        print("NUM OF LDEVS : " + str(r.json()["numOfLdevs"]))
        print("USED CAPACITY RATE : " + str(r.json()["usedCapacityRate"]))
        print("AVAILABLE VOLUME CAPACITY : " + str(r.json()["availableVolumeCapacity"]))
        print("RAID LEVEL : " + str(r.json()["raidLevel"]))
        print("RAID TYPE : " + str(r.json()["raidType"]))
        print("CLPR ID : " + str(r.json()["clprId"]))
        print("DRIVE TYPE : " + r.json()["driveType"])
        print("DRIVE TYPE NAME : " + str(r.json()["driveTypeName"]))
        print("DRIVE SPEED : " + str(r.json()["driveSpeed"]))
        print("IS ENCRYPTION ENABLED : " + str(r.json()["isEncryptionEnabled"]))
        print("TOTAL CAPACITY : " + str(r.json()["totalCapacity"]))
        print("PHYSICAL TOTAL CAPACITY : " + str(r.json()["physicalCapacity"]))
        print("IS ACCELERATED COMPRESSION ENABLED : " + str(r.json()["isAcceleratedCompressionEnabled"]))
        print()
    
    
  7. Get information about drives.

    Get information about drives that belong to a parity group by specifying its parity group number.

        # step4 Get drives #
        print("Get drives")
        url = block_storage_api.drives_parity_group(PARITY_GROUP_ID)
        r = requests.get(url, headers=headers,
                         auth=USER_CREDENTIAL, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        
        drive_ids = []
        for d in r.json()["data"]:
            drive_ids.append(d["driveLocationId"])
            print("DRIVE LOCATION ID : " + str(r.json()["driveLocationId"]))
        print()
    
    
  8. Delete the parity group.

    Delete the parity group by specifying its parity group number.

        # step5 Delete the parity group #
        print("Delete the parity group")
        url = block_storage_api.parity_group(PARITY_GROUP_ID)
        invoke_async_command("delete", url, None)
    

    The invoke_async_command function issues a request to delete the parity group, checks the execution statuses of the jobs that were executed asynchronously, and then returns the URL of the deleted parity group as the execution result.

  9. Create a parity group.

    Create a parity group by specifying a parity group number and drive IDs.

        # step6 Create the parity group #
        print("Create the parity group")
        url = block_storage_api.parity_groups()
        body = {
            "parityGroupId": PARITY_GROUP_ID,
            "driveLocationIds": drive_ids,
            "raidType": raidType,
            "isEncryptionEnabled": True,
            "isCopyBackModeEnabled": isCopyBackModeEnabled,
            "isAcceleratedCompressionEnabled": isAcceleratedCompressionEnabled,
            "clprId": clprId
        }
        invoke_async_command("post", url, body)
    
    

    The invoke_async_command function issues a request to create a parity group, checks the execution statuses of jobs that were executed asynchronously, and then returns the URL of the created parity group as the execution result.

  10. Get information about the parity group.

    To check whether the operations up to this step have been correctly applied to the resource, get information about the parity group by specifying the parity group number you acquired when you created the parity group. From the acquired information, the sample coding outputs the following information: number of LDEVs in the parity group, usage rate of the parity group, available capacity, RAID level, RAID type, CLPR number, code indicating the drive type of the drives belonging to the parity group, drive type of the drives that belong to the parity group, rotation speed of the drives belonging to the parity group, value of the encryption setting of the parity group, total logical capacity of the parity group, total physical capacity of the parity group, and value of the accelerated compression setting of the parity group.

        # step7 Get the parity group #
        print("Get the parity group")
        url = block_storage_api.parity_group(PARITY_GROUP_ID)
        r = requests.get(url, headers=headers,
                         auth=USER_CREDENTIAL, verify=False)
        if r.status_code != http.client.OK:
            raise requests.HTTPError(r)
        
        print("NUM OF LDEVS : " + str(r.json()["numOfLdevs"]))
        print("USED CAPACITY RATE : " + str(r.json()["usedCapacityRate"]))
        print("AVAILABLE VOLUME CAPACITY : " + str(r.json()["availableVolumeCapacity"]))
        print("RAID LEVEL : " + str(r.json()["raidLevel"]))
        print("RAID TYPE : " + str(r.json()["raidType"]))
        print("CLPR ID : " + str(r.json()["clprId"]))
        print("DRIVE TYPE : " + r.json()["driveType"])
        print("DRIVE TYPE NAME : " + str(r.json()["driveTypeName"]))
        print("DRIVE SPEED : " + str(r.json()["driveSpeed"]))
        print("IS ENCRYPTION ENABLED : " + str(r.json()["isEncryptionEnabled"]))
        print("TOTAL CAPACITY : " + str(r.json()["totalCapacity"]))
        print("PHYSICAL TOTAL CAPACITY : " + str(r.json()["physicalCapacity"]))
        print("IS ACCELERATED COMPRESSION ENABLED : " + str(r.json()["isAcceleratedCompressionEnabled"]))
        print()
    
    
  11. Output error messages.

    In the sample coding, processing for communication errors, HTTP request errors, and job execution errors is described. If a communication error occurs, an error message is output. If an HTTP request error occurs, the error code, the error message, and the response body are output. If a job execution error occurs, all of the contents included in the job execution result are output.

    except requests.ConnectionError:
        sys.stderr.write("Connection Error!\n")
        sys.stderr.write(traceback.format_exc())
    except requests.HTTPError as he:
        sys.stderr.write("HTTP Error! status code : ")
        sys.stderr.write(str(he.args[0].status_code) + "\n")
        sys.stderr.write(he.args[0].text + "\n")
    except Exception as e:
        sys.stderr.write(traceback.format_exc())
        for msg in e.args:
            sys.stderr.write(str(msg) + "\n")
    
    
  12. Discard the session.

    After a set of operations is completed, discard the session. Specify the session ID that was obtained when the session was generated. The "finally" statement in the sample coding makes sure that the session will be discarded even if an error occurs while the API is running. After the session is discarded, the processing ends.

    finally:
        # step8 Discard the session #
        print("Discard the session")
        url = block_storage_api.discard_session(session_id)
        r = requests.delete(url, headers=headers, verify=False)
        try:
            if r.status_code != http.client.OK:
                raise requests.HTTPError(r)
        except requests.HTTPError as he:
            sys.stderr.write("HTTP Error! status code : ")
            sys.stderr.write(str(he.args[0].status_code) + "\n")
            sys.stderr.write(he.args[0].text + "\n")
    
        print("Operation was completed.")
        sys.exit()
    

Functions used in the sample coding

This section explains the functions used in the sample coding.
Generating a URL

In the sample coding, the functions that generate the URL to be used in the HTTP request are defined in the BlockStorageAPI class, and are called from the main processing when needed. If you specify the required parameters for each target resource or the method to use, these functions generate and return the corresponding URL. The following explains the sample coding for BlockStorageAPI.

In this sample coding, the storage device ID to be used in the URL is generated from the model name and serial number of the storage system specified by the parameters. The storage device ID is generated by replacing the model name specified by the STORAGE_MODEL_DICT parameter with the fixed value of the type of the storage system. If necessary, change the value to match the system environment and requirements.

# coding:utf-8
"""
This class creates a URL for the REST API
to manage block storage
"""

# For VSP E990
STORAGE_MODEL_DICT = {"VSP E990": "936000"}

# For VSP G350, G370, G700, G900, VSP F350, F370, F700, F900
STORAGE_MODEL_DICT = {
                      "VSP G900": "886000",
                      "VSP G700": "886000",
                      "VSP G370": "886000",
                      "VSP G350": "882000",
                      "VSP F900": "886000",
                      "VSP F700": "886000",
                      "VSP F370": "886000",
                      "VSP F350": "882000"}

# For VSP 5000 series
STORAGE_MODEL_DICT = {
                      "VSP 5500H": "900000",
                      "VSP 5500": "900000",
                      "VSP 5100H": "900000",
                      "VSP 5100": "900000"}

class BlockStorageAPI():
    # GUM IP address
    gum_ip_addr = None
    # port number
    port = None
    # storage URL
    base_url = None
    # object URL
    object_url = None
    # service URL
    service_url = None
    # storage device ID
    storage_id = None

Generate the common part, in the following format, of the requests in the URL:

  • For the objects domain:

    protocol://host-name:port-number/ConfigurationManager/version/objects
  • For the services domain:

    protocol://host-name:port-number/ConfigurationManager/version/services
    def __init__(self, gum_ip_addr, port, storage_model,
                 serial_number):
        self.gum_ip_addr = gum_ip_addr
        self.port = port
        self.storage_id = STORAGE_MODEL_DICT[storage_model] \
            + serial_number
        self.base_url = "https://" + \
            self.gum_ip_addr + ":" + self.port + \
            "/ConfigurationManager/v1"
        self.object_url = "/objects"
        self.service_url = "/services"

Next, define the functions according to the target resource and the operation. For example, the URL for creating a volume is generated by the block_storage_api.ldevs that corresponds to ldevs.

    def get_storage_id(self):
        return self.storage_id

    def ldevs(self):
        url = self.base_url + self.object_url + "/ldevs"
        return url

    def ldev(self, object_id):
        url = self.ldevs() + "/" + str(object_id)
        return url

    def host_groups(self):
        url = self.base_url + self.object_url + "/host-groups"
        return url

    def host_wwns(self):
        url = self.base_url + self.object_url + "/host-wwns"
        return url

    def luns(self):
        url = self.base_url + self.object_url + "/luns"
        return url

    def local_copy_pairs(self):
        url = self.base_url + self.object_url + \
            "/local-clone-copypairs"
        return url

    def split_local_copy_pair_template(self, pair_url):
        url = pair_url + "/actions/split"
        return url

    def split_local_copy_pair(self, pair_url):
        url = pair_url + "/actions/split/invoke"
        return url

    def generate_session(self):
        url = self.base_url + self.object_url + "/sessions"
        return url

    def discard_session(self, object_id):
        url = self.base_url + self.object_url + "/sessions/" + \
            str(object_id)
        return url

    def lock(self):
        url = self.base_url + self.service_url + \
            "/resource-group-service/" + \
            "actions/lock/invoke"
        return url

    def unlock(self):
        url = self.base_url + self.service_url + \
            "/resource-group-service/" + \
            "actions/unlock/invoke"
        return url

    def remote_storage(self):
        url = self.base_url + self.object_url + \
            "/remote-storages"
        return url

    def remote_copy_pairs(self):
        url = self.base_url + self.object_url + \
            "/remote-mirror-copypairs"
        return url

    def job(self, object_id):
        url = self.base_url + self.object_url + "/jobs/" + \
            str(object_id)
        return url

    def affected_resource(self, affected_resource):
        url = "https://" + self.gum_ip_addr + ":" + \
              self.port \
              + affected_resource
        return url

    def api_version(self):
        url = "https://" + self.gum_ip_addr + ":" + \
              self.port \
            + "/ConfigurationManager/configuration/version"
        return url

    def file_upload(self):
        url = self.base_url + self.object_url + \
            "/actions/file-upload/invoke"
        return url

    def auditlog_syslog(self):
        url = self.base_url + self.object_url + \
              "/auditlog-syslog-servers" + "/instance"
        return url

    def auditlog_syslog_send_test(self):
        url = self.auditlog_syslog() + \
            "/actions/send-test/invoke"
        return url

    def drives(self):
        url = self.base_url + self.object_url + "/drives"
        return url

    def drives_parity_group(self, parity_group_id):
        url = self.drives() + "?parityGroupId=" + str(parity_group_id)
        return url

    def parity_groups(self):
        url = self.base_url + self.object_url + "/parity-groups"
        return url

    def parity_group(self, object_id):
        url = self.parity_groups() + "/" + str(object_id)
        return url

    def encryption_keys(self):
        url = self.base_url + self.object_url + "/encryption-keys"
        return url

    def encryption_key(self, object_id):
        url = self.encryption_keys() + "/" + str(object_id)
        return url

    def encryption_key_file(self):
        url = self.encryption_key("file")
        return url

    def encryption_key_file_backup(self):
        url = self.encryption_key_file() + "/actions/backup/invoke"
        return url

    def encryption_key_file_restore(self):
        url = self.encryption_key_file() + "/actions/restore/invoke"
        return url

Issuing an HTTP request and verifying the status of asynchronous processing

In the REST API, operations such as creating an object or changing its attributes are registered as jobs and run asynchronously. For asynchronous processing, you must confirm that the jobs are complete before obtaining the resource to which the request execution result is applied. In the sample coding, the invoke_async_command function is used to issue the request and to perform the processing for waiting for the completion of the jobs. After the method type, URL, and request body are specified, the invoke_async_command function issues the request according to the specified method, waits for the completion of the job, and then returns the resource information.

The invoke_async_command function is defined in the coding samples for volume allocation, ShadowImage pair operation, and registration of remote storage system information. The following examples show the contents of coding samples for the invoke_async_command function.

First, define the function that gets the job status. This function is called by using the invoke_async_command function.
"""
Check whether the asynchronous command was finished.
@param job_id the job ID to identify
       the asynchronous command
@return r the response data
"""


def check_update(job_id):
    url = block_storage_api.job(str(job_id))
    r = requests.get(url, headers=headers, verify=False)
    return r
Next, define the invoke_async_command function. Specify the settings so that when a request is generated, the request body is generated in JSON format. Specify the authentication information by using the token obtained when a session was generated.
"""
Execute the HTTP request (POST or PATCH)
@param method_type HTTP request method (POST or PATCH)
@param url URL to execute HTTP method
@param body The information of a resource
@return job_result.json()["affectedResources"][0]
         URL of an affected resource
"""


def invoke_async_command(method_type, url, body):
    if method_type == "patch":
        r = requests.patch(url, headers=headers,
                         data=json.dumps(body), verify=False)
    elif method_type == "post":
        r = requests.post(
            url,
            headers=headers,
            data=json.dumps(body),
            verify=False)
    if r.status_code != http.client.ACCEPTED:
        raise requests.HTTPError(r)
    print("Request was accepted. JOB URL : " +
          r.json()["self"])
After the request is issued, the job status is repeatedly obtained until the job status changes to Completed. If the job does not finish before the maximum number of retries specified in the parameter is reached, the processing ends. In addition, if an error occurs for the job, the error code is obtained and the processing ends.
    status = "Initializing"
    job_result = None
    retry_count = 1
    wait_time = FIRST_WAIT_TIME
    while status != "Completed":
        if retry_count > MAX_RETRY_COUNT:
            raise Exception("Timeout Error! "
                            "Operation was not completed.")
        time.sleep(wait_time)
        job_result = check_update(r.json()["jobId"])
        status = job_result.json()["status"]
        double_time = wait_time * 2
        if double_time < 120:
            wait_time = double_time
        else:
            wait_time = 120
        retry_count += 1
    if job_result.json()["state"] == "Failed":
        error_obj = job_result.json()["error"]
        if "errorCode" in error_obj:
            if "SSB1" in error_obj["errorCode"]:
                print("Error! SSB code : ",
                      error_obj["errorCode"]["SSB1"],
                      ", ", error_obj["errorCode"]["SSB2"])
            elif "errorCode" in error_obj["errorCode"]:
                print("Error! error code : ",
                      error_obj["errorCode"]["errorCode"])
        raise Exception("Job Error!", job_result.text)
After the job is complete and the status changes to Completed, the URL of the resource to which the job execution result is applied is obtained. The first result is obtained because only one result is returned to affectedResources.
    print("Async job was succeeded. affected resource : " +
          job_result.json()["affectedResources"][0])
    return job_result.json()["affectedResources"][0]
Getting status changes for asynchronous processing

The wait_until_jobstatus_is_changed function gets the status of the jobs that were asynchronously run by the REST API, waits until the job status changes to the specified execution status, and then returns the resource information. The wait_until_jobstatus_is_changed function is defined in the coding samples for a TrueCopy pair operation. The following example shows the contents of coding samples for the wait_until_jobstatus_is_changed function.

First, define the function that gets the job status. This function is called by using the wait_until_jobstatus_is_changed function.

"""
Check whether the asynchronous command was finished.

@param storage_api storage_api
@param job_id the job ID to identify
      the asynchronous command
@param headers the array of the http headers
@return r the response data
"""


def check_update(storage_api, job_id, headers):
    url = storage_api.job(str(job_id))
    r = requests.get(url, headers=headers, verify=False)
    return r

Next, define the wait_until_jobstatus_is_changed function. For changed_status, specify the job status that needs to be detected when a job status has changed to that status. If True is specified for is_retry_count_enabled, after processing is tried again for the number of times specified in the MAX_RETRY_COUNT parameter, a timeout error will be returned. If False is specified, processing will wait until a job moves to the specified status.

"""
Wait until the job status is changed

@param storage_api storage_api
@param headers the array of the http headers
@param job_id the job ID to identify
       the asynchronous command
@param changed_status job status after waiting
@param is_retry_count_enabled if true, wait
       until MAX_RETRY_COUNT. if false, wait forever
       until job status is changed.
@return job_result.json()["affectedResources"][0]
         URL of an affected resource
"""


def wait_until_jobstatus_is_changed(
        storage_api,
        headers,
        job_id,
        changed_status,
        is_retry_count_enabled):
    status = "Initializing"
    retry_count = 1
    wait_time = FIRST_WAIT_TIME
    while status != changed_status:
        if status == "Completed":
            print("Status was already changed" +
                  "to Completed.")
            break
        if is_retry_count_enabled and \
                retry_count > MAX_RETRY_COUNT:
            raise Exception("Timeout Error! "
                            "Operation was not completed.")
        time.sleep(wait_time)
        job_result = check_update(storage_api,
                                  job_id, headers)
        status = job_result.json()["status"]
        double_time = wait_time * 2
        if double_time < 120:
            wait_time = double_time
        else:
            wait_time = 120
        retry_count += 1
    if job_result.json()["state"] == "Failed":
        error_obj = job_result.json()["error"]
        if "errorCode" in error_obj:
            if "SSB1" in error_obj["errorCode"]:
                print("Error! SSB code : ",
                      error_obj["errorCode"]["SSB1"],
                      ", ", error_obj["errorCode"]["SSB2"])
            elif "errorCode" in error_obj["errorCode"]:
                print("Error! error code : ",
                      error_obj["errorCode"]["errorCode"])
        raise Exception("Job Error!", job_result.text)
    print("Async job was succeeded. affected resource : " +
          job_result.json()["affectedResources"][0])
    return job_result.json()["affectedResources"][0]