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

feat(instances): a VM's options in one list, each saved when it can be

Settings > Options was four forms under bold sentences, most of them
disabled while the VM ran. The owner chose this layout from three
rendered options: one list, each row a setting with its description on
the left and its control on the right: VM details (title, description),
Template, primary video model, QEMU guest agent. vCPU hot plug moved to
Resize > CPU, next to the vCPU counts.

Codex reviewed the four forms; these are fixed:

- Title, description and the video model are saved while the VM runs:
  they are in the persistent definition, used at the next start.
- The template flag has its own form and view, set_template, for
  superusers and staff, and only for a shut-off VM: a running VM made a
  template left the quota while it kept running. Turning a template
  back into a VM checks the quota there, and each change is audited.
  The title form no longer touches the flag.
- A new video model stays the primary one; the old model's memory and
  head settings, which are its own, go with it.
- The agent and vCPU hot plug forms show the persistent definition; they
  showed the running VM's, so a pending change looked undone.
- Enabling the guest agent adds its channel only where it is missing:
  saving Enabled again failed, and a channel pending the next start kept
  a running VM from getting one.
- The agent line tells a VM that is not running, one without a channel,
  and a connected or not connected agent apart.
This commit is contained in:
catborise 2026-10-30 16:03:43 +03:00
parent 46188d3230
commit a993c12db0
13 changed files with 272 additions and 172 deletions

View file

@ -2165,45 +2165,47 @@ def update_console(request, pk):
@require_POST
@serialize_user_quota
@serialize_instance_mutation
def change_options(request, pk):
"""The title and description of the persistent definition: they apply
at the next start, so a running VM's are changed too."""
instance = get_instance(request.user, pk, perm_type="change")
try:
userinstance = instance.userinstance_set.get(user=request.user)
except UserInstance.DoesNotExist:
userinstance = UserInstance(is_change=False)
if request.user.is_superuser or userinstance.is_change:
# Only superusers and staff may (un)mark templates. The
# checkbox is disabled for everyone else, and a disabled checkbox is
# not submitted, so for them the flag must be left alone.
if request.user.is_superuser or request.user.is_staff:
is_template = bool(request.POST.get("is_template", False))
# A template is not counted and cannot be started: making it a
# VM again adds it to the owner's usage, so the quota applies.
if instance.is_template and not is_template and not request.user.is_superuser:
disk_sum = sum(int(disk.get("size") or 0) >> 30 for disk in instance.disks)
quota_msg = utils.check_user_quota(request.user, 1, instance.vcpu, instance.memory, disk_sum)
if quota_msg:
messages.error(request, quota_refused(quota_msg, _(
"User %(quota_msg)s quota reached, '%(instance_name)s' stays a template!"
) % {"quota_msg": quota_msg, "instance_name": instance.name}))
is_template = True
instance.is_template = is_template
instance.save(update_fields=["is_template"])
options = {}
for post in request.POST:
if post in ["title", "description"]:
options[post] = request.POST.get(post, "")
with audit_vm(request, instance, "edit options"):
instance.proxy.set_options(options)
options = {key: request.POST.get(key, "") for key in ("title", "description") if key in request.POST}
with audit_vm(request, instance, "edit options"):
instance.proxy.set_options(options)
return get_safe_redirect(
request, default=reverse("instances:instance", args=[instance.id]) + "#options"
)
@require_POST
@serialize_user_quota
@serialize_instance_mutation
def set_template(request, pk):
"""Marks a shut-off VM as a template, or a template as a VM again; only
superusers and staff. A template is not counted and cannot be started:
making it a VM again adds it to the owner's usage, so the quota applies."""
instance = get_instance(request.user, pk, perm_type="change")
if not (request.user.is_superuser or request.user.is_staff):
raise PermissionDenied
is_template = bool(request.POST.get("is_template"))
if instance.proxy.get_status() != 5:
messages.error(request, _("Shut the VM down to change whether it is a template."))
return _back(request, pk, "options")
if instance.is_template and not is_template and not request.user.is_superuser:
disk_sum = sum(int(disk.get("size") or 0) >> 30 for disk in instance.disks)
quota_msg = utils.check_user_quota(request.user, 1, instance.vcpu, instance.memory, disk_sum)
if quota_msg:
messages.error(request, quota_refused(quota_msg, _(
"User %(quota_msg)s quota reached, '%(instance_name)s' stays a template!"
) % {"quota_msg": quota_msg, "instance_name": instance.name}))
return _back(request, pk, "options")
with audit_vm(request, instance, "mark as template" if is_template else "unmark as template"):
instance.is_template = is_template
instance.save(update_fields=["is_template"])
return _back(request, pk, "options")
def getvvfile(request, pk):
instance = get_instance(request.user, pk)
# The .vv file contains the VNC password: same rule as the console.