container: Allow taking snapshots of VMs

- Use qcow2 image format so that snapshots of VMs can be taken.

- Snapshots of running VMs can't yet taken yet. But once the VM is stopped,
snapshots are possible.

Tests:

- Bring up a stable VM freshly after destroying. Work with the VM, stop it and
take a snapshot using virt-manager.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2025-03-08 17:51:31 -08:00 committed by James Valleroy
parent 7764b0a2c7
commit bbb59e16de
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808

View File

@ -318,7 +318,7 @@ LIBVIRT_DOMAIN_XML_TEMPLATE = '''<domain type="kvm">
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type="file" device="disk">
<driver name="qemu" type="raw"/>
<driver name="qemu" type="qcow2"/>
<source file="{image_file}"/>
<target dev="vda" bus="virtio"/>
<address type="pci" domain="0x0000" bus="0x04" slot="0x00" function="0x0"/>
@ -1356,10 +1356,10 @@ class VM(Machine):
except subprocess.CalledProcessError:
pass
image_file = _get_image_file(self.distribution)
qcow_image = self._create_qcow_image()
domain_xml = LIBVIRT_DOMAIN_XML_TEMPLATE.format(
domain_name=self.machine_name, memory_mib='2048', cpus='4',
image_file=image_file, source_dir=_get_project_folder())
image_file=qcow_image, source_dir=_get_project_folder())
with tempfile.NamedTemporaryFile() as file_handle:
file_handle.write(domain_xml.encode())
@ -1389,12 +1389,24 @@ class VM(Machine):
return
logger.info('Running `virsh destroy %s`', self.machine_name)
self._virsh(['destroy', self.machine_name], stdout=subprocess.DEVNULL)
try:
self._virsh(['undefine', self.machine_name],
stdout=subprocess.DEVNULL)
except subprocess.CalledProcessError:
pass
self._destroy_qcow_image()
def destroy(self) -> None:
"""Remove all traces of the VM from the host."""
logger.info('Running `virsh undefine %s`', self.machine_name)
self._virsh(['undefine', self.machine_name], stdout=subprocess.DEVNULL)
try:
self._virsh(['undefine', self.machine_name],
stdout=subprocess.DEVNULL)
except subprocess.CalledProcessError:
pass
self._destroy_qcow_image()
def get_ip_address(self) -> str | None:
"""Return the IP address assigned to the VM."""
@ -1426,6 +1438,27 @@ class VM(Machine):
"""Run virsh to control the virtual machine."""
return subprocess.run(['sudo', 'virsh'] + args, check=check, **kwargs)
def _create_qcow_image(self) -> pathlib.Path:
"""Convert raw image into qcow2 image.
qcow2 image format allows snapshots of VM.
"""
image_file = _get_image_file(self.distribution)
qcow_image = image_file.with_suffix('.qcow2')
logger.info('Creating qcow2 image: %s', qcow_image)
subprocess.run([
'sudo', 'qemu-img', 'convert', '-f', 'raw', '-O', 'qcow2',
image_file, qcow_image
], check=True)
return qcow_image
def _destroy_qcow_image(self) -> None:
"""Delete the qcow image."""
image_file = _get_image_file(self.distribution)
qcow_image = image_file.with_suffix('.qcow2')
logger.info('Removing qcow2 image: %s', qcow_image)
qcow_image.unlink(missing_ok=True)
def subcommand_up(arguments: argparse.Namespace):
"""Download, setup and bring up the container."""