mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
- A failed cancel hook logged "worker task cancel failed": it now names
the job, its kind, resource and user, with the traceback for a fault
of the panel.
- novncd logged "Console connection failed" without the VM or the user.
- django-axes wrote four INFO lines a login ("Reset 0 access
attempts"...); logins are on the Logs page, so it logs warnings only.
- The new VM form's machine types and disk buses answered 200 with an
empty object when libvirt failed, and nothing was logged: the list
stayed empty, the form could not go on and said nothing. They answer
400 with the error, as the pool's volume list does, the warning is
logged with the compute, and the form's lists show an error they get
with the page's messages.
75 lines
4.3 KiB
Python
75 lines
4.3 KiB
Python
"""The create wizard's first page sends the browser to the next page for the
|
|
chosen architecture and machine type; the URL is built from placeholders."""
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import shutil
|
|
import unittest
|
|
|
|
from computes.models import Compute
|
|
from django.conf import settings
|
|
from django.contrib.auth import get_user_model
|
|
from django.template.loader import render_to_string
|
|
from django.test import RequestFactory, TestCase
|
|
from lxml import html
|
|
|
|
SCRIPT = (settings.BASE_DIR / "static/js/create_machine.js").read_text()
|
|
|
|
|
|
def function(name):
|
|
"""The page script's function of this name."""
|
|
return re.search(rf"\nfunction {name}\(.*?\n}}\n", SCRIPT, re.S).group(0)
|
|
|
|
|
|
class CreateWizardUrlTests(TestCase):
|
|
def render_page(self):
|
|
compute = Compute.objects.create(name="cw", hostname="10.0.0.6", login="u", password="p", type=1)
|
|
request = RequestFactory().get("/")
|
|
request.user = get_user_model().objects.create_superuser("cw-admin", "cw@example.com", "pw")
|
|
return compute, html.fromstring(render_to_string("create_instance_w1.html", {"compute": compute}, request=request))
|
|
|
|
@unittest.skipUnless(shutil.which("node"), "node is not installed")
|
|
def test_arch_and_machine_names_land_in_their_segments(self):
|
|
_, page = self.render_page()
|
|
create_url = page.get_element_by_id("next").get("data-create-url")
|
|
script = ("const document = {querySelector: () => ({dataset: {createUrl: %s}})};" % json.dumps(create_url)
|
|
+ function("create_url"))
|
|
cases = {
|
|
("x86_64", "q35"): "x86_64/q35/",
|
|
("ppc64le", "pseries"): "ppc64le/pseries/", # contains "pc"
|
|
("aarch64", "virt"): "aarch64/virt/",
|
|
}
|
|
for (arch, machine), tail in cases.items():
|
|
js = "%s console.log(create_url(%r, %r));" % (script, arch, machine)
|
|
out = subprocess.run(["node", "-e", js], capture_output=True, text=True, timeout=30)
|
|
with self.subTest(arch=arch):
|
|
self.assertTrue(out.stdout.strip().endswith(f"/{tail}"), out.stdout + out.stderr)
|
|
|
|
@unittest.skipUnless(shutil.which("node"), "node is not installed")
|
|
def test_next_waits_for_a_machine_type(self):
|
|
load_machines = function("load_machines")
|
|
for machines, disabled in (([], True), (["q35"], False)):
|
|
js = ("const next = {disabled: false}, select = {dataset: {machinesUrl: '/__arch__/'}, replaceChildren() {}};"
|
|
"const document = {querySelector: sel => sel === '#next' ? next : select};"
|
|
"const Option = function () {};"
|
|
f"const $ = {{getJSON: (url, f) => {{ f({{machines: {machines!r}}}); return {{fail() {{}}}}; }}}};"
|
|
"const showLoadError = null;"
|
|
f"{load_machines} load_machines('x86_64'); console.log(next.disabled);")
|
|
out = subprocess.run(["node", "-e", js], capture_output=True, text=True, timeout=30)
|
|
with self.subTest(machines=machines):
|
|
self.assertEqual(out.stdout.strip(), str(disabled).lower(), out.stderr)
|
|
|
|
@unittest.skipUnless(shutil.which("node"), "node is not installed")
|
|
def test_a_newer_architecture_aborts_the_older_load(self):
|
|
load_machines = function("load_machines")
|
|
js = ("const next = {}, select = {dataset: {machinesUrl: '/__arch__/'}, children: ['pc'], replaceChildren(...items) { this.children = items; }};"
|
|
"const document = {querySelector: sel => sel === '#next' ? next : select};"
|
|
"const Option = function (text) { this.text = text; }; const requests = [];"
|
|
"const $ = {getJSON: (url, fill) => { const r = {fill, abort() { r.aborted = true; }, fail() { return r; }}; requests.push(r); return r; }};"
|
|
"const showLoadError = null;"
|
|
f"{load_machines} load_machines('x86_64'); const loading = select.children; load_machines('aarch64');"
|
|
"requests[1].fill({machines: ['virt']});"
|
|
"console.log(JSON.stringify({loading, aborted: requests.map(r => !!r.aborted), filled: select.children.map(o => o.text)}));")
|
|
out = subprocess.run(["node", "-e", js], capture_output=True, text=True, timeout=30)
|
|
self.assertEqual(json.loads(out.stdout), {"loading": [], "aborted": [True, False], "filled": ["virt"]}, out.stderr)
|