Skip to content

Commit

Permalink
test: Use a Q35 machine by default in tests
Browse files Browse the repository at this point in the history
This is a newer chipset and creating a virtual machine in the UI or
with virt-install will very likely use it. Thus, let's also use it in
our tests.

This requires the following changes and exceptions:

 - XML diffs related to host devices are more involved now and maybe
   less predictable. Avoid parsing XML diffs and rather do it more
   explicitly in Python code.

 - The machines now have a watchdog by default that can't be
   removed. Use the older i440fx chipsets for the relevant test, which
   supports hotpluggable watchdogs.

 - The machines no longer support IDE. Use a i440fx machine in the one
   test where that matters.

 - A Q35 machine in RHEL 8 does not allow a lot of hotplugging so we
   keep that as the default there.

 - The "linux2022" OS gets a guest agent channel by default, which
   will report a IPv6 link-local address. This affects a pixel test.
  • Loading branch information
mvollmer authored and martinpitt committed May 8, 2024
1 parent acd4b02 commit 7e810da
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 75 deletions.
8 changes: 6 additions & 2 deletions test/check-machines-disks
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class TestMachinesDisks(machineslib.VirtualMachinesCase):

# Virtio bus type should not be shown for CDROM devices
m.execute("touch /var/lib/libvirt/novell.iso")
m.execute("virsh attach-disk --domain subVmTest1 --source /var/lib/libvirt/novell.iso --target hda --type cdrom --mode readonly --persistent")
m.execute("virsh attach-disk --domain subVmTest1 --source /var/lib/libvirt/novell.iso --target hda --type cdrom --targetbus sata --mode readonly --persistent")
# virsh attach-disk want send an event for offline VM changes
b.reload()
b.enter_page('/machines')
Expand Down Expand Up @@ -1128,7 +1128,10 @@ class TestMachinesDisks(machineslib.VirtualMachinesCase):
m = self.machine
prefix = "#vm-subVmTest1-disks-adddisk"

self.createVm("subVmTest1")
# We want a "pc-i440fx" machine that supports IDE etc, and
# "linux2016" gives us one.

self.createVm("subVmTest1", os="linux2016")

# Prepare file for Custom File disk type
m.execute("touch /var/lib/libvirt/novell.iso")
Expand Down Expand Up @@ -1433,6 +1436,7 @@ class TestMachinesDisks(machineslib.VirtualMachinesCase):
).execute()

self.createVm("subVmTest2")

self.goToMainPage()
self.waitVmRow("subVmTest2")
self.goToVmPage("subVmTest2")
Expand Down
110 changes: 46 additions & 64 deletions test/check-machines-hostdevs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ import testlib
from machinesxmls import PCI_HOSTDEV, SCSI_HOST_HOSTDEV, USB_HOSTDEV, USB_HOSTDEV_NONEXISTENT


def find_usb_hostdev(root, vendor_id, product_id):
for hostdev in root.find('devices').iter('hostdev'):
source = hostdev.find('source')
if source.find('vendor').get('id') == vendor_id and source.find('product').get('id') == product_id:
return True
return False


def find_pci_hostdev(root, slot_parts):
for hostdev in root.find('devices').iter('hostdev'):
addr = hostdev.find('source').find('address')
addr_parts = [int(addr.get(a), 16) for a in ['domain', 'bus', 'slot', 'function']]
if slot_parts == addr_parts:
return True
return False


