1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 11:46:49 +00:00
Commit graph

29 commits

Author SHA1 Message Date
catborise
018ca69a3b fix(storages): a volume a running VM uses is not cloned
The copy was read while the guest wrote: a raw disk was copied with
data of different instants, and a qcow2 one failed on qemu-img's image
lock with a message that did not say why. Such a clone is refused, with
the running VMs named, when it is queued and again before it runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kk4h2TcUiaWmU5MUszYSea
2026-11-07 23:56:04 +03:00
catborise
0610c23a34 fix(api): an error's status says what it is, and its body is always {"detail"}
- libvirt errors all answered 400. A VM, pool or volume the host does not
  have now answers 404, a volume in use 409 (vrtManager.util.InUse) and a
  host that cannot be reached 503, which a client may retry.
- A template's power on or cycle and a stopped pool answered
  {"status": ...} with 409; they answer {"detail": ...}, as every other
  refusal. The schema documents these 409s (ErrorSerializer).
- The volume list and a volume of a stopped pool answered 200 with an
  empty list or a made-up volume; they answer 409.
- An API URL that names nothing answered the HTML error page; it answers
  a JSON 404 after the API's authentication.
- A session that must change its password was redirected to the HTML
  form; an API request of it gets the JSON 403 a token gets.
2026-11-07 13:29:46 +03:00
catborise
bdc8a6755c test: each test is in the tests package of the code it tests
Tests that build vrtManager objects and touch no view, form or model of
an app lived in that app; vrtManager/tests already holds such tests, so
the convention was split. They move to vrtManager/tests: the firmware,
CPU mode and PCIe port definitions of a new VM (test_create), USB and PCI
host devices, the console listener, host CPU reads, interface reads,
deletes and XML, network filters, PCI passthrough rules, node devices,
network changes, addresses and leases, pool XML, secrets, and the live
tests that use vrtManager only (disk resize, ISO, snapshots, XML, live
clone). livetest.py, the live-test helper six apps import, and the
network test driver move with them.

