Cloneの再同期

Cloneを再同期するスクリプトを記載します。このスクリプトは、QuickOPCを使用した定期的なフルバックアップを作成する運用で使用します。詳細は、QuickOPCを使用した定期的なフルバックアップの作成を参照してください。

Crontabなどを使用して、このスクリプトを再同期したいタイミングで実行することで、任意の周期でCloneを更新できます。

備考

スクリプトを実行中は、Cloneのデータの整合性を保証するために、業務VolumeのI/Oやアプリケーションの処理を瞬間的に停止させ、静止点を作成する必要があります。スクリプトの処理が完了したあとは、論理的な更新データのコピーは完了しているため、バックグラウンドコピーの完了を待たずに業務を再開させることができます。

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

  1. POST /volume/{volume_id}/resync/{backup_volume_id} APIを使用してCloneの再同期を開始する。

quickopc_resync.py


import sys

from eternus_rest import EtdxBaseApi

# Change the following values for your environment.
storage_url = "https://192.168.0.1:5665"
storage_user_name = "username"
storage_password = "password"

# Change the following values as required.
# Specify a list of clone pairs. A clone pair is a tuple with a source volume
# ID and a destination volume ID of a QuickOPC.
# If you want to resync multiple QuickOPCs at the same time, list the clone
# pairs.
# Examples:
# clone_pair_list = [(100001,1100001),(200001,1200001)]
# This is specified to resync the following QuickOPCs at the same time.
# QuickOPC#0 (Source volume ID 100001, destination volume ID 1100001)
# QuickOPC#1 (Source volume ID 200001, destination volume ID 1200001)
clone_pair_list = [(100001, 1100001), (200001, 1200001)]

# If you want to schedule this script, you can use cron as follows.
# Examples: It is scheduled to resynchronize daily at 20:00. To ensure data
# consistency, host access must be stopped before resynchronization.
# crontab -e
# 0 20 * * * python3 /root/quickopc_resync.py >> /root/cron.log 2>&1


class EtdxApi(EtdxBaseApi):
    def __init__(self, base_url, boxid=None, proxies=None):
        super().__init__(base_url, boxid=boxid, proxies=proxies)

    def quickopc_is_tracking(self, clone_volume_id_list):
        """Check if all the clones using QuickOPCs are in "Tracking" phase.

        Args:
            clone_volume_id_list (list[int]): List of the clone volume IDs
              using QuickOPCs to check.

        Returns:
            bool: True if all the specified QuickOPCs are in "Tracking" phase.
              False otherwise. Returns false if retrieving QuickOPC
              information failed.
        """
        r = super().get("/api/v1/volume/copysession?volume_id={0}&"
                        "fields=volume_name,status,phase"
                        .format(",".join(map(str, clone_volume_id_list))))
        data = r.json()
        if r.status_code != 200 or data["list_count"] == 0:
            print("Failed to get clones.")
            print(r)
            print(data)
            return False
        try:
            status_list = [i["status"] for i in data["volume_list"]]
            phase_list = [i["phase"] for i in data["volume_list"]]
        except Exception:
            print("Some specified volumes are not clones.")
            print(data)
            return False
        # Returns false if not all of the specified QuickOPCs are in
        # "Active" status.
        if status_list.count("Active") != len(clone_volume_id_list):
            print("Failed to get information about some clones or some clones"
                  " have an error.")
            print(r)
            print(data)
            return False
        # Returns false if not all of the volumes are in "tracking" or
        # "tracking_and_copying" phase.
        if (len(phase_list) != phase_list.count("Tracking")
                + phase_list.count("Tracking_and_Copying")):
            print("Some clones are not in \"Tracking\" phase.")
            print(r)
            print(data)
            return False
        return True

    def quickopc_resync(self, clone_pair_list):
        """Resync QuickOPCs

        Args:
            clone_pair_list (list[tuple]): List of clone pairs of source and
              destination volume of QuickOPC.

        Returns:
            list[int]: List of clone pairs of QuickOPCs which have been
              started synchronization.
        """
        job_id_list = []
        for clone_pair in clone_pair_list:
            volume_id, clone_volume_id = clone_pair
            r = super().post("/api/v1/volume/{0!s}/resync/{1!s}"
                             .format(volume_id, clone_volume_id))
            if r.status_code != 202:
                print("Failed to request the clone pair {0!s} resync."
                      .format(clone_pair))
                print(r)
                print(r.json())
                continue
            clone_pair_job = (int(r.json()["job_id"]), clone_pair)
            job_id_list.append(clone_pair_job)

        # Wait for the job completion
        success_pair_list = []
        for clone_pair_job in job_id_list:
            job_id = clone_pair_job[0]
            job_r = super().wait_job(job_id)
            if job_r.json()["status"] != "Success":
                print("Failed to resync the clone pair {0!s}."
                      .format(clone_pair_job[1]))
                print(job_r)
                print(job_r.json())
                continue
            success_pair_list.append(clone_pair_job[1])
        return success_pair_list


def main():
    storage = EtdxApi(storage_url)

    # Login
    if not storage.login(storage_user_name, storage_password):
        return False

    print("Resyncing the QuickOPCs.")
    print("Clone pair list to resync:", clone_pair_list)

    # Verify if QuickOPC can be resynchronized.
    clone_volume_id_list = [i[1] for i in clone_pair_list]
    if not storage.quickopc_is_tracking(clone_volume_id_list):
        print("Some QuickOPCs cannot be resynchronized.")
        return False

    # Initialize return value as True, since the procedure continues even if
    # error occurs.
    rtn = True

    # Resync QuickOPCs
    success_clone_pair_list = storage.quickopc_resync(clone_pair_list)
    if set(success_clone_pair_list) != set(clone_pair_list):
        print("Failed to resync some QuickOPCs.")
        print("List of clone pairs failed synchronized:",
              list(set(clone_pair_list) - set(success_clone_pair_list)))
        rtn = False
    if success_clone_pair_list == []:
        print("Failed to resync all the specified QuickOPCs.")
        return False
    print("Clone pair list that was started to resync QuickOPCs:",
          success_clone_pair_list)

    # Get information of all the specified QuickOPCs.
    for clone_volume_id in clone_volume_id_list:
        r = storage.get("/api/v1/volume/{0!s}/copysession"
                        .format(clone_volume_id))
        print(r)
        print(r.json())

    # Logout
    storage.logout()
    return rtn


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