diff --git a/api-daemon/pvcapid/flaskapi.py b/api-daemon/pvcapid/flaskapi.py index f4e6dcd0..c40eccc5 100755 --- a/api-daemon/pvcapid/flaskapi.py +++ b/api-daemon/pvcapid/flaskapi.py @@ -2298,7 +2298,7 @@ class API_VM_Backup(Resource): @RequestParser( [ { - "name": "target_path", + "name": "backup_path", "required": True, "helptext": "A local filesystem path on the primary coordinator must be specified", }, @@ -2321,7 +2321,7 @@ class API_VM_Backup(Resource): - vm parameters: - in: query - name: target_path + name: backup_path type: string required: true description: A local filesystem path on the primary coordinator to store the backup @@ -2353,17 +2353,17 @@ class API_VM_Backup(Resource): type: object id: Message """ - target_path = reqargs.get("target_path", None) + backup_path = reqargs.get("backup_path", None) incremental_parent = reqargs.get("incremental_parent", None) retain_snapshot = bool(strtobool(reqargs.get("retain_snapshot", "false"))) return api_helper.vm_backup( - vm, target_path, incremental_parent, retain_snapshot + vm, backup_path, incremental_parent, retain_snapshot ) @RequestParser( [ { - "name": "target_path", + "name": "backup_path", "required": True, "helptext": "A local filesystem path on the primary coordinator must be specified", }, @@ -2383,7 +2383,7 @@ class API_VM_Backup(Resource): - vm parameters: - in: query - name: target_path + name: backup_path type: string required: true description: A local filesystem path on the primary coordinator where the backup is stored @@ -2409,11 +2409,9 @@ class API_VM_Backup(Resource): type: object id: Message """ - target_path = reqargs.get("target_path", None) + backup_path = reqargs.get("backup_path", None) backup_datestring = reqargs.get("backup_datestring", None) - return api_helper.vm_remove_backup( - vm, target_path, backup_datestring - ) + return api_helper.vm_remove_backup(vm, backup_path, backup_datestring) api.add_resource(API_VM_Backup, "/vm//backup") @@ -2424,7 +2422,7 @@ class API_VM_Restore(Resource): @RequestParser( [ { - "name": "target_path", + "name": "backup_path", "required": True, "helptext": "A local filesystem path on the primary coordinator must be specified", }, @@ -2448,7 +2446,7 @@ class API_VM_Restore(Resource): - vm parameters: - in: query - name: target_path + name: backup_path type: string required: true description: A local filesystem path on the primary coordinator where the backup is stored @@ -2480,11 +2478,11 @@ class API_VM_Restore(Resource): type: object id: Message """ - target_path = reqargs.get("target_path", None) + backup_path = reqargs.get("backup_path", None) backup_datestring = reqargs.get("backup_datestring", None) retain_snapshot = bool(strtobool(reqargs.get("retain_snapshot", "true"))) return api_helper.vm_restore( - vm, target_path, backup_datestring, retain_snapshot + vm, backup_path, backup_datestring, retain_snapshot ) diff --git a/api-daemon/pvcapid/helper.py b/api-daemon/pvcapid/helper.py index 24656f15..677faf89 100755 --- a/api-daemon/pvcapid/helper.py +++ b/api-daemon/pvcapid/helper.py @@ -474,7 +474,7 @@ def vm_define( def vm_backup( zkhandler, domain, - target_path, + backup_path, incremental_parent=None, retain_snapshot=False, ): @@ -484,7 +484,7 @@ def vm_backup( retflag, retdata = pvc_vm.backup_vm( zkhandler, domain, - target_path, + backup_path, incremental_parent, retain_snapshot, ) @@ -528,7 +528,7 @@ def vm_remove_backup( def vm_restore( zkhandler, domain, - target_path, + backup_path, datestring, retain_snapshot=False, ): @@ -538,7 +538,7 @@ def vm_restore( retflag, retdata = pvc_vm.restore_vm( zkhandler, domain, - target_path, + backup_path, datestring, retain_snapshot, ) diff --git a/client-cli/pvc/cli/cli.py b/client-cli/pvc/cli/cli.py index 93d6647c..4629d195 100644 --- a/client-cli/pvc/cli/cli.py +++ b/client-cli/pvc/cli/cli.py @@ -1611,7 +1611,7 @@ def cli_vm_backup(): @click.command(name="create", short_help="Create a backup of a virtual machine.") @connection_req @click.argument("domain") -@click.argument("target_path") +@click.argument("backup_path") @click.option( "-i", "--incremental", @@ -1627,11 +1627,11 @@ def cli_vm_backup(): default=False, help="Retain volume snapshot for future incremental use (full only).", ) -def cli_vm_backup_create(domain, target_path, incremental_parent, retain_snapshot): +def cli_vm_backup_create(domain, backup_path, incremental_parent, retain_snapshot): """ - Create a backup of virtual machine DOMAIN to TARGET_PATH on the cluster primary coordinator. DOMAIN may be a UUID or name. + Create a backup of virtual machine DOMAIN to BACKUP_PATH on the cluster primary coordinator. DOMAIN may be a UUID or name. - TARGET_PATH must be a valid absolute directory path on the cluster "primary" coordinator (see "pvc node list") allowing writes from the API daemon (normally running as "root"). The TARGET_PATH should be a large storage volume, ideally a remotely mounted filesystem (e.g. NFS, SSHFS, etc.) or non-Ceph-backed disk; PVC does not handle this path, that is up to the administrator to configure and manage. + BACKUP_PATH must be a valid absolute directory path on the cluster "primary" coordinator (see "pvc node list") allowing writes from the API daemon (normally running as "root"). The BACKUP_PATH should be a large storage volume, ideally a remotely mounted filesystem (e.g. NFS, SSHFS, etc.) or non-Ceph-backed disk; PVC does not handle this path, that is up to the administrator to configure and manage. The backup will export the VM configuration, metainfo, and a point-in-time snapshot of all attached RBD volumes, using a datestring formatted backup name (i.e. YYYYMMDDHHMMSS). @@ -1639,7 +1639,7 @@ def cli_vm_backup_create(domain, target_path, incremental_parent, retain_snapsho Incremental snapshots are possible by specifying the "-i"/"--incremental" option along with a source backup datestring. The snapshots from that source backup must have been retained using the "-r"/"--retain-snapshots" option. Retaining snapshots of incremental backups is not supported as incremental backups cannot be chained. - Full backup volume images are sparse-allocated, however it is recommended for safety to consider their maximum allocated size when allocated space for the TARGET_PATH. Incremental volume images are generally small but are dependent entirely on the rate of data change in each volume. + Full backup volume images are sparse-allocated, however it is recommended for safety to consider their maximum allocated size when allocated space for the BACKUP_PATH. Incremental volume images are generally small but are dependent entirely on the rate of data change in each volume. """ echo( @@ -1648,7 +1648,7 @@ def cli_vm_backup_create(domain, target_path, incremental_parent, retain_snapsho newline=False, ) retcode, retmsg = pvc.lib.vm.vm_backup( - CLI_CONFIG, domain, target_path, incremental_parent, retain_snapshot + CLI_CONFIG, domain, backup_path, incremental_parent, retain_snapshot ) if retcode: echo(CLI_CONFIG, "done.") @@ -1664,7 +1664,7 @@ def cli_vm_backup_create(domain, target_path, incremental_parent, retain_snapsho @connection_req @click.argument("domain") @click.argument("backup_datestring") -@click.argument("target_path") +@click.argument("backup_path") @click.option( "-r/-R", "--retain-snapshot/--remove-snapshot", @@ -1673,11 +1673,11 @@ def cli_vm_backup_create(domain, target_path, incremental_parent, retain_snapsho default=True, help="Retain or remove restored (parent, if incremental) snapshot.", ) -def cli_vm_backup_restore(domain, backup_datestring, target_path, retain_snapshot): +def cli_vm_backup_restore(domain, backup_datestring, backup_path, retain_snapshot): """ - Restore the backup BACKUP_DATESTRING of virtual machine DOMAIN stored in TARGET_PATH on the cluster primary coordinator. DOMAIN may be a UUID or name. + Restore the backup BACKUP_DATESTRING of virtual machine DOMAIN stored in BACKUP_PATH on the cluster primary coordinator. DOMAIN may be a UUID or name. - TARGET_PATH must be a valid absolute directory path on the cluster "primary" coordinator (see "pvc node list") allowing reads from the API daemon (normally running as "root"). The TARGET_PATH should be a large storage volume, ideally a remotely mounted filesystem (e.g. NFS, SSHFS, etc.) or non-Ceph-backed disk; PVC does not handle this path, that is up to the administrator to configure and manage. + BACKUP_PATH must be a valid absolute directory path on the cluster "primary" coordinator (see "pvc node list") allowing reads from the API daemon (normally running as "root"). The BACKUP_PATH should be a large storage volume, ideally a remotely mounted filesystem (e.g. NFS, SSHFS, etc.) or non-Ceph-backed disk; PVC does not handle this path, that is up to the administrator to configure and manage. The restore will import the VM configuration, metainfo, and the point-in-time snapshot of all attached RBD volumes. Incremental backups will be automatically handled. @@ -1694,7 +1694,7 @@ def cli_vm_backup_restore(domain, backup_datestring, target_path, retain_snapsho newline=False, ) retcode, retmsg = pvc.lib.vm.vm_restore( - CLI_CONFIG, domain, target_path, backup_datestring, retain_snapshot + CLI_CONFIG, domain, backup_path, backup_datestring, retain_snapshot ) if retcode: echo(CLI_CONFIG, "done.") @@ -1710,10 +1710,10 @@ def cli_vm_backup_restore(domain, backup_datestring, target_path, retain_snapsho @connection_req @click.argument("domain") @click.argument("backup_datestring") -@click.argument("target_path") -def cli_vm_backup_remove(domain, backup_datestring, target_path): +@click.argument("backup_path") +def cli_vm_backup_remove(domain, backup_datestring, backup_path): """ - Remove the backup BACKUP_DATESTRING, including snapshots, of virtual machine DOMAIN stored in TARGET_PATH on the cluster primary coordinator. DOMAIN may be a UUID or name. + Remove the backup BACKUP_DATESTRING, including snapshots, of virtual machine DOMAIN stored in BACKUP_PATH on the cluster primary coordinator. DOMAIN may be a UUID or name. WARNING: Removing an incremental parent will invalidate any existing incremental backups based on that backup. """ @@ -1724,7 +1724,7 @@ def cli_vm_backup_remove(domain, backup_datestring, target_path): newline=False, ) retcode, retmsg = pvc.lib.vm.vm_remove_backup( - CLI_CONFIG, domain, target_path, backup_datestring + CLI_CONFIG, domain, backup_path, backup_datestring ) if retcode: echo(CLI_CONFIG, "done.") diff --git a/client-cli/pvc/lib/vm.py b/client-cli/pvc/lib/vm.py index 459a01b7..e74c5e30 100644 --- a/client-cli/pvc/lib/vm.py +++ b/client-cli/pvc/lib/vm.py @@ -433,16 +433,16 @@ def vm_locks(config, vm): return retstatus, response.json().get("message", "") -def vm_backup(config, vm, target_path, incremental_parent=None, retain_snapshot=False): +def vm_backup(config, vm, backup_path, incremental_parent=None, retain_snapshot=False): """ Create a backup of {vm} and its volumes to a local primary coordinator filesystem path API endpoint: POST /vm/{vm}/backup - API arguments: target_path={target_path}, incremental_parent={incremental_parent}, retain_snapshot={retain_snapshot} + API arguments: backup_path={backup_path}, incremental_parent={incremental_parent}, retain_snapshot={retain_snapshot} API schema: {"message":"{data}"} """ params = { - "target_path": target_path, + "backup_path": backup_path, "incremental_parent": incremental_parent, "retain_snapshot": retain_snapshot, } @@ -454,19 +454,21 @@ def vm_backup(config, vm, target_path, incremental_parent=None, retain_snapshot= return True, response.json().get("message", "") -def vm_remove_backup(config, vm, target_path, backup_datestring): +def vm_remove_backup(config, vm, backup_path, backup_datestring): """ Remove a backup of {vm}, including snapshots, from a local primary coordinator filesystem path API endpoint: DELETE /vm/{vm}/backup - API arguments: target_path={target_path}, backup_datestring={backup_datestring} + API arguments: backup_path={backup_path}, backup_datestring={backup_datestring} API schema: {"message":"{data}"} """ params = { - "target_path": target_path, + "backup_path": backup_path, "backup_datestring": backup_datestring, } - response = call_api(config, "delete", "/vm/{vm}/backup".format(vm=vm), params=params) + response = call_api( + config, "delete", "/vm/{vm}/backup".format(vm=vm), params=params + ) if response.status_code != 200: return False, response.json().get("message", "") @@ -474,16 +476,16 @@ def vm_remove_backup(config, vm, target_path, backup_datestring): return True, response.json().get("message", "") -def vm_restore(config, vm, target_path, backup_datestring, retain_snapshot=False): +def vm_restore(config, vm, backup_path, backup_datestring, retain_snapshot=False): """ Restore a backup of {vm} and its volumes from a local primary coordinator filesystem path API endpoint: POST /vm/{vm}/restore - API arguments: target_path={target_path}, backup_datestring={backup_datestring}, retain_snapshot={retain_snapshot} + API arguments: backup_path={backup_path}, backup_datestring={backup_datestring}, retain_snapshot={retain_snapshot} API schema: {"message":"{data}"} """ params = { - "target_path": target_path, + "backup_path": backup_path, "backup_datestring": backup_datestring, "retain_snapshot": retain_snapshot, } diff --git a/daemon-common/vm.py b/daemon-common/vm.py index 75140c0f..b391691a 100644 --- a/daemon-common/vm.py +++ b/daemon-common/vm.py @@ -1309,7 +1309,7 @@ def get_list( def backup_vm( - zkhandler, domain, target_path, incremental_parent=None, retain_snapshot=False + zkhandler, domain, backup_path, incremental_parent=None, retain_snapshot=False ): tstart = time.time() @@ -1317,7 +1317,10 @@ def backup_vm( # 0. Validations # Disallow retaining snapshots with an incremental parent if incremental_parent is not None and retain_snapshot: - return False, 'ERROR: Retaining snapshots of incremental backups is not supported!' + return ( + False, + "ERROR: Retaining snapshots of incremental backups is not supported!", + ) # Validate that VM exists in cluster dom_uuid = getDomainUUID(zkhandler, domain) @@ -1325,15 +1328,15 @@ def backup_vm( return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain) # Validate that the target path is valid - if not re.match(r"^/", target_path): + if not re.match(r"^/", backup_path): return ( False, - f"ERROR: Target path {target_path} is not a valid absolute path on the primary coordinator!", + f"ERROR: Target path {backup_path} is not a valid absolute path on the primary coordinator!", ) - # Ensure that target_path (on this node) exists - if not os.path.isdir(target_path): - return False, f"ERROR: Target path {target_path} does not exist!" + # Ensure that backup_path (on this node) exists + if not os.path.isdir(backup_path): + return False, f"ERROR: Target path {backup_path} does not exist!" # 1. Get information about VM vm_detail = get_list(zkhandler, limit=dom_uuid, is_fuzzy=False)[1][0] @@ -1345,7 +1348,7 @@ def backup_vm( if disk["type"] != "rbd": continue - pool, volume = disk["name"].split('/') + pool, volume = disk["name"].split("/") retcode, retdata = ceph.get_list_volume(zkhandler, pool, volume, is_fuzzy=False) if not retcode or len(retdata) != 1: @@ -1353,7 +1356,10 @@ def backup_vm( retdata = "No volumes returned." elif len(retdata) > 1: retdata = "Multiple volumes returned." - return False, f"ERROR: Failed to get volume details for {pool}/{volume}: {retdata}" + return ( + False, + f"ERROR: Failed to get volume details for {pool}/{volume}: {retdata}", + ) try: size = retdata[0]["stats"]["size"] @@ -1396,8 +1402,8 @@ def backup_vm( snapshot_name = f"backup_{datestring}" # 4. Create destination directory - vm_target_root = f"{target_path}/{domain}" - vm_target_backup = f"{target_path}/{domain}/{domain}.{datestring}.pvcdisks" + vm_target_root = f"{backup_path}/{domain}" + vm_target_backup = f"{backup_path}/{domain}/{domain}.{datestring}.pvcdisks" if not os.path.isdir(vm_target_backup): try: os.makedirs(vm_target_backup) @@ -1463,7 +1469,10 @@ def backup_vm( "datestring": datestring, "incremental_parent": incremental_parent, "vm_detail": vm_detail, - "backup_files": [(f"{domain}.{datestring}.pvcdisks/{p}.{v}.{export_fileext}", s) for p, v, s in vm_volumes], + "backup_files": [ + (f"{domain}.{datestring}.pvcdisks/{p}.{v}.{export_fileext}", s) + for p, v, s in vm_volumes + ], } with open(f"{vm_target_root}/{domain}.{datestring}.pvcbackup", "w") as fh: jdump(vm_backup, fh) @@ -1488,18 +1497,24 @@ def backup_vm( retlines = list() if is_snapshot_remove_failed: - retlines.append(f"WARNING: Failed to remove snapshot(s) as requested for volume(s) {', '.join(which_snapshot_remove_failed)}: {', '.join(msg_snapshot_remove_failed)}") + retlines.append( + f"WARNING: Failed to remove snapshot(s) as requested for volume(s) {', '.join(which_snapshot_remove_failed)}: {', '.join(msg_snapshot_remove_failed)}" + ) myhostname = gethostname().split(".")[0] if retain_snapshot: - retlines.append(f"Successfully backed up VM '{domain}' ({backup_type}@{datestring}, snapshots retained) to '{myhostname}:{target_path}' in {ttot}s.") + retlines.append( + f"Successfully backed up VM '{domain}' ({backup_type}@{datestring}, snapshots retained) to '{myhostname}:{backup_path}' in {ttot}s." + ) else: - retlines.append(f"Successfully backed up VM '{domain}' ({backup_type}@{datestring}) to '{myhostname}:{target_path}' in {ttot}s.") + retlines.append( + f"Successfully backed up VM '{domain}' ({backup_type}@{datestring}) to '{myhostname}:{backup_path}' in {ttot}s." + ) - return True, '\n'.join(retlines) + return True, "\n".join(retlines) -def remove_backup(zkhandler, domain, source_path, datestring): +def remove_backup(zkhandler, domain, backup_path, datestring): tstart = time.time() # 0. Validation @@ -1509,27 +1524,29 @@ def remove_backup(zkhandler, domain, source_path, datestring): return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain) # Validate that the source path is valid - if not re.match(r"^/", source_path): + if not re.match(r"^/", backup_path): return ( False, - f"ERROR: Source path {source_path} is not a valid absolute path on the primary coordinator!", + f"ERROR: Source path {backup_path} is not a valid absolute path on the primary coordinator!", ) - # Ensure that source_path (on this node) exists - if not os.path.isdir(source_path): - return False, f"ERROR: Source path {source_path} does not exist!" + # Ensure that backup_path (on this node) exists + if not os.path.isdir(backup_path): + return False, f"ERROR: Source path {backup_path} does not exist!" # Ensure that domain path (on this node) exists - backup_source_path = f"{source_path}/{domain}" - if not os.path.isdir(backup_source_path): - return False, f"ERROR: Source VM path {backup_source_path} does not exist!" + backup_backup_path = f"{backup_path}/{domain}" + if not os.path.isdir(backup_backup_path): + return False, f"ERROR: Source VM path {backup_backup_path} does not exist!" # Ensure that the archives are present - backup_source_pvcbackup_file = f"{backup_source_path}/{domain}.{datestring}.pvcbackup" + backup_source_pvcbackup_file = ( + f"{backup_backup_path}/{domain}.{datestring}.pvcbackup" + ) if not os.path.isfile(backup_source_pvcbackup_file): return False, "ERROR: The specified source backup files do not exist!" - backup_source_pvcdisks_path = f"{backup_source_path}/{domain}.{datestring}.pvcdisks" + backup_source_pvcdisks_path = f"{backup_backup_path}/{domain}.{datestring}.pvcdisks" if not os.path.isdir(backup_source_pvcdisks_path): return False, "ERROR: The specified source backup files do not exist!" @@ -1544,8 +1561,8 @@ def remove_backup(zkhandler, domain, source_path, datestring): is_snapshot_remove_failed = False which_snapshot_remove_failed = list() msg_snapshot_remove_failed = list() - for volume_file, _ in backup_source_details.get('backup_files'): - pool, volume, _ = volume_file.split('/')[-1].split('.') + for volume_file, _ in backup_source_details.get("backup_files"): + pool, volume, _ = volume_file.split("/")[-1].split(".") snapshot = f"backup_{datestring}" retcode, retmsg = ceph.remove_snapshot(zkhandler, pool, volume, snapshot) if not retcode: @@ -1568,18 +1585,24 @@ def remove_backup(zkhandler, domain, source_path, datestring): retlines = list() if is_snapshot_remove_failed: - retlines.append(f"WARNING: Failed to remove snapshot(s) as requested for volume(s) {', '.join(which_snapshot_remove_failed)}: {', '.join(msg_snapshot_remove_failed)}") + retlines.append( + f"WARNING: Failed to remove snapshot(s) as requested for volume(s) {', '.join(which_snapshot_remove_failed)}: {', '.join(msg_snapshot_remove_failed)}" + ) if is_files_remove_failed: - retlines.append(f"WARNING: Failed to remove backup file(s) from {source_path}: {msg_files_remove_failed}") + retlines.append( + f"WARNING: Failed to remove backup file(s) from {backup_path}: {msg_files_remove_failed}" + ) myhostname = gethostname().split(".")[0] - retlines.append(f"Removed VM backup {datestring} for '{domain}' from '{myhostname}:{source_path}' in {ttot}s.") + retlines.append( + f"Removed VM backup {datestring} for '{domain}' from '{myhostname}:{backup_path}' in {ttot}s." + ) - return True, '\n'.join(retlines) + return True, "\n".join(retlines) -def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False): +def restore_vm(zkhandler, domain, backup_path, datestring, retain_snapshot=False): tstart = time.time() # 0. Validations @@ -1592,23 +1615,25 @@ def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False ) # Validate that the source path is valid - if not re.match(r"^/", source_path): + if not re.match(r"^/", backup_path): return ( False, - f"ERROR: Source path {source_path} is not a valid absolute path on the primary coordinator!", + f"ERROR: Source path {backup_path} is not a valid absolute path on the primary coordinator!", ) - # Ensure that source_path (on this node) exists - if not os.path.isdir(source_path): - return False, f"ERROR: Source path {source_path} does not exist!" + # Ensure that backup_path (on this node) exists + if not os.path.isdir(backup_path): + return False, f"ERROR: Source path {backup_path} does not exist!" # Ensure that domain path (on this node) exists - backup_source_path = f"{source_path}/{domain}" - if not os.path.isdir(backup_source_path): - return False, f"ERROR: Source VM path {backup_source_path} does not exist!" + backup_backup_path = f"{backup_path}/{domain}" + if not os.path.isdir(backup_backup_path): + return False, f"ERROR: Source VM path {backup_backup_path} does not exist!" # Ensure that the archives are present - backup_source_pvcbackup_file = f"{backup_source_path}/{domain}.{datestring}.pvcbackup" + backup_source_pvcbackup_file = ( + f"{backup_backup_path}/{domain}.{datestring}.pvcbackup" + ) if not os.path.isfile(backup_source_pvcbackup_file): return False, "ERROR: The specified source backup files do not exist!" @@ -1623,7 +1648,7 @@ def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False incremental_parent = backup_source_details.get("incremental_parent", None) if incremental_parent is not None: backup_source_parent_pvcbackup_file = ( - f"{backup_source_path}/{domain}.{incremental_parent}.pvcbackup" + f"{backup_backup_path}/{domain}.{incremental_parent}.pvcbackup" ) if not os.path.isfile(backup_source_parent_pvcbackup_file): return ( @@ -1635,7 +1660,10 @@ def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False with open(backup_source_parent_pvcbackup_file) as fh: backup_source_parent_details = jload(fh) except Exception as e: - return False, f"ERROR: Failed to read source incremental parent backup details: {e}" + return ( + False, + f"ERROR: Failed to read source incremental parent backup details: {e}", + ) # 2. Import VM config and metadata in provision state try: @@ -1661,12 +1689,20 @@ def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False which_snapshot_remove_failed = list() msg_snapshot_remove_failed = list() if incremental_parent is not None: - for volume_file, volume_size in backup_source_details.get('backup_files'): - pool, volume, _ = volume_file.split('/')[-1].split('.') + for volume_file, volume_size in backup_source_details.get("backup_files"): + pool, volume, _ = volume_file.split("/")[-1].split(".") try: - parent_volume_file = [f[0] for f in backup_source_parent_details.get('backup_files') if f[0].split('/')[-1].replace('.rbdimg', '') == volume_file.split('/')[-1].replace('.rbddiff', '')][0] + parent_volume_file = [ + f[0] + for f in backup_source_parent_details.get("backup_files") + if f[0].split("/")[-1].replace(".rbdimg", "") + == volume_file.split("/")[-1].replace(".rbddiff", "") + ][0] except Exception as e: - return False, f"ERROR: Failed to find parent volume for volume {pool}/{volume}; backup may be corrupt or invalid: {e}" + return ( + False, + f"ERROR: Failed to find parent volume for volume {pool}/{volume}; backup may be corrupt or invalid: {e}", + ) # First we create the expected volumes then clean them up # This process is a bit of a hack because rbd import does not expect an existing volume, @@ -1681,27 +1717,45 @@ def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False f"rbd remove {pool}/{volume}" ) if retcode: - return False, f"ERROR: Failed to remove temporary RBD volume '{pool}/{volume}': {stderr}" + return ( + False, + f"ERROR: Failed to remove temporary RBD volume '{pool}/{volume}': {stderr}", + ) # Next we import the parent images retcode, stdout, stderr = common.run_os_command( - f"rbd import --export-format 2 --dest-pool {pool} {source_path}/{domain}/{parent_volume_file} {volume}" + f"rbd import --export-format 2 --dest-pool {pool} {backup_path}/{domain}/{parent_volume_file} {volume}" ) if retcode: - return False, f"ERROR: Failed to import parent backup image {parent_volume_file}: {stderr}" + return ( + False, + f"ERROR: Failed to import parent backup image {parent_volume_file}: {stderr}", + ) # Then we import the incremental diffs retcode, stdout, stderr = common.run_os_command( - f"rbd import-diff {source_path}/{domain}/{volume_file} {pool}/{volume}" + f"rbd import-diff {backup_path}/{domain}/{volume_file} {pool}/{volume}" ) if retcode: - return False, f"ERROR: Failed to import incremental backup image {volume_file}: {stderr}" + return ( + False, + f"ERROR: Failed to import incremental backup image {volume_file}: {stderr}", + ) # Finally we remove the parent and child snapshots (no longer required required) if retain_snapshot: - retcode, retmsg = ceph.add_snapshot(zkhandler, pool, volume, f"backup_{incremental_parent}", zk_only=True) + retcode, retmsg = ceph.add_snapshot( + zkhandler, + pool, + volume, + f"backup_{incremental_parent}", + zk_only=True, + ) if not retcode: - return False, f"ERROR: Failed to add imported image snapshot for {parent_volume_file}: {retmsg}" + return ( + False, + f"ERROR: Failed to add imported image snapshot for {parent_volume_file}: {retmsg}", + ) else: retcode, stdout, stderr = common.run_os_command( f"rbd snap rm {pool}/{volume}@backup_{incremental_parent}" @@ -1719,8 +1773,8 @@ def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False msg_snapshot_remove_failed.append(retmsg) else: - for volume_file, volume_size in backup_source_details.get('backup_files'): - pool, volume, _ = volume_file.split('/')[-1].split('.') + for volume_file, volume_size in backup_source_details.get("backup_files"): + pool, volume, _ = volume_file.split("/")[-1].split(".") # First we create the expected volumes then clean them up # This process is a bit of a hack because rbd import does not expect an existing volume, @@ -1735,26 +1789,44 @@ def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False f"rbd remove {pool}/{volume}" ) if retcode: - return False, f"ERROR: Failed to remove temporary RBD volume '{pool}/{volume}': {stderr}" + return ( + False, + f"ERROR: Failed to remove temporary RBD volume '{pool}/{volume}': {stderr}", + ) # Then we perform the actual import retcode, stdout, stderr = common.run_os_command( - f"rbd import --export-format 2 --dest-pool {pool} {source_path}/{domain}/{volume_file} {volume}" + f"rbd import --export-format 2 --dest-pool {pool} {backup_path}/{domain}/{volume_file} {volume}" ) if retcode: - return False, f"ERROR: Failed to import backup image {volume_file}: {stderr}" + return ( + False, + f"ERROR: Failed to import backup image {volume_file}: {stderr}", + ) # Finally we remove the source snapshot (not required) if retain_snapshot: - retcode, retmsg = ceph.add_snapshot(zkhandler, pool, volume, f"backup_{incremental_parent}", zk_only=True) + retcode, retmsg = ceph.add_snapshot( + zkhandler, + pool, + volume, + f"backup_{incremental_parent}", + zk_only=True, + ) if not retcode: - return False, f"ERROR: Failed to add imported image snapshot for {volume_file}: {retmsg}" + return ( + False, + f"ERROR: Failed to add imported image snapshot for {volume_file}: {retmsg}", + ) else: retcode, stdout, stderr = common.run_os_command( f"rbd snap rm {pool}/{volume}@backup_{datestring}" ) if retcode: - return False, f"ERROR: Failed to remove imported image snapshot for {volume_file}: {stderr}" + return ( + False, + f"ERROR: Failed to remove imported image snapshot for {volume_file}: {stderr}", + ) # 5. Start VM retcode, retmsg = start_vm(zkhandler, domain) @@ -1766,9 +1838,13 @@ def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False retlines = list() if is_snapshot_remove_failed: - retlines.append(f"WARNING: Failed to remove hanging snapshot(s) as requested for volume(s) {', '.join(which_snapshot_remove_failed)}: {', '.join(msg_snapshot_remove_failed)}") + retlines.append( + f"WARNING: Failed to remove hanging snapshot(s) as requested for volume(s) {', '.join(which_snapshot_remove_failed)}: {', '.join(msg_snapshot_remove_failed)}" + ) myhostname = gethostname().split(".")[0] - retlines.append(f"Successfully restored VM backup {datestring} for '{domain}' from '{myhostname}:{source_path}' in {ttot}s.") + retlines.append( + f"Successfully restored VM backup {datestring} for '{domain}' from '{myhostname}:{backup_path}' in {ttot}s." + ) - return True, '\n'.join(retlines) + return True, "\n".join(retlines)