Suspending Data Transfer to a Remote Storage System Backup Volume

Below is a script that suspends data transfer to the remote storage system and resynchronizes the Clone of the remote storage system backup volume (copy destination volume of the REC Session in the asynchronous stack mode) before starting operations (before accessing Business Volumes). This script is used in the operation of periodically backing up the business volume on the remote storage system using REC in asynchronous stack mode and QuickOPC. For more information, see the description of Creating Backups to Remote Storage Systems Using REC and QuickOPC.

Note

If there is any data that has not been transferred to the remote storage system, the script terminates abnormally.

The sequence of script processing is as follows.

  1. Use GET /copysession/{copysession_id} API to get the copy session information to suspend and see if it is possible to suspend.

    If the Status is not Active or the Phase is not Equivalent, the copy session cannot be suspended for one of the following reasons.

    • The Copy Session is in an abnormal state or is being suspended.

    • There is untransferred data.

  2. Use GET /volume/{volume_id}/copysession API to determine if the backup volume Clones can be resynchronized.

    if the Status is not Active or the Phase is Copying, the Clone cannot be resynchronized for one of the following reasons.

    • An error has occurred.

    • A deletion is being performed.

  3. Suspend the REC Session using POST /copysession/{copysession_id}/suspend API.

  4. Use POST /volume/{volume_id}/resync/{backup_volume_id} API to initiate a resynchronization of the backup volume and Clone.

rec_stack_before_operation.py


import sys

from eternus_rest import EtdxBaseApi

# Change the following values for your environment.
# Source storage (storage that has the source volume)
storage1_url = "https://192.168.0.1:5665"
storage1_user_name = "username"
storage1_password = "password"
# Target storage (storage that has the destination volume)
storage2_url = "https://192.168.0.2:5665"
storage2_user_name = "username"
storage2_password = "password"

# Change the following values as required.
# This case is for a REC(Stack) session from the volume on storage1 to
# storage2.
# The destination volume of REC has been cloned using QuickOPC on storage2.
# You can paste the outputs of rec_stack_create.
# REC session ID on storage1 created by the script rec_stack_create.
storage1_copysession_id = 1
# Backup volume ID created by the script rec_stack_create as the destination
# volume of REC.
storage2_volume_id = 200002
# Clone volume ID created by the script rec_stack_create as the clone for the
# REC destination volume.
storage2_clone_volume_id = 300003

# If you want to schedule this script, you can use cron as follows.
# Examples: It is scheduled to execute this pre-processing daily at 8:00.
# crontab -e
# 0 08 * * * python3 /root/rec_stack_before_operation.py >> /root/cron.log 2>&1