class HostDevAddDialog(object):
def __init__(
self, test_obj, connection_name, dev_type="usb_device", dev_id=0, vm_dev_id=1, remove=True, fail_message=None
Expand Down Expand Up @@ -75,8 +92,8 @@ class HostDevAddDialog(object):

def add(self):
b = self.test_obj.browser
self.run_admin(f"virsh -c qemu:///{self.connection_name} dumpxml subVmTest1 > /tmp/vmdir/vmxml1",
self.connection_name)
self._orig_xml = self.run_admin(f"virsh -c qemu:///{self.connection_name} dumpxml subVmTest1",
self.connection_name)
b.click(".pf-v5-c-modal-box__footer button:contains(Add)")
if self.fail_message:
b.wait_in_text(".pf-v5-c-modal-box__body .pf-v5-c-alert__title", self.fail_message)
Expand All @@ -92,38 +109,33 @@ class HostDevAddDialog(object):
b.wait_in_text(f"#vm-subVmTest1-hostdev-{self.vm_dev_id}-vendor", self._vendor)

def verify_backend(self):
m = self.test_obj.machine
self.run_admin(f"virsh -c qemu:///{self.connection_name} dumpxml subVmTest1 > /tmp/vmdir/vmxml2",
self.connection_name)
m.execute("diff /tmp/vmdir/vmxml1 /tmp/vmdir/vmxml2 | sed -e 's/^>//;1d' > /tmp/vmdir/vmdiff")
xml = self.run_admin(f"virsh -c qemu:///{self.connection_name} dumpxml subVmTest1", self.connection_name)
xml_root = ET.fromstring(xml)
orig_xml_root = ET.fromstring(self._orig_xml)

if self.dev_type == "usb_device":
vendor_id = m.execute("cat /tmp/vmdir/vmdiff | xmllint --xpath 'string(//hostdev/source/vendor/@id)' -").strip()
product_id = m.execute("cat /tmp/vmdir/vmdiff | xmllint --xpath 'string(//hostdev/source/product/@id)' -").strip()
# Find all devices with the expected vendor and model and
# check whether one of them has been added

output = self.run_admin(f"virsh -c qemu:///{self.connection_name} nodedev-list --cap {self.dev_type}",
self.connection_name)
devices = output.splitlines()
devices = list(filter(None, devices))
for dev in devices:
if self.dev_type == "usb_device":
self.run_admin(f"virsh -c qemu:///{self.connection_name} nodedev-dumpxml --device {dev} > /tmp/vmdir/nodedevxml",
self.connection_name)
vendor = m.execute(f"xmllint --xpath 'string(//device/capability/vendor[starts-with(@id, \"{vendor_id}\")])' - 2>&1 < /tmp/vmdir/nodedevxml")
product = m.execute(f"xmllint --xpath 'string(//device/capability/product[starts-with(@id, \"{product_id}\")])' < /tmp/vmdir/nodedevxml - 2>&1")

if vendor.strip() == self._vendor and product.strip() == self._model:
node_xml = self.run_admin(f"virsh -c qemu:///{self.connection_name} nodedev-dumpxml --device {dev}",
self.connection_name)
cap = ET.fromstring(node_xml).find('capability')
vendor = cap.find('vendor')
product = cap.find('product')
if vendor.text == self._vendor and product.text == self._model:
p_id = product.get('id')
v_id = vendor.get('id')
if find_usb_hostdev(xml_root, v_id, p_id) and not find_usb_hostdev(orig_xml_root, v_id, p_id):
return

elif self.dev_type == "pci":
domain = int(m.execute("cat /tmp/vmdir/vmdiff | xmllint --xpath 'string(//hostdev/source/address/@domain)' -"), base=16)
bus = int(m.execute("cat /tmp/vmdir/vmdiff | xmllint --xpath 'string(//hostdev/source/address/@bus)' -"), base=16)
slot = int(m.execute("cat /tmp/vmdir/vmdiff | xmllint --xpath 'string(//hostdev/source/address/@slot)' -"), base=16)
func = int(m.execute("cat /tmp/vmdir/vmdiff | xmllint --xpath 'string(//hostdev/source/address/@function)' -"), base=16)

slot_parts = re.split(r":|\.", self._slot)

if int(slot_parts[0], 16) == domain and int(slot_parts[1], 16) == bus and int(slot_parts[2], 16) == slot and int(slot_parts[3], 16) == func:
slot_parts = [int(p, 16) for p in re.split(r":|\.", self._slot)]
if find_pci_hostdev(xml_root, slot_parts) and not find_pci_hostdev(orig_xml_root, slot_parts):
return

raise Exception("Verification failed. No matching node device was found in VM's xml.")
Expand Down Expand Up @@ -284,7 +296,6 @@ class TestMachinesHostDevs(machineslib.VirtualMachinesCase):

def testHostDevAddMultipleDevices(self, connectionName='system'):
b = self.browser
m = self.machine

self.run_admin("mkdir /tmp/vmdir", connectionName)
self.addCleanup(self.run_admin, "rm -rf /tmp/vmdir/", connectionName)
Expand All @@ -307,11 +318,11 @@ class TestMachinesHostDevs(machineslib.VirtualMachinesCase):
b.set_checked(".pf-v5-c-table input[name='checkrow1']", True)
slot2 = b.text("#vm-subVmTest1-hostdevs-dialog table tbody tr:nth-child(2) td:nth-child(4) dd")

# PCI devies will be sorted in the UI by slot
# PCI devices will be sorted in the UI by slot
if slot1 > slot2:
(slot1, slot2) = (slot2, slot1)

self.run_admin(f"virsh -c qemu:///{connectionName} dumpxml subVmTest1 > /tmp/vmdir/vmxml1", connectionName)
orig_xml = self.run_admin(f"virsh -c qemu:///{connectionName} dumpxml subVmTest1", connectionName)
b.click(".pf-v5-c-modal-box__footer button:contains(Add)")
b.wait_not_present("#vm-subVmTest1-hostdevs-dialog")

Expand All @@ -321,47 +332,18 @@ class TestMachinesHostDevs(machineslib.VirtualMachinesCase):
b.wait_visible("#vm-subVmTest1-hostdev-2-product")
b.wait_in_text("#slot-2", slot2)

self.run_admin(f"virsh -c qemu:///{connectionName} dumpxml subVmTest1 > /tmp/vmdir/vmxml2", connectionName)
vm_diff = m.execute("diff /tmp/vmdir/vmxml1 /tmp/vmdir/vmxml2 | sed -e 's/^>//;1d'") # Print difference between XMLs before and after adding host devices
vm_diff = f'<root>{vm_diff}</root>' # Diff contains 2 <hostdevice> elements. Add root to ease xml parsing
xml = self.run_admin(f"virsh -c qemu:///{connectionName} dumpxml subVmTest1", connectionName)

root = ET.fromstring(vm_diff)
xml_root = ET.fromstring(xml)
orig_root = ET.fromstring(orig_xml)

hostdev1_address_elem = root[0].find('source').find('address')
hostdev2_address_elem = root[1].find('source').find('address')
slot_parts1 = [int(p, 16) for p in re.split(r":|\.", slot1)]
self.assertTrue(find_pci_hostdev(xml_root, slot_parts1))
self.assertFalse(find_pci_hostdev(orig_root, slot_parts1))

hostdev1_domain = hostdev1_address_elem.get('domain')[2:] # Remove '0x' prefix from hex number
hostdev1_bus = hostdev1_address_elem.get('bus')[2:]
hostdev1_slot = hostdev1_address_elem.get('slot')[2:]
hostdev1_function = hostdev1_address_elem.get('function')[2:]
hostdev2_domain = hostdev2_address_elem.get('domain')[2:]
hostdev2_bus = hostdev2_address_elem.get('bus')[2:]
hostdev2_slot = hostdev2_address_elem.get('slot')[2:]
hostdev2_function = hostdev2_address_elem.get('function')[2:]

slot_parts1 = re.split(r":|\.", slot1)
slot_parts2 = re.split(r":|\.", slot2)
# Cannot guarantee order of host devices in VM's XML, try in different order in case of failure
try:
self.assertEqual(slot_parts1[0], hostdev1_domain)
self.assertEqual(slot_parts1[1], hostdev1_bus)
self.assertEqual(slot_parts1[2], hostdev1_slot)
self.assertEqual(slot_parts1[3], hostdev1_function)

self.assertEqual(slot_parts2[0], hostdev2_domain)
self.assertEqual(slot_parts2[1], hostdev2_bus)
self.assertEqual(slot_parts2[2], hostdev2_slot)
self.assertEqual(slot_parts2[3], hostdev2_function)
except AssertionError:
self.assertEqual(slot_parts1[0], hostdev2_domain)
self.assertEqual(slot_parts1[1], hostdev2_bus)
self.assertEqual(slot_parts1[2], hostdev2_slot)
self.assertEqual(slot_parts1[3], hostdev2_function)

self.assertEqual(slot_parts2[0], hostdev1_domain)
self.assertEqual(slot_parts2[1], hostdev1_bus)
self.assertEqual(slot_parts2[2], hostdev1_slot)
self.assertEqual(slot_parts2[3], hostdev1_function)
slot_parts2 = [int(p, 16) for p in re.split(r":|\.", slot2)]
self.assertTrue(find_pci_hostdev(xml_root, slot_parts2))
self.assertFalse(find_pci_hostdev(orig_root, slot_parts2))


if __name__ == '__main__':
Expand Down
3 changes: 2 additions & 1 deletion test/check-machines-lifecycle
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ class TestMachinesLifecycle(machineslib.VirtualMachinesCase):
b = self.browser
m = self.machine

args = self.createVm("subVmTest1")
# We want no initial watchdog
args = self.createVm("subVmTest1", os="linux2016")

self.login_and_go("/machines", user=user, superuser=superuser)

Expand Down
2 changes: 1 addition & 1 deletion test/check-machines-networks
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ class TestMachinesNetworks(machineslib.VirtualMachinesCase):

mac = b.text("#vm-subVmTest1-network-1-mac")
next_mac = self.get_next_mac(mac)
m.execute(f'virsh attach-interface --persistent subVmTest1 bridge virbr0 --mac {next_mac}')
m.execute(f'virsh attach-interface --persistent subVmTest1 bridge virbr0 --model virtio --mac {next_mac}')

# Wait for the edit button
b.click("#vm-subVmTest1-network-1-edit-dialog")
Expand Down
9 changes: 8 additions & 1 deletion test/check-machines-nics
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class TestMachinesNICs(machineslib.VirtualMachinesCase):
# If the IP will change or get assigned after fetching the domain data the user will not see any
# changes until they refresh the page, since there is not any signal associated with this change
testlib.wait(lambda: "1" in self.machine.execute("virsh domifaddr subVmTest1 | grep 192.168.122. | wc -l"), delay=3)
if "rhel-8" not in m.image:
testlib.wait(lambda: "1" in self.machine.execute("virsh domifaddr --source agent subVmTest1 | grep fe80:: | wc -l"), delay=3)

self.goToVmPage("subVmTest1")

b.wait_in_text("#vm-subVmTest1-network-1-type", "network")
Expand All @@ -56,11 +59,15 @@ class TestMachinesNICs(machineslib.VirtualMachinesCase):
b.wait_in_text('#vm-subVmTest1-networks td[data-label="Source"]', "TAP device")
b.wait_in_text("#vm-subVmTest1-network-1-tapdevice", "vnet0")
b.wait_in_text("#vm-subVmTest1-network-1-ipv4-address", "192.168.122.")
if "rhel-8" not in m.image:
b.wait_in_text("#vm-subVmTest1-network-1-ipv6-address", "fe80::")
b.wait_visible('td[data-label="IP address"].pf-m-width-20')
b.wait_in_text("#vm-subVmTest1-network-1-state", "up")
b.assert_pixels("#vm-subVmTest1-networks",
"vm-details-nics-card",
ignore=["#vm-subVmTest1-network-1-mac", "#vm-subVmTest1-network-1-ipv4-address"],
ignore=["#vm-subVmTest1-network-1-mac",
"#vm-subVmTest1-network-1-ipv4-address",
"#vm-subVmTest1-network-1-ipv6-address"],
# MAC and IP address values are not static, and their values affect the width of their columns and neighbouring columns
# With medium layout this variable width of the columns makes pixel tests references of the table different each test run
skip_layouts=["medium", "rtl"])
Expand Down
12 changes: 8 additions & 4 deletions test/check-machines-settings
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,8 @@ class TestMachinesSettings(machineslib.VirtualMachinesCase):
b = self.browser
m = self.machine

args = self.createVm("subVmTest1")
# We want a machine without watchdog
args = self.createVm("subVmTest1", os="linux2016")

self.login_and_go("/machines")
self.waitPageInit()
Expand All @@ -473,7 +474,7 @@ class TestMachinesSettings(machineslib.VirtualMachinesCase):
b.click("#machines-cpu-modal-dialog-apply") # Save
b.wait_not_present("#machines-cpu-modal-dialog")

# Change watchdog
# Add watchdog
b.click("#vm-subVmTest1-watchdog-button")
b.wait_visible("#vm-subVmTest1-watchdog-modal")
b.click("#reset")
Expand Down Expand Up @@ -618,7 +619,10 @@ class TestMachinesSettings(machineslib.VirtualMachinesCase):
# check no watchdog is present in VM
self.assertNotIn("watchdog", m.execute("virsh dumpxml subVmTest1 --inactive"))

args = self.createVm("subVmTest1", running=False)
# We want a machine with hotpluggable watchdog, and no initial
# watchdog. Asking for os="linux2016" gives us that.

args = self.createVm("subVmTest1", running=False, os="linux2016")

self.login_and_go("/machines")
self.waitPageInit()
Expand Down Expand Up @@ -692,7 +696,7 @@ class TestMachinesSettings(machineslib.VirtualMachinesCase):
m.execute("virsh destroy subVmTest1; virsh start subVmTest1")
b.wait_not_present("#watchdog-tooltip")

args = self.createVm("subVmTest2")
args = self.createVm("subVmTest2", os="linux2016")
self.goToMainPage()
self.waitPageInit()
self.goToVmPage("subVmTest2")
Expand Down
7 changes: 6 additions & 1 deletion test/machineslib.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,14 @@ def startLibvirt(self, m):
if ! echo "$out" | grep -q 'Active.*yes'; then virsh net-start default; fi""")
m.execute(r"until virsh net-info default | grep 'Active:\s*yes'; do sleep 1; done")

def createVm(self, name, graphics='none', ptyconsole=False, running=True, memory=128, connection='system', machine=None, os="linux2016"):
def createVm(self, name, graphics='none', ptyconsole=False, running=True, memory=128, connection='system', machine=None, os=None):
m = machine or self.machine

if os is None:
# q35 in rhel-8 can't do a lot of hotplugging, so we stick
# with i440fx by default there.
os = "linux2022" if "rhel-8" not in m.image else "linux2016"

image_file = m.pull("alpine")

if connection == "system":
Expand Down

0 comments on commit 7e810da

Please sign in to comment.