Reducing Snapshot Generations
Below is a script that reduces the number of Snapshot generations. Use this script for operations that use SnapOPC+ to back up generation-managed differential data. For more information, see the description of Generation Managed Differential Data Backup with SnapOPC+.
The sequence of script processing is as follows.
Use GET /volume/{volume_id}/copysession API to get the snapshot information.
Sort the snapshot information obtained in 1 from the oldest creation time.
Remove older snapshots using DELETE /volume/{volume_id} API, starting with the oldest snapshot.
Repeat 3 for the number of generations for which the reduction is desired.
snapopcplus_reduce.py
import sys
from datetime import datetime
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.
# Volume ID of the source volume of SnapOPC+ to reduce generations.
volume_id = 100001
# Number of generations of SnapOPC+ after reducing.
generation_count = 5
class EtdxApi(EtdxBaseApi):
def __init__(self, base_url, boxid=None, proxies=None):
super().__init__(base_url, boxid=boxid, proxies=proxies)
def snapopcplus_get_snapshot_list(self, volume_id):
"""Get the SnapOPC+ snapshots about the specified volume.
Args:
volume_id (int): Volume ID of the source volume of SnapOPC+ to get.
Returns:
list[obj]: A list of snapshot objects of SnapOPC+. Returns null
list if fails to get information.
"""
r = super().get("/api/v1/volume/{0!s}/copysession?backup_type=snapshot"
"&is_manual_snapshot=false".format(volume_id))
if r.status_code != 200 or r.json()["backup_volume_list"] == []:
print("Failed to get snapshots or the volume has no "
"SnapOPC+ snaphosts.")
print(r)
print(r.json())
return []
return r.json()["backup_volume_list"]
def snapopcplus_delete_volumes(self, sorted_snapshot_volume_id_list,
delete_count):
"""Delete specified number of SnapOPC+ snapshots one by one.
Snapshot of SnapOPC+ can be deleted from the oldest ones.
Args:
sorted_snapshot_volume_id_list (list[int]): Sorted list of
snapshot volume ID of SnapOPC+.
delete_count (int): Number of snapshots to delete.
Returns:
list[int]: List of deleted snapshot volume IDs.
"""
deleted_volume_id_list = []
for i in range(delete_count):
snapshot_volume_id = sorted_snapshot_volume_id_list[i]
r = super().delete("/api/v1/volume/{0!s}"
.format(snapshot_volume_id))
if r.status_code != 202:
print("Failed to request the volume (ID: {0!s}) deletion."
.format(snapshot_volume_id))
print(r)
print(r.json())
break
# Wait for the job completion
job_id = r.json()["job_id"]
job_r = super().wait_job(job_id)
if job_r.json()["status"] != "Success":
print("Failed to delete the volume (ID: {0!s})."
.format(snapshot_volume_id))
print(job_r)
print(job_r.json())
continue
deleted_volume_id_list.append(snapshot_volume_id)
return deleted_volume_id_list
def snapopcplus_reduce_generations(self, volume_id, generation_count):
"""Reduce generations of SnapOPC+
Args:
volume_id (int): Volume ID of the source volume of SnapOPC+ to
reduce generations.
generation_count (int): Number of generations of SnapOPC+ after
reducing.
Returns:
bool: True if successful, False otherwise.
"""
# Get snapshots of SnapOPC+
snapshot_list = self.snapopcplus_get_snapshot_list(volume_id)
if snapshot_list == []:
return False
# Validate the generation_count
if len(snapshot_list) <= generation_count:
print("Specified generation_count is greater than or equal to the "
"current number of generations.")
print("Current number of generations:", len(snapshot_list))
print("Specified generation_count:", generation_count)
return False
# Sort SnapOPC+ by backup_time, since destination volume of SnapOPC+
# can be deleted oldest one.
sorted_snapshot_list = \
sorted(snapshot_list,
key=lambda x: datetime.strptime(
x["backup_time"], '%Y-%m-%dT%H:%M:%SZ'))
# Delete the snapshots
sorted_snapshot_volume_id_list = \
[int(i["volume_href"].split("/")[4]) for i in sorted_snapshot_list]
deleted_volume_id_list = \
self.snapopcplus_delete_volumes(
sorted_snapshot_volume_id_list,
len(sorted_snapshot_volume_id_list) - generation_count)
if (len(deleted_volume_id_list) !=
len(sorted_snapshot_volume_id_list) - generation_count):
print("Failed to delete some SnapOPC+ snapshots.")
else:
print("Deleted SnapOPC+ snapshots.")
print("Deleted volumes:", deleted_volume_id_list)
print("Current number of generations:",
len(sorted_snapshot_volume_id_list)
- len(deleted_volume_id_list))
return (len(deleted_volume_id_list) ==
len(sorted_snapshot_volume_id_list) - generation_count)
def main():
storage = EtdxApi(storage_url)
# Login
if not storage.login(storage_user_name, storage_password):
return False
# Initialize return value as True, since the procedure continues even if
# error occurs.
rtn = True
# Reduce generations of SnapOPC+
print("Reducing the number of generation of the SnapOPC+ of volume "
"(ID: {0!s}) to {1!s}.".format(volume_id, generation_count))
if not storage.snapopcplus_reduce_generations(volume_id, generation_count):
print("Failed to reduce the number of generations of the SnapOPC+.")
rtn = False
else:
print("Succeeded to reduce the number of generations of the SnapOPC+.")
# Get information of the SnapOPC+
r = storage.get("/api/v1/volume/{0!s}/copysession?backup_type=snapshot"
"&is_manual_snapshot=false".format(volume_id))
print(r)
print(r.json())
# Logout
storage.logout()
return rtn
if __name__ == '__main__':
if not main():
sys.exit(1)

