リモート筐体のバックアップVolumeへのデータ転送のサスペンド

業務開始前(業務Volumeへのアクセスを開始する前)に、リモート筐体へのデータ転送をサスペンドし、リモート筐体のバックアップVolume(非同期StackモードのREC SessionのCopy先Volume)のCloneを再同期するスクリプトを記載します。このスクリプトは、非同期StackモードのRECとQuickOPCを使用して、リモート筐体に業務Volumeのバックアップを定期的に作成する運用で使用します。詳細は、RECとQuickOPCを使用したリモート筐体へのバックアップの作成を参照してください。

備考

リモート筐体への未転送データが残っている場合、スクリプトは異常終了します。

スクリプトの処理の流れは以下です。

  1. GET /copysession/{copysession_id} APIを使用し、サスペンド対象のCopy Session情報を取得し、サスペンド可能かどうかを確認する。

    StatusがActiveではない、またはPhaseがEquivalentではない場合は、以下のどちらかの理由によりサスペンドは不可。

    • Copy Sessionの状態が異常またはSuspend中

    • 未転送データが存在する

  2. GET /volume/{volume_id}/copysession APIを使用し、バックアップVolumeのCloneの再同期が可能かどうかを確認する。

    StatusがActive以外、またはPhaseがCopyingの場合は、以下のどちらかの理由により再同期は不可。

    • 異常が発生している

    • 削除中

  3. POST /copysession/{copysession_id}/suspend APIを使用し、REC Sessionをサスペンドする。

  4. POST /volume/{volume_id}/resync/{backup_volume_id} APIを使用し、バックアップVolumeと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)