Others go to their app: the quota with a host down to instances, the
401/403 Logs rows (webvirtcloud's middleware) to webvirtcloud, the VM
page's tab script test to instances, novncd's own tests out of the
console views tests, the compute lock and input validation tests out of
the computes views tests, the interface views out of the forms tests.
instances/tests/test_views.py, entirely live, is test_live_views.py.

Test modules imported fixtures from each other; shared ones are now in
vrtManager/tests/fakes.py (migration fakes, node devices, IOMMU groups,
leases, a NAT network, a Ceph secret), interfaces/tests/fakes.py,
console/tests/daemons.py (novncd loaded once), livetest.wait_for and
instances/tests/create_page.BODY. Every live test checks
livetest.enabled(). The suite runs the same 2097 tests.
2026-11-06 13:48:04 +03:00
catborise
cad392f79c fix(jobs): VM create and volume clone check the user and the name again in the worker
Every other job re-checks its permission and state in precheck before any
effect; these two checked only when queued. A queued create of a user who
is no longer a superuser, or whose VM name a host took meanwhile, ran; so
did a volume clone whose name a volume made outside the panel took.
2026-11-05 16:16:47 +03:00
catborise
e79841ab1a fix(storages): a pool the host does not have goes back to the pools list
Opening /computes/<id>/storage/<pool>/ for a pool the host does not
have (or a host that does not answer) raised libvirt's error out of
the view: a 500. Now the pools list shows the reason, as the network
page does.
2026-11-03 14:53:05 +03:00
catborise
1e66367bb4 feat(operations): a job's error and warning show in the reader's language
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.
2026-11-02 23:49:45 +03:00
catborise
f77439f398 fix(storages): a refused new volume comes back in its dialog
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.
2026-10-31 18:35:09 +03:00
catborise
9d3e3bd315 feat(storages): the pool page's dialogs in the panel's dialog look
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.
2026-10-31 18:35:09 +03:00
catborise
a95ec40f69 fix(virtsecrets): a secret in use is not deleted, and a password is stored as typed
- A secret is in use when a VM names it in a disk, a host device or its
  vTPM, or a pool in its source, by UUID or by its usage within the usage
  type, as they run and as saved. The vTPM, host devices and pools' saved
  definitions were not checked before.
- A Ceph key is taken in base64, as `ceph auth get-key` prints it; any other
  value is a password or passphrase stored as typed, spaces included.
- A new secret takes its value at once; a value refused leaves no secret.
- TLS and vTPM secrets can be created; the usage has no length limit.
- Every POST redirects; the list is read once and only with its users; a
  secret, VM or pool gone meanwhile is left out.
- domain_trees() is shared by the in-use checks of secrets, network filters,
  networks and pools; the storage page's Ceph secret picker reads the same
  listing.
2026-10-29 10:10:43 +03:00
catborise
3aa198304a feat(storages): the storage pages on Bootstrap's tables, cards, badges and menus
- The pool list is a card per pool, two to a row, as the host
  Overview's Hardware and Software cards: its name with Active or
  Stopped and Autostart badges in the header, then its type, its path
  (an RBD pool's Ceph pool), its volume count and its space as the
  Overview shows it (computes/pool_bar.html, without the name again);
  a stopped pool's space is unavailable, not zero.
  wvmStorages.get_storages_info gives the path, autostart and percent
  used, which computes.views.host_pools computed for itself.
- A pool's page has a Space card (percent, used of size, of what: the
  filesystem of its path, its volume group or the Ceph cluster, which
  other pools may share; free) and a Pool card (state, autostart,
  type, path) instead of a two-column list mixing facts and forms.
  Start or Stop is its heading's button; autostart and deleting a
  stopped pool are in its menu; the confirmations stay.
- The volume table drops the row number; a volume's capacity and what
  it has allocated share a column hidden on a phone; its format is a
  badge; its actions are one group, Clone and a menu with Delete,
  named for the volume.
- "New pool", "New volume" and "Upload ISO" are labelled buttons
  instead of bare green pluses; their dialogs' titles name them.
- The New pool dialog is one form (design reviewed with Codex): the
  pool's type as radio cards, as the VM create page chooses its disk
  source (their style moves to webvirtcloud.css), one name, and only
  the chosen type's fields, the others hidden and disabled.
  - A directory's or a share's mount path follows the name
    (/var/lib/libvirt/<name>) until it is edited: no default path of
    another pool any more.
  - ISO images is a directory preset named iso; when the host has an
    iso pool, a card links to it instead.
  - Ceph offers the host's Ceph secrets by their names, not every
    secret by UUID; without one it says so and links the Secrets page.
  - LVM asks to agree that the device is erased, the server too.
  - AddStgPool checks each type's fields, a name or a path another
    pool has, an RBD secret that is not one of the host's Ceph secrets,
    and the names "." and "..". A refused pool, or one libvirt refuses,
    opens the dialog again with what was entered, the reasons beside
    the fields and focus on the first; the audit row for a taken name
    stays.
2026-10-28 19:27:21 +03:00
catborise
fa5a8c4d6e fix(storages): the pool page measures its pool fresh, shares one clone dialog and says what it cannot show
- The page lists the volumes first: that refreshes the pool, and libvirt
  measures a pool's space only when it is refreshed, so the size and
  usage shown were as of the previous refresh.
- A space that cannot be read says so instead of 0 bytes, and the
  pool's status, read from the same pool info, no longer fails the
  page after it; a stopped pool says its space and volumes are
  unavailable until it is started, instead of 0 bytes and nothing.
- The API's pool is measured after the same refresh, and a stopped
  pool lists no volumes there instead of failing.
- A clone the job refuses (a name in use, a volume gone) is a message
  on the page, not a server error.
- An ISO image is recognised by its name (storages.jobs.is_iso):
  libvirt calls its format raw, so the template's "iso" check never
  disabled its Clone button. The clone job refuses one too; the upload
  uses the same rule.
- One clone dialog serves every volume, filled and reset by the button
  that opens it, instead of one dialog per volume in the page; its
  fields have labels and its title names the dialog.
- Volume rows are searched by name only (data-search), with a message
  when none matches, in a live region for screen readers; names show
  in full. The search script loads only
  with the volume table: on a stopped pool's page it failed for want
  of a search box. An empty pool says "This pool has no volumes".
- The unused wvmStorage.get_volumes_details goes: update_volumes is
  the same.
2026-10-28 19:20:35 +03:00
catborise
726f330dd0 fix(api): host API checks what the web forms check
- An API volume takes the name and size the web form takes. Its name went
  into the volume XML unchecked, so a name could add elements such as a
  backing store pointing at another VM's disk; the name is also escaped
  there now. A size of 0 and metadata preallocation on a non-qcow2 volume
  are refused or dropped, as on the web form.
- An API compute update with an empty or null password keeps the stored
  one, as the web form does; it emptied it and cut the host off.
- The architectures endpoint answers 404 for an unknown compute instead of
  500; an unrouted copy of it is removed.
2026-10-26 17:47:18 +03:00
catborise
63aba5d9c9 fix(storages): pool pages work while a volume is cloned, failed pools are undefined
- libvirt refuses to refresh a pool while a volume is cloned in it. The pool
  page, the volume list of the VM and create pages, upload planning and the
  API no longer fail on that refusal; they use the list libvirt has.
- A new pool that does not start (unreachable NFS server, a device LVM
  cannot use) is undefined again, so the name is free for another try.
- A pool reporting no size, such as an iSCSI pool without LUNs, shows its
  page instead of a division error.
- Each clone dialog shows its own format and metadata rows, so a format
  is never shown without Convert ticked and silently ignored.
- The new-volume size is labelled in GiB and is at least 1; an inactive
  pool shows no "None" volume count.
2026-10-26 15:36:57 +03:00
catborise
b24746c1cb fix(logs): blocking a blocked user writes no row; one upload submit path
Follow-ups from the review of the audit log layers:

- Blocking a user who is already blocked, or unblocking one who is
  not, changes nothing and now writes no row, like any other edit
  that changes nothing.
- write_chunk takes the request instead of the user and an optional
  request; the view always had one, so the branch only tests used
  is gone and every chunk 0 goes through the same audited submit.
- Tests: a busy clone creates no job and its row carries the page's
  message; a later chunk that fails writes no submit row; libvirt
  refusing a network or pool delete is a failed row.
2026-10-23 14:23:32 +03:00
catborise
d5df187de4 fix(networks, storages): a name in use is refused by the page again
The create forms of networks and storage pools checked the new name
against the host's list of networks or pools, but that list holds one
dict per object, so the name never matched and the check never fired;
libvirt refused the duplicate with its own wording. The check now
compares names: the page shows its own message, nothing is sent to
libvirt, and the Logs page gets a failed row "...: add: name already
in use".
2026-10-23 14:07:47 +03:00
catborise
5b8793ad4c feat(logs): changes to networks, storage, interfaces, secrets and filters are logged
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.
2026-10-23 13:30:21 +03:00
catborise
2dee5ba41c fix(computes): a deleted compute's VMs keep their owners
Deleting a compute removed its VMs from the panel together with their
owners, while the VMs kept running on the host: added again, the host's
VMs came back with no owners. A compute's delete now treats each VM as
one that left its compute: its owners follow the same UUID on another
compute or wait in a tombstone for the retention period. One function,
forget(), does this for the refresh and for the delete; the API's
compute delete goes through the same model delete.

The delete holds the compute exclusive and locks its row before its
VMs', as the reconcile does: a reconcile, VM operation or job on the
compute makes it busy instead of losing owners to a concurrent restore
or deadlocking with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 13:24:22 +03:00
catborise
887b807451 fix: a network, pool or secret a VM needs is not stopped or deleted
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>
2026-10-22 08:34:55 +03:00
catborise
ef1faf57c8 fix(storages): an unexpected ISO upload failure is logged
The upload view sent the error of an unexpected failure to the browser
but left nothing in the server log, so whoever runs the server had no
trace of it. It is logged with its traceback now; the superuser still
sees the message.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 21:43:56 +03:00
catborise
eb828b364d fix(storages): Ceph and NFS pool values are escaped, host names may be long
create_storage_ceph and create_storage_netfs wrote the pool, host, user,
secret and format values into the pool XML unescaped and unchecked: the
page's patterns were the only guard, and a direct POST could break or
extend the XML. The values now go through util.xml_escape, as the
network code does. The page's host fields stopped at 20 characters, so
nfs01.storage.example.com could not be typed: they take a host name's
253; the Ceph pool and user fields lose the limit the server never had.
2026-10-19 20:43:45 +03:00
catborise
45b92c8638 fix(storages): the pool form in the page no longer refuses paths the server accepts
The create pool modals kept their own pattern attributes, older and
stricter than the server's check: a logical source took only lower-case
letters, digits and "/", dir and NFS paths no "." or ":", and the NFS
fields stopped at 40 characters (the form allows 100). The browser
refused /dev/mapper/vg0-data, /dev/disk/by-id/wwn-..., /srv/vm.images or
/export/vm_images.v2 before the server saw them. The path fields keep
"required" and leave the check to the server, which shows its message.
2026-10-19 16:24:57 +03:00
catborise
c8b0cb3a4a fix(storages): pool sources take real device and export paths, a leading special character is refused
The source check of a new logical or NFS pool accepted only letters,
digits and "/", which refused paths such as /dev/mapper/vg0-data,
/dev/disk/by-id/wwn-..., /dev/disk/by-path/pci-0000:00:1f.2-ata-1 or
/export/vm_images. The target check let any first character but "-"
through, so a quote, "<" or "&" reached the pool XML and libvirt failed
on it.

Target and source now share one expression: letters, digits, "/", "_",
".", ":" and "-" (not first, not right after "/"), still in linear time.
The Vagrant computes' NFS export becomes /srv/wvc-nfs, created through the
page in the live pool test.
2026-10-19 12:10:33 +03:00
catborise
baeb023da2 fix(storages): a new NFS pool starts when its mount point does not exist
create_storage_netfs started the pool without a build, so a netfs pool
whose target directory was missing failed to mount ("mount point ... does
not exist"): the page showed the error and the pool stayed defined and
inactive. It now starts with VIR_STORAGE_POOL_CREATE_WITH_BUILD, as the
dir and logical pools do, which makes the mount point.

The Vagrant computes export /srv/wvcnfs to 127.0.0.1, and the live pool
test creates an NFS pool on it at a missing mount point when
TEST_LIBVIRT_NFS_EXPORT is set.
2026-10-19 11:58:06 +03:00
catborise
f13a90c047 fix(storages): a new directory pool starts when its directory does not exist
create_storage built only logical pools, so a dir pool whose target path
was missing failed to start ("cannot open directory"): the page showed the
libvirt error and the pool stayed defined, inactive and without autostart.
The pool is now started with VIR_STORAGE_POOL_CREATE_WITH_BUILD, which
makes the directory, or for a logical pool the volume group as the
separate build call did.

A live test creates a dir pool at a missing path on the storages page;
the LVM live test still creates its logical pool the same way.
2026-10-19 11:45:51 +03:00
catborise
56b4b03500 feat(storages): an ISO upload is a job
The upload session lives in a job instead of flocks and .meta/.done
files; the job panel shows and cancels it; an upload idle for an hour is
ended and its partial file deleted; the page no longer reloads after an
upload.
2026-10-16 11:53:59 +03:00
catborise
49232c6e1d feat(storages): volume clone runs as a job 2026-10-15 09:28:43 +03:00
catborise
68812ddbdf refactor(computes): the lock helpers take claims instead of file locks 2026-10-14 23:27:57 +03:00
catborise
c970f8e853 fix(storages): keep volumes a VM still needs
libvirt deletes any volume it is asked to. The storage page and the API
deleted a VM's disk, the base image of another volume or the file a
snapshot was taken on without a check, breaking the VM or its
snapshots.

wvmStorage.del_volume now refuses a volume a domain on the compute uses
as a disk or a backing file of one, a file of a domain's snapshots, or
the backing file of another volume. The check is the one destroy
already used, moved to wvmConnect and extended to snapshot files, so
destroy also keeps the files of another VM's snapshots. The storage
page shows the refusal; the API answers 400 with it, as for other
refusals. Deleting a disk from a VM refuses after the detach when the
VM's own snapshots need the volume, and says the volume was kept.

Network disks count by their source name, pool volume disks by their
volume's path. A volume whose metadata cannot be read stops the check
instead of counting as unused; only one removed meanwhile is skipped.
The check and the delete hold the compute lock, so no VM operation
attaches the volume in between; a busy compute is a message on the page
and 409 in the API.
2026-10-13 09:13:02 +03:00
catborise
c55f52f025 test: each app keeps its tests in a tests package
131 test modules sat next to the code they test; instances alone had
51 of them beside views.py and utils.py. They move into a
tests/ package per app, and the JavaScript checks into
webvirtcloud/tests/js. Each non-empty tests.py becomes tests/test_views.py;
the empty generated stubs are removed.

Imports of the app's own modules become absolute, paths built from
__file__ go one level up, the live helper is instances.tests.livetest,
and coverage omits */tests/*. The suite discovers the same 960 tests as
before.
2026-10-12 15:17:30 +03:00