1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +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

@ -1,8 +1,6 @@
"""
Only superusers and staff may mark or unmark a VM as a template.
The UI only disables the checkbox for other users, and a disabled checkbox is
not submitted, so the server must not derive is_template from its absence for
them.
Only superusers and staff may mark or unmark a VM as a template, on its own
form: the title and description form leaves the flag alone.
"""
from unittest.mock import patch
@ -13,6 +11,7 @@ from django.urls import reverse
from accounts.models import UserInstance
from computes.models import Compute
from instances.models import Instance
from logs.models import Logs
class TemplateFlagTestCase(TestCase):
@ -36,16 +35,15 @@ class TemplateFlagTestCase(TestCase):
for user in (self.owner, self.staff_owner):
UserInstance.objects.create(instance=self.instance, user=user, is_change=True)
def _change_options(self, user, data, status=302):
def _post(self, user, action, data, status=302):
self.client.force_login(user)
with patch("instances.models.wvmInstance") as wvm:
wvm.return_value.get_status.return_value = 5
# unmarking a template checks the owner's quota with the VM's size
wvm.return_value.get_vcpu.return_value = 1
wvm.return_value.get_memory.return_value = 1024
wvm.return_value.get_disk_devices.return_value = []
res = self.client.post(
reverse("instances:change_options", args=[self.instance.id]), data
)
res = self.client.post(reverse(f"instances:{action}", args=[self.instance.id]), data)
self.assertEqual(res.status_code, status)
self.instance.refresh_from_db()
@ -54,23 +52,30 @@ class TemplateFlagTestCase(TestCase):
self.instance.save(update_fields=["is_template"])
def test_owner_cannot_mark_vm_as_template(self):
self._change_options(self.owner, {"title": "t", "is_template": "True"})
self._post(self.owner, "set_template", {"is_template": "True"}, status=403)
self.assertFalse(self.instance.is_template)
def test_owner_cannot_change_a_template(self):
# a template is changed only by superusers and staff owners
self._make_template()
self._change_options(self.owner, {"title": "new title", "description": ""}, status=403)
self._post(self.owner, "change_options", {"title": "new title", "description": ""}, status=403)
self.assertTrue(self.instance.is_template)
def test_staff_owner_can_mark_and_unmark_template(self):
self._change_options(self.staff_owner, {"title": "t", "is_template": "True"})
self._post(self.staff_owner, "set_template", {"is_template": "True"})
self.assertTrue(self.instance.is_template)
# An unchecked checkbox is not submitted: for staff that means "not a template".
self._change_options(self.staff_owner, {"title": "t"})
# an unchecked switch is not submitted: "not a template"
self._post(self.staff_owner, "set_template", {})
self.assertFalse(self.instance.is_template)
self.assertEqual(list(Logs.objects.filter(message__startswith="VM").order_by("id").values_list("message", "success")),
[("VM tpl-vm: mark as template", True), ("VM tpl-vm: unmark as template", True)])
def test_superuser_can_unmark_template(self):
self._make_template()
self._change_options(self.superuser, {"title": "t"})
self._post(self.superuser, "set_template", {})
self.assertFalse(self.instance.is_template)
def test_the_title_form_leaves_the_template_alone(self):
self._make_template()
self._post(self.superuser, "change_options", {"title": "t", "description": ""})
self.assertTrue(self.instance.is_template)