The Logs page now records every change an admin makes to a compute's
libvirt objects, with the compute's name in the host column:
- networks: add, start, stop, delete, autostart, DHCP range, fixed
addresses, QoS and XML edits;
- storage pools: add, start, stop, delete, autostart, from web and
API; volumes: add and delete (web and API), clone and ISO upload;
- host interfaces: add, start, stop, delete;
- secrets: add, set value (never the value), delete;
- network filters: add, clone, delete, XML edits, rules and
references.
A refused or failed change is a failed row with its reason (libvirt
text through safe_text). The volume clone and upload jobs write their
row when they end; a refused submission is a failed row at once. A
volume row names the volume libvirt created (a dir pool adds .qcow2 or
.img). Volume create or delete through the API on an inactive pool
(409) is a failed row.
Network filters: the rows read "Network filter f1: add" instead of
"f1 network filter is created", and their host is the compute's name
instead of its hostname. A duplicate name or UUID no longer raises a
bare exception, and a filter XML that cannot be parsed no longer ends
in UnboundLocalError.
Stopping a network cut the running VMs on it off, deleting one or a
secret kept the VMs that name it from starting, and stopping a pool
took the disks of running VMs away, all without a word. Each is now
refused with the VMs (and, for a secret, the pools) that need it:
stopping while a running VM uses it, deleting while a VM's definition
names it. A disk given by its file path does not need its pool's
definition. The API's pool stop is refused the same way.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The usage list of the create-secret dialog came from the map that names
the usage types of existing secrets, so it offered none and tls too; the
form takes ceph, volume and iscsi only, and picking none or tls always
failed with "Select a valid choice". The dialog now lists the form's own
choices.
The edit dialog of a secret was filled with its current value, so every
readable secret's value was in the secrets page's HTML. A private
secret, or one with no value yet, cannot be read: its field held the
text "None", which is valid base64, and saving the dialog unchanged set
the secret to the three bytes it decodes to. The dialog now only writes
a new value: the field starts empty, is a password field and is
required. The page no longer reads secret values, and get_secret_value,
used only for that, is gone.
get_secret_value returned bytes, so the edit dialog showed b'cHc=' (the
bytes' repr) for the base64 value cHc=. Saving the dialog as shown fed
"b'cHc='" to b64decode, which dropped the quotes and the b and stored
other bytes: the secret silently changed (pw became m\xc1\xdc). The value
is now base64 text, and set_secret_value decodes with validate=True, so
a value that is not base64 is refused with a message instead. The field
had an invalid pattern ([a-zA-Z0-9]$+, ignored by browsers) and a 45
character limit; it takes base64 of any length.
create_secret wrote the usage value into the secret XML with an f-string,
unchecked: a quote, "<" or "&" broke the XML or added to it. The page's
pattern ([a-z0-9. ]+) was the only check and refused real values: a
volume secret's path ("/") and an iSCSI IQN ("-", ":"). The XML is now
built with lxml, so the value is text whatever it holds, and the input
keeps only required and the form's 100 characters; libvirt reports a
value it does not take.
A live test creates a volume, an iSCSI and a Ceph secret with real
values through the page.