-
-
+
+
+
+ {% if instance.status == 1 %}
+ {% trans "Updates every 5 seconds." %}
+ {% else %}
+ {% trans "The VM is not running: nothing is measured." %}
+ {% endif %}
+
+ {% if request.user.is_superuser %}
+
+ {% trans "Graphs" %}
+ {% trans "Logs" %}
-
- {% for net in instance.networks %}
-
- {% endfor %}
- {% for disk in instance.disks %}
-
- {% endfor %}
-
+ {% endif %}
-
-
-
-
-
- {% trans "Date" %}
- {% trans "User" %}
- {% trans "Message" %}
- {% trans "Result" %}
-
-
-
-
- {% trans 'None' %}...
-
-
-
+
+
+
+
+
+
{% trans "CPU" %}
+ {% trans "of the host" %}
+
+
+
+
+
+
{% trans "Memory" %}
+
+
+
+
+
+
+
+ {% for net in instance.networks %}
+
+ {% trans "Network" %} · {{ net.mac }}
+
+
+
+ {% endfor %}
+ {% for disk in instance.disks %}
+
+ {% trans "Disk" %} · {{ disk.dev }}
+
+
+
+ {% endfor %}
+
+
-
+ {% if request.user.is_superuser %}
+
+
+
+
+
+ {% trans "Date" %}
+ {% trans "User" %}
+ {% trans "Message" %}
+ {% trans "Result" %}
+
+
+
+
+
+
+ {% trans "The latest rows, newest first." %}
+ {% trans "All of this VM's logs" %}
+
+
+ {% endif %}
+{% if request.user.is_superuser %}
+{% endif %}
diff --git a/instances/tests/test_stats_sample.py b/instances/tests/test_stats_sample.py
index b24d4635..1c427ad7 100644
--- a/instances/tests/test_stats_sample.py
+++ b/instances/tests/test_stats_sample.py
@@ -40,7 +40,7 @@ class StatsViewTestCase(TestCase):
self.proxy.usage.return_value = {
"cpu": 12.345,
"disks": [{"dev": "vda", "rd": 2_000_000, "wr": None}],
- "nics": [{"dev": 0, "rx": 8_000_000, "tx": 0}],
+ "nics": [{"mac": "52:54:00:00:00:01", "rx": 8_000_000, "tx": 0}],
"window": 9.96,
"sample": SAMPLE,
}
@@ -58,7 +58,7 @@ class StatsViewTestCase(TestCase):
self.assertIsNone(self.previous())
self.assertEqual((data["cpudata"], data["window"]), (12.3, 10.0))
self.assertEqual(data["blkdata"], [{"dev": "vda", "data": [2.0, None]}])
- self.assertEqual(data["netdata"], [{"dev": 0, "data": [8.0, 0.0]}])
+ self.assertEqual(data["netdata"], [{"mac": "52:54:00:00:00:01", "data": [8.0, 0.0]}])
self.stats(sample=data["sample"])
self.assertEqual(self.previous(), SAMPLE)
@@ -178,7 +178,9 @@ class StatsScriptTestCase(TestDriverVM, TestCase):
memdata: {used: 1024, total: 1024},
// vdb was attached after the page loaded
blkdata: [{dev: "vdb", data: [9, 9]}, {dev: "vda", data: [1.5, null]}],
- netdata: [{dev: 0, data: [2, 3]}]});
+ // a NIC is known by its MAC: one added later has no chart
+ netdata: [{mac: "52:54:00:00:00:99", data: [7, 7]},
+ {mac: "52:54:00:00:00:31", data: [2, 3]}]});
poll.request();
poll.onData(answer(3, "token-1"));
poll.request();
diff --git a/instances/tests/test_stats_tab.py b/instances/tests/test_stats_tab.py
new file mode 100644
index 00000000..6f8320b9
--- /dev/null
+++ b/instances/tests/test_stats_tab.py
@@ -0,0 +1,47 @@
+"""The Stats tab: a chart for the CPU, the memory and each NIC and disk, and
+the VM's log rows for superusers only, whose log view is the only one."""
+
+from types import SimpleNamespace
+
+from django.template.loader import render_to_string
+from django.test import RequestFactory, SimpleTestCase
+from lxml import html
+
+UUID = "abcdabcd-0000-0000-0000-000000000000"
+
+
+def tab(status=1, superuser=True):
+ request = RequestFactory().get("/")
+ request.user = SimpleNamespace(is_superuser=superuser)
+ instance = SimpleNamespace(
+ id=1, uuid=UUID, status=status,
+ networks=[{"mac": "52:54:00:00:00:01"}], disks=[{"dev": "vda"}, {"dev": "vdb"}],
+ )
+ return html.fromstring(render_to_string("instances/stats_tab.html", {"instance": instance}, request))
+
+
+class StatsTabTests(SimpleTestCase):
+ def test_every_device_has_a_chart_and_a_reading(self):
+ doc = tab()
+ for name in ("cpu", "mem", "net52:54:00:00:00:01", "blkvda", "blkvdb"):
+ with self.subTest(name=name):
+ self.assertTrue(doc.xpath(f"//canvas[@id='{name}Chart']"))
+ self.assertTrue(doc.xpath(f"//*[@id='{name}Now']"))
+ self.assertIn("52:54:00:00:00:01", doc.text_content())
+
+ def test_only_a_superuser_gets_the_logs(self):
+ for superuser in (True, False):
+ with self.subTest(superuser=superuser):
+ doc = tab(superuser=superuser)
+ self.assertEqual(bool(doc.xpath("//button[@data-bs-target='#logs']")), superuser)
+ self.assertEqual(bool(doc.xpath("//table[@id='logs_table']")), superuser)
+
+ def test_the_logs_link_to_all_of_the_vm_s_rows(self):
+ self.assertEqual(tab().xpath("//a[text()=\"All of this VM's logs\"]/@href"), [f"/admin/logs/?q={UUID}"])
+
+ def test_a_vm_that_is_not_running_says_nothing_is_measured(self):
+ for status, measured in ((1, True), (3, False), (5, False)):
+ with self.subTest(status=status):
+ text = tab(status=status).text_content()
+ self.assertEqual("Updates every 5 seconds." in text, measured)
+ self.assertEqual("nothing is measured" in text, not measured)
diff --git a/instances/views.py b/instances/views.py
index a443e94c..b6e91a33 100755
--- a/instances/views.py
+++ b/instances/views.py
@@ -431,7 +431,7 @@ def stats(request, pk):
"memdata": mem_usage,
# MB/s and Mbit/s
"blkdata": [{"dev": disk["dev"], "data": [mega(disk["rd"]), mega(disk["wr"])]} for disk in usage["disks"]],
- "netdata": [{"dev": nic["dev"], "data": [mega(nic["rx"]), mega(nic["tx"])]} for nic in usage["nics"]],
+ "netdata": [{"mac": nic["mac"], "data": [mega(nic["rx"]), mega(nic["tx"])]} for nic in usage["nics"]],
"timeline": time.strftime("%H:%M:%S"),
}
if "sample" in usage:
diff --git a/logs/tests/test_logs.py b/logs/tests/test_logs.py
index d4a2889a..cbd2444c 100644
--- a/logs/tests/test_logs.py
+++ b/logs/tests/test_logs.py
@@ -5,6 +5,7 @@ from django.urls import reverse
from computes.models import Compute
from instances.models import Instance
from logs.audit import write
+from logs.views import VM_LOG_ROWS
UUID = "12345678-1234-1234-1234-123456789012"
@@ -25,6 +26,13 @@ class VMLogsTestCase(TestCase):
self.assertEqual(response.status_code, 200)
return [(row["message"], row["result"]) for row in response.json()]
+ def test_only_the_latest_rows(self):
+ for n in range(VM_LOG_ROWS + 1):
+ write("admin", "c1", "test-vm", f"VM test-vm: row {n}", True, uuid=UUID)
+ shown = self.shown()
+ self.assertEqual(len(shown), VM_LOG_ROWS)
+ self.assertEqual(shown[0][0], f"VM test-vm: row {VM_LOG_ROWS}")
+
def test_rows_of_the_vm_with_their_result(self):
write("admin", "c1", "test-vm", "VM test-vm: power on", True, uuid=UUID)
write("admin", "c1", "test-vm", "VM test-vm: power off: not running", False, uuid=UUID)
diff --git a/logs/views.py b/logs/views.py
index 4c848f34..6829561b 100644
--- a/logs/views.py
+++ b/logs/views.py
@@ -6,6 +6,9 @@ from admin.decorators import superuser_only
from instances.models import Instance
from logs.models import Logs
+# the rows of a VM on its Stats tab; the Logs page has them all
+VM_LOG_ROWS = 50
+
@superuser_only
def vm_logs(request, pk):
@@ -13,7 +16,7 @@ def vm_logs(request, pk):
vm = get_object_or_404(Instance, pk=pk)
results = {None: _("Unknown"), True: _("Done"), False: _("Failed")}
logs = []
- for log in Logs.objects.filter(instance_uuid=vm.uuid).order_by("-date", "-id"):
+ for log in Logs.objects.filter(instance_uuid=vm.uuid).order_by("-date", "-id")[:VM_LOG_ROWS]:
logs.append({
"user": log.user,
"host": log.host,
diff --git a/vrtManager/instance.py b/vrtManager/instance.py
index 24ffadc2..0b579612 100644
--- a/vrtManager/instance.py
+++ b/vrtManager/instance.py
@@ -1378,7 +1378,7 @@ class wvmInstance(wvmConnect):
and target.get("dev")
):
disks.append(target.get("dev")) # blockStats takes the target name
- # all NICs, in the order of the page's charts; one without a target is not measured
+ # all NICs, known by their MAC; one without a target is not measured
nics = []
for nic in tree.findall("devices/interface"):
mac, target = nic.find("mac"), nic.find("target")
@@ -1391,7 +1391,7 @@ class wvmInstance(wvmConnect):
return {
"cpu": 0,
"disks": [{"dev": dev, "rd": 0, "wr": 0} for dev in disks],
- "nics": [{"dev": i, "rx": 0, "tx": 0} for i in range(len(nics))],
+ "nics": [{"mac": mac, "rx": 0, "tx": 0} for mac, target in nics],
}
key = [self.instance.ID(), self.host_info[2], disks, nics]
@@ -1418,8 +1418,8 @@ class wvmInstance(wvmConnect):
for dev, old, new in zip(disks, before[1], after[1])
],
"nics": [
- {"dev": i, "rx": _rate(old[0], new[0], seconds, 8), "tx": _rate(old[1], new[1], seconds, 8)}
- for i, (old, new) in enumerate(zip(before[2], after[2]))
+ {"mac": mac, "rx": _rate(old[0], new[0], seconds, 8), "tx": _rate(old[1], new[1], seconds, 8)}
+ for (mac, target), old, new in zip(nics, before[2], after[2])
],
"window": seconds,
}
@@ -1436,7 +1436,7 @@ class wvmInstance(wvmConnect):
usage = rates(before, counters) or {
"cpu": None,
"disks": [{"dev": dev, "rd": None, "wr": None} for dev in disks],
- "nics": [{"dev": i, "rx": None, "tx": None} for i in range(len(nics))],
+ "nics": [{"mac": mac, "rx": None, "tx": None} for mac, target in nics],
"window": None,
}
usage["sample"] = {"key": key, "counters": counters}
diff --git a/vrtManager/tests/test_usage.py b/vrtManager/tests/test_usage.py
index 7077d03c..0ef44192 100644
--- a/vrtManager/tests/test_usage.py
+++ b/vrtManager/tests/test_usage.py
@@ -86,8 +86,8 @@ class UsageTestCase(unittest.TestCase):
self.assertEqual(
result["disks"], [{"dev": "vda", "rd": 1000, "wr": 3000}, {"dev": "vdb", "rd": 2000, "wr": 6000}]
)
- # bits; the NIC without a target is not measured and keeps its place
- self.assertEqual(result["nics"], [{"dev": 0, "rx": None, "tx": None}, {"dev": 1, "rx": 800, "tx": 400}])
+ # bits; the NIC without a target is not measured
+ self.assertEqual(result["nics"], [{"mac": "52:54:00:00:00:01", "rx": None, "tx": None}, {"mac": "52:54:00:00:00:02", "rx": 800, "tx": 400}])
self.assertEqual(result["sample"]["counters"][3], 101.0)
def test_since_the_previous_sample_without_waiting(self):
@@ -98,7 +98,7 @@ class UsageTestCase(unittest.TestCase):
self.time.sleep.assert_not_called()
self.assertEqual((result["cpu"], result["window"]), (12.5, 9.5))
self.assertEqual(result["disks"][1], {"dev": "vdb", "rd": 2000, "wr": 6000}) # per second
- self.assertEqual(result["nics"][1], {"dev": 1, "rx": 800, "tx": 400})
+ self.assertEqual(result["nics"][1], {"mac": "52:54:00:00:00:02", "rx": 800, "tx": 400})
self.assertEqual(result["sample"]["counters"][3], 110.5)
def test_a_sample_of_another_run_host_or_devices_is_not_used(self):
@@ -169,7 +169,7 @@ class UsageTestCase(unittest.TestCase):
{
"cpu": 0,
"disks": [{"dev": "vda", "rd": 0, "wr": 0}, {"dev": "vdb", "rd": 0, "wr": 0}],
- "nics": [{"dev": 0, "rx": 0, "tx": 0}, {"dev": 1, "rx": 0, "tx": 0}],
+ "nics": [{"mac": "52:54:00:00:00:01", "rx": 0, "tx": 0}, {"mac": "52:54:00:00:00:02", "rx": 0, "tx": 0}],
},
)