class EtdxApi(EtdxBaseApi):
    def __init__(self, base_url, boxid=None, proxies=None):
        """Constructor of EtdxApi

        If storage is used as target storage of REC without calling login
        method in this script, need to specify the boxid of this storage
        as follows.
        storage2 = EtdxApi(storage2_url,
                           boxid="00ETERNUSDXHS3ET00000A####EI000001######")

        Args:
            base_url ([type]): [description]
            boxid ([type], optional): [description]. Defaults to None.
            proxies ([type], optional): [description]. Defaults to None.
        """
        super().__init__(base_url, boxid=boxid, proxies=proxies)

    def login(self, user_name="", password=""):
        """Create a session of RESTful API and get boxid.

        Args:
            user_name (str, optional): User name to login. Defaults to "".
            password (str, optional): Password for the user. Defaults to "".

        Returns:
            bool: True if successful, False otherwise.
        """
        rtn = super().login(user_name, password)
        if rtn and self.boxid is None:
            r = super().get("/api/v1/storagesystem")
            if r.status_code != 200:
                print(r)
                print(r.json())
                print("Failed to get boxid.")
                return False
            self.boxid = r.json()["boxid"]
        return rtn

    def rec_is_equivalent(self, copysession_id):
        """Check if the specified REC session is in "equivalent" phase.

        Args:
            copysession_id (int): Copy Session ID to check.

        Returns:
            bool: True if the specified REC session is in "Equivalent" phase.
              False otherwise. Returns false if retrieving copy session
              information failed.
        """
        r = super().get("/api/v1/copysession/{0!s}?fields=status,phase"
                        .format(copysession_id))
        if r.status_code != 200:
            print("Failed to get information about the copy session "
                  "(ID: {0!s}).".format(copysession_id))
            print(r)
            print(r.json())
            return False
        if (r.json()["status"] not in ["Active"]
                or r.json()["phase"] not in ["Equivalent"]):
            print("Copy session (ID: {0!s}) is not in \"Active\" status or"
                  " \"Equivalent\" phase.".format(storage1_copysession_id))
            print(r)
            print(r.json())
            return False
        return True

    def rec_suspend(self, copysession_id, force=False):
        """Suspend REC session

        Args:
            copysession_id (int): Copy session ID to suspend
            force (bool, optional): True to force suspend. Defaults to False.

        Returns:
            bool: True if successful, False otherwise.
        """
        if force:
            r = super().post("/api/v1/copysession/{0!s}/suspend?force=true"
                             .format(copysession_id))
        else:
            r = super().post("/api/v1/copysession/{0!s}/suspend"
                             .format(copysession_id))
        if r.status_code != 202:
            print("Failed to request REC session suspend.")
            print(r)
            print(r.json())
            return False

        # Wait for the job completion
        job_r = super().wait_job(r.json()["job_id"])
        if job_r.json()["status"] != "Success":
            print("Failed to suspend the REC session.")
            print(job_r)
            print(job_r.json())
            return False
        return True

    def quickopc_is_tracking(self, clone_volume_id):
        """Check if the specified QuickOPC is in "Tracking" phase.

        Args:
            clone_volume_id (int): Volume ID of QuickOPC clone to check.

        Returns:
            bool: True if the QuickOPC is in "Tracking" phase.
              False otherwise. Returns false if retrieving QuickOPC
              information failed.
        """
        r = super().get("/api/v1/volume/{0!s}/copysession?fields=status,phase"
                        .format(clone_volume_id))
        data = r.json()
        if r.status_code != 200 or "status" not in data or "phase" not in data:
            print("Failed to get information of the clone (Volume ID: {0!s})."
                  .format(clone_volume_id))
            print(r)
            print(data)
            return False
        if (data["status"] not in ["Active"]
                or data["phase"] not in ["Tracking", "Tracking_and_Copying"]):
            print("Clone (Volume ID: {0!s}) is not in \"Active\" status or"
                  " \"Tracking\" phase.".format(clone_volume_id))
            print(data)
            return False
        return True

    def quickopc_resync(self, volume_id, backup_volume_id):
        """Resync QuickOPC

        Args:
            volume_id (int): Volume ID of the QuickOPC source volume to resync.
            backup_volume_id (int): Volume ID of the QuickOPC destination
              volume to resync.

        Returns:
            bool: True if successful, False otherwise.
        """
        r = super().post("/api/v1/volume/{0!s}/resync/{1!s}"
                         .format(volume_id, backup_volume_id))
        if r.status_code != 202:
            print("Failed to request the QuickOPC resync.")
            print(r)
            print(r.json())
            return False

        # Wait for the job completion
        job_r = super().wait_job(r.json()["job_id"])
        if job_r.json()["status"] != "Success":
            print("Failed to resync QuickOPC.")
            print(job_r)
            print(job_r.json())
            return False
        return True


def main():
    storage1 = EtdxApi(storage1_url)
    storage2 = EtdxApi(storage2_url)

    # Login
    if not storage1.login(storage1_user_name, storage1_password):
        return False
    if not storage2.login(storage2_user_name, storage2_password):
        return False

    print("Start pre-processing.")

    # Validate the condition of REC session and clone.
    # Validate the completion of the REC backup.
    if not storage1.rec_is_equivalent(storage1_copysession_id):
        print("REC session phase Error.")
        return False
    # Verify the phase of the clone, since clone resync cannot execute if not
    # in "Tracking" phase.
    if not storage2.quickopc_is_tracking(storage2_clone_volume_id):
        print("QuickOPC Phase Error.")
        return False

    # Pre-processing for the REC.
    # Suspend REC session
    print("Suspending the REC session (ID: {0!s})."
          .format(storage1_copysession_id))
    if not storage1.rec_suspend(storage1_copysession_id):
        return False
    print("REC session has been suspended.")

    # Pre-processing for the clone.
    # Resync clone
    print("Resyncing the clone (Volume ID: {0!s})."
          .format(storage2_clone_volume_id))
    if not storage2.quickopc_resync(storage2_volume_id,
                                    storage2_clone_volume_id):
        return False
    print("Clone Resync is started.")

    print("Pre-processing completed.")

    # Logout
    storage1.logout()
    storage2.logout()
    return True


if __name__ == '__main__':
    if not main():
        sys.exit(1)