mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 03:36:48 +00:00
feat(instances): the VM lists follow changes by themselves
A VM list showed what it read when it was loaded: a VM stopped from inside its guest or with virsh, or made or removed elsewhere, showed only after a reload, unlike the VM page, which reloads when its VM's state changes. The lists of all VMs and of one host's VMs now poll the states of their VMs, at once and then every 5 s as the VM page does, and reload when an answer differs from the one before. The states come from one libvirt call per host; a user who does not see hosts gets only their own VMs. The page in use (a dialog, a field, an unsaved XML edit, a job outcome shown) holds the reload until it is free; that check is shared with the job panel.
This commit is contained in:
parent
f88deb22c1
commit
eb6e6ca626
15 changed files with 276 additions and 11 deletions
76
webvirtcloud/tests/js/vm_states.test.js
Normal file
76
webvirtcloud/tests/js/vm_states.test.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Behaviour of static/js/vm_states.js (with static/js/poll.js) under a fake
|
||||
// clock, document and jQuery. Run by webvirtcloud/tests/test_poll_js.py:
|
||||
// node vm_states.test.js static/js/poll.js static/js/vm_states.js
|
||||
const fs = require("fs");
|
||||
const assert = require("assert");
|
||||
|
||||
let now = 0, timers = [], nextId = 1;
|
||||
global.setTimeout = (fn, ms) => { const id = nextId++; timers.push({ id, at: now + ms, fn }); return id; };
|
||||
global.clearTimeout = (id) => { timers = timers.filter((t) => t.id !== id); };
|
||||
function advance(ms) {
|
||||
const end = now + ms;
|
||||
for (;;) {
|
||||
timers.sort((a, b) => a.at - b.at);
|
||||
const t = timers[0];
|
||||
if (!t || t.at > end) break;
|
||||
timers.shift(); now = t.at; t.fn();
|
||||
}
|
||||
now = end;
|
||||
}
|
||||
global.document = { hidden: false, addEventListener() {}, activeElement: null, querySelector: () => null };
|
||||
global.window = { backgroundJobRunning: false, location: { reload() { window.reloads += 1; } }, reloads: 0 };
|
||||
|
||||
// each poll answers with what the test puts in `answer`
|
||||
let answer = null, asked = [];
|
||||
global.$ = { getJSON(url) {
|
||||
asked.push(url);
|
||||
const value = answer;
|
||||
const r = { always(f) { f(); return r; }, done(f) { f(value); return r; } };
|
||||
return r;
|
||||
} };
|
||||
eval(fs.readFileSync(process.argv[2], "utf8"));
|
||||
eval(fs.readFileSync(process.argv[3], "utf8"));
|
||||
|
||||
const base = { "1": { ok: true, vms: { "u-a": ["web", 1], "u-b": ["db", 5] } }, "2": { ok: false } };
|
||||
followVmStates("/instances/states/?compute=1");
|
||||
|
||||
// the first answer, asked at once, is where it starts; the same VMs in another order change nothing
|
||||
answer = { computes: base };
|
||||
advance(0);
|
||||
assert.strictEqual(asked.length, 1, "asks at once");
|
||||
answer = { computes: { "2": { ok: false }, "1": { ok: true, vms: { "u-b": ["db", 5], "u-a": ["web", 1] } } } };
|
||||
advance(5000);
|
||||
assert.deepStrictEqual(asked, ["/instances/states/?compute=1", "/instances/states/?compute=1"], "asks every 5 s");
|
||||
assert.strictEqual(window.reloads, 0, "nothing changed");
|
||||
|
||||
// a VM stopped, a new VM, a removed one, a renamed one, a host read again: each reloads
|
||||
for (const host1 of [{ ok: true, vms: { "u-a": ["web", 5], "u-b": ["db", 5] } },
|
||||
{ ok: true, vms: { "u-a": ["web", 1], "u-b": ["db", 5], "u-c": ["new", 5] } },
|
||||
{ ok: true, vms: { "u-a": ["web", 1] } }, { ok: true, vms: { "u-a": ["web2", 1], "u-b": ["db", 5] } }]) {
|
||||
window.reloads = 0;
|
||||
answer = { computes: { "1": host1, "2": { ok: false } } };
|
||||
advance(5000);
|
||||
assert.strictEqual(window.reloads, 1, JSON.stringify(host1));
|
||||
answer = { computes: base }; // the reloaded page starts again from here
|
||||
advance(5000);
|
||||
window.reloads = 0;
|
||||
}
|
||||
|
||||
// not while the page is in use or the job panel shows something: the next answer tells again
|
||||
for (const [what, set, unset] of [
|
||||
["a dialog", () => { document.querySelector = (s) => s === ".modal.show" ? {} : null; }, () => { document.querySelector = () => null; }],
|
||||
["the filter", () => { document.activeElement = { matches: () => true }; }, () => { document.activeElement = null; }],
|
||||
["a job outcome", () => { window.backgroundJobRunning = true; }, () => { window.backgroundJobRunning = false; }],
|
||||
]) {
|
||||
window.reloads = 0;
|
||||
answer = { computes: { "1": { ok: true, vms: { "u-a": ["web", 5], "u-b": ["db", 5] } }, "2": { ok: false } } };
|
||||
set();
|
||||
advance(5000);
|
||||
assert.strictEqual(window.reloads, 0, "waits for " + what);
|
||||
unset();
|
||||
advance(5000);
|
||||
assert.strictEqual(window.reloads, 1, "then reloads: " + what);
|
||||
answer = { computes: base };
|
||||
advance(5000);
|
||||
}
|
||||
console.log("ok");
|
||||
|
|
@ -32,6 +32,7 @@ ALLOWED_FOR_ANY_USER = {
|
|||
"index": "landing page",
|
||||
"instances:index": "lists only the user's own instances",
|
||||
"instances:find_address": "searches only the user's own instances",
|
||||
"instances:states": "the VM list's poll: only the user's own instances",
|
||||
"accounts:login": "login page",
|
||||
"accounts:email_otp": "part of the login flow (its own issues are tracked separately)",
|
||||
"accounts:logout": "logout",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,17 @@ class PollTestCase(unittest.TestCase):
|
|||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
@unittest.skipUnless(shutil.which("node"), "node is not installed")
|
||||
def test_vm_states_js(self):
|
||||
result = subprocess.run(
|
||||
["node", str(BASE / "webvirtcloud/tests/js/vm_states.test.js"), str(BASE / "static/js/poll.js"),
|
||||
str(BASE / "static/js/vm_states.js")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
def test_pages_poll_through_poll_js(self):
|
||||
for template in POLLING_TEMPLATES:
|
||||
with self.subTest(template=template):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue