mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
test: a LUN of an iSCSI pool attaches live as a block disk
The Vagrant computes serve a 1 GiB LUN on 127.0.0.1 (targetcli). A live test, run with TEST_LIBVIRT_ISCSI_TARGET, starts a transient iSCSI pool on it and attaches the LUN from the VM page to a stopped and a running VM: both definitions get a raw block disk on its /dev/disk/by-path link.
This commit is contained in:
parent
c46212bb38
commit
8d175bbd54
3 changed files with 121 additions and 3 deletions
|
|
@ -691,19 +691,20 @@ export TEST_LIBVIRT_HOST=compute1 TEST_LIBVIRT_TYPE=2 # 1 TCP, 2 SSH, 3 TLS, 4
|
|||
export TEST_LIBVIRT_LOGIN=root TEST_LIBVIRT_PASSWORD= # login/password for TCP and TLS
|
||||
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
|
||||
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. 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. 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
|
||||
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
|
||||
python manage.py test
|
||||
vagrant destroy -f
|
||||
```
|
||||
|
|
|
|||
15
Vagrantfile
vendored
15
Vagrantfile
vendored
|
|
@ -2,7 +2,8 @@
|
|||
# that migration runs between them: AlmaLinux 10 with nested KVM, set up by
|
||||
# dev/libvirt-bootstrap.sh, with a SASL user for TCP and the host user's SSH
|
||||
# key for root (SASL user wvc, password wvc-test). The empty second disk
|
||||
# /dev/vdb is for the LVM tests (TEST_LIBVIRT_LVM_DEVICE).
|
||||
# /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).
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "almalinux/10"
|
||||
|
|
@ -29,5 +30,17 @@ Vagrant.configure(2) do |config|
|
|||
echo wvc-test | saslpasswd2 -a libvirt -p wvc
|
||||
install -d -m 700 /root/.ssh
|
||||
cat /tmp/wvc/key.pub >> /root/.ssh/authorized_keys
|
||||
dnf -y install targetcli
|
||||
systemctl enable --now target
|
||||
T=iqn.2026-10.test.wvc:lun
|
||||
targetcli ls /iscsi/$T > /dev/null 2>&1 || {
|
||||
targetcli /backstores/fileio create wvc /var/lib/wvc-iscsi.img 1G
|
||||
targetcli /iscsi create $T
|
||||
targetcli /iscsi/$T/tpg1/portals delete 0.0.0.0 3260
|
||||
targetcli /iscsi/$T/tpg1/portals create 127.0.0.1 3260
|
||||
targetcli /iscsi/$T/tpg1/luns create /backstores/fileio/wvc
|
||||
targetcli /iscsi/$T/tpg1 set attribute generate_node_acls=1 demo_mode_write_protect=0
|
||||
targetcli saveconfig
|
||||
}
|
||||
SHELL
|
||||
end
|
||||
|
|
|
|||
104
instances/tests/test_live_iscsi.py
Normal file
104
instances/tests/test_live_iscsi.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
"""Live check that a LUN of an iSCSI pool attaches to a VM.
|
||||
|
||||
An iSCSI volume's path is a /dev/disk/by-path link, not the pool target
|
||||
plus the volume name, and the pool knows no format: the LUN attaches as
|
||||
<disk type='block'> with a raw driver. Runs only with TEST_LIBVIRT_HOST
|
||||
and TEST_LIBVIRT_ISCSI_TARGET (the IQN of a target with at least one LUN
|
||||
on 127.0.0.1 of that host, open to any initiator: the Vagrant computes
|
||||
serve one) set.
|
||||
"""
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import libvirt
|
||||
from computes.models import Compute
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from lxml import etree
|
||||
from vrtManager.connection import connection_manager
|
||||
|
||||
from . import livetest
|
||||
from instances.models import Instance
|
||||
from instances.utils import refr
|
||||
|
||||
P = livetest.PREFIX
|
||||
POOL = P + "iscsi"
|
||||
# a running q35 VM hotplugs the disk into a free root port; the USB
|
||||
# controller and the balloon take two
|
||||
PCI = "<controller type='pci' index='0' model='pcie-root'/>" + "<controller type='pci' model='pcie-root-port'/>" * 3
|
||||
|
||||
|
||||
class LiveIscsiTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if not (livetest.enabled() and os.environ.get("TEST_LIBVIRT_ISCSI_TARGET")):
|
||||
raise unittest.SkipTest("Set TEST_LIBVIRT_HOST and TEST_LIBVIRT_ISCSI_TARGET to run the iSCSI tests")
|
||||
cls.compute_args = dict(
|
||||
hostname=os.environ["TEST_LIBVIRT_HOST"],
|
||||
login=os.environ.get("TEST_LIBVIRT_LOGIN", ""),
|
||||
password=os.environ.get("TEST_LIBVIRT_PASSWORD", ""),
|
||||
type=int(os.environ.get("TEST_LIBVIRT_TYPE", 4)),
|
||||
)
|
||||
cls.conn = connection_manager.get_connection(*cls.compute_args.values())
|
||||
livetest.cleanup(cls.conn)
|
||||
cls.inventory = livetest.inventory(cls.conn)
|
||||
cls.pool = cls.conn.storagePoolCreateXML(
|
||||
f"<pool type='iscsi'><name>{POOL}</name><source><host name='127.0.0.1'/>"
|
||||
f"<device path='{os.environ['TEST_LIBVIRT_ISCSI_TARGET']}'/></source>"
|
||||
"<target><path>/dev/disk/by-path</path></target></pool>",
|
||||
0,
|
||||
)
|
||||
super().setUpClass()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
try:
|
||||
livetest.cleanup(cls.conn)
|
||||
cls.pool.destroy() # transient: gone once stopped
|
||||
finally:
|
||||
super().tearDownClass()
|
||||
if livetest.inventory(cls.conn) != cls.inventory:
|
||||
raise AssertionError("Live tests changed objects outside the test namespace")
|
||||
|
||||
def setUp(self):
|
||||
self.compute = Compute.objects.create(name="live-iscsi", details="test", **self.compute_args)
|
||||
admin = get_user_model().objects.create_superuser("live_admin", "l@example.com", "pw")
|
||||
self.client.force_login(admin)
|
||||
|
||||
def tearDown(self):
|
||||
livetest.cleanup(self.conn)
|
||||
|
||||
def disks(self, dom, flags=0):
|
||||
return [
|
||||
(d.get("type"), d.find("driver").get("type"), d.find("source").get("dev"), d.find("target").get("dev"))
|
||||
for d in etree.fromstring(dom.XMLDesc(flags)).findall("devices/disk")
|
||||
]
|
||||
|
||||
def test_a_lun_attaches_as_a_block_disk(self):
|
||||
lun = self.pool.listAllVolumes()[0]
|
||||
for running in (False, True):
|
||||
with self.subTest(running=running):
|
||||
dom = self.conn.defineXML(
|
||||
f"<domain type='kvm'><name>{P}iscsi-{int(running)}</name><memory unit='MiB'>128</memory>"
|
||||
"<vcpu>1</vcpu><os><type arch='x86_64' machine='q35'>hvm</type></os><features><acpi/></features>"
|
||||
f"<devices>{PCI}</devices></domain>"
|
||||
)
|
||||
refr(self.compute)
|
||||
inst = Instance.objects.get(compute=self.compute, uuid=dom.UUIDString())
|
||||
if running:
|
||||
dom.create()
|
||||
|
||||
self.client.post(
|
||||
reverse("instances:add_existing_vol", args=[inst.id]),
|
||||
{"selected_storage": POOL, "vols": lun.name(), "bus": "virtio", "cache": "default"},
|
||||
HTTP_REFERER=reverse("instances:instance", args=[inst.id]),
|
||||
)
|
||||
|
||||
expected = [("block", "raw", lun.path(), "vda")]
|
||||
self.assertEqual(self.disks(dom, libvirt.VIR_DOMAIN_XML_INACTIVE), expected)
|
||||
self.assertEqual(self.disks(dom), expected)
|
||||
if running:
|
||||
self.assertEqual(dom.blockInfo("vda")[0], lun.info()[1]) # what the guest sees
|
||||
livetest.cleanup(self.conn)
|
||||
Loading…
Add table
Add a link
Reference in a new issue