1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/instances/api/serializers.py
catborise 02412b7ea5 feat(jobs): deleting a VM is a job that recovery finishes
The delete ran in the request: undefine, then the VM's record, then its
disk files. A request that died in between (a gunicorn worker past its
timeout, OOM, a restart), or a database write that failed after the
undefine, left a record that later showed as removed outside the panel,
and the disk files the user asked to delete were never deleted.

Deleting a VM is now an instance.delete job. It holds the compute
exclusively, as the request did, so no other VM attaches a file it is
about to delete. The job chooses the files before it stops the VM (a host
that cannot be checked still stops nothing) and records them first. It
then stops and undefines the VM, removes its record, and deletes the
files, each with its own Logs row. Recovery decides from libvirt: a VM
still on its host means the delete failed; a VM gone means libvirt did
it, so recovery removes the record and deletes the recorded files that
are still there.

The page queues the job and returns to the VM page, where the job panel
follows it and opens the VM list when it ends; disks that were kept are
the result's warning. The API's delete answers 202 with the job and
needs a submission_key. backing_path moves to wvmConnect, as recovery
has no domain to ask.
2026-11-06 09:49:31 +03:00

146 lines
4.6 KiB
Python

from instances.models import CreateInstance, Flavor, Instance, MigrateInstance
from instances.utils import nic_macs
from rest_framework import serializers
class InstanceSerializer(serializers.ModelSerializer):
class Meta:
model = Instance
fields = ["id", "compute", "name", "uuid", "is_template", "created", "drbd"]
class InstanceDetailsSerializer(serializers.ModelSerializer):
def get_fields(self):
fields = super().get_fields()
# the host's ISO images are a superuser's, as on the VM page
request = self.context.get("request")
if not (request and request.user.is_superuser):
del fields["media_iso"]
return fields
class Meta:
model = Instance
fields = [
"id",
"compute",
"status",
"uuid",
"name",
"title",
"description",
"is_template",
"created",
"drbd",
"arch",
"machine",
"vcpu",
"memory",
"firmware",
"nvram",
"bootmenu",
"boot_order",
"disks",
"media",
"media_iso",
"snapshots",
"networks",
"console_type",
"console_port",
"console_keymap",
"console_listener_address",
"video_model",
"guest_agent_ready",
"autostart",
]
class FlavorSerializer(serializers.ModelSerializer):
class Meta:
model = Flavor
fields = ["id", "label", "memory", "vcpu", "disk"]
# as the web form (instances.forms.FlavorForm): MiB, vCPUs and GiB, each at least 1
extra_kwargs = {"memory": {"min_value": 1}, "vcpu": {"min_value": 1}, "disk": {"min_value": 1}}
class DestroyOptionsSerializer(serializers.Serializer):
"""What a VM's delete takes besides the VM: from the web form's checkboxes
or an API body, JSON or form-encoded ("false" is false)."""
delete_disk = serializers.BooleanField(default=False)
delete_nvram = serializers.BooleanField(default=False)
class DestroySerializer(DestroyOptionsSerializer):
"""The API's delete: a job, which a new submission key per request names."""
submission_key = serializers.CharField(write_only=True)
class CreateInstanceSerializer(serializers.ModelSerializer):
# BIOS, UEFI or UEFI Secure Boot, as the host offers; left out: the machine's default
firmware = serializers.CharField(required=False)
video = serializers.CharField(initial="vga")
storage = serializers.CharField(initial="default")
cache_mode = serializers.CharField(initial="none")
virtio = serializers.BooleanField(initial=True)
qemu_ga = serializers.BooleanField(initial=True)
submission_key = serializers.CharField(write_only=True)
def validate(self, attrs):
try:
nic_macs(attrs.get("mac"), attrs.get("networks", ""))
except ValueError as err:
raise serializers.ValidationError({"mac": str(err)})
return attrs
class Meta:
model = CreateInstance
fields = [
"name",
"firmware",
"tpm",
"vcpu",
"vcpu_mode",
"memory",
"networks",
"mac",
"nwfilter",
"storage",
"hdd_size",
"cache_mode",
"meta_prealloc",
"virtio",
"qemu_ga",
"cloud_init",
"video",
"listener_addr",
"submission_key",
]
class MigrateSerializer(serializers.ModelSerializer):
live = serializers.BooleanField(default=True)
offline = serializers.BooleanField(default=False)
# left out, also from a form-encoded request: auto converge on
autoconverge = serializers.BooleanField(default=True)
compress = serializers.BooleanField(default=False)
unsafe = serializers.BooleanField(default=False)
# {target: pool of the destination} of the disks copied there; delete_source deletes their source files
disk_pools = serializers.DictField(child=serializers.CharField(allow_blank=True), default=dict)
delete_source = serializers.BooleanField(default=False)
submission_key = serializers.CharField(write_only=True)
class Meta:
model = MigrateInstance
fields = [
"instance",
"target_compute",
"live",
"offline",
"autoconverge",
"compress",
"unsafe",
"disk_pools",
"delete_source",
"submission_key",
]