From baeb023da2c274828fe0aea566494ce73461cf9f Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 19 Oct 2026 11:57:13 +0300 Subject: [PATCH] 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. --- README.md | 5 ++-- Vagrantfile | 6 +++++ storages/tests/test_live_pools.py | 45 ++++++++++++++++++++----------- vrtManager/storage.py | 3 ++- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 43a73cc5..21f4a719 100644 --- a/README.md +++ b/README.md @@ -703,19 +703,20 @@ export TEST_LIBVIRT_LOGIN=root TEST_LIBVIRT_PASSWORD= # login/password for TCP export TEST_LIBVIRT_GUEST_IMAGE=/var/lib/libvirt/images/linux.qcow2 # optional, see below export TEST_LIBVIRT_LVM_DEVICE=/dev/vdb # optional: an empty disk of 4 GiB or more on the host, wiped by the LVM tests export TEST_LIBVIRT_ISCSI_TARGET=iqn.2026-10.test.wvc:lun # optional: a target on the host's 127.0.0.1, open to any initiator, with a LUN +export TEST_LIBVIRT_NFS_EXPORT=/srv/wvcnfs # optional: a directory the host exports to its 127.0.0.1 over NFS python manage.py test ``` `TEST_LIBVIRT_GUEST_IMAGE` enables the tests that need a guest OS (disk and NIC hot-unplug, vCPU hotplug, guest agent, ACPI power off). It must be an installed, BIOS-bootable Linux qcow2 image with qemu-guest-agent, in a storage pool of the host other than `wvc-test`; the tests boot copies of it from overlays and only read the image. Shut down any VM using it first. -Two disposable compute nodes for these tests come from the `Vagrantfile`: AlmaLinux 10 with nested KVM, set up by `dev/libvirt-bootstrap.sh` (with this checkout's gstfsd), a SASL user for TCP (`wvc`, password `wvc-test`) and your `~/.ssh/id_ed25519.pub` for root; migration runs between them, and each has an empty second disk (`/dev/vdb`) for the LVM tests and serves a 1 GiB iSCSI LUN on 127.0.0.1 for the iSCSI test. They accept TLS logins (SASL) with certificates from a throwaway CA that `vagrant up` makes in `.vagrant/tls`; the TLS run takes its client certificate from there through `HOME`. It needs Vagrant with the `vagrant-libvirt` plugin, a KVM host with `qemu-kvm` and `cyrus-sasl-md5`, and a user in the `libvirt` group. On a host that also runs Docker, let the VMs' network (the bridge of `virsh net-info vagrant-libvirt`, `virbr1` here) through Docker's forward rules (`sudo iptables -I DOCKER-USER -i virbr1 -j ACCEPT` and `sudo iptables -I DOCKER-USER -o virbr1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`). +Two disposable compute nodes for these tests come from the `Vagrantfile`: AlmaLinux 10 with nested KVM, set up by `dev/libvirt-bootstrap.sh` (with this checkout's gstfsd), a SASL user for TCP (`wvc`, password `wvc-test`) and your `~/.ssh/id_ed25519.pub` for root; migration runs between them, and each has an empty second disk (`/dev/vdb`) for the LVM tests and serves a 1 GiB iSCSI LUN on 127.0.0.1 for the iSCSI test and an NFS export (`/srv/wvcnfs`) to 127.0.0.1 for the NFS pool test. They accept TLS logins (SASL) with certificates from a throwaway CA that `vagrant up` makes in `.vagrant/tls`; the TLS run takes its client certificate from there through `HOME`. It needs Vagrant with the `vagrant-libvirt` plugin, a KVM host with `qemu-kvm` and `cyrus-sasl-md5`, and a user in the `libvirt` group. On a host that also runs Docker, let the VMs' network (the bridge of `virsh net-info vagrant-libvirt`, `virbr1` here) through Docker's forward rules (`sudo iptables -I DOCKER-USER -i virbr1 -j ACCEPT` and `sudo iptables -I DOCKER-USER -o virbr1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`). ```bash vagrant up --provider=libvirt C1=$(vagrant ssh compute1 -c "hostname -I" | awk '{print $1}') C2=$(vagrant ssh compute2 -c "hostname -I" | awk '{print $1}') for h in $C1 $C2; do ssh-keygen -R $h; ssh -o StrictHostKeyChecking=accept-new root@$h true; done # new VMs, new host keys export TEST_LIBVIRT_HOST=$C1 TEST_LIBVIRT_TYPE=1 TEST_LIBVIRT_LOGIN=wvc TEST_LIBVIRT_PASSWORD=wvc-test -export TEST_LIBVIRT_SSH_HOST=$C2 TEST_LIBVIRT_SSH_LOGIN=root TEST_LIBVIRT_MIGRATE_ADDRESS=$C2 TEST_LIBVIRT_LVM_DEVICE=/dev/vdb TEST_LIBVIRT_ISCSI_TARGET=iqn.2026-10.test.wvc:lun +export TEST_LIBVIRT_SSH_HOST=$C2 TEST_LIBVIRT_SSH_LOGIN=root TEST_LIBVIRT_MIGRATE_ADDRESS=$C2 TEST_LIBVIRT_LVM_DEVICE=/dev/vdb TEST_LIBVIRT_ISCSI_TARGET=iqn.2026-10.test.wvc:lun TEST_LIBVIRT_NFS_EXPORT=/srv/wvcnfs python manage.py test HOME=$PWD/.vagrant/tls TEST_LIBVIRT_TYPE=3 TEST_LIBVIRT_SSH_HOST= python manage.py test # compute1 again, over TLS vagrant destroy -f diff --git a/Vagrantfile b/Vagrantfile index 586df600..9aaa033c 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -4,6 +4,7 @@ # key for root (SASL user wvc, password wvc-test). The empty second disk # /dev/vdb is for the LVM tests (TEST_LIBVIRT_LVM_DEVICE); each also serves a # 1 GiB iSCSI LUN on 127.0.0.1 for the iSCSI tests (TEST_LIBVIRT_ISCSI_TARGET), +# exports /srv/wvcnfs to 127.0.0.1 for the NFS pool test (TEST_LIBVIRT_NFS_EXPORT), # and accepts TLS logins (SASL) with a certificate from a throwaway CA. # The CA is made here once; each compute signs its own certificate with it. @@ -60,6 +61,11 @@ Vagrant.configure(2) do |config| targetcli /iscsi/$T/tpg1 set attribute generate_node_acls=1 demo_mode_write_protect=0 targetcli saveconfig } + dnf -y install nfs-utils + install -d /srv/wvcnfs + echo "/srv/wvcnfs 127.0.0.1(rw,no_root_squash)" > /etc/exports.d/wvc.exports + systemctl enable --now nfs-server + exportfs -r install -D -m 644 /tmp/wvc/cacert.pem /etc/pki/CA/cacert.pem install -d /etc/pki/libvirt/private echo "subjectAltName=IP:$(hostname -I | cut -d' ' -f1)" > /tmp/wvc/ext diff --git a/storages/tests/test_live_pools.py b/storages/tests/test_live_pools.py index b1127d0b..3791036e 100644 --- a/storages/tests/test_live_pools.py +++ b/storages/tests/test_live_pools.py @@ -1,6 +1,7 @@ -"""Live check that a directory pool created on the storages page starts -when its directory does not exist yet: libvirt builds it. Runs only with -TEST_LIBVIRT_HOST set.""" +"""Live check that a directory or NFS pool created on the storages page +starts when its directory does not exist yet: libvirt builds it. Runs only +with TEST_LIBVIRT_HOST set; the NFS case needs TEST_LIBVIRT_NFS_EXPORT, a +directory the host exports to its 127.0.0.1 (the Vagrant computes do).""" import os import unittest @@ -13,8 +14,7 @@ from django.urls import reverse from instances.tests import livetest from vrtManager.connection import connection_manager -POOL = livetest.PREFIX + "dir" -PATH = f"/var/lib/libvirt/{POOL}" +P = livetest.PREFIX @unittest.skipUnless(livetest.enabled(), "Set TEST_LIBVIRT_HOST to run the live libvirt tests") @@ -27,27 +27,40 @@ class LivePoolsTestCase(TestCase): type=int(os.environ.get("TEST_LIBVIRT_TYPE", 1)), ) self.conn = connection_manager.get_connection(*args.values()) - self.remove_pool() - self.addCleanup(self.remove_pool) self.compute = Compute.objects.create(name="live-pools", **args) self.client.force_login(get_user_model().objects.create_superuser("live-pools", "lp@example.com", "x")) - def remove_pool(self): + def remove_pool(self, name): try: - pool = self.conn.storagePoolLookupByName(POOL) + pool = self.conn.storagePoolLookupByName(name) except libvirt.libvirtError: return if pool.isActive(): - pool.destroy() + pool.destroy() # unmounts an NFS pool pool.delete(0) # removes the directory pool.undefine() - def test_a_missing_directory_is_built(self): - response = self.client.post( - reverse("storages", args=[self.compute.id]), - {"create": "", "name": POOL, "stg_type": "dir", "target": PATH}, + def assert_created(self, data): + self.remove_pool(data["name"]) + self.addCleanup(self.remove_pool, data["name"]) + response = self.client.post(reverse("storages", args=[self.compute.id]), {"create": "", **data}) + self.assertRedirects( + response, reverse("storage", args=[self.compute.id, data["name"]]), fetch_redirect_response=False ) - self.assertRedirects(response, reverse("storage", args=[self.compute.id, POOL]), fetch_redirect_response=False) - pool = self.conn.storagePoolLookupByName(POOL) + pool = self.conn.storagePoolLookupByName(data["name"]) self.assertTrue(pool.isActive()) self.assertTrue(pool.autostart()) + + def test_a_missing_directory_is_built(self): + self.assert_created({"name": P + "dir", "stg_type": "dir", "target": f"/var/lib/libvirt/{P}dir"}) + + @unittest.skipUnless(os.environ.get("TEST_LIBVIRT_NFS_EXPORT"), "Set TEST_LIBVIRT_NFS_EXPORT to run the NFS pool test") + def test_a_missing_mount_point_is_built(self): + self.assert_created({ + "name": P + "nfs", + "stg_type": "netfs", + "netfs_host": "127.0.0.1", + "source": os.environ["TEST_LIBVIRT_NFS_EXPORT"], + "source_format": "nfs", + "target": f"/var/lib/libvirt/{P}nfs", + }) diff --git a/vrtManager/storage.py b/vrtManager/storage.py index 5bab3afb..7cc39122 100644 --- a/vrtManager/storage.py +++ b/vrtManager/storage.py @@ -101,7 +101,8 @@ class wvmStorages(wvmConnect): """ self.define_storage(xml, 0) stg = self.get_storage(name) - stg.create(0) + # builds the mount point + stg.create(libvirt.VIR_STORAGE_POOL_CREATE_WITH_BUILD) stg.setAutostart(1) return stg