mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 03:36:48 +00:00
fix(instances): turning a template back into a VM is checked against the quota
Templates do not count towards a user's quota and cannot be started. Staff owners may mark and unmark their VMs as templates, so a staff user could mark a VM as a template, use the freed quota elsewhere, and unmark it again: the VM came back without any check, over the quota. For users with a quota (everyone but superusers), turning a template back into a VM now passes check_user_quota with the VM's size (one VM, its vCPUs, memory and disks). If the quota is reached or cannot be verified, it stays a template and the user is told why. Marking a VM as a template frees quota and is not checked. The view takes the per-user quota lock like the other quota-checked changes. The existing template-flag test gives its mocked VM a size. The new tests fail on the previous code.
This commit is contained in:
parent
f5276655fd
commit
1fb74d6378
3 changed files with 86 additions and 2 deletions
|
|
@ -38,7 +38,11 @@ class TemplateFlagTestCase(TestCase):
|
|||
|
||||
def _change_options(self, user, data):
|
||||
self.client.force_login(user)
|
||||
with patch("instances.models.wvmInstance"):
|
||||
with patch("instances.models.wvmInstance") as wvm:
|
||||
# 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
|
||||
)
|
||||
|
|
|
|||
68
instances/test_template_quota.py
Normal file
68
instances/test_template_quota.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""Templates do not count towards the quota and cannot be started. Turning a
|
||||
template back into a VM adds it to its owner's usage, so for users with a
|
||||
quota it is checked like any other increase."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from accounts.models import UserInstance
|
||||
from computes.models import Compute
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.messages import get_messages
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from instances.models import Instance
|
||||
from instances.utils import QUOTA_UNVERIFIED
|
||||
|
||||
|
||||
class TemplateFlagQuotaTests(TestCase):
|
||||
def setUp(self):
|
||||
User = get_user_model()
|
||||
self.staff = User.objects.create_user("tq-staff", password="pw", is_staff=True)
|
||||
compute = Compute.objects.create(name="tq", hostname="10.0.0.5", login="u", password="p", type=1)
|
||||
self.vm = Instance.objects.create(compute=compute, name="tq-vm", uuid="12345678-0000-0000-0000-00000000aaaa")
|
||||
UserInstance.objects.create(user=self.staff, instance=self.vm, is_change=True)
|
||||
|
||||
def post(self, user, is_template, quota=""):
|
||||
self.client.force_login(user)
|
||||
data = {"title": "", "description": ""}
|
||||
if is_template:
|
||||
data["is_template"] = "on"
|
||||
with patch("instances.models.wvmInstance") as wvm, patch(
|
||||
"instances.views.utils.check_user_quota", return_value=quota
|
||||
) as check:
|
||||
wvm.return_value.get_vcpu.return_value = 2
|
||||
wvm.return_value.get_memory.return_value = 2048
|
||||
wvm.return_value.get_disk_devices.return_value = [{"size": 10 << 30}]
|
||||
response = self.client.post(reverse("instances:change_options", args=[self.vm.id]), data)
|
||||
self.vm.refresh_from_db()
|
||||
return check, [str(m) for m in get_messages(response.wsgi_request)]
|
||||
|
||||
def test_making_a_template_needs_no_quota(self):
|
||||
check, _ = self.post(self.staff, is_template=True)
|
||||
self.assertTrue(self.vm.is_template)
|
||||
check.assert_not_called()
|
||||
|
||||
def test_turning_a_template_into_a_vm_is_checked(self):
|
||||
Instance.objects.filter(pk=self.vm.pk).update(is_template=True)
|
||||
check, _ = self.post(self.staff, is_template=False)
|
||||
self.assertFalse(self.vm.is_template)
|
||||
check.assert_called_once_with(self.staff, 1, 2, 2048, 10)
|
||||
|
||||
def test_over_quota_the_template_stays_a_template(self):
|
||||
Instance.objects.filter(pk=self.vm.pk).update(is_template=True)
|
||||
_, messages = self.post(self.staff, is_template=False, quota="cpu")
|
||||
self.assertTrue(self.vm.is_template)
|
||||
self.assertTrue(any("quota" in m for m in messages), messages)
|
||||
|
||||
def test_an_unverifiable_quota_keeps_it_a_template(self):
|
||||
Instance.objects.filter(pk=self.vm.pk).update(is_template=True)
|
||||
_, messages = self.post(self.staff, is_template=False, quota=QUOTA_UNVERIFIED)
|
||||
self.assertTrue(self.vm.is_template)
|
||||
self.assertTrue(any("cannot be reached" in m for m in messages), messages)
|
||||
|
||||
def test_superusers_have_no_quota(self):
|
||||
Instance.objects.filter(pk=self.vm.pk).update(is_template=True)
|
||||
admin = get_user_model().objects.create_superuser("tq-admin", "tq@example.com", "pw")
|
||||
check, _ = self.post(admin, is_template=False, quota="cpu")
|
||||
self.assertFalse(self.vm.is_template)
|
||||
check.assert_not_called()
|
||||
|
|
@ -1957,6 +1957,7 @@ def update_console(request, pk):
|
|||
|
||||
|
||||
@require_POST
|
||||
@serialize_user_quota
|
||||
@serialize_instance_mutation
|
||||
def change_options(request, pk):
|
||||
instance = get_instance(request.user, pk, perm_type="change")
|
||||
|
|
@ -1970,7 +1971,18 @@ def change_options(request, pk):
|
|||
# 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:
|
||||
instance.is_template = bool(request.POST.get("is_template", False))
|
||||
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 = {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue