A job stored its error and its result's warning as English text, much
of it built with the values in it (volume paths, a disk, libvirt's
error), so no catalog could translate it, and the job pages and panel
showed English whatever the language.
A job now stores them as messages: our English text, marked for
translation, and its values; libvirt's own text is a message without
values and is shown as it is. The jobs API and pages put them in the
reader's language. OperationError keeps its text and values apart for
the same reason, so a refusal raised while a job is submitted is shown
translated too. Messages one after another still read as sentences.
Errors stored before are kept as one message each, shown as they were.
The job panel on VM and storage pages wrote its buttons and notes in
English (Cancel, Open, Refresh, Dismiss, Finishing..., Files not
removed, Worker unavailable, Cancel failed) and its state badge as the
state's code, and the jobs API gave the phase untranslated. The panel
now takes its texts and the state labels from the page's settings, and
the API gives the phase in the viewer's language, as it gives the
label; the job pages show that phase.
A job that cannot be cancelled and ran past its time limit had
"(over its time limit)" written into its phase, which then matched no
translation. The phase stays as the job sets it, and the pages say the
job is past its limit.
The VM page's and the Settings page's views marked their messages with
gettext_noop, so every message they gave (a refused resize, a saved
setting, a QoS change, a busy VM) showed in English whatever the
language, though the catalogs translate them. They translate in the
request's language now; their module-level texts lazily. The Settings
page's group titles stay English in the view, as the page links them
by their slugs and translates them itself.
Two values went into translated messages as English codes: a job's
state ("... is running on this resource") and a QoS direction ("Remove
the inbound limit ..."). They are named as the pages name them.
The Chinese catalog had not been extracted since 2023: of the panel's
1749 messages, 878 were missing and 455 carried old fuzzy guesses, and
some of those marked done said something else ("Delete API token" read
"Delete SSH key").
Every message is translated again, with one glossary for the panel's
terms, placeholders and markup checked against the English, and the
panel offers Simplified Chinese again. The CI check covers it too.
One message of the VM page (the console password's help) never reached
any catalog: its escaped quotes cut it short at extraction. It is
written with single quotes now, and translated in both catalogs.
The language choice on the Settings page listed every language Django
knows, 99 of them. Six catalogs (Czech under the wrong code, German,
Spanish, French, Russian, Ukrainian) translated none of the panel's
messages, so choosing one of them, like most of the others, showed
English. The Turkish catalog lacked the four newest messages.
The panel now offers English and Turkish (LANGUAGES). The empty
catalogs and the English one, which has nothing to translate, are
removed. Turkish translates every message again. CI checks that the
offered catalogs translate every message and that their compiled
files are up to date; the README says how to update them.
With threaded workers a request waiting on a stuck host holds one
thread, but each new request to that host (a VM list's poll every 5 s,
an F5) held another until the threads ran out.
In a process at most two web requests now use one host at a time,
counted by its name whatever the login. Another waits up to the connect
timeout (5 s) for one of them to end, and is then refused as the host
not answering: VM lists show the host as not reachable, pages show the
connection error. A request counts on the host it connected to last,
until it ends, so one that reads several hosts in turn holds none it
has left. Threads outside a request (the job worker's, the consoles')
are not counted: a job holds its host for as long as it runs.
A host that is up but does not answer (a hung NFS mount, for one) keeps
a libvirt call waiting: libvirt's keepalive does not end it, since the
daemon still answers keepalives. Each such request held a whole sync
worker, and once they held every worker the panel stopped answering,
pages without libvirt too; F5 added another.
gunicorn now runs threaded workers, 4 threads each, and a worker takes
no more connections than it has threads, so one whose threads are all
busy leaves new requests to the others. A threaded worker keeps
reporting in while a thread waits, so gunicorn's timeout no longer ends
the request: the worker now exits itself when a request runs past the
timeout (600 s), and gunicorn starts a new one. Short claims expire on
that bound.
The address search runs one at a time in a process, as it did with one
request per worker.
Two disks of a VM with the same file name in different directories
were both offered the same destination pool by default. The form was
sent, and the migration job refused it only then: both copies would
be one file.
The plan now gives each such disk, by default, a pool no earlier disk
of that name is copied into, and says under its choice that another
disk has the file name. A choice that still puts them in one pool is
refused by the job, as before.
A pool whose refresh failed, one busy with a volume copy for example,
was skipped without a word, so a hand-copied image stayed missing with
no reason given. The other pools are still refreshed; each failure is
shown with libvirt's error.
The API comment no longer says listing the ISO images refreshes pools.
The VM page refreshed every active storage pool to list the ISO images
for its CD-ROM drives. A pool holding large disk images took up to
seconds to refresh, most of the page's time, on every page load.
The page now lists the ISO images libvirt already knows. Images
uploaded through the panel are known at once. For an image copied to a
pool by hand, the Removable media section has a Refresh ISO list button
that refreshes the active pools and returns to the page.
The Content-Security-Policy now also sends script-src 'self': a script
injected into a page, inline or as an event handler, does not run. The
templates have none of their own any more, and a test keeps it so.
worker-src 'self' blob: lets the XML editor (Ace) and the API docs
(Redoc) start their workers, which they make from blob: URLs. The
Swagger UI page is drf-spectacular's split view, which serves its start
script as a file instead of inline.
The host pages lose their last inline scripts: the overview's charts
(host_graphs.js), the New pool, New network and New interface dialogs
(pool_dialog.js, network_dialog.js, interface_dialog.js), a network's
and a network filter's XML editors and dialogs (network_page.js,
nwfilter_page.js, nwfilters_page.js), the secrets page's labels
(secrets_page.js, its texts from the view as usage_texts) and a pool's
page with the ISO upload (storage_page.js). Their URLs and messages are
data attributes of the elements they belong to; the upload reads its
token and address from its form. No template has an inline script or
an inline event handler any more.
The VM lists lose their inline scripts and handlers: host_groups.js
collapses a host's VMs in the grouped list, new_vm_dialog.js reads the
new-VM dialog's CPU bars and capacity lines, and console_window.js
(loaded by every page) opens a console button's window, on the lists and
on the VM page alike. A list row's Start no longer calls the please-wait
dialog, which the list pages do not have (the call only raised an
error). The VM page's URL placeholders take the __name__ form the other
pages use.
The create wizard's two pages lose their inline scripts and handlers:
create_machine.js (machine types of the chosen architecture, Next, the
XML editor) and create_instance.js (volume and network pickers, disk
source, presets and flavors, row lists, a refused form restored). Their
URLs and messages are data attributes of #select_chipset, #next and
#create-vm-form; URL parts use __name__ placeholders, so the volume list
URL is now the routed one with its trailing slash. The XML form shows
the please-wait dialog through data-please-wait. The page script tests
read the static files.
The VM page's thirteen inline script blocks and its inline handlers move
to static files: instance_page.js (tabs, OS details, clone names and
MACs, Add disk dialog, boot order, host devices, migrate plan, console
window, switches that send their form), instance_stats.js (charts and
the Logs view) and instance_status.js (the reload when the VM's state
changes). Their values and messages come as data attributes of the
elements they belong to, page-wide ones on #vm-summary; a form marked
data-please-wait shows the please-wait dialog. Three blocks that did
nothing are dropped: popovers (the page has none), the video model
preselection (the server selects it) and the delete form's poll stop
(that form is on the destroy page). The HTML sink inventory now also
reads the app's own scripts.
A step towards a Content-Security-Policy without inline scripts. The
scripts every page or many pages share now load from static files: the
error focus of base.html, the please-wait dialog, the sign-in forms'
password toggle and busy button, the jobs pages' reload while a job
runs, and the start of the job panel and of the VM list's state poll,
whose values come as json_script data. Cancel links go back through a
data-history-back attribute instead of a javascript: URL. The IE8
html5shiv/respond.js block, X-UA-Compatible and shrink-to-fit are gone:
no supported browser reads them.
Both workflows ran on a push to any branch and on its pull request, so
every commit of a pull request ran the whole matrix twice, the two runs
sharing the runners: a pull request waited about ten minutes for its
checks. A push now runs them for master only; a pull request, also one
stacked on another, is tested by its pull_request run.
The buttons of the VM page's section bar and of the Settings bar under it
sat at the start of the row and left its end empty. Both bars now share
the whole row out among their buttons (Bootstrap's nav-fill), also when
they wrap on a narrow screen.
A template's page was a VM's: it opened on Power, with only a note there
that a template is not started, while its clone form waited in a section
of Settings, next to a console, access, snapshots and stats that a VM
that never runs has no use for. A template's page now carries a Template
badge and a Clone button, opens on the clone form, and leaves out Power,
Access, Snapshot, Stats, the console settings, host devices and the
guest agent and address lines; Resize, Settings and Destroy stay for
whoever may change it. A page that no section opens shows its first.
A VM made from a template came out at the template's size and shut off,
and was then resized and started by hand. The Clone tab now asks for the
clone's vCPUs and memory, filled in with the VM's, and whether to start
it. After the copy, before the clone is recorded, it is resized with the
Resize tab's own functions and started; a step that fails is a warning
of the job, and the clone stays. The quota counts the larger of the size
asked and the VM's, so a resize that fails leaves the clone at a size it
allowed.
When the job ends the page opens the clone's, not the cloned VM's again:
a template's page, shut off, read as a clone that had not started. An
outcome kept for a warning links to it.
A libvirtError made in a test takes the message of the thread's last real
libvirt error: in a live run, after another test's closed connection,
"no marker" read "client socket is closed" and the test failed. It now
checks that the move raised that very error, as the test after it does.
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.
Since VM creation, clones, migrations and uploads run as jobs, the page
came back before they ended and the job panel then showed the outcome
with a Refresh button: a new VM, for one, showed only after a click. A
job of the page that succeeds without a warning now reloads the page,
which shows what it changed. The outcome box stays, with Refresh, for a
job that failed or warned, and when a reload would lose something: a
dialog open, a field in use, an XML edit not saved, or another job still
running, shown or awaited. A remembered job is taken when the page
renders, so the reload does not see it again.
Without a migration address libvirt sends a migrating VM to the
destination's own host name, which the source host often cannot resolve:
a live migration to a host added without one failed ("Unable to resolve
address ..."), also when the other host had one. The destination's
address on the Computes page, which the panel reaches it at, is now the
default; the migration address stays for another network. A host the
panel reaches on its loopback keeps libvirt's default.
A live migration whose source host could not resolve the destination's
name for the migration stream failed with "The host of this virtual
machine cannot be reached right now": libvirt reports that failure as a
connection error, and a job's error hides those behind that sentence so
as not to name hosts. Only superusers migrate, so the migration job now
fails with libvirt's own words, here "Unable to resolve address ...",
which points at the destination's Migration address.
The Migrate tab shows a running VM's live migration switch, on by
default. A VM shut down after the tab opened, as one is to move it shut
off, was then refused: "A shut-off VM cannot be migrated live". A
shut-off VM now moves offline whatever the request says about live, as
the tab tells for a shut-off VM; the tuning options are for a running VM
only, as before. An offline request for a running or paused VM is still
refused.
A shut-off VM with TPM state, as every Windows 11 VM, was refused for
migration: the state is a file of the source host, and an offline move
would start it there with a new TPM. Such a VM now migrates as a shut-off
VM with disks to copy does: its UEFI variables are copied, it is started
paused, its CPUs never run, migrates live, which carries the TPM state,
and is shut off on the destination. A shut-off VM with a saved state is
refused before any of that, for every way it moves: started paused, it
would resume and then lose the saved memory.
The clone tab says that a clone gets a new, empty TPM, and the destroy
page that the TPM state goes with the VM.
A VM made without a TPM, a Windows 10 to be upgraded to Windows 11, had
no way to get one but the XML editor. The Options tab has a TPM 2.0
switch, as the RNG one: it adds an emulated TPM of the host's model to
the definition for the next start, or removes it. Its state stays on the
host until the VM is undefined, so a TPM added again has it back. A TPM
the switch did not make (passthrough, 1.2, encrypted, with its own state
path) is left to the XML editor, and a host without swtpm says so.
A running VM got its new CD-ROM on SCSI, the one bus the panel could plug
in then, and Windows does not see a virtio-scsi drive before the driver
on that ISO is installed. Add CD-ROM is now a dialog that chooses the bus
and the ISO: a running VM takes USB, first, which every guest sees and
which hot-plugs, or SCSI; a shut-off one SATA (IDE on pc), SCSI or USB.
libvirt has no empty USB drive, so a USB CD-ROM takes its ISO when it is
added and is detached instead of ejected, also while the VM runs.
A USB CD-ROM is a CD drive on QEMU's usb-bot only. Where QEMU has none
(RHEL 9), libvirt took usb-storage, which shows the guest a hard disk
that Windows does not read; the drive now asks for usb-bot, and such a
host refuses it with libvirt's message.
nginx ends a proxied websocket that sends nothing for 60 seconds, and the
VNC console sends nothing while the VM's screen stays the same: an idle
console, an installer's progress screen, closed after a minute and had to
be connected again. novncd now pings the browser every 30 seconds. The SSH
tunnel of an SSH compute sends a keepalive as often, for a firewall on the
way that ends idle connections.
Windows 11 does not install without Secure Boot and a TPM 2.0, and the new
VM form offered neither: its firmware list was BIOS and one UEFI loader
guessed from file names, which on RHEL never matched the Secure Boot one.
The firmware choices are now BIOS, UEFI and UEFI Secure Boot, as the
host's libvirt reports them (its firmware features from libvirt 12.1,
before that its loaders), and libvirt picks the firmware itself: UEFI
without Secure Boot, or with Microsoft's keys enrolled. A TPM 2.0 switch,
shown when the host has swtpm, adds an emulated TPM. The create job
refuses a firmware or a TPM the host does not offer, and the API takes
both. The path-guessing helpers are gone.
A panel on another machine or in a container (the Docker setup, also on
the compute) reaches gstfsd at the compute's address, so most
installations need GSTFSD_BIND_HOST and GSTFSD_ALLOW_REMOTE; the step
named them only in its text. They now come with their own commands,
which replace any earlier lines, so a re-run or a half-set file ends with
the two.
When a VM stopped, its console's stream could end before the worker's
next check saw the VM shut off. The check then found no stream to drop and
said nothing, or, seeing the VM still running, opened the console again,
which libvirt refused, and the session ended. The worker now remembers
whether the VM ran at its last check, and a console that cannot be opened
again because the VM stopped meanwhile is a stop: the terminal says the VM
is not running and the console opens again when it starts. A new stream is
the console's before its callback is registered, which may end it at once.
The live serial test's VM now repeats its BIOS banner (it reboots right
after finding no boot disk), so a console attached late still gets one,
and the test waits for the superuser's console to show it before the VM
stops. A run of the whole live suite over the socket missed the banner of
the paused-then-resumed VM before.
The New pool form asks for the erase confirmation on a logical pool since
the storage pages were redone; the live test's pool request lacked it, so
the pool was never created and both LVM tests failed in setUp.
The Turkish catalog had not been extracted since 2024: of the panel's
1727 messages, 817 were missing and 489 carried old fuzzy guesses, so
most pages, dialogs and messages showed in English, and some fuzzy
guesses said something else.
The catalog is extracted again (obsolete entries dropped) and every
message is translated, the earlier terms kept (Sanal Makine, Sunucu,
Depolama havuzu, Birim, Anlık görüntü), placeholders and markup checked
against the English. The Settings page's names and descriptions come
from the database through migrations that did not mark them for
translation; they are marked now (gettext_noop, the stored values
unchanged), so they are in the catalog too.
Deleting or detaching a disk, deleting a network card, forcing a VM off,
reverting or deleting a snapshot, stopping or deleting a pool, a
network, an interface, a filter or a secret, and the other actions that
ask first opened the browser's yes/no box (confirm()), and the ISO
upload told its mistakes in the browser's alert().
One dialog in the base page now asks for all of them: its title and
confirming button carry the button's own words, its body the button's
question, the confirming button red for a destructive action. Confirmed,
the button is clicked again, so its form sends what it did before. A
button inside another dialog (the flavors) hides that one while asking
and shows it again after; Cancel gives the focus back, and the
confirming button acts once. The ISO upload's mistakes show in its
dialog's error box, announced.
An existing q35 VM whose PCIe root ports are all used takes no new
virtio disk or network device while it runs, and the only way to give
it more was editing its XML.
The Disks tab of a PCIe machine now counts its slots, "PCIe slots: 0
free of 7", in red when none is free, with what its next start will
have when that differs, and Add PCIe slots adds four root ports to its
persistent definition: libvirt numbers and places them, and a running
VM gets them at its next start. The no-slot advice of Add disk and Add
network device points at it.
The Add disk dialog kept Bootstrap's default 500px width when it got
its source cards, so their help and the name's hint broke over two
lines and left gaps between the fields. It now opens large, scrollable
and full screen on a phone, as the New storage pool and New network
dialogs that use the same cards.
On a q35 machine each PCIe device (a virtio disk, a network card) takes
a root port of its own, libvirt makes only as many ports as the VM is
defined with, and a running VM gets no new one: adding a virtio disk to
such a VM failed with libvirt's "No more available PCI slots".
A new q35 VM is now defined with the PCIe root and 14 root ports, as
virt-install makes them: libvirt puts the VM's devices into them and
leaves the rest free for devices added while it runs.
When a running VM has no free slot, Add disk and Add network device say
what to do instead of libvirt's words: for a disk, the scsi bus when
the VM has a SCSI controller (its disks need no slot of their own), or
adding it with the VM shut down, where libvirt makes the port; for a
network device, shutting the VM down. A new volume whose attach was
refused stays in its pool, as before, to attach as an existing volume.
The host pages' dialogs (new network, fixed address, DHCP range,
bandwidth limit, new interface, new filter, filter rule, filter clone,
new storage pool, new secret, a secret's value) and the VM page's XML
review closed with a grey filled Cancel beside their action; the
redone dialogs use an outlined Cancel, so the action is the one filled
button.
A New volume form the pool page refused, for a name with a space or a
character not allowed, redirected back with the reasons at the top of
the page and lost what was typed.
The form now posts to the pool page itself: a valid one makes the
volume and redirects as before, a refused one renders the page with
the dialog open on what was entered, the refused field marked with its
reason and focused. A pool stopped meanwhile has no dialog: its page
shows the reasons. The separate create_volume view and URL go.
The New volume, Clone image and Upload ISO dialogs of a pool set their
labels in a narrow column beside the fields and closed with a grey
Close; Clone image showed the volume's name in red and offered bare
Convert and Metadata boxes, New volume an unlabelled meta_prealloc box.
Now their labels sit above the fields, tied to them: New volume asks
for the name, with what it may hold, then the size and the format side
by side and "Preallocate metadata (qcow2)"; Clone image names the
volume in monospace, asks for the name of the copy and offers "Convert
it to another format", its format and the same preallocation; Upload
ISO asks for the ISO file and says to keep the page open while the
browser sends it. Each closes with Cancel and its action; the posted
fields are unchanged.
The Add disk, Add network device and bandwidth dialogs of the VM page
set their labels in a narrow column beside the fields and closed with
a grey Close: Add disk split "GB" over two lines and offered an
unexplained Metadata box, its existing-volume pool was a dropdown of
links that wrote a hidden field; the network list prefixed every
choice with "Network" or "Interface"; the bandwidth fields asked for
"kilobytes" where libvirt counts KiB/s and KiB.
Now their labels sit above the fields, tied to them, in two columns.
Add disk is one form whose source, New disk or Existing volume, is a
radio card as in the new VM form, the chosen source's fields shown and
sent and its action taken; the size has a GiB suffix and
"Preallocate metadata (qcow2)", the existing volume's pool is a select
that loads its volumes, the bus and the cache are shared. Add network
device picks its
source from Networks and Interfaces groups and says how its MAC is
filled; the bandwidth limit names its units and when a running VM gets
it. Move says when the host has no other pool and is disabled then.
Every dialog closes with Cancel and its action; the posted fields are
unchanged.
An outlined secondary button (Cancel, a row's ⋯ menu, the console and
VM action buttons) takes the theme's secondary colour, which sits near
the page background in many Bootswatch themes: its text was at 1.3:1
in Darkly and Materia, under 3:1 in a dozen others. It now takes the
page's own text colour and a muted border, set on the button itself
because some themes set it there and not through Bootstrap's variable;
its hover and disabled looks stay the theme's.
The disk Edit dialog showed a disk without a serial as "None" in its
Serial box, so saving it untouched gave the disk the serial "None".
The box and the format box are now empty for a missing value.
The dialog also hid most of its nine fields behind three tabs, said
nothing about a running VM, greyed the bus without a reason and its
switch labels did not toggle them. Now one body shows the disk's bus,
format and size under the title; a running VM's note that the changes
apply at its next start; the path and the Read-only and Shareable
switches in a Source card; the four performance modes in a grid; and
the bus, serial and image format folded under Device, with why the bus
is locked and that changing the format does not convert the image.
Cancel is outlined and Save the primary action.
The Edit dialog of a VM's network device selected the listed network
or interface whose name matched the device's source, and the first one
when none did: saving only a new model moved a macvtap device or a
bridge the host's lists did not show onto the first network, a bridge
named like a network became that network, and a macvtap device chosen
again lost its mode.
The dialog now offers the device's own source first, "<name>, as it
is", and posts nothing for it; the view passes no source and the
device's source element is left as it is. Choosing another source
changes it as before.
The Edit dialog of a VM's network device listed its fields in an
order of its own, named the source list "Net Source" with every option
prefixed "Network" or "Interface", said nothing about a running VM and
closed with Close and Apply.
Now it reads like the disk Edit dialog: the device's model and MAC
under the title, a running VM's note that the changes apply at its
next start, the source chosen from Networks and Interfaces groups, the
model and the NWFilter side by side, the MAC in monospace, and Cancel
and Save. The posted fields are unchanged.
The Network tab gave each network device a card with its buttons in a
footer, two cards to a row, as the disks had.
Each device is now a row of the same kind of list as the disks: eth N
with its target, the source, the IPv4 addresses and the link switch,
and Edit, Add QoS and Delete at the row's end; the device details, the
QoS limits and the macvtap note still fold out below it. A row holds
its Edit and Add QoS dialogs, so only rows sit in the list.
The Disks tab gave each disk a card: its capacity in large type, its
pool, its path and its buttons stacked, two cards to a row, so a VM
with a few disks filled the screen.
Each disk is now a row of one list: the device with its bus and format,
the capacity and the space used with a thin bar, the pool and the path,
and Edit, Move, Detach and Delete at the row's end; the device details
still fold out below it. A qcow2 file whose metadata takes it past its
capacity shows a full bar.
The pages many views share were a horizontal form whose Cancel went
back in a primary colour beside a green Save, and a delete question in
a yellow alert squeezed into half the width, which wrapped word by word
on a phone.
Both now sit in a centred column with their heading: the form's fields
with their labels above in a card, Cancel outlined and Save the primary
action; the delete page asks "Delete <name>?" in a card with a red
edge, says what deleting does, and offers Cancel and Delete.
The form for a user's rights on a VM names them as the VM's Users tab
does: Change settings, Delete the VM, and Change console settings,
which only counts together with Change settings.