1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/computes/tests/test_capacity.py
catborise 1284b93705 feat(computes): memory given as a share; the overview in Bootstrap cards and lists
The memory given read "24.0 GB given of 32.0 GB": long, and the share
of the host's memory, what one looks for, had to be worked out. It is
now told as the vCPUs are: the share large ("75 %", amber over 90 %,
red over 100 %), then "24.0 GB / 32.0 GB", the bar and "12.0 GB
running". The Computes list's memory column shares it.

Every card of the overview has a Bootstrap card-header with an icon:
VMs, vCPUs, Memory, Storage, CPU in use and Memory in use (with the
current reading at its right), Hardware, Software and connection. The
last two were label and value lists under indented headings; they are
list-group-flush rows now, the label at the left, the value at the
right: counts in semibold, the hypervisor's and libvirt's versions as
badges, the guest architectures as light badges, the emulator and the
connection as code. The section headings, indented by the panel's
page-header margin unlike the cards, are gone: each card names itself.
The status line and notes are not paragraphs, which the panel indents.
2026-10-27 16:32:39 +03:00

142 lines
7.9 KiB
Python

"""What each compute has given its VMs (Compute.capacity) and the Computes
page that shows it."""
from unittest.mock import patch
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from libvirt import libvirtError
from lxml import html
GIB = 1024 ** 3
VMS = {"running": 3, "paused": 1, "shutoff": 2, "other": 0}
def pool(name, size_gib, free_gib, status=1):
return {"name": name, "status": status, "size": size_gib * GIB, "free": free_gib * GIB}
class CapacityTests(TestCase):
def setUp(self):
self.compute = Compute(name="host-one", hostname="192.0.2.1", login="u", password="p", type=1)
self.compute.status = True
@patch("computes.models.wvmStorages")
@patch("computes.models.wvmHostDetails")
def test_a_host_tells_what_it_gave_and_its_active_pools(self, details, storages):
details.return_value.get_capacity.return_value = {"cpus": 16, "vcpus": {"all": 24, "running": 8}}
storages.return_value.get_storages_info.return_value = [
pool("nfs", 900, 800), pool("off", 10, 0, status=0), pool("default", 500, 120), pool("iso", 100, 90)]
# the default pool first, the others by name
self.assertEqual(self.compute.capacity, {
"cpus": 16, "vcpus": {"all": 24, "running": 8}, "ratio": 1.5,
"pools": [pool("default", 500, 120), pool("iso", 100, 90), pool("nfs", 900, 800)]})
@patch("computes.models.wvmStorages")
@patch("computes.models.wvmHostDetails")
def test_pools_that_cannot_be_read_leave_the_cpus_and_memory(self, details, storages):
details.return_value.get_capacity.return_value = {"cpus": 16, "vcpus": {"all": 24, "running": 8}}
storages.return_value.get_storages_info.side_effect = libvirtError("cannot refresh pool")
self.assertEqual(self.compute.capacity, {
"cpus": 16, "vcpus": {"all": 24, "running": 8}, "ratio": 1.5, "pools": None})
@patch("computes.models.wvmHostDetails")
def test_a_host_whose_read_fails_has_no_capacity(self, details):
# a VM that goes while the host is read, for one
details.return_value.get_capacity.side_effect = libvirtError("Domain not found")
self.assertIsNone(self.compute.capacity)
@patch("computes.models.wvmHostDetails")
def test_a_down_host_is_not_read(self, details):
self.compute.status = False
self.assertIsNone(self.compute.capacity)
details.assert_not_called()
class ComputesPageTests(TestCase):
def test_the_list_shows_what_each_compute_has_given(self):
admin = get_user_model().objects.create_superuser("cap-admin", "cap@example.com", "pw")
self.client.force_login(admin)
c1 = Compute.objects.create(name="host-one", hostname="192.0.2.1", login="u", password="p", type=1)
Compute.objects.create(name="host-two", hostname="192.0.2.2", login="u", password="p", type=1)
found = {c1.id: {"cpus": 16, "memory": 64 * GIB, "vcpus": {"all": 40, "running": 24},
"assigned": {"all": 48 * GIB, "running": 32 * GIB}, "ratio": 2.5, "vms": VMS,
"pools": [pool("default", 500, 120)]}}
with patch.object(Compute, "capacity", new=property(lambda c: found.get(c.id))), \
patch.object(Compute, "status", new=True):
page = html.fromstring(self.client.get(reverse("computes")).content)
headers = [" ".join(th.text_content().split()) for th in page.xpath("//thead//th")]
self.assertEqual(headers, ["Host", "vCPUs", "Memory", "Storage", "Actions"])
one, two = page.xpath("//tbody/tr")
cells = [" ".join(td.text_content().split()) for td in one.xpath("td")]
self.assertIn("host-one", cells[0])
self.assertIn("2.5:1", cells[1])
self.assertIn("40 vCPUs on 16 CPUs 24 running", cells[1])
self.assertIn("75 % 48.0 GB / 64.0 GB", cells[2])
self.assertIn("32.0 GB running", cells[2])
self.assertIn("120.0 GB free of 500.0 GB", cells[3])
# host-two could not be read: one cell says so
self.assertIn("capacity could not be read", " ".join(two.text_content().split()))
def test_the_default_pool_shows_and_the_others_open_on_a_click(self):
admin = get_user_model().objects.create_superuser("cap-pools", "pools@example.com", "pw")
self.client.force_login(admin)
Compute.objects.create(name="host-one", hostname="192.0.2.1", login="u", password="p", type=1)
pools = [pool("default", 500, 120), pool("backup", 900, 800), pool("iso", 100, 90), pool("nfs", 2000, 1000)]
given = {"cpus": 16, "memory": 64 * GIB, "vcpus": {"all": 8, "running": 8}, "ratio": 0.5,
"assigned": {"all": 8 * GIB, "running": 8 * GIB}, "pools": pools, "vms": VMS}
with patch.object(Compute, "capacity", new=given), patch.object(Compute, "status", new=True):
page = html.fromstring(self.client.get(reverse("computes")).content)
(storage,) = page.xpath("//tbody/tr/td[4]")
shown = " ".join(" ".join(storage.xpath("./div//text()")).split())
self.assertIn("default", shown)
self.assertNotIn("backup", shown)
(more,) = storage.xpath(".//details")
self.assertEqual(" ".join(more.xpath("summary")[0].text_content().split()), "3 more pools")
self.assertEqual(more.xpath(".//div[@class='small']/text()"), ["backup", "iso", "nfs"])
class SingularTests(TestCase):
def test_one_vcpu_on_one_cpu_is_singular(self):
admin = get_user_model().objects.create_superuser("cap-one", "one@example.com", "pw")
self.client.force_login(admin)
c = Compute.objects.create(name="host-one", hostname="192.0.2.1", login="u", password="p", type=1)
given = {"cpus": 1, "memory": 4 * GIB, "vcpus": {"all": 1, "running": 1},
"assigned": {"all": GIB, "running": GIB}, "ratio": 1.0, "pools": [], "vms": VMS}
with patch.object(Compute, "capacity", new=given), patch.object(Compute, "status", new=True):
page = html.fromstring(self.client.get(reverse("computes")).content)
line = self.client.get(reverse("compute_capacity", args=[c.id])).content.decode()
(cell,) = page.xpath("//tbody/tr/td[2]")
self.assertIn("1 vCPU on 1 CPU", " ".join(cell.text_content().split()))
self.assertEqual(" ".join(line.split()), "Given to VMs: 1 vCPU (1.0:1), 1.0 GB memory.")
class NewVmDialogLineTests(TestCase):
"""The new-VM dialog loads one line per host when it opens."""
def setUp(self):
admin = get_user_model().objects.create_superuser("cap-line", "line@example.com", "pw")
self.client.force_login(admin)
self.compute = Compute.objects.create(name="host-one", hostname="192.0.2.1", login="u", password="p", type=1)
def line(self, given):
with patch.object(Compute, "capacity", new=given):
response = self.client.get(reverse("compute_capacity", args=[self.compute.id]))
self.assertEqual(response["Content-Type"], "text/plain; charset=utf-8")
return " ".join(response.content.decode().split())
def test_the_line_tells_what_the_host_has_given(self):
given = {"cpus": 16, "memory": 64 * GIB, "vcpus": {"all": 24, "running": 8},
"assigned": {"all": 48 * GIB, "running": 32 * GIB}, "ratio": 1.5,
"pools": [pool("a&b", 500, 120), pool("iso", 100, 90)]}
# plain text: a pool name is not escaped
self.assertEqual(self.line(given), "Given to VMs: 24 vCPUs (1.5:1), 48.0 GB memory. "
"Pool a&b: 120.0 GB free.")
def test_a_host_without_pools_or_capacity_says_so(self):
given = {"cpus": 2, "memory": 4 * GIB, "vcpus": {"all": 0, "running": 0},
"assigned": {"all": 0, "running": 0}, "ratio": 0, "pools": []}
self.assertEqual(self.line(given), "Given to VMs: 0 vCPUs (0.0:1), 0 bytes memory.")
self.assertEqual(self.line(None), "The capacity could not be read.")