Reversing the REC Session

The following shows a script that reverses a synchronous and asynchronous consistency REC Session, copies data from the mirrored volume to the operation volume, and then reverses the REC Session back to its original state. This script is used in operations that create a mirrored volume in a remote storage system using REC in synchronous or asynchronous consistency mode. For more information, see the description of Creating a Mirrored Volume in a Remote Storage System with REC (Synchronous Mode) or Creating a Mirrored Volume in a Remote Storage with REC (Asynchronous Consistency Mode).

Note

For notes on reversing the Copy Session, see the description of Reversing the REC Session.

The sequence of script processing is as follows.

  1. Use GET /copysession/{copysession_id} API to get the Copy Session information to reverse.

  2. Determine whether the Copy Session can be reversed or not.

  3. If the Status of the Copy Session is not Suspended, Suspend the Copy Session using POST /copysession/{copysession_id}/suspend API.

  4. Use POST /copysession/{copysession_id}/reverse API to reverse the Copy Session.

  5. Resume the Copy Session using POST /copysession/{copysession_id}/resume API to start the transfer of data from the mirror volume to the operation volume.

  6. Use GET /copysession/{copysession_id} API to periodically retrieve Copy Session information and wait for Phase to Equivalent.

  7. Use POST /copysession/{copysession_id}/suspend API to suspend the Copy Session.

  8. Use POST /copysession/{copysession_id}/reverse API to reverse the Copy Session (return to the original orientation).

  9. If the Copy Session is suspended in 3, use the POST /copysession/{copysession_id}/resume API to return the Copy Session Status to the state it was in prior to the script execution (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)