mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-10 20:26:48 +00:00
perf(instances): a VM page does not wait for the guest agent's addresses
A running VM's page read its addresses while it rendered: the guest agent, when its channel is connected but it does not answer, holds the page (and a gunicorn worker) for its whole 10 s timeout, on every load. The page now shows them once it has loaded: vm_addresses.js asks the new instances/<pk>/addresses/ for each NIC's addresses and fills the header's IPv4 and each network card's IPv4 and IPv6.
This commit is contained in:
parent
bf736942eb
commit
aab8dbf1df
18 changed files with 600 additions and 449 deletions
35
instances/tests/js/vm_addresses.test.js
Normal file
35
instances/tests/js/vm_addresses.test.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Behaviour of static/js/vm_addresses.js with a fake document and fetch.
|
||||
// Run by instances/tests/test_vm_addresses.py:
|
||||
// node vm_addresses.test.js static/js/vm_addresses.js
|
||||
const fs = require("fs");
|
||||
const assert = require("assert");
|
||||
|
||||
function place(family, macs, empty) {
|
||||
const classes = new Set(["text-body-secondary"]);
|
||||
return { dataset: { addressFamily: family, addressMac: macs, empty }, textContent: "Reading…",
|
||||
classList: { toggle(name, on) { if (on) classes.add(name); else classes.delete(name); } }, classes };
|
||||
}
|
||||
// the header (the VM's NICs), a NIC's IPv4 and IPv6, a NIC the guest has no address on
|
||||
const header = place("ipv4", "52:54:00:00:00:01 52:54:00:00:00:02 ", "—");
|
||||
const ipv4 = place("ipv4", "52:54:00:00:00:01", "No IPv4 address known");
|
||||
const ipv6 = place("ipv6", "52:54:00:00:00:01", "unknown");
|
||||
const other = place("ipv4", "52:54:00:00:00:02", "No IPv4 address known");
|
||||
global.document = { querySelectorAll: () => [header, ipv4, ipv6, other], currentScript: { dataset: { url: "/instances/7/addresses/" } } };
|
||||
let asked = null;
|
||||
global.fetch = (url) => {
|
||||
asked = url;
|
||||
// a guest's own bridge (docker0) is not one of the VM's NICs
|
||||
return Promise.resolve({ ok: true, json: () => ({
|
||||
"52:54:00:00:00:01": { ipv4: ["192.0.2.10"], ipv6: ["2001:db8::10/64"] },
|
||||
"02:42:ac:11:00:01": { ipv4: ["172.17.0.1"], ipv6: [] } }) });
|
||||
};
|
||||
eval(fs.readFileSync(process.argv[2], "utf8"));
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(asked, "/instances/7/addresses/");
|
||||
assert.strictEqual(header.textContent, "192.0.2.10", "only the VM's NICs");
|
||||
assert.strictEqual(ipv4.textContent, "192.0.2.10");
|
||||
assert.strictEqual(ipv6.textContent, "2001:db8::10/64");
|
||||
assert.ok(!ipv4.classes.has("text-body-secondary"), "an address is not muted");
|
||||
assert.strictEqual(other.textContent, "No IPv4 address known");
|
||||
assert.ok(other.classes.has("text-body-secondary"));
|
||||
}, 0);
|
||||
|
|
@ -140,7 +140,8 @@ class PageControlsTestCase(PageControls, TestCase):
|
|||
self.assertEqual([a.text_content().strip() for a in owner_page.xpath(links)], ["Instances"])
|
||||
self.assertIn("Guest agent not connected", self.last_page)
|
||||
self.assertIn("vda 2.0\xa0GB · vdb 1.0\xa0GB", self.last_page)
|
||||
self.assertEqual(owner_page.xpath("normalize-space(//dt[.='IPv4']/following-sibling::dd)"), "—")
|
||||
# filled by its script once the page has loaded ("—" when it has none)
|
||||
self.assertEqual(owner_page.xpath("//dt[.='IPv4']/following-sibling::dd/@data-empty"), ["—"])
|
||||
|
||||
def test_superuser_sees_every_control(self):
|
||||
superuser = get_user_model().objects.create_superuser("ctl_super", "ctl_super@example.com", "x")
|
||||
|
|
|
|||
|
|
@ -82,6 +82,16 @@ class PendingEditsTestCase(TestCase):
|
|||
self.assertEqual(doc.xpath("//form[.//input[@name='qos_average']]//input[@name='qos_direction']/@value"),
|
||||
["inbound", "outbound"])
|
||||
|
||||
def test_the_page_leaves_the_addresses_to_its_script(self):
|
||||
nic = {"type": "network", "mac": "52:54:10:00:00:01", "nic": "default", "state": "up", "model": "virtio",
|
||||
"filterref": "", "target": "vnet0", "inbound": [], "outbound": []}
|
||||
doc = self.page(nics=([nic], [nic]))
|
||||
self.assertEqual(doc.xpath("//*[@data-address-family]/@data-address-family"), ["ipv4", "ipv4", "ipv6"])
|
||||
# the header's are those of the VM's NICs, not the guest's own bridges
|
||||
self.assertEqual(doc.xpath("//dt[.='IPv4']/following-sibling::dd/@data-address-mac"), ["52:54:10:00:00:01 "])
|
||||
self.assertEqual(doc.xpath("//script[contains(@src, 'vm_addresses')]/@data-url"),
|
||||
[reverse("instances:nic_addresses", args=[self.instance.id])])
|
||||
|
||||
def test_the_nics_are_rows_of_one_list_with_their_dialogs(self):
|
||||
nic = {"type": "network", "mac": "52:54:10:00:00:01", "nic": "default", "state": "up", "model": "virtio",
|
||||
"ipv4": None, "ipv6": None, "filterref": "", "target": "", "inbound": [], "outbound": []}
|
||||
|
|
|
|||
59
instances/tests/test_vm_addresses.py
Normal file
59
instances/tests/test_vm_addresses.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""A VM page does not wait for the guest agent, which may take its whole
|
||||
timeout: its script asks for the addresses once the page has loaded."""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import PropertyMock, patch
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from libvirt import libvirtError
|
||||
|
||||
from computes.models import Compute
|
||||
from instances.api.serializers import InstanceDetailsSerializer
|
||||
from instances.models import Instance
|
||||
|
||||
|
||||
class AddressesTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.client.force_login(get_user_model().objects.create_superuser("ad-admin", "ad@example.com", "x"))
|
||||
compute = Compute.objects.create(name="ad", hostname="127.0.0.1:1", login="root", password="", type=1)
|
||||
self.instance = Instance.objects.create(compute=compute, name="ad-vm", uuid="aaaaaaaa-1111-2222-3333-555555555555")
|
||||
self.url = reverse("instances:nic_addresses", args=[self.instance.id])
|
||||
|
||||
def addresses(self, **proxy):
|
||||
with patch("instances.models.wvmInstance") as wvm:
|
||||
wvm.return_value.configure_mock(**proxy)
|
||||
return self.client.get(self.url).json()
|
||||
|
||||
def test_each_nic_s_addresses_by_mac(self):
|
||||
found = {"52:54:10:00:00:01": (["192.0.2.10"], ["2001:db8::10/64"]), "52:54:10:00:00:02": (["192.0.2.20"], None)}
|
||||
self.assertEqual(self.addresses(**{"get_addresses.return_value": found}), {
|
||||
"52:54:10:00:00:01": {"ipv4": ["192.0.2.10"], "ipv6": ["2001:db8::10/64"]},
|
||||
"52:54:10:00:00:02": {"ipv4": ["192.0.2.20"], "ipv6": []}})
|
||||
|
||||
def test_a_host_that_cannot_be_read_gives_none(self):
|
||||
self.assertEqual(self.addresses(**{"get_addresses.side_effect": libvirtError("unable to connect")}), {})
|
||||
|
||||
def test_the_api_s_vm_details_keep_each_nic_s_addresses(self):
|
||||
nics = [{"mac": "52:54:10:00:00:01", "type": "network"}, {"mac": "52:54:10:00:00:02", "type": "network"}]
|
||||
with patch("instances.models.wvmInstance") as wvm, \
|
||||
patch.object(Instance, "networks", new_callable=PropertyMock, return_value=nics):
|
||||
wvm.return_value.get_addresses.return_value = {"52:54:10:00:00:01": (["192.0.2.10"], None)}
|
||||
networks = InstanceDetailsSerializer(self.instance).get_networks(self.instance)
|
||||
self.assertEqual(networks, [{"mac": "52:54:10:00:00:01", "type": "network", "ipv4": ["192.0.2.10"], "ipv6": None},
|
||||
{"mac": "52:54:10:00:00:02", "type": "network", "ipv4": None, "ipv6": None}])
|
||||
|
||||
|
||||
class AddressesScriptTestCase(unittest.TestCase):
|
||||
@unittest.skipUnless(shutil.which("node"), "node is not installed")
|
||||
def test_vm_addresses_js(self):
|
||||
base = Path(settings.BASE_DIR)
|
||||
result = subprocess.run(["node", str(base / "instances/tests/js/vm_addresses.test.js"),
|
||||
str(base / "static/js/vm_addresses.js")], capture_output=True, text=True, timeout=60)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
Loading…
Add table
Add a link
Reference in a new issue