Backup Snapshot - Py
Backup Snapshot - Py
/usr/bin/python
#
# Copyright (c) 2017 Nutanix Inc. All rights reserved.
#
# Disclaimer:
# Usage of this tool must be under guidance of Nutanix Support or an
# authorised partner.
# Summary:
# This script provides utility methods for dealing with snapshots
# created by any backup software. This tool is meant to be used to clean up
# such snapshots that have been left behind, for whatever reason.
# Version of the script: Version 2
# Compatible software version(s): AOS 5.1.1.1 and above with AHV hypervisors.
# Brief syntax usage:
# Example: 'backup_snapshots --list_for_vm <VM UUID> <user_name>';
# Please run 'backup_snapshots -h' for usage details.
# Caveats:
# Command line options for volume group snapshots such as '--list_for_vg',
# '--delete_vg_snapshot', and '--delete_for_vg' do not work in software
# versions prior to AOS 5.6.2, because volume_group_snapshots APIs are first
# introduced in 5.6.2.
#
# Change log
# ----------------------------------------------------------------------------
# Date Ticket Addressed
# ============================================================================
# 2019/11/01 bug fixed for delete snapshots.
# 2020/03/31 VG snapshot support, minor bug fixes.
# ----------------------------------------------------------------------------
#
import env
import argparse
import datetime
import getpass
import json
import operator
import requests
import time
cluster_version = version_utils.get_cluster_version(numeric=True)
if (version_utils.compare_versions(cluster_version, "5.11") >= 0):
from insights_interface.cpdb_interface.cpdb_query import (ALL, EQ, COL, STR)
else:
from util.db.cpdb_query import (ALL, EQ, COL, STR)
class Password(argparse.Action):
def __call__(self, parser, namespace, values, option_string):
if values is None:
values = getpass.getpass()
setattr(namespace, self.dest, values)
class Snapshot(object):
"""
The class represents a snapshot and its state. The snapshot could be
a proper VM / VG snapshot, a VM / VG snapshot missing cerebro backend
snapshot or a stale snapshot with only backend cerebro snapshot.
"""
def __init__(self, pd_name, cerebro_snap_uuid, ctime):
self._pd_name = pd_name
self._cerebro_snap_uuid = cerebro_snap_uuid
self._ctime = ctime
self._vm_uuid = None
self._vg_uuid = None
self._snap_uuid = None
@property
def pd_name(self):
return self._pd_name
@property
def cerebro_snap_uuid(self):
return self._cerebro_snap_uuid
@property
def ctime(self):
return self._ctime
@property
def vm_uuid(self):
return self._vm_uuid
@vm_uuid.setter
def vm_uuid(self, value):
self._vm_uuid = value
@property
def vg_uuid(self):
return self._vg_uuid
@vg_uuid.setter
def vg_uuid(self, value):
self._vg_uuid = value
@property
def snap_uuid(self):
return self._snap_uuid
@snap_uuid.setter
def snap_uuid(self, value):
self._snap_uuid = value
@property
def complete(self):
return self.snap_uuid and self.ctime and self.pd_name
namedtuple("Snapshot",
"pd_name cerebro_snap_uuid ctime vm_uuid snap_uuid")
def print_table_of_snapshots(snapshots):
"""
The function prints a well-fromated table for snapshots.
Args:
snapshots(Iterable list of objects with type 'Snapshot'):
list of snapshots to print.
"""
# Sort the snapshots.
snapshots = sorted(snapshots, key=operator.attrgetter('ctime'),
reverse=True)
snapshots = sorted(snapshots, key=operator.attrgetter('vg_uuid'))
snapshots = sorted(snapshots, key=operator.attrgetter('pd_name'),
reverse=True)
snapshots = sorted(snapshots, key=operator.attrgetter('complete'),
reverse=True)
print_row(header)
for ss in snapshots:
ctime = datetime.datetime.fromtimestamp(
ss.ctime / (1000 * 1000)).strftime(
'%Y-%m-%d %H:%M') if ss.ctime else None
entity_type = None
entity_uuid = None
if ss.vm_uuid:
entity_type = "VM"
entity_uuid = ss.vm_uuid
elif ss.vg_uuid:
entity_type="VG"
entity_uuid=ss.vg_uuid
print_row([ss.snap_uuid, entity_type, entity_uuid, ctime,
ss.cerebro_snap_uuid, ss.pd_name])
class BackupSnapshotClient(object):
"""
A simple python client to manage the snapshots created by backup software.
"""
def __query_all_pd_names(self):
"""
Returns a set of all async PDs in the cluster including the name/UUID
of the system protection domain.
"""
cerebro_client = CerebroInterfaceTool()
pd_names = set()
arg = ListProtectionDomainsArg()
ret = cerebro_client.list_protection_domains(arg)
for pd_name in ret.protection_domain_name:
pd_names.add(PD(pd_name, False))
return pd_names
def __get_vm_snapshots_from_intent_db(self):
"""
Queries the Aplos Intent Engine database for all the scoped snapshots whose
intent is in the database. This is done to get the snapshot ID seen by the
outside world. Returns a map of the form:
<key=Cerebro snapshot UUID, Value=tuple(VM UUID, external snapshot UUID)>.
return self.__get_snapshots_from_intent_db(
snapshot_kind_name="vm_snapshot",
get_entity_uuid_from_spec=get_entity_uuid_from_spec)
def __get_vg_snapshots_from_intent_db(self):
"""
Queries the Aplos IntentEngine database for all the VG snapshots whose
intent is in the database.
The function returns a map of the form:
<key=Cerebro snapshot UUID, Value=tuple(VG UUID, external snapshot UUID)>.
The map includes only the snapshot in kComplete state.
"""
def get_entity_uuid_from_spec(spec_json):
"""
Fetch the VG UUID from VG snapshot intent spec in Aplos.
"""
return spec_json["resources"]["volume_group_reference"]["uuid"]
return self.__get_snapshots_from_intent_db(
snapshot_kind_name="volume_group_snapshot",
get_entity_uuid_from_spec=get_entity_uuid_from_spec)
return snapshots
The function returns the above set by joining set of the backend snapshot
and set of entries from Aplos Intent Engine database using
cerebro backend snapshot UUID.
Returns:
Set of objects with type 'Snapshot'.
"""
scoped_snaps_from_cerebro = set()
if pd_name:
scoped_snaps_from_cerebro.update(
self.__query_pd_scoped_snapshots(pd_name))
else:
pds = self.__query_all_pd_names()
for pd in pds:
scoped_snaps_from_cerebro.update(
self.__query_pd_scoped_snapshots(pd.name))
vm_snaps_from_aplos = self.__get_vm_snapshots_from_intent_db()
vg_snaps_from_aplos = self.__get_vg_snapshots_from_intent_db()
cerebro_snapshot_uuids = set()
for snap in scoped_snaps_from_cerebro:
cerebro_snap_uuid = snap.cerebro_snap_uuid
cerebro_snapshot_uuids.add(cerebro_snap_uuid)
# Populate information for complete VM snapshots.
if cerebro_snap_uuid in vm_snaps_from_aplos:
snap.vm_uuid = vm_snaps_from_aplos[cerebro_snap_uuid][0]
snap.snap_uuid = vm_snaps_from_aplos[cerebro_snap_uuid][1]
# In case when there are dangling intent specs in Aplos (possible because
# cerebro snapshots can be manually deleted by mistake), we also
# need to list them and indicate that the intent specs are left behind.
# We should only do this when we are trying to list all scoped snapshots
# in the system. Otherwise, we don't have enough information to determine
# which cerebro snapshots are lacking intent specs in Aplos.
if not pd_name:
return scoped_snaps_from_cerebro
scoped_snaps_to_print = set()
for ss in scoped_snaps:
if ss.vm_uuid == vm_uuid:
scoped_snaps_to_print.add(ss)
backup_snaps = self.__list_backup_snapshots()
if not backup_snaps:
print("There is no VG backup snapshot to delete")
return
def delete_all_snapshots(self):
"""
Deletes all the backup snapshots in the cluster.
"""
scoped_snaps = self.__list_backup_snapshots()
if not scoped_snaps:
print ("There are no backup snapshots to delete")
return
for ss in scoped_snaps:
if ss.snap_uuid:
# The snapshot has a corresponding intent spec. Hence, we need to
# delete through intent gateway.
if ss.vm_uuid:
self.delete_vm_snapshot(ss.snap_uuid)
else:
self.delete_vg_snapshot(ss.snap_uuid)
else:
# The snapshot does not have a corresponding intent spec. Delete
# the snapshot through Cerebro instead.
self.__delete_scoped_snap_through_cerebro(ss.cerebro_snap_uuid)
vg_snaps_from_aplos = self.__get_vg_snapshots_from_intent_db()
if cerebro_snap_uuid in vg_snaps_from_aplos:
print ("Cannot delete the cerebro snapshot since it has Aplos metadata")
return
self.__delete_scoped_snap_through_cerebro(cerebro_snap_uuid)
print ("Deleted snapshot through cerbro : %s" % cerebro_snap_uuid)
# Remove the complete snapshots that are older than specified num of days.
print("Deleting following snapshots that are older than %s days" %
num_of_days)
print_table_of_snapshots(scoped_snaps_to_delete)
for ss in scoped_snaps_to_delete:
if ss.vm_uuid:
self.delete_vm_snapshot(ss.snap_uuid)
else:
self.delete_vg_snapshot(ss.snap_uuid)
def list_stale_snapshots_missing_snapshot_objects(self):
"""
Lists all stale snapshots that has proper entry in IDF but no
snapshot data, i.e, no cerebro backend snapshot.
"""
# List all backup snapshots in the cluster.
snap_list = self.__list_backup_snapshots()
if __name__ == "__main__":
parser = argparse.ArgumentParser("backup_snapshots")
parser.add_argument("user", help="Username of the account")
parser.add_argument("password", action=Password, nargs='?',
help="Password of the account. Do not enter on the "
"command line.")
group = parser.add_mutually_exclusive_group()
group.add_argument("--list_for_pd",
help="List all the backup snapshots of the PD",
metavar="<Name of the PD>", type=str)
group.add_argument("--list_for_vm",
help="List all the backup snapshots of the VM",
metavar="<UUID of the VM>",
type=str)
group.add_argument("--list_for_vg",
help="List all the backup snapshots of the VG",
metavar="<UUID of the VG>",
type=str)
group.add_argument("--list_all",
help="List all backup snapshots in the cluster",
action="store_true")
group.add_argument("--list_stale_snapshots_missing_snapshot_objects",
help="Lists all stale snapshots that its Cerebro "
"backend snapshot is missing",
action="store_true")
group.add_argument("--delete_vm_snapshot",
help="Delete the VM snapshot specified by UUID",
metavar="<VM Snapshot UUID>",
type=str)
group.add_argument("--delete_vg_snapshot",
help="Delete the VG snapshot specified by UUID",
metavar="<VG Snapshot UUID>",
type=str)
group.add_argument("--delete_for_vm",
help="Delete the snapshots of the virtual machine "
"specified by UUID",
metavar="<UUID of the VM>",
type=str)
group.add_argument("--delete_for_vg",
help="Delete the snapshots of the volume group "
"specified by UUID",
metavar="<UUID of the VG>",
type=str)
group.add_argument("--delete_for_pd",
help="Delete the snapshots of the protection domain "
"specified by pd_name",
metavar="<Name of the PD>")
group.add_argument("--delete_all",
help="Delete all backup snapshots in the cluster",
action="store_true")
group.add_argument("--delete_snapshot_from_cerebro",
help="Delete the snapshot specified by cerebro UUID",
metavar="<Cerebro Snapshot UUID>",
type=str)
group.add_argument("--delete_snapshot_older_than",
help="Delete the snapshot older than given number of "
"days",
metavar="<Number of days>",
type=int)
args = parser.parse_args()
if args.list_all:
client.print_scoped_snaps_in_pd(None)
if args.list_for_pd:
client.print_scoped_snaps_in_pd(args.list_for_pd)
if args.list_for_vm:
client.print_vm_scoped_snaps(args.list_for_vm)
if args.list_for_vg:
client.print_vg_scoped_snaps(args.list_for_vg)
if args.list_stale_snapshots_missing_snapshot_objects:
client.list_stale_snapshots_missing_snapshot_objects()
if args.delete_vm_snapshot:
client.delete_vm_snapshot(args.delete_vm_snapshot)
if args.delete_vg_snapshot:
client.delete_vg_snapshot(args.delete_vg_snapshot)
if args.delete_for_vm:
client.delete_snapshots_for_vm(args.delete_for_vm)
if args.delete_for_vg:
client.delete_snapshots_for_vg(args.delete_for_vg)
if args.delete_for_pd:
client.delete_snapshots_for_pd(args.delete_for_pd)
if args.delete_all:
client.delete_all_snapshots()
if args.delete_snapshot_from_cerebro:
client.delete_scoped_snap_through_cerebro(
args.delete_snapshot_from_cerebro)
if args.delete_snapshot_older_than:
client.delete_snapshot_older_than_given_num_of_days(
args.delete_snapshot_older_than)