Add VM snapshot removal

This commit is contained in:
Joshua Boniface 2024-05-17 10:55:42 -04:00
parent 553c1e670e
commit 0c240a5129
5 changed files with 172 additions and 8 deletions

View File

@ -3130,6 +3130,48 @@ class API_VM_Snapshot(Resource):
snapshot_name = reqargs.get("snapshot_name", None) snapshot_name = reqargs.get("snapshot_name", None)
return api_helper.create_vm_snapshot(vm, snapshot_name=snapshot_name) return api_helper.create_vm_snapshot(vm, snapshot_name=snapshot_name)
@RequestParser(
[
{
"name": "snapshot_name",
"required": True,
"helptext": "A snapshot name must be specified",
},
]
)
@Authenticator
def delete(self, vm, reqargs):
"""
Remove a snapshot of a VM's disks and configuration
---
tags:
- vm
parameters:
- in: query
name: snapshot_name
type: string
required: true
description: The name of the snapshot to remove
responses:
200:
description: OK
schema:
type: object
id: Message
400:
description: Execution error
schema:
type: object
id: Message
404:
description: Not found
schema:
type: object
id: Message
"""
snapshot_name = reqargs.get("snapshot_name", None)
return api_helper.remove_vm_snapshot(vm, snapshot_name)
api.add_resource(API_VM_Snapshot, "/vm/<vm>/snapshot") api.add_resource(API_VM_Snapshot, "/vm/<vm>/snapshot")

View File

