1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 20:26:48 +00:00

feat(instances): external snapshots use libvirt's snapshot API

Create, revert and delete are one libvirt call each. The pflash-to-rom
loader rewrite, the hand-written block commit and the s1./s2. names are
gone, so UEFI VMs can take external snapshots, a revert keeps disks added
later, and deleting an older snapshot keeps the newer one.

Create refuses what libvirt could not revert or delete later: internal
snapshots on the VM, a saved state, a running snapshot job, disks that
are not files, CD-ROM media, a branch after a revert, and memory without
a running VM or room in the first disk's pool. A disk-only revert keeps
the VM running, paused or shut off; one with memory resumes it as saved.
A revert in the same second as the previous one, which libvirt fails
after stopping the VM, is retried once.
A snapshot the old code took of a UEFI VM saved the loader as rom; its
revert is refused (deleting it still works).

The snapshots tab lists internal and external snapshots separately, can
save memory too, shows running snapshot jobs, and the revert confirmation
names the disks it detaches and that NVRAM is not restored.
This commit is contained in:
catborise 2026-10-12 14:07:33 +03:00
parent c55f52f025
commit ff1508b3c2
13 changed files with 994 additions and 311 deletions

View file

@ -2,7 +2,12 @@ from computes.models import Compute
from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from libvirt import VIR_DOMAIN_XML_SECURE, libvirtError
from libvirt import (
VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL,
VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL,
VIR_DOMAIN_XML_SECURE,
libvirtError,
)
from vrtManager.instance import wvmInstance
from webvirtcloud.settings import QEMU_CONSOLE_LISTENER_ADDRESSES
@ -253,13 +258,24 @@ class Instance(models.Model):
def video_models(self):
return self.proxy.get_video_models(self.arch, self.machine)
@cached_property
def snapshots(self) -> list[dict]:
return sorted(self.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"])
def _snapshots(self, flag):
return sorted(self.proxy.get_snapshot(flag), reverse=True, key=lambda k: k["date"])
@cached_property
def external_snapshots(self):
return sorted(self.proxy.get_external_snapshots(), reverse=True, key=lambda k: k["date"])
def snapshots(self) -> list[dict]:
return self._snapshots(VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL | VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL)
@cached_property
def internal_snapshots(self) -> list[dict]:
return self._snapshots(VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL)
@cached_property
def external_snapshots(self) -> list[dict]:
return self._snapshots(VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL)
@cached_property
def snapshot_jobs(self) -> list[dict]:
return self.proxy.get_snapshot_jobs()
@cached_property
def inst_xml(self):