REC Sessionの反転

同期モードおよび非同期ConsistencyモードのREC Sessionを反転させ、ミラーVolumeのデータを業務Volumeにコピーしたあとに、REC Sessionを再度反転させて元の状態に戻すスクリプトを記載します。このスクリプトは、同期モードまたは非同期ConsistencyモードのRECを使用し、リモート筐体にミラーVolumeを作成する運用で使用します。詳細は、REC(同期モード)を使用したリモート筐体へのミラーVolumeの作成、またはREC(非同期Consistencyモード)を使用したリモート筐体へのミラーVolumeの作成を参照してください。

備考

Copy Sessionの反転についての留意事項は、REC Sessionの反転を参照してください。

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

  1. GET /copysession/{copysession_id} APIを使用して、反転対象のCopy Session情報を取得する。

  2. Copy Sessionが反転可能なStatusかどうかを判定する。

  3. Copy SessionのStatusがSuspendedではない場合、POST /copysession/{copysession_id}/suspend APIを使用してCopy SessionをSuspendする。

  4. POST /copysession/{copysession_id}/reverse APIを使用して、Copy Sessionを反転する。

  5. POST /copysession/{copysession_id}/resume APIを使用してCopy SessionをResumeし、ミラーVolumeから業務Volumeへのデータ転送を開始する。

  6. GET /copysession/{copysession_id} APIを使用してCopy Session情報を定期的に取得し、PhaseがEquivalentになるまで待機する。

  7. POST /copysession/{copysession_id}/suspend APIを使用して、Copy SessionをSuspendする。

  8. POST /copysession/{copysession_id}/reverse APIを使用して、Copy Sessionを反転する(元の方向に戻す)。

  9. 3でCopy SessionをSuspendしていた場合は、POST /copysession/{copysession_id}/resume APIを使用して、Copy SessionのStatusをスクリプト実行前の状態(Active)に戻す。

rec_sync_consis_recover.py


import sys
import time

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"