@ -789,6 +789,30 @@ def create_vm_snapshot(
return output, retcode return output, retcode
@ZKConnection(config)
def remove_vm_snapshot(
zkhandler,
domain,
snapshot_name,
):
"""
Take a snapshot of a VM.
"""
retflag, retdata = pvc_vm.remove_vm_snapshot(
zkhandler,
domain,
snapshot_name,
)
if retflag:
retcode = 200
else:
retcode = 400
output = {"message": retdata.replace('"', "'")}
return output, retcode
@ZKConnection(config) @ZKConnection(config)
def vm_attach_device(zkhandler, vm, device_spec_xml): def vm_attach_device(zkhandler, vm, device_spec_xml):
""" """

View File

@ -1787,7 +1787,7 @@ def cli_vm_snapshot():
@connection_req @connection_req
@click.argument("domain") @click.argument("domain")
@click.argument("snapshot_name", required=False, default=None) @click.argument("snapshot_name", required=False, default=None)
def cli_vm_snapshot_create(domain, snapshot_name): def cli_vm_snapshot_remove(domain, snapshot_name):
""" """
Create a snapshot of the disks and XML configuration of virtual machine DOMAIN, with the Create a snapshot of the disks and XML configuration of virtual machine DOMAIN, with the
optional name SNAPSHOT_NAME. DOMAIN may be a UUID or name. optional name SNAPSHOT_NAME. DOMAIN may be a UUID or name.
@ -1812,6 +1812,32 @@ def cli_vm_snapshot_create(domain, snapshot_name):
finish(retcode, retmsg) finish(retcode, retmsg)
###############################################################################
# > pvc vm snapshot remove
###############################################################################
@click.command(name="remove", short_help="Remove a snapshot of a virtual machine.")
@connection_req
@click.argument("domain")
@click.argument("snapshot_name")
def cli_vm_snapshot_create(domain, snapshot_name):
"""
Remove the snapshot SNAPSHOT_NAME of the disks and XML configuration of virtual machine DOMAIN,
DOMAIN may be a UUID or name.
"""
echo(
CLI_CONFIG,
f"Removing snapshot '{snapshot_name}' of VM '{domain}'... ",
newline=False,
)
retcode, retmsg = pvc.lib.vm.vm_remove_snapshot(CLI_CONFIG, domain, snapshot_name)
if retcode:
echo(CLI_CONFIG, "done.")
else:
echo(CLI_CONFIG, "failed.")
finish(retcode, retmsg)
############################################################################### ###############################################################################
# > pvc vm backup # > pvc vm backup
############################################################################### ###############################################################################
@ -6350,6 +6376,7 @@ cli_vm.add_command(cli_vm_migrate)
cli_vm.add_command(cli_vm_unmigrate) cli_vm.add_command(cli_vm_unmigrate)
cli_vm.add_command(cli_vm_flush_locks) cli_vm.add_command(cli_vm_flush_locks)
cli_vm_snapshot.add_command(cli_vm_snapshot_create) cli_vm_snapshot.add_command(cli_vm_snapshot_create)
cli_vm_snapshot.add_command(cli_vm_snapshot_remove)
cli_vm.add_command(cli_vm_snapshot) cli_vm.add_command(cli_vm_snapshot)
cli_vm_backup.add_command(cli_vm_backup_create) cli_vm_backup.add_command(cli_vm_backup_create)
cli_vm_backup.add_command(cli_vm_backup_restore) cli_vm_backup.add_command(cli_vm_backup_restore)

View File

@ -519,6 +519,25 @@ def vm_create_snapshot(config, vm, snapshot_name=None):
return True, response.json().get("message", "") return True, response.json().get("message", "")
def vm_remove_snapshot(config, vm, snapshot_name):
"""
Remove a snapshot of a VM's disks and configuration
API endpoint: DELETE /vm/{vm}/snapshot
API arguments: snapshot_name=snapshot_name
API schema: {"message":"{data}"}
"""
params = {"snapshot_name": snapshot_name}
response = call_api(
config, "delete", "/vm/{vm}/snapshot".format(vm=vm), params=params
)
if response.status_code != 200:
return False, response.json().get("message", "")
else:
return True, response.json().get("message", "")
def vm_vcpus_set(config, vm, vcpus, topology, restart): def vm_vcpus_set(config, vm, vcpus, topology, restart):
""" """
Set the vCPU count of the VM with topology Set the vCPU count of the VM with topology

View File

@ -1249,7 +1249,7 @@ def get_list(
# #
# VM Snapshot Tasks # VM Snapshot Tasks
# #
def create_vm_snapshot(zkhandler, domain, snapshot_name=None): def create_vm_snapshot(zkhandler, domain, snapshot_name=None, is_backup=False):
# 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:
@ -1310,7 +1310,7 @@ def create_vm_snapshot(zkhandler, domain, snapshot_name=None):
"domain_snapshot.is_backup", "domain_snapshot.is_backup",
snapshot_name, snapshot_name,
), ),
False, is_backup,
), ),
( (
("domain.snapshots", dom_uuid, "domain_snapshot.xml", snapshot_name), ("domain.snapshots", dom_uuid, "domain_snapshot.xml", snapshot_name),
@ -1336,14 +1336,66 @@ def create_vm_snapshot(zkhandler, domain, snapshot_name=None):
) )
def remove_vm_snapshot(zkhandler, domain, snapshot_name, remove_backup=False):
# Validate that VM exists in cluster
dom_uuid = getDomainUUID(zkhandler, domain)
if not dom_uuid:
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
if not zkhandler.exists(
("domain.snapshots", dom_uuid, "domain_snapshot.name", snapshot_name)
):
return (
False,
f'ERROR: Could not find snapshot "{snapshot_name}" of VM "{domain}"!',
)
if (
zkhandler.read(
("domain.snapshots", dom_uuid, "domain_snapshot.is_backup", snapshot_name)
)
and not remove_backup
):
# Disallow removing backups normally, but expose `remove_backup` flag for internal usage by refactored backup handlers
return (
False,
f'ERROR: Snapshot "{snapshot_name}" of VM "{domain}" is a backup; please remove with "pvc backup"!',
)
tstart = time.time()
_snapshots = zkhandler.read(
("domain.snapshots", dom_uuid, "domain_snapshot.rbd_snapshots", snapshot_name)
)
rbd_snapshots = _snapshots.split(",")
for snap in rbd_snapshots:
name, rbd = snap.split("@")
pool, volume = rbd.split("/")
ret, msg = ceph.remove_snapshot(zkhandler, pool, volume, name)
if not ret:
return False, msg
ret = zkhandler.delete(
("domain.snapshots", dom_uuid, "domain_snapshot.name", snapshot_name)
)
if not ret:
return (
False,
f'ERROR: Failed to delete snapshot "{snapshot_name}" of VM "{domain}" in Zookeeper.',
)
tend = time.time()
ttot = round(tend - tstart, 2)
return (
True,
f'Successfully removed snapshot "{snapshot_name}" of VM "{domain}" in {ttot}s.',
)
def rollback_vm_snapshot(zkhandler, domain, snapshot_name): def rollback_vm_snapshot(zkhandler, domain, snapshot_name):
pass pass
def remove_vm_snapshot(zkhandler, domain, snapshot_name):
pass
# #
# VM Backup Tasks # VM Backup Tasks
# #