リモート筐体のバックアップからのデータのリストア
リモート筐体のバックアップ(非同期StackモードのREC Sessionのコピー先VolumeおよびそのClone)からデータをリストアするスクリプトを記載します。このスクリプトは、非同期StackモードのRECとQuickOPCを使用して、リモート筐体に業務Volumeのバックアップを定期的に作成する運用で使用します。詳細は、RECとQuickOPCを使用したリモート筐体へのバックアップの作成を参照してください。
このスクリプトは、REC SessionのStatusを確認して、リモート筐体のバックアップVolume(非同期StackモードのREC SessionのCopy先Volume)からリストアするか、リモート筐体のバックアップVolumeのCloneからリストアするかを自動で判定します。
このスクリプトは、RECとQuickOPCを使用したリモート筐体へのバックアップの作成で記載したリモート筐体にBackup Volumeを作っている環境でのみ使用できます。それ以外の環境でデータをリストアする際には利用できません。
このスクリプトは、REC Sessionの状態が正常の場合に使用できます。筐体が被災して物理的に破損し、RECの経路がHaltになった場合は、経路を復旧してからリモート筐体のBackup VolumeまたはCloneからREC Sessionを再作成することでデータの復旧を実施してください。
スクリプトの処理の流れは以下です。
GET /copysession/{copysession_id} APIを使用してREC Sessionの情報を取得し、REC Sessionが反転可能かどうかを確認する(StatusがErrorの場合、またはすでに反転済みの場合は処理終了)。
GET /copysession/{copysession_id} APIを使用してREC Sessionの情報を取得し、バックアップVolumeからリストアするか、バックアップVolumeのCloneからリストアするかを判定する(Suspend中の場合はバックアップVolumeから、Suspend中ではない場合はバックアップVolumeのCloneからリストアする)。
リモート筐体のバックアップVolumeからリストアする場合、以下の処理を実施する。
POST /copysession/{copysession_id}/reverse APIを使用してREC Sessionの方向を反転する。
POST /copysession/{copysession_id}/resume APIを使用してREC Sessionを再開する。
GET /copysession/{copysession_id} APIを使用してREC Sessionの情報を定期的に取得し、リモート筐体からローカル筐体へのデータ転送が完了するのを待つ。
POST /copysession/{copysession_id}/suspend APIを使用してREC SessionをSuspendする。
POST /copysession/{copysession_id}/reverse APIを使用してREC Sessionの方向を反転する(元の方向に戻す)。
リモート筐体のバックアップVolumeのCloneからリストアする場合、以下の処理を実施する。
POST /copysession/{copysession_id}/suspend APIを使用してREC SessionをSuspendする。
POST /copysession/{copysession_id}/reverse APIを使用してREC Sessionを反転する。
POST /volume/{volume_id}/restore/{backup_volume_id} APIを使用して、リモート筐体のバックアップVolumeのCloneのデータを、リモート筐体のバックアップVolumeにコピーする。
POST /copysession/{copysession_id}/resume APIを使用してREC Sessionを再開させ、リモート筐体からローカル筐体へのデータ転送を開始する。
GET /copysession/{copysession_id} APIを使用してREC Sessionの情報を定期的に取得し、リモート筐体からローカル筐体へのデータ転送が完了するのを待つ。
POST /copysession/{copysession_id}/suspend APIを使用してREC SessionをSuspendする。
GET /volume/{volume_id}/copysession APIを使用してリモート筐体のバックアップVolumeのCloneから、リモート筐体のバックアップVolumeへのデータコピーが完了しているかどうかを確認する。
POST /copysession/{copysession_id}/reverse APIを使用して、REC Sessionの方向をリストア前の状態に戻す。
POST /copysessoin/{copysession_id}/resume APIを使用して、REC Sessionを再開する。
rec_stack_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"
# Target storage (storage that has the destination volume)
storage2_url = "https://192.168.0.2:5665"
storage2_user_name = "username"
storage2_password = "password"
# 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
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_is_suspended(self, copysession_id):
"""Check if the REC session is in "Suspended" status.
Args:
copysession_id (int): Copy session ID to check.
Returns:
bool: True if the REC session is in "Suspended". False otherwise.
Returns false if retrieving copy session information failed.
"""
r = super().get("/api/v1/copysession/{0!s}?fields=status"
.format(copysession_id))
if r.status_code != 200:
print(r)
print(r.json())
raise Exception("Failed to get information about the "
"copy session.")
if r.json()["status"] == "Suspended":
return True
return False
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_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 quickopc_wait_for_restore(self, volume_id):
"""Wait for the restore copy session from the clone to complete.
Args:
volume_id (int): Volume ID of the original of the clone.
Returns:
bool: True if the restore copy session completed. False if
retrieving copy session information failed or some error
occurred on the copy session.
"""
while True:
r = super().get("/api/v1/volume/{0!s}/copysession"
.format(volume_id))
data = r.json()
if r.status_code != 200:
print("Failed to get information of the copy session of volume"
" (ID: {0!s})."
.format(volume_id))
print(r)
print(data)
return False
# If the original volume of clone is not a copy destination, the
# restore copy session is considered complete.
if not data["is_copy_destination_volume"]:
break
# If the copy session is not a restore, the restore copy session
# is considered complete.
if data["backup_type"] != "Restored":
break
# If the restore copy session is in "Suspend" or "Error" status.
if data["status"] in ["Suspended", "Error"]:
print("Restore Copy session is not in expected status ({0!s})"
.format(data["status"]))
print(data)
return False
time.sleep(10)
return True
def quickopc_restore(self, volume_id, backup_volume_id):
"""Restore the QuickOPC.
Args:
volume_id (int): Volume ID of the original of the clone.
backup_volume_id (int): Volume ID of the clone.
Returns:
bool: True if successful, False otherwise.
"""
r = super().post("/api/v1/volume/{0!s}/restore/{1!s}"
.format(volume_id, backup_volume_id))
if r.status_code != 202:
print("Failed to request the QuickOPC restore.")
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 restore the QuickOPC.")
print(job_r)
print(job_r.json())
return False
return True
def rec_stack_recover_under_operating(self, copysession_id):
"""Recovery process under operating.
Args:
copysession_id (int): Copy session ID of the REC.
Returns:
bool: True if successful, False otherwise.
"""
# Reverse the REC session
print("Reversing 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
print("Reversing REC session.")
if not self.rec_reverse(copysession_id):
return False
return True
def rec_stack_recover_not_operating(self, copysession_id, remote_storage,
volume_id, clone_volume_id):
"""Recovery process while not operating.
Args:
copysession_id (int): Copy session ID of the REC.
remote_storage (obj): Object of the destination storage of the REC.
volume_id (int): Volume ID of the destination volume of the REC.
clone_volume_id (int): Volume ID of the clone of the REC
destination volume.
Returns:
bool: True if successful, False otherwise.
"""
# Reverse the REC session
print("Suspending the REC session.")
if not self.rec_suspend(copysession_id, force=True):
return False
print("Reversing the REC session.")
if not self.rec_reverse(copysession_id):
return False
# Restore the data from clone to the destination volume of the REC.
print("Restoring data from the clone (volume ID: {0!s}) to the REC "
"destination volume (ID: {1!s})."
.format(clone_volume_id, volume_id))
if not remote_storage.quickopc_restore(volume_id, clone_volume_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
# Wait for the restore copy session from the clone to complete.
print("Waiting for the restore copy session from the clone to "
"complete.")
if not remote_storage.quickopc_wait_for_restore(volume_id):
return False
print("Reversing REC session.")
if not self.rec_reverse(copysession_id):
return False
print("Resuming REC session.")
if not self.rec_resume(copysession_id):
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 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:
print("Check the copy session whether the recovery process can work.")
if not storage1.rec_can_recover(storage1_copysession_id):
print("Could not start recovery.")
return False
if storage1.rec_is_suspended(storage1_copysession_id):
print("Starting Recovery during operation.")
if not storage1.rec_stack_recover_under_operating(
storage1_copysession_id):
print("Failed to recover.")
return False
else:
print("Starting Recovery during shutdown.")
if not storage1.rec_stack_recover_not_operating(
storage1_copysession_id,
storage2,
storage2_volume_id,
storage2_clone_volume_id):
print("Failed to recover.")
return False
except Exception as e:
print(e)
return False
print("Recovery completed.")
# Logout
storage1.logout()
storage2.logout()
return True
if __name__ == '__main__':
if not main():
sys.exit(1)

