1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/instances/tests/test_quota_lock.py
catborise 42a1783178 fix(instances): the Resize tab changes only what was asked
Disk sizes were pre-filled rounded to whole GiB, so a 3.5 GiB disk showed
4 and was grown, for good, whenever another disk was resized; an empty
field or a disk outside a pool was a 500. A disk's field is now empty with
its size as the placeholder: an empty field leaves the disk alone, a
larger whole number grows it, anything else is refused with a message.

vCPUs change only on a shut-off VM, also on a direct request: a running
VM got them at its next start, past the quota counted against its live
count. Current vCPUs or memory above the maximum are refused instead of
failing (and dropping vCPU hot-plug) or being capped by libvirt. A running
VM's maximum memory select is disabled, since its maximum is kept, and a
custom memory value is sent only while its box is ticked.
2026-10-26 13:49:37 +03:00

63 lines
2.6 KiB
Python

import threading
import time
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.db import connection
from django.test import Client, TransactionTestCase, override_settings
from django.urls import reverse
from instances.models import Instance
QUOTA_CPUS = 4
# Sessions and messages in cookies: concurrent writes to the in-memory test
# database would fail for reasons unrelated to the quota race.
@override_settings(
SESSION_ENGINE="django.contrib.sessions.backends.signed_cookies",
MESSAGE_STORAGE="django.contrib.messages.storage.cookie.CookieStorage",
)
class QuotaRaceTestCase(TransactionTestCase):
"""Two concurrent resizes of one user's VMs must not both pass the quota."""
def setUp(self):
self.user = get_user_model().objects.create_user("quota_user", password="pw")
compute = Compute.objects.create(name="quota-compute", hostname="127.0.0.1", login="", password="", type=4)
self.vms = [
Instance.objects.create(compute=compute, name=f"quota-vm-{i}", uuid=f"1111111{i}-2222-3333-4444-555555555555")
for i in range(2)
]
for vm in self.vms:
UserInstance.objects.create(user=self.user, instance=vm, is_change=True)
self.allocated = 2 # two VMs with one vCPU each
def fake_quota(self, user, instance, cpu, memory, disk):
seen = self.allocated
time.sleep(0.3) # widen the window between the check and the change
return "cpu" if seen + cpu > QUOTA_CPUS else ""
def fake_resize(self, cur_vcpu, vcpu):
self.allocated += int(vcpu) - 1
def resize(self, vm):
client = Client()
client.force_login(self.user)
client.post(reverse("instances:resizevm_cpu", args=[vm.id]), {"vcpu": 3, "cur_vcpu": 3})
connection.close()
def test_second_concurrent_resize_is_refused(self):
with patch("instances.views.utils.check_user_quota", self.fake_quota), patch(
"instances.models.wvmInstance"
) as wvm:
wvm.return_value.get_vcpu.return_value = 1
wvm.return_value.instance.isActive.return_value = False # vCPUs change on a shut-off VM
wvm.return_value.resize_cpu.side_effect = self.fake_resize
threads = [threading.Thread(target=self.resize, args=(vm,)) for vm in self.vms]
for t in threads:
t.start()
for t in threads:
t.join()
self.assertEqual(wvm.return_value.resize_cpu.call_count, 1)
self.assertLessEqual(self.allocated, QUOTA_CPUS)