リモート筐体のバックアップVolumeへのデータ転送のサスペンド
業務開始前(業務Volumeへのアクセスを開始する前)に、リモート筐体へのデータ転送をサスペンドし、リモート筐体のバックアップVolume(非同期StackモードのREC SessionのCopy先Volume)のCloneを再同期するスクリプトを記載します。このスクリプトは、非同期StackモードのRECとQuickOPCを使用して、リモート筐体に業務Volumeのバックアップを定期的に作成する運用で使用します。詳細は、RECとQuickOPCを使用したリモート筐体へのバックアップの作成を参照してください。
リモート筐体への未転送データが残っている場合、スクリプトは異常終了します。
スクリプトの処理の流れは以下です。
GET /copysession/{copysession_id} APIを使用し、サスペンド対象のCopy Session情報を取得し、サスペンド可能かどうかを確認する。
StatusがActiveではない、またはPhaseがEquivalentではない場合は、以下のどちらかの理由によりサスペンドは不可。
Copy Sessionの状態が異常またはSuspend中
未転送データが存在する
GET /volume/{volume_id}/copysession APIを使用し、バックアップVolumeのCloneの再同期が可能かどうかを確認する。
StatusがActive以外、またはPhaseがCopyingの場合は、以下のどちらかの理由により再同期は不可。
異常が発生している
削除中
POST /copysession/{copysession_id}/suspend APIを使用し、REC Sessionをサスペンドする。
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)