# Copy session ID to recover the source volume data.
storage1_copysession_id = 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_can_recover(self, copysession_id):
        """Check if the REC session can start the recovery.

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

        Returns:
            bool: True if the specified REC session can start the recovery.
              False otherwise. Returns false if retrieving copy session
              information failed.
        """
        r = super().get("/api/v1/copysession/{0!s}?fields=status,"
                        "is_local_source_volume".format(copysession_id))
        data = r.json()
        if r.status_code != 200:
            print("Failed to get information about the copy session.")
            print(r)
            print(data)
            return False
        if data["status"] not in ["Active", "Suspended"]:
            print("The REC session is not in normal status.")
            print(data)
            return False
        if not data["is_local_source_volume"]:
            print("The REC session might be in recovering.")
            print(data)
            return False
        return True

    def rec_wait_for_equivalent(self, copysession_id):
        """Wait for the REC session phase to be equivalent.

        Args:
            copysession_id (int): Copy session ID to wait.

        Returns:
            bool: True if the copy session phase is equivalent.
              False if error occurs.
        """
        while True:
            r = super().get("/api/v1/copysession/{0!s}".format(copysession_id))
            data = r.json()
            if r.status_code != 200 or data["status"] == "Error":
                print("Failed to get information of the copy session or"
                      " some error occurred on the copy session.")
                print(r)
                print(data)
                return False
            if data["phase"] == "Equivalent":
                break

            print("The REC session is not yet equivalent. Check again in"
                  " 60 seconds.")
            print("Total Data Size: ", data["total_data_size"])
            print("Copied Data Size:", data["copied_data_size"])
            print("Progress:        ", data["progress"])
            print("Elapsed Time:    ", data["elapsed_time"])
            time.sleep(60)

        return True

    def rec_suspend(self, copysession_id, force=False):
        """Suspend the 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 the 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 rec_resume(self, copysession_id):
        """Resume the REC session

        Args:
            copysession_id (int): Copy session ID to resume.

        Returns:
            bool: True if successful, False otherwise.
        """
        r = super().post("/api/v1/copysession/{0!s}/resume"
                         .format(copysession_id))
        if r.status_code != 202:
            print("Failed to request the REC session resume.")
            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 resume the REC session.")
            print(job_r)
            print(job_r.json())
            return False
        return True

    def rec_consistency_wait_for_suspend(self, copysession_id):
        """Wait for the REC concurrent copy session to suspend.

        Args:
            copysession_id (int): Copy session ID to suspend.

        Returns:
            bool: True if suspend the copy session is completed.
              False if error occurs.
        """
        while True:
            r = super().get("/api/v1/copysession/{0!s}".format(copysession_id))
            data = r.json()
            if r.status_code != 200:
                print("Failed to get information of the copy session.")
                print(r)
                print(data)
                return False

            if data["status"] == "Error":
                print("The copy session is in error status.")
                print(data)
                return False

            if data["status"] != "Suspended":
                print("Failed to suspend the copy session. Stop access to the"
                      " source volume of the copy session and try again.")
                print(data)
                return False

            if data["concurrent_suspend_result"] == "Error":
                print("Failed to suspend the copy session.")
                print(data)
                return False

            if (data["concurrent_suspend_result"] == "Success"
                    and data["status"] == "Suspended"):
                break

            # Get it again after remain_time_to_finish_suspend.
            if data["remain_time_to_finish_suspend"] > 10:
                time.sleep(data["remain_time_to_finish_suspend"])
            else:
                time.sleep(10)

        return True

    def rec_reverse(self, copysession_id):
        """Reverse the REC session

        Args:
            copysession_id (int): Copy session ID to reverse.

        Returns:
            bool: True if successful, False otherwise.
        """
        r = super().post("/api/v1/copysession/{0!s}/reverse"
                         .format(copysession_id))
        if r.status_code != 202:
            print("Failed to request the REC session reverse.")
            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 reverse the REC session.")
            print(job_r)
            print(job_r.json())
            return False
        return True

    def rec_recover(self, copysession_id):
        """Restore the source volume data from the REC destination volume.

        Args:
            copysession_id (int): Copy session ID of the REC.

        Returns:
            bool: True if successful, False otherwise.
        """
        is_suspended = False
        is_consistency = False
        # Get the information of the copy session.
        r = super().get("/api/v1/copysession/{0!s}".format(copysession_id))
        data = r.json()
        if r.status_code != 200:
            print("Failed to get information of the copy session.")
            print(r)
            print(data)
            return False
        if data["status"] == "Suspended":
            is_suspended = True
        if data["transfer_mode"] == "Consistency":
            is_consistency = True

        # Execute suspend if the REC session is not suspended.
        if not is_suspended:
            print("Suspending the REC session.")
            if not self.rec_suspend(copysession_id):
                return False
        # Need to wait for the completion of suspend if the REC is consistency.
        if is_consistency:
            print("Waiting for the REC session to complete suspend.")
            if not self.rec_consistency_wait_for_suspend(copysession_id):
                print("Some error occurred before suspend completion of"
                      " the REC session.")
                return False

        # Reverse the REC session
        print("Reversing the REC session.")
        if not self.rec_reverse(copysession_id):
            return False
        # Restore the data from the destination volume to the source
        # volume of the REC.
        print("Resuming the REC session.")
        if not self.rec_resume(copysession_id):
            return False
        print("Waiting for the REC session to be equivalent.")
        if not self.rec_wait_for_equivalent(copysession_id):
            return False
        print("The data restore Completed.")

        # Restore the REC session to its former state.
        print("Suspending the REC session.")
        if not self.rec_suspend(copysession_id):
            return False
        # Need to wait for the completion of suspend if the REC is consistency.
        if is_consistency:
            print("Waiting for the REC session to complete suspend.")
            if not self.rec_consistency_wait_for_suspend(copysession_id):
                print("Some error occurred before suspend completion of"
                      " the REC session.")
                return False
        print("Reversing the REC session.")
        if not self.rec_reverse(copysession_id):
            return False
        # Execute resume if needed.
        if not is_suspended:
            print("Resuming the REC session.")
            if not self.rec_resume(copysession_id):
                return False

        return True


def main():
    storage1 = EtdxApi(storage1_url)

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

    print("Start recovery of the source volume data about the REC session"
          " (ID: {0!s}).".format(storage1_copysession_id))

    # Change recovery process according to the operation condition.
    try:
        if not storage1.rec_can_recover(storage1_copysession_id):
            print("Could not start recovery.")
            return False
        if not storage1.rec_recover(storage1_copysession_id):
            print("Failed to recover.")
            return False
    except Exception as e:
        print(e)
        return False

    print("Recovery completed.")

    # Logout
    storage1.logout()
    return True


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