Handle snapshots on restore and provide options
Also rename the retain option to remove superfluous plural.
This commit is contained in:
parent
0769f1ea52
commit
55ca131c2c
|
@ -2307,7 +2307,7 @@ class API_VM_Backup(Resource):
|
||||||
"required": False,
|
"required": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "retain_snapshots",
|
"name": "retain_snapshot",
|
||||||
"required": False,
|
"required": False,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -2331,11 +2331,11 @@ class API_VM_Backup(Resource):
|
||||||
required: false
|
required: false
|
||||||
description: A previous backup datestamp to use as an incremental parent; if unspecified a full backup is taken
|
description: A previous backup datestamp to use as an incremental parent; if unspecified a full backup is taken
|
||||||
- in: query
|
- in: query
|
||||||
name: retain_snapshots
|
name: retain_snapshot
|
||||||
type: boolean
|
type: boolean
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
description: Whether or not to retain this backup's volume snapshots to use as a future incremental parent
|
description: Whether or not to retain this backup's volume snapshots to use as a future incremental parent; full backups only
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: OK
|
description: OK
|
||||||
|
@ -2355,9 +2355,9 @@ class API_VM_Backup(Resource):
|
||||||
"""
|
"""
|
||||||
target_path = reqargs.get("target_path", None)
|
target_path = reqargs.get("target_path", None)
|
||||||
incremental_parent = reqargs.get("incremental_parent", None)
|
incremental_parent = reqargs.get("incremental_parent", None)
|
||||||
retain_snapshots = bool(strtobool(reqargs.get("retain_snapshots", "false")))
|
retain_snapshot = bool(strtobool(reqargs.get("retain_snapshot", "false")))
|
||||||
return api_helper.vm_backup(
|
return api_helper.vm_backup(
|
||||||
vm, target_path, incremental_parent, retain_snapshots
|
vm, target_path, incremental_parent, retain_snapshot
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2377,7 +2377,11 @@ class API_VM_Restore(Resource):
|
||||||
"name": "backup_datestring",
|
"name": "backup_datestring",
|
||||||
"required": True,
|
"required": True,
|
||||||
"helptext": "A backup datestring must be specified",
|
"helptext": "A backup datestring must be specified",
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"name": "retain_snapshot",
|
||||||
|
"required": False,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@Authenticator
|
@Authenticator
|
||||||
|
@ -2398,6 +2402,12 @@ class API_VM_Restore(Resource):
|
||||||
type: string
|
type: string
|
||||||
required: true
|
required: true
|
||||||
description: The backup datestring identifier (e.g. 20230102030405)
|
description: The backup datestring identifier (e.g. 20230102030405)
|
||||||
|
- in: query
|
||||||
|
name: retain_snapshot
|
||||||
|
type: boolean
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
description: Whether or not to retain the (parent, if incremental) volume snapshot after restore
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: OK
|
description: OK
|
||||||
|
@ -2417,8 +2427,9 @@ class API_VM_Restore(Resource):
|
||||||
"""
|
"""
|
||||||
target_path = reqargs.get("target_path", None)
|
target_path = reqargs.get("target_path", None)
|
||||||
backup_datestring = reqargs.get("backup_datestring", None)
|
backup_datestring = reqargs.get("backup_datestring", None)
|
||||||
|
retain_snapshot = bool(strtobool(reqargs.get("retain_snapshot", "true")))
|
||||||
return api_helper.vm_restore(
|
return api_helper.vm_restore(
|
||||||
vm, target_path, backup_datestring
|
vm, target_path, backup_datestring, retain_snapshot
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -476,7 +476,7 @@ def vm_backup(
|
||||||
domain,
|
domain,
|
||||||
target_path,
|
target_path,
|
||||||
incremental_parent=None,
|
incremental_parent=None,
|
||||||
retain_snapshots=False,
|
retain_snapshot=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Back up a VM to a local (primary coordinator) filesystem path.
|
Back up a VM to a local (primary coordinator) filesystem path.
|
||||||
|
@ -486,7 +486,7 @@ def vm_backup(
|
||||||
domain,
|
domain,
|
||||||
target_path,
|
target_path,
|
||||||
incremental_parent,
|
incremental_parent,
|
||||||
retain_snapshots,
|
retain_snapshot,
|
||||||
)
|
)
|
||||||
|
|
||||||
if retflag:
|
if retflag:
|
||||||
|
@ -504,6 +504,7 @@ def vm_restore(
|
||||||
domain,
|
domain,
|
||||||
target_path,
|
target_path,
|
||||||
datestring,
|
datestring,
|
||||||
|
retain_snapshot=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Restore a VM from a local (primary coordinator) filesystem path.
|
Restore a VM from a local (primary coordinator) filesystem path.
|
||||||
|
@ -513,6 +514,7 @@ def vm_restore(
|
||||||
domain,
|
domain,
|
||||||
target_path,
|
target_path,
|
||||||
datestring,
|
datestring,
|
||||||
|
retain_snapshot,
|
||||||
)
|
)
|
||||||
|
|
||||||
if retflag:
|
if retflag:
|
||||||
|
|
|
@ -1606,13 +1606,13 @@ def cli_vm_flush_locks(domain):
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-r",
|
"-r",
|
||||||
"--retain-snapshots",
|
"--retain-snapshot",
|
||||||
"retain_snapshots",
|
"retain_snapshot",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=False,
|
default=False,
|
||||||
help="Retain volume snapshots for future incremental use.",
|
help="Retain volume snapshot for future incremental use (full only).",
|
||||||
)
|
)
|
||||||
def cli_vm_backup(domain, target_path, incremental_parent, retain_snapshots):
|
def cli_vm_backup(domain, target_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 TARGET_PATH on the cluster primary coordinator. DOMAIN may be a UUID or name.
|
||||||
|
|
||||||
|
@ -1622,7 +1622,7 @@ def cli_vm_backup(domain, target_path, incremental_parent, retain_snapshots):
|
||||||
|
|
||||||
The virtual machine DOMAIN may be running, and due to snapshots the backup should be crash-consistent, but will be in an unclean state and this must be considered when restoring from backups.
|
The virtual machine DOMAIN may be running, and due to snapshots the backup should be crash-consistent, but will be in an unclean state and this must be considered when restoring from backups.
|
||||||
|
|
||||||
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. Arbitrary snapshots, assuming they are valid for all attached RBD volumes, may also be used, as long as they are prefixed with "backup_". Retaining snapshots of incremental backups is supported, though it is not recommended to "chain" incremental backups in this way as it can make managing restores more difficult.
|
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 TARGET_PATH. Incremental volume images are generally small but are dependent entirely on the rate of data change in each volume.
|
||||||
"""
|
"""
|
||||||
|
@ -1633,7 +1633,7 @@ def cli_vm_backup(domain, target_path, incremental_parent, retain_snapshots):
|
||||||
newline=False,
|
newline=False,
|
||||||
)
|
)
|
||||||
retcode, retmsg = pvc.lib.vm.vm_backup(
|
retcode, retmsg = pvc.lib.vm.vm_backup(
|
||||||
CLI_CONFIG, domain, target_path, incremental_parent, retain_snapshots
|
CLI_CONFIG, domain, target_path, incremental_parent, retain_snapshot
|
||||||
)
|
)
|
||||||
if retcode:
|
if retcode:
|
||||||
echo(CLI_CONFIG, "done.")
|
echo(CLI_CONFIG, "done.")
|
||||||
|
@ -1650,7 +1650,15 @@ def cli_vm_backup(domain, target_path, incremental_parent, retain_snapshots):
|
||||||
@click.argument("domain")
|
@click.argument("domain")
|
||||||
@click.argument("backup_datestring")
|
@click.argument("backup_datestring")
|
||||||
@click.argument("target_path")
|
@click.argument("target_path")
|
||||||
def cli_vm_restore(domain, backup_datestring, target_path):
|
@click.option(
|
||||||
|
"-r/-R",
|
||||||
|
"--retain-snapshot/--remove-snapshot",
|
||||||
|
"retain_snapshot",
|
||||||
|
is_flag=True,
|
||||||
|
default=True,
|
||||||
|
help="Retain or remove restored (parent, if incremental) snapshot.",
|
||||||
|
)
|
||||||
|
def cli_vm_restore(domain, backup_datestring, target_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 TARGET_PATH on the cluster primary coordinator. DOMAIN may be a UUID or name.
|
||||||
|
|
||||||
|
@ -1659,6 +1667,10 @@ def cli_vm_restore(domain, backup_datestring, target_path):
|
||||||
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.
|
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.
|
||||||
|
|
||||||
A VM named DOMAIN must not exist; if the VM already exists, it must be removed before restoring. Renaming is not sufficient as the UUID will remain the same.
|
A VM named DOMAIN must not exist; if the VM already exists, it must be removed before restoring. Renaming is not sufficient as the UUID will remain the same.
|
||||||
|
|
||||||
|
If the "-r"/"--retain-snapshot" option is specified (the default), for incremental restores, only the parent snapshot is kept; for full restores, the restored snapshot is kept. If the "-R"/"--remove-snapshot" option is specified, the imported snapshot is removed.
|
||||||
|
|
||||||
|
WARNING: The "-R"/"--remove-snapshot" option will invalidate any existing incremental backups based on the same incremental parent for the restored VM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
echo(
|
echo(
|
||||||
|
@ -1667,7 +1679,7 @@ def cli_vm_restore(domain, backup_datestring, target_path):
|
||||||
newline=False,
|
newline=False,
|
||||||
)
|
)
|
||||||
retcode, retmsg = pvc.lib.vm.vm_restore(
|
retcode, retmsg = pvc.lib.vm.vm_restore(
|
||||||
CLI_CONFIG, domain, target_path, backup_datestring
|
CLI_CONFIG, domain, target_path, backup_datestring, retain_snapshot
|
||||||
)
|
)
|
||||||
if retcode:
|
if retcode:
|
||||||
echo(CLI_CONFIG, "done.")
|
echo(CLI_CONFIG, "done.")
|
||||||
|
|
|
@ -433,18 +433,18 @@ def vm_locks(config, vm):
|
||||||
return retstatus, response.json().get("message", "")
|
return retstatus, response.json().get("message", "")
|
||||||
|
|
||||||
|
|
||||||
def vm_backup(config, vm, target_path, incremental_parent=None, retain_snapshots=False):
|
def vm_backup(config, vm, target_path, incremental_parent=None, retain_snapshot=False):
|
||||||
"""
|
"""
|
||||||
Create a backup of {vm} and its volumes to a local primary coordinator filesystem path
|
Create a backup of {vm} and its volumes to a local primary coordinator filesystem path
|
||||||
|
|
||||||
API endpoint: POST /vm/{vm}/backup
|
API endpoint: POST /vm/{vm}/backup
|
||||||
API arguments: target_path={target_path}, incremental_parent={incremental_parent}, retain_snapshots={retain_snapshots}
|
API arguments: target_path={target_path}, incremental_parent={incremental_parent}, retain_snapshot={retain_snapshot}
|
||||||
API schema: {"message":"{data}"}
|
API schema: {"message":"{data}"}
|
||||||
"""
|
"""
|
||||||
params = {
|
params = {
|
||||||
"target_path": target_path,
|
"target_path": target_path,
|
||||||
"incremental_parent": incremental_parent,
|
"incremental_parent": incremental_parent,
|
||||||
"retain_snapshots": retain_snapshots,
|
"retain_snapshot": retain_snapshot,
|
||||||
}
|
}
|
||||||
response = call_api(config, "post", "/vm/{vm}/backup".format(vm=vm), params=params)
|
response = call_api(config, "post", "/vm/{vm}/backup".format(vm=vm), params=params)
|
||||||
|
|
||||||
|
@ -454,17 +454,18 @@ def vm_backup(config, vm, target_path, incremental_parent=None, retain_snapshots
|
||||||
return True, response.json().get("message", "")
|
return True, response.json().get("message", "")
|
||||||
|
|
||||||
|
|
||||||
def vm_restore(config, vm, target_path, backup_datestring):
|
def vm_restore(config, vm, target_path, backup_datestring, retain_snapshot=False):
|
||||||
"""
|
"""
|
||||||
Restore a backup of {vm} and its volumes from a local primary coordinator filesystem path
|
Restore a backup of {vm} and its volumes from a local primary coordinator filesystem path
|
||||||
|
|
||||||
API endpoint: POST /vm/{vm}/restore
|
API endpoint: POST /vm/{vm}/restore
|
||||||
API arguments: target_path={target_path}, backup_datestring={backup_datestring}
|
API arguments: target_path={target_path}, backup_datestring={backup_datestring}, retain_snapshot={retain_snapshot}
|
||||||
API schema: {"message":"{data}"}
|
API schema: {"message":"{data}"}
|
||||||
"""
|
"""
|
||||||
params = {
|
params = {
|
||||||
"target_path": target_path,
|
"target_path": target_path,
|
||||||
"backup_datestring": backup_datestring,
|
"backup_datestring": backup_datestring,
|
||||||
|
"retain_snapshot": retain_snapshot,
|
||||||
}
|
}
|
||||||
response = call_api(config, "post", "/vm/{vm}/restore".format(vm=vm), params=params)
|
response = call_api(config, "post", "/vm/{vm}/restore".format(vm=vm), params=params)
|
||||||
|
|
||||||
|
|
|
@ -1113,13 +1113,14 @@ def getCephSnapshots(zkhandler, pool, volume):
|
||||||
return snapshot_list
|
return snapshot_list
|
||||||
|
|
||||||
|
|
||||||
def add_snapshot(zkhandler, pool, volume, name):
|
def add_snapshot(zkhandler, pool, volume, name, zk_only=False):
|
||||||
if not verifyVolume(zkhandler, pool, volume):
|
if not verifyVolume(zkhandler, pool, volume):
|
||||||
return False, 'ERROR: No volume with name "{}" is present in pool "{}".'.format(
|
return False, 'ERROR: No volume with name "{}" is present in pool "{}".'.format(
|
||||||
volume, pool
|
volume, pool
|
||||||
)
|
)
|
||||||
|
|
||||||
# 1. Create the snapshot
|
# 1. Create the snapshot
|
||||||
|
if not zk_only:
|
||||||
retcode, stdout, stderr = common.run_os_command(
|
retcode, stdout, stderr = common.run_os_command(
|
||||||
"rbd snap create {}/{}@{}".format(pool, volume, name)
|
"rbd snap create {}/{}@{}".format(pool, volume, name)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1307,12 +1307,16 @@ def get_list(
|
||||||
|
|
||||||
|
|
||||||
def backup_vm(
|
def backup_vm(
|
||||||
zkhandler, domain, target_path, incremental_parent=None, retain_snapshots=False
|
zkhandler, domain, target_path, incremental_parent=None, retain_snapshot=False
|
||||||
):
|
):
|
||||||
|
|
||||||
tstart = time.time()
|
tstart = time.time()
|
||||||
|
|
||||||
# 0. Validations
|
# 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!'
|
||||||
|
|
||||||
# Validate that VM exists in cluster
|
# Validate that VM exists in cluster
|
||||||
dom_uuid = getDomainUUID(zkhandler, domain)
|
dom_uuid = getDomainUUID(zkhandler, domain)
|
||||||
if not dom_uuid:
|
if not dom_uuid:
|
||||||
|
@ -1466,7 +1470,7 @@ def backup_vm(
|
||||||
is_snapshot_remove_failed = False
|
is_snapshot_remove_failed = False
|
||||||
which_snapshot_remove_failed = list()
|
which_snapshot_remove_failed = list()
|
||||||
msg_snapshot_remove_failed = list()
|
msg_snapshot_remove_failed = list()
|
||||||
if not retain_snapshots:
|
if not retain_snapshot:
|
||||||
for pool, volume, _ in vm_volumes:
|
for pool, volume, _ in vm_volumes:
|
||||||
if ceph.verifySnapshot(zkhandler, pool, volume, snapshot_name):
|
if ceph.verifySnapshot(zkhandler, pool, volume, snapshot_name):
|
||||||
retcode, retmsg = ceph.remove_snapshot(
|
retcode, retmsg = ceph.remove_snapshot(
|
||||||
|
@ -1485,7 +1489,7 @@ def backup_vm(
|
||||||
retlines.append(f"WARNING: Failed to remove snapshot as requested for volume(s) {', '.join(which_snapshot_remove_failed)}: {', '.join(msg_snapshot_remove_failed)}")
|
retlines.append(f"WARNING: Failed to remove snapshot as requested for volume(s) {', '.join(which_snapshot_remove_failed)}: {', '.join(msg_snapshot_remove_failed)}")
|
||||||
|
|
||||||
myhostname = gethostname().split(".")[0]
|
myhostname = gethostname().split(".")[0]
|
||||||
if retain_snapshots:
|
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}:{target_path}' in {ttot}s.")
|
||||||
else:
|
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}:{target_path}' in {ttot}s.")
|
||||||
|
@ -1493,7 +1497,7 @@ def backup_vm(
|
||||||
return True, '\n'.join(retlines)
|
return True, '\n'.join(retlines)
|
||||||
|
|
||||||
|
|
||||||
def restore_vm(zkhandler, domain, source_path, datestring):
|
def restore_vm(zkhandler, domain, source_path, datestring, retain_snapshot=False):
|
||||||
|
|
||||||
tstart = time.time()
|
tstart = time.time()
|
||||||
|
|
||||||
|
@ -1613,6 +1617,11 @@ def restore_vm(zkhandler, domain, source_path, datestring):
|
||||||
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)
|
# 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)
|
||||||
|
if not retcode:
|
||||||
|
return False, f"ERROR: Failed to add imported image snapshot for {parent_volume_file}: {retmsg}"
|
||||||
|
else:
|
||||||
retcode, stdout, stderr = common.run_os_command(
|
retcode, stdout, stderr = common.run_os_command(
|
||||||
f"rbd snap rm {pool}/{volume}@backup_{incremental_parent}"
|
f"rbd snap rm {pool}/{volume}@backup_{incremental_parent}"
|
||||||
)
|
)
|
||||||
|
@ -1651,6 +1660,11 @@ def restore_vm(zkhandler, domain, source_path, datestring):
|
||||||
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)
|
# 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)
|
||||||
|
if not retcode:
|
||||||
|
return False, f"ERROR: Failed to add imported image snapshot for {volume_file}: {retmsg}"
|
||||||
|
else:
|
||||||
retcode, stdout, stderr = common.run_os_command(
|
retcode, stdout, stderr = common.run_os_command(
|
||||||
f"rbd snap rm {pool}/{volume}@backup_{datestring}"
|
f"rbd snap rm {pool}/{volume}@backup_{datestring}"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue