mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
Networks, Interfaces, Storages, Storage, Volume, MigrateInstance and CreateInstance were unmanaged models with no table, there only so that ModelSerializers and the new VM ModelForm could take their fields. They made content types and migrations, and their DO_NOTHING foreign keys added reverse accessors that meant nothing (Compute.host, Compute.target_host, Instance.source_host). The serializers are plain Serializers and NewVMForm a Form, field for field as the models gave them (types, lengths, required, defaults, the minimum of 1 for vCPUs, memory and disk size; the form's labels are still derived from the field names). Compared field by field before and after, the API schema changes only where the models were wrong: - no int64 bounds on the listing fields; - the interface fields lose the titles that named the wrong column (type was "Status", state "Device", mac "Forward"); - a pool's volumes is a list of objects (it was described as an integer) and a volume's allocation an integer (it was untyped). One migration per app deletes the models; none touches a table.
152 lines
6.5 KiB
Python
152 lines
6.5 KiB
Python
import re
|
|
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from .models import Flavor
|
|
from .utils import nic_macs
|
|
|
|
|
|
class FlavorForm(forms.ModelForm):
|
|
# the units the create form fills in from a flavor
|
|
memory = forms.IntegerField(label=_("Memory (MiB)"), min_value=1)
|
|
vcpu = forms.IntegerField(label=_("vCPUs"), min_value=1)
|
|
disk = forms.IntegerField(label=_("Disk (GiB)"), min_value=1)
|
|
|
|
class Meta:
|
|
model = Flavor
|
|
fields = "__all__"
|
|
|
|
|
|
class ConsoleForm(forms.Form):
|
|
console_type = forms.ChoiceField(
|
|
label=_("Console type"), required=False, widget=forms.Select(attrs={"class": "form-select"})
|
|
)
|
|
password_action = forms.ChoiceField(
|
|
label=_("Password"),
|
|
choices=[
|
|
("keep", _("Keep the password")),
|
|
("generate", _("Generate a new one")),
|
|
("set", _("Set a new one")),
|
|
("none", _("No password")),
|
|
],
|
|
widget=forms.RadioSelect(attrs={"class": "form-check-input"}),
|
|
)
|
|
password = forms.CharField(
|
|
label=_("New password"),
|
|
widget=forms.PasswordInput(attrs={"class": "form-control", "autocomplete": "new-password"}),
|
|
required=False,
|
|
strip=False,
|
|
)
|
|
|
|
def __init__(self, *args, instance, is_superuser, **kwargs):
|
|
# the VM's settings as the initial values: a save changes only the fields edited
|
|
self.instance = instance
|
|
self.is_superuser = is_superuser
|
|
listener = instance.config_console_listener_address
|
|
kwargs["initial"] = {
|
|
"console_type": instance.config_console_type,
|
|
"listen_on": listener,
|
|
"password_action": "keep",
|
|
"keymap": instance.config_console_keymap or "auto",
|
|
}
|
|
super(ConsoleForm, self).__init__(*args, **kwargs)
|
|
types = [("vnc", "VNC")]
|
|
if settings.SERIAL_CONSOLE_ENABLED:
|
|
types.append(("pty", _("Serial (xterm; the guest needs a getty on ttyS0)")))
|
|
self.fields["console_type"].choices = types
|
|
# the VNC settings are shown and saved while the console is VNC; a
|
|
# switch to Serial ignores them
|
|
if "pty" in (instance.config_console_type, self.data.get("console_type")):
|
|
del self.fields["password_action"], self.fields["password"]
|
|
return
|
|
keymap_choices = [("auto", _("Auto"))] + list((c, c) for c in settings.QEMU_KEYMAPS)
|
|
# Only administrators choose the listen address: beyond localhost the
|
|
# console is reachable from the network. A disabled field ignores
|
|
# posted values.
|
|
listeners = list(settings.QEMU_CONSOLE_LISTENER_ADDRESSES)
|
|
# an address set elsewhere (the XML editor) is offered, so a save keeps it
|
|
if listener and listener not in dict(listeners):
|
|
listeners.append((listener, listener))
|
|
self.fields["listen_on"] = forms.ChoiceField(
|
|
label=_("Listen on"),
|
|
choices=listeners,
|
|
disabled=not is_superuser,
|
|
required=is_superuser,
|
|
widget=forms.Select(attrs={"class": "form-select"}),
|
|
)
|
|
self.fields["keymap"] = forms.ChoiceField(
|
|
label=_("Keymap"), choices=keymap_choices, widget=forms.Select(attrs={"class": "form-select"})
|
|
)
|
|
|
|
def clean(self):
|
|
data = super().clean()
|
|
action = data.get("password_action")
|
|
if action == "set":
|
|
password = data.get("password", "")
|
|
if not password:
|
|
self.add_error("password", _("Enter the new password."))
|
|
# VNC uses 8 bytes; libvirt refuses a longer password
|
|
elif len(password.encode()) > 8:
|
|
self.add_error("password", _("A VNC password takes at most 8 bytes."))
|
|
# A console that listens beyond localhost (set by an administrator)
|
|
# keeps its password unless an administrator removes it.
|
|
if action == "none" and not self.is_superuser and not self.instance.proxy.console_listens_locally():
|
|
self.add_error(
|
|
"password_action",
|
|
_("The console listens beyond localhost; only an administrator can remove its password."),
|
|
)
|
|
return data
|
|
|
|
|
|
class NewVMForm(forms.Form):
|
|
"""The new VM form (create_instance_w2.html); the API takes CreateInstanceSerializer."""
|
|
|
|
name = forms.CharField(max_length=64)
|
|
firmware = forms.CharField(max_length=64)
|
|
# an emulated TPM 2.0, as Windows 11 needs
|
|
tpm = forms.BooleanField(required=False)
|
|
vcpu = forms.IntegerField(min_value=1)
|
|
# a mode of libvirt's (host-model, host-passthrough, maximum) or a CPU model's name
|
|
vcpu_mode = forms.CharField(max_length=64)
|
|
disk = forms.IntegerField(required=False)
|
|
memory = forms.IntegerField(min_value=1)
|
|
networks = forms.CharField(max_length=256)
|
|
nwfilter = forms.CharField(max_length=256, required=False)
|
|
net_model = forms.CharField(max_length=256, required=False)
|
|
storage = forms.CharField(max_length=256, required=False)
|
|
template = forms.CharField(max_length=256, required=False)
|
|
images = forms.CharField(max_length=256, required=False)
|
|
cache_mode = forms.CharField(max_length=16)
|
|
hdd_size = forms.IntegerField(min_value=1, required=False)
|
|
meta_prealloc = forms.BooleanField(required=False)
|
|
virtio = forms.BooleanField(required=False, initial=True)
|
|
qemu_ga = forms.BooleanField(required=False)
|
|
mac = forms.CharField(max_length=255, required=False) # one per NIC, comma separated
|
|
add_cdrom = forms.CharField(max_length=16)
|
|
# a NoCloud seed on a CD-ROM: the hostname and the creating user's SSH keys
|
|
cloud_init = forms.BooleanField(required=False)
|
|
add_input = forms.CharField(max_length=16)
|
|
video = forms.CharField(max_length=16)
|
|
listener_addr = forms.ChoiceField(choices=settings.QEMU_CONSOLE_LISTENER_ADDRESSES)
|
|
|
|
def clean_name(self):
|
|
name = self.cleaned_data["name"]
|
|
have_symbol = re.match("^[a-zA-Z0-9._-]+$", name)
|
|
if not have_symbol:
|
|
raise forms.ValidationError(
|
|
_(
|
|
"The name of the virtual machine must not contain any special characters"
|
|
)
|
|
)
|
|
return name
|
|
|
|
def clean(self):
|
|
# one MAC per NIC at most, each a MAC address, as the job checks
|
|
cleaned = super().clean()
|
|
try:
|
|
nic_macs(cleaned.get("mac") or "", cleaned.get("networks") or "")
|
|
except ValueError as err:
|
|
self.add_error("mac", str(err))
|
|
return cleaned
|