From f8b3d3c372643e7ff417bc4d7df80de503e7e601 Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Wed, 3 Jul 2024 18:32:32 +0200 Subject: [PATCH 01/10] WIP New test: join 2 hosts into a pool FIXME: dependency on tests from another module don't seem to get properly located by pytest-dependency (--ignore-unknown-dependency required) --- jobs.py | 2 + lib/host.py | 11 ++++ .../test-sequences/uefi-82-nosr+pool.lst | 3 + tests/install/test-sequences/uefi-82-nosr.lst | 3 + tests/install/test_pool.py | 59 +++++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 tests/install/test-sequences/uefi-82-nosr+pool.lst create mode 100644 tests/install/test-sequences/uefi-82-nosr.lst create mode 100644 tests/install/test_pool.py diff --git a/jobs.py b/jobs.py index aa5e24f1..abe9e673 100755 --- a/jobs.py +++ b/jobs.py @@ -487,6 +487,8 @@ # running quicktest on zfsvol generates dangling TAP devices that are hard to # cleanup. Bug needs to be fixed before enabling quicktest on zfsvol. "tests/storage/zfsvol/test_zfsvol_sr.py::TestZfsvolVm::test_quicktest", + # not meant to be run from jobs.py (yet) + "tests/install/test_pool.py", ] # Returns the vm filename or None if a host_version is passed and matches the one specified diff --git a/lib/host.py b/lib/host.py index 1cd76667..d7569962 100644 --- a/lib/host.py +++ b/lib/host.py @@ -434,6 +434,17 @@ def yum_restore_saved_state(self): self.saved_packages_list = None self.saved_rollback_id = None + def shutdown(self, verify=False): + logging.info("Shutdown host %s" % self) + try: + self.ssh(['shutdown']) + except commands.SSHCommandFailed as e: + # ssh connection may get killed by the shutdown and terminate with an error code + if "closed by remote host" not in e.stdout: + raise + if verify: + wait_for_not(self.is_enabled, "Wait for host down") + def reboot(self, verify=False): logging.info("Reboot host %s" % self) try: diff --git a/tests/install/test-sequences/uefi-82-nosr+pool.lst b/tests/install/test-sequences/uefi-82-nosr+pool.lst new file mode 100644 index 00000000..6b02767f --- /dev/null +++ b/tests/install/test-sequences/uefi-82-nosr+pool.lst @@ -0,0 +1,3 @@ +tests/install/test.py::TestNested::test_tune_firstboot[None-uefi-821.1-host2-iso-nosr] +tests/install/test.py::TestNested::test_boot_inst[uefi-821.1-host2-iso-nosr] +tests/install/test_pool.py::test_join_pool[uefi-821.1] diff --git a/tests/install/test-sequences/uefi-82-nosr.lst b/tests/install/test-sequences/uefi-82-nosr.lst new file mode 100644 index 00000000..3f6caffe --- /dev/null +++ b/tests/install/test-sequences/uefi-82-nosr.lst @@ -0,0 +1,3 @@ +tests/install/test.py::TestNested::test_install[uefi-821.1-iso-nosr] +tests/install/test.py::TestNested::test_tune_firstboot[None-uefi-821.1-host1-iso-nosr] +tests/install/test.py::TestNested::test_boot_inst[uefi-821.1-host1-iso-nosr] diff --git a/tests/install/test_pool.py b/tests/install/test_pool.py new file mode 100644 index 00000000..e598333d --- /dev/null +++ b/tests/install/test_pool.py @@ -0,0 +1,59 @@ +import logging +import os +import pytest + +from lib import pxe +from lib.common import wait_for +from lib.pool import Pool + +from data import HOSTS_IP_CONFIG + +MAINTESTS = "tests/install/test.py::TestNested" + +# FIXME without --ignore-unknown-dependency, SKIPPED +# "because it depends on tests/install/test.py::TestNested::test_firstboot_install[uefi-821.1-host1-iso-nosr]" +@pytest.mark.usefixtures("xcpng_chained") +@pytest.mark.parametrize("mode", ( + "821.1", +)) +@pytest.mark.parametrize("firmware", ("uefi", "bios")) +@pytest.mark.continuation_of( + lambda params, firmware: [ + dict(vm="vm1", + image_test=f"{MAINTESTS}::test_firstboot_install[{firmware}-{params}-host1-iso-nosr]", + scope="session"), + dict(vm="vm2", + image_vm="vm1", + image_test=f"{MAINTESTS}::test_firstboot_install[{firmware}-{params}-host2-iso-nosr]", + scope="session"), + ], + param_mapping={"params": "mode", "firmware": "firmware"}) +def test_join_pool(firmware, mode, create_vms): + (master_vm, slave_vm) = create_vms + master_mac = master_vm.vifs()[0].param_get('MAC') + logging.info("Master VM has MAC %s", master_mac) + slave_mac = slave_vm.vifs()[0].param_get('MAC') + logging.info("Slave VM has MAC %s", slave_mac) + + master_vm.start() + slave_vm.start() + wait_for(master_vm.is_running, "Wait for master VM running") + wait_for(slave_vm.is_running, "Wait for slave VM running") + + master_vm.ip = HOSTS_IP_CONFIG['HOSTS']['DEFAULT'] + slave_vm.ip = HOSTS_IP_CONFIG['HOSTS']['host2'] + + wait_for(lambda: not os.system(f"nc -zw5 {master_vm.ip} 22"), + "Wait for ssh up on Master VM", retry_delay_secs=5) + wait_for(lambda: not os.system(f"nc -zw5 {slave_vm.ip} 22"), + "Wait for ssh up on Slave VM", retry_delay_secs=5) + + pool = Pool(master_vm.ip) + slave = Pool(slave_vm.ip).master + slave.join_pool(pool) + + slave.shutdown() + pool.master.shutdown() + + wait_for(lambda: slave_vm.is_halted(), "Wait for Slave VM to be halted") + wait_for(lambda: master_vm.is_halted(), "Wait for Master VM to be halted") From 7355621926634652363d1bbdbd6d26cd985be4bb Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Tue, 27 Aug 2024 16:47:38 +0200 Subject: [PATCH 02/10] WIP arp_clear FIXME: how useful? --- conftest.py | 1 + lib/pxe.py | 7 +++++++ tests/install/conftest.py | 2 ++ tests/install/test_pool.py | 2 ++ 4 files changed, 12 insertions(+) diff --git a/conftest.py b/conftest.py index 738b6842..1be3669f 100644 --- a/conftest.py +++ b/conftest.py @@ -169,6 +169,7 @@ def setup_host(hostname_or_ip, *, config=None): vif = host_vm.vifs()[0] mac_address = vif.param_get('MAC') logging.info("Nested host has MAC %s", mac_address) + pxe.arp_clear_for(mac_address) host_vm.start() wait_for(host_vm.is_running, "Wait for nested host VM running") diff --git a/lib/pxe.py b/lib/pxe.py index eb004a66..73cf2934 100644 --- a/lib/pxe.py +++ b/lib/pxe.py @@ -45,3 +45,10 @@ def arp_addresses_for(mac_address): ) candidate_ips = output.splitlines() return candidate_ips + +def arp_clear_for(mac_address): + for stray_ip in arp_addresses_for(mac_address): + output = ssh( + PXE_CONFIG_SERVER, + ['arp', '-d', stray_ip] + ) diff --git a/tests/install/conftest.py b/tests/install/conftest.py index c7c6cd2a..981f735d 100644 --- a/tests/install/conftest.py +++ b/tests/install/conftest.py @@ -296,6 +296,8 @@ def vm_booted_with_installer(host, remastered_iso, create_vms): host_vm.insert_cd(os.path.basename(remote_iso)) try: + pxe.arp_clear_for(mac_address) + host_vm.start() wait_for(host_vm.is_running, "Wait for host VM running") diff --git a/tests/install/test_pool.py b/tests/install/test_pool.py index e598333d..876cb791 100644 --- a/tests/install/test_pool.py +++ b/tests/install/test_pool.py @@ -35,7 +35,9 @@ def test_join_pool(firmware, mode, create_vms): slave_mac = slave_vm.vifs()[0].param_get('MAC') logging.info("Slave VM has MAC %s", slave_mac) + pxe.arp_clear_for(master_mac) master_vm.start() + pxe.arp_clear_for(slave_mac) slave_vm.start() wait_for(master_vm.is_running, "Wait for master VM running") wait_for(slave_vm.is_running, "Wait for slave VM running") From 59456523c709aacab54992127b8460d00f6b14d4 Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Thu, 5 Sep 2024 11:52:43 +0200 Subject: [PATCH 03/10] WIP test RPU FIXME: had to keep a copy of perform_upgrade() after its functionality was moved to vm_booted_with_installer fixture, must find a proper solution for that --- lib/installer.py | 54 +++++++- .../uefi-82-nosr+pool+rpu83nightly.lst | 1 + .../test-sequences/uefi-82-nosr+pool.lst | 1 - tests/install/test_pool.py | 119 ++++++++++++++++-- 4 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 tests/install/test-sequences/uefi-82-nosr+pool+rpu83nightly.lst diff --git a/lib/installer.py b/lib/installer.py index b2e00e3b..dd6010dc 100644 --- a/lib/installer.py +++ b/lib/installer.py @@ -1,8 +1,10 @@ import logging +import os import time import xml.etree.ElementTree as ET -from lib.commands import ssh, SSHCommandFailed +from lib import pxe +from lib.commands import local_cmd, scp, ssh, SSHCommandFailed from lib.common import wait_for class AnswerFile: @@ -128,6 +130,56 @@ def monitor_upgrade(*, ip): ).returncode == 1, "Wait for installer to terminate") +# FIXME essentially duplicates vm_booted_with_installer fixture +def perform_upgrade(*, iso, host_vm, host): + vif = host_vm.vifs()[0] + mac_address = vif.param_get('MAC') + logging.info("Host VM has MAC %s", mac_address) + + try: + remote_iso = host.pool.push_iso(iso) + host_vm.insert_cd(os.path.basename(remote_iso)) + + try: + pxe.arp_clear_for(mac_address) + + host_vm.start() + wait_for(host_vm.is_running, "Wait for host VM running") + + # catch host-vm IP address + wait_for(lambda: pxe.arp_addresses_for(mac_address), + "Wait for DHCP server to see Host VM in ARP tables", + timeout_secs=10 * 60) + ips = pxe.arp_addresses_for(mac_address) + logging.info("Host VM has IPs %s", ips) + assert len(ips) == 1 + host_vm.ip = ips[0] + + # host may not be up if ARP cache was filled + wait_for(lambda: local_cmd(["ping", "-c1", host_vm.ip], check=False), + "Wait for host up", timeout_secs=10 * 60, retry_delay_secs=10) + wait_for(lambda: local_cmd(["nc", "-zw5", host_vm.ip, "22"], check=False), + "Wait for ssh up on host", timeout_secs=10 * 60, retry_delay_secs=5) + + yield host_vm + + logging.info("Shutting down Host VM") + poweroff(host_vm.ip) + wait_for(host_vm.is_halted, "Wait for host VM halted") + + except Exception as e: + logging.critical("caught exception %s", e) + host_vm.shutdown(force=True) + raise + except KeyboardInterrupt: + logging.warning("keyboard interrupt") + host_vm.shutdown(force=True) + raise + + host_vm.eject_cd() + finally: + host.pool.remove_iso(remote_iso) + def monitor_restore(*, ip): # wait for "yum install" phase to start wait_for(lambda: ssh(ip, ["grep", diff --git a/tests/install/test-sequences/uefi-82-nosr+pool+rpu83nightly.lst b/tests/install/test-sequences/uefi-82-nosr+pool+rpu83nightly.lst new file mode 100644 index 00000000..af37c15e --- /dev/null +++ b/tests/install/test-sequences/uefi-82-nosr+pool+rpu83nightly.lst @@ -0,0 +1 @@ +tests/install/test_pool.py::test_pool_rpu[uefi-821.1-83nightly] diff --git a/tests/install/test-sequences/uefi-82-nosr+pool.lst b/tests/install/test-sequences/uefi-82-nosr+pool.lst index 6b02767f..cfe2d5ff 100644 --- a/tests/install/test-sequences/uefi-82-nosr+pool.lst +++ b/tests/install/test-sequences/uefi-82-nosr+pool.lst @@ -1,3 +1,2 @@ tests/install/test.py::TestNested::test_tune_firstboot[None-uefi-821.1-host2-iso-nosr] tests/install/test.py::TestNested::test_boot_inst[uefi-821.1-host2-iso-nosr] -tests/install/test_pool.py::test_join_pool[uefi-821.1] diff --git a/tests/install/test_pool.py b/tests/install/test_pool.py index 876cb791..8dd9f404 100644 --- a/tests/install/test_pool.py +++ b/tests/install/test_pool.py @@ -2,20 +2,21 @@ import os import pytest -from lib import pxe -from lib.common import wait_for +from lib import commands, installer, pxe +from lib.common import wait_for, vm_image +from lib.installer import AnswerFile from lib.pool import Pool -from data import HOSTS_IP_CONFIG +from data import HOSTS_IP_CONFIG, NFS_DEVICE_CONFIG MAINTESTS = "tests/install/test.py::TestNested" # FIXME without --ignore-unknown-dependency, SKIPPED # "because it depends on tests/install/test.py::TestNested::test_firstboot_install[uefi-821.1-host1-iso-nosr]" @pytest.mark.usefixtures("xcpng_chained") -@pytest.mark.parametrize("mode", ( - "821.1", -)) +@pytest.mark.parametrize(("orig_version", "iso_version"), [ + ("821.1", "83nightly"), +]) @pytest.mark.parametrize("firmware", ("uefi", "bios")) @pytest.mark.continuation_of( lambda params, firmware: [ @@ -27,23 +28,31 @@ image_test=f"{MAINTESTS}::test_firstboot_install[{firmware}-{params}-host2-iso-nosr]", scope="session"), ], - param_mapping={"params": "mode", "firmware": "firmware"}) -def test_join_pool(firmware, mode, create_vms): + param_mapping={"params": "orig_version", "firmware": "firmware"}) +@pytest.mark.answerfile( + lambda firmware: AnswerFile("UPGRADE").top_append( + {"TAG": "source", "type": "local"}, + {"TAG": "existing-installation", "CONTENTS": {"uefi": "nvme0n1", "bios": "sda"}[firmware]}, + ), + param_mapping={"firmware": "firmware"}) +def test_pool_rpu(host, remastered_iso, create_vms, + firmware, orig_version, iso_version): (master_vm, slave_vm) = create_vms master_mac = master_vm.vifs()[0].param_get('MAC') logging.info("Master VM has MAC %s", master_mac) slave_mac = slave_vm.vifs()[0].param_get('MAC') logging.info("Slave VM has MAC %s", slave_mac) - pxe.arp_clear_for(master_mac) master_vm.start() - pxe.arp_clear_for(slave_mac) slave_vm.start() wait_for(master_vm.is_running, "Wait for master VM running") wait_for(slave_vm.is_running, "Wait for slave VM running") master_vm.ip = HOSTS_IP_CONFIG['HOSTS']['DEFAULT'] + logging.info("Expecting master VM to have IP %s", master_vm.ip) + slave_vm.ip = HOSTS_IP_CONFIG['HOSTS']['host2'] + logging.info("Expecting slave VM to have IP %s", slave_vm.ip) wait_for(lambda: not os.system(f"nc -zw5 {master_vm.ip} 22"), "Wait for ssh up on Master VM", retry_delay_secs=5) @@ -51,11 +60,95 @@ def test_join_pool(firmware, mode, create_vms): "Wait for ssh up on Slave VM", retry_delay_secs=5) pool = Pool(master_vm.ip) + + # create pool with shared SR + slave = Pool(slave_vm.ip).master slave.join_pool(pool) - slave.shutdown() + sr = pool.master.sr_create("nfs", "NFS Shared SR", NFS_DEVICE_CONFIG, + shared=True, verify=True) + + # create and start VMs + vms = ( + pool.master.import_vm(vm_image('mini-linux-x86_64-bios'), sr_uuid=sr.uuid), + pool.master.import_vm(vm_image('mini-linux-x86_64-bios'), sr_uuid=sr.uuid), + ) + + for vm in vms: + vm.start() + + wait_for(lambda: all(vm.is_running() for vm in vms), "Wait for VMs running") + wait_for(lambda: all(vm.try_get_and_store_ip() for vm in vms), + "Wait for VM IPs", timeout_secs=5 * 60) + wait_for(lambda: all(vm.is_management_agent_up() for vm in vms), + "Wait for management agents up") + + logging.info("VMs dispatched as %s", [vm.get_residence_host().uuid for vm in vms]) + + # do RPU + + # evacuate master + vms_to_migrate = [vm for vm in vms if vm.get_residence_host().uuid == pool.master.uuid] + logging.info("Expecting migration of %s", ([vm.uuid for vm in vms_to_migrate],)) + pool.master.xe("host-evacuate", {"host": pool.master.uuid}) + wait_for(lambda: all(vm.get_residence_host().uuid != pool.master.uuid for vm in vms_to_migrate), + "Wait for VM migration") + + # upgrade master pool.master.shutdown() + wait_for(lambda: master_vm.is_halted(), "Wait for Master VM to be halted", timeout_secs=5 * 60) + installer.perform_upgrade(iso=remastered_iso, host_vm=master_vm, host=host) + pxe.arp_clear_for(master_mac) + master_vm.start() + wait_for(master_vm.is_running, "Wait for Master VM running") - wait_for(lambda: slave_vm.is_halted(), "Wait for Slave VM to be halted") - wait_for(lambda: master_vm.is_halted(), "Wait for Master VM to be halted") + wait_for(lambda: pxe.arp_addresses_for(master_mac), + "Wait for DHCP server to see Master VM in ARP tables", + timeout_secs=10 * 60) + ips = pxe.arp_addresses_for(master_mac) + logging.info("Master VM has IPs %s", ips) + assert len(ips) == 1 + master_vm.ip = ips[0] + + wait_for(lambda: not os.system(f"nc -zw5 {master_vm.ip} 22"), + "Wait for ssh back up on Master VM", retry_delay_secs=5) + wait_for(pool.master.is_enabled, "Wait for XAPI to be ready", timeout_secs=30 * 60) + + # evacuate slave + vms_to_migrate = vms + logging.info("Expecting migration of %s", ([vm.uuid for vm in vms_to_migrate],)) + pool.master.xe("host-evacuate", {"host": slave.uuid}) + wait_for(lambda: all(vm.get_residence_host().uuid != slave.uuid for vm in vms), + "Wait for VM migration") + + # upgrade slave + slave.shutdown() + wait_for(lambda: slave_vm.is_halted(), "Wait for Slave VM to be halted", timeout_secs=5 * 60) + installer.perform_upgrade(iso=remastered_iso, host_vm=slave_vm, host=host) + pxe.arp_clear_for(slave_mac) + slave_vm.start() + wait_for(slave_vm.is_running, "Wait for Slave VM running") + + wait_for(lambda: pxe.arp_addresses_for(slave_mac), + "Wait for DHCP server to see Slave VM in ARP tables", + timeout_secs=10 * 60) + ips = pxe.arp_addresses_for(slave_mac) + logging.info("Slave VM has IPs %s", ips) + assert len(ips) == 1 + slave_vm.ip = ips[0] + + wait_for(lambda: not os.system(f"nc -zw5 {slave_vm.ip} 22"), + "Wait for ssh back up on Slave VM", retry_delay_secs=5) + wait_for(slave.is_enabled, "Wait for XAPI to be ready", timeout_secs=30 * 60) + + logging.info("Migrating a VM back to slave") + vms[1].migrate(slave) + + # cleanup + + slave.shutdown() + pool.master.shutdown() + wait_for(lambda: slave_vm.is_halted(), "Wait for Slave VM to be halted", timeout_secs=5 * 60) + wait_for(lambda: master_vm.is_halted(), "Wait for Master VM to be halted", timeout_secs=5 * 60) + # FIXME destroy shared SR contents From 6b356a3f22a640c328427f2bf84d3e043da82cfe Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Fri, 30 Aug 2024 14:32:01 +0200 Subject: [PATCH 04/10] Allow installation of a 3rd host --- data.py-dist | 1 + tests/install/test.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data.py-dist b/data.py-dist index 89be8bb2..60bce6cb 100644 --- a/data.py-dist +++ b/data.py-dist @@ -43,6 +43,7 @@ HOSTS_IP_CONFIG = { 'HOSTS': { # 'DEFAULT': '192.16.0.1', # 'host2': '192.16.0.2', +# 'host3': '192.16.0.3', }, # 'NETMASK': '255.255.0.0', # 'GATEWAY': '192.16.0.254', diff --git a/tests/install/test.py b/tests/install/test.py index 79a6d52d..4d800d11 100644 --- a/tests/install/test.py +++ b/tests/install/test.py @@ -101,7 +101,7 @@ def helper_vm_with_plugged_disk(running_vm, create_vms): @pytest.mark.usefixtures("xcpng_chained") @pytest.mark.parametrize("local_sr", ("nosr", "ext", "lvm")) @pytest.mark.parametrize("source_type", ("iso", "net")) - @pytest.mark.parametrize("machine", ("host1", "host2")) + @pytest.mark.parametrize("machine", ("host1", "host2", "host3")) @pytest.mark.parametrize("version", ( "83nightly", "83rcnet", "83rc1", "83b2", "83b1", @@ -293,7 +293,7 @@ def _test_firstboot(self, create_vms, mode, *, machine='DEFAULT'): @pytest.mark.usefixtures("xcpng_chained") @pytest.mark.parametrize("local_sr", ("nosr", "ext", "lvm")) @pytest.mark.parametrize("source_type", ("iso", "net")) - @pytest.mark.parametrize("machine", ("host1", "host2")) + @pytest.mark.parametrize("machine", ("host1", "host2", "host3")) @pytest.mark.parametrize("version", ( "83nightly", "83rcnet", "83rc1", "83b2", "83b1", From 149daf6d4c8200f35d1b630c8f4156089f10f363 Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Wed, 11 Sep 2024 18:09:23 +0200 Subject: [PATCH 05/10] jobs.py: enable logging Useful to get the debug output from commands.ssh() Signed-off-by: Yann Dirson --- jobs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jobs.py b/jobs.py index abe9e673..ac9eeb49 100755 --- a/jobs.py +++ b/jobs.py @@ -2,6 +2,7 @@ import argparse import json +import logging import subprocess import sys from lib.commands import ssh @@ -712,6 +713,7 @@ def action_run(args): sys.exit(1) def main(): + logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.DEBUG) parser = argparse.ArgumentParser(description="Manage test jobs") parser.add_argument("-v", "--host-version", help="host version to match VM filters.") subparsers = parser.add_subparsers(dest="action", metavar="action") From fa0069c5437e84e58d49911523759910211207bf Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Wed, 25 Sep 2024 11:47:42 +0200 Subject: [PATCH 06/10] local_cmd: fix error display Error code would be concatenated with command output without even a space. Signed-off-by: Yann Dirson --- lib/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commands.py b/lib/commands.py index 78891fbd..5330a7af 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -216,7 +216,7 @@ def local_cmd(cmd, check=True, decode=True): errorcode_msg = "" if res.returncode == 0 else " - Got error code: %s" % res.returncode command = " ".join(cmd) - logging.debug(f"[local] {command}{errorcode_msg}{_ellide_log_lines(output_for_logs)}") + logging.debug(f"[local] {command}{errorcode_msg}, output: {_ellide_log_lines(output_for_logs)}") if res.returncode and check: raise LocalCommandFailed(res.returncode, output_for_logs, command) From c47596b2019b2b716c79c7b1c8f220a9a8dc6c48 Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Thu, 26 Sep 2024 13:10:27 +0200 Subject: [PATCH 07/10] ssh: simplify addition of ssh options Signed-off-by: Yann Dirson --- lib/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/commands.py b/lib/commands.py index 5330a7af..79931110 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -62,14 +62,14 @@ def _ellide_log_lines(log): def _ssh(hostname_or_ip, cmd, check, simple_output, suppress_fingerprint_warnings, background, target_os, decode, options): opts = list(options) - opts.append('-o "BatchMode yes"') + opts.append('-o BatchMode=yes') if suppress_fingerprint_warnings: # Suppress warnings and questions related to host key fingerprints # because on a test network IPs get reused, VMs are reinstalled, etc. # Based on https://unix.stackexchange.com/a/365976/257493 - opts.append('-o "StrictHostKeyChecking no"') - opts.append('-o "LogLevel ERROR"') - opts.append('-o "UserKnownHostsFile /dev/null"') + opts.extend(['-o StrictHostKeyChecking=no', + '-o LogLevel=ERROR', + '-o UserKnownHostsFile=/dev/null']) command = " ".join(cmd) if background and target_os != "windows": From 0b48827bfd624fae219833e90a575b9e41d8156a Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Mon, 30 Sep 2024 16:14:28 +0200 Subject: [PATCH 08/10] WIP indirect upgrades --- scripts/gen-indirect-installs.sh | 32 ++++++++++++++++++++++++++++++++ scripts/lib.bash | 2 ++ 2 files changed, 34 insertions(+) create mode 100755 scripts/gen-indirect-installs.sh diff --git a/scripts/gen-indirect-installs.sh b/scripts/gen-indirect-installs.sh new file mode 100755 index 00000000..4aaf9e7c --- /dev/null +++ b/scripts/gen-indirect-installs.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -e + +# creation of reference installations to be used for basis of upgrade tests + +# * needs at least --hosts=$NEST +# * ch821 and xs8 needs ISO in the cache + +. $(dirname $0)/lib.bash + +TESTCLASS="tests/install/test.py::TestNested" +for conf in uefi-75+821.1-iso-ext; do + IFS=- read fw versions media sr < <(echo "$conf") + IFS=+ read origversion stepversion < <(echo "$versions") + TESTS=( + $TESTCLASS::test_install[$fw-$origversion-$media-$sr] + $TESTCLASS::test_tune_firstboot[None-$fw-$origversion-host1-$media-$sr] + $TESTCLASS::test_boot_inst[$fw-$origversion-host1-$media-$sr] + + $TESTCLASS::test_upgrade[$fw-$origversion-$stepversion-host1-$media-$sr] + $TESTCLASS::test_boot_upg[$fw-$origversion-$stepversion-host1-$media-$sr] + + #$TESTCLASS::test_upgrade[$fw-$origversion-$stepversion-83nightly-host1-$media-$sr] + ) + run_pytest "$conf" \ + --log-file=test-genref-$conf.log \ + --reruns=5 --only-rerun=TimeoutError \ + "$@" \ + "${TESTS[@]}" +done + +report_failures diff --git a/scripts/lib.bash b/scripts/lib.bash index 4ae2d5c9..312ff16e 100644 --- a/scripts/lib.bash +++ b/scripts/lib.bash @@ -46,6 +46,8 @@ run_pytest() { # reference configurations, to be upgraded to nightly +# FIXME this is also where we take the list to create all ref +# installs, 75/76 should be separated REFVERSIONS=( 83rc1 83b2 83b1 821.1 81 80 From 8f6094d155693eb0d4fe0560175e3be1c0af12b3 Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Mon, 30 Sep 2024 19:13:08 +0200 Subject: [PATCH 09/10] WIP installation on a disk with a Dell utility partition --- tests/install/conftest.py | 28 ++++++++++++++++++++++++++-- tests/install/test.py | 24 ++++++++++++++---------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/tests/install/conftest.py b/tests/install/conftest.py index 981f735d..9a23a086 100644 --- a/tests/install/conftest.py +++ b/tests/install/conftest.py @@ -116,7 +116,19 @@ def installer_iso(request): @pytest.fixture(scope='function') def install_disk(request): firmware = request.getfixturevalue("firmware") - yield {"uefi": "nvme0n1", "bios": "sda"}[firmware] + if firmware.startswith("uefi"): + yield "nvme0n1" + elif firmware.startswith("bios"): + yield "sda" + else: + assert False, f"unknown firmware {firmware!r}" + +@pytest.fixture(scope='function') +def answerfile_maybe_tweak_parttable(request, answerfile): + firmware = request.getfixturevalue("firmware") + if firmware.endswith("+dell"): + answerfile.top_append(dict(TAG="script", stage="installation-start", + type="url", CONTENTS="file:///root/preinstall-utilitypart.sh")) # Remasters the ISO sepecified by `installer_iso` mark, with: # - network and ssh support activated, and .ssh/authorized_key so tests can @@ -131,7 +143,7 @@ def install_disk(request): # in contexts where the same IP is reused by successively different MACs # (when cloning VMs from cache) @pytest.fixture(scope='function') -def remastered_iso(installer_iso, answerfile): +def remastered_iso(installer_iso, answerfile, install_disk): iso_file = installer_iso['iso'] unsigned = installer_iso['unsigned'] @@ -227,6 +239,18 @@ def remastered_iso(installer_iso, answerfile): chmod +x "$INSTALLIMG/etc/init.d/S12test-pingpxe" fi +cat > "$INSTALLIMG/root/preinstall-utilitypart.sh" <<'EOF' +#!/bin/sh +set -ex + +# Dell utility partition +sgdisk --zap-all /dev/{install_disk} +sfdisk /dev/{install_disk} << 'EOP' +unit: sectors +p1 : start= 2048, size= 32768, Id=de +EOP +EOF + cat > "$INSTALLIMG/root/postinstall.sh" <<'EOF' #!/bin/sh set -ex diff --git a/tests/install/test.py b/tests/install/test.py index 4d800d11..8620afda 100644 --- a/tests/install/test.py +++ b/tests/install/test.py @@ -32,7 +32,7 @@ class TestNested: "xs8", "ch821.1", "xs70", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) @pytest.mark.vm_definitions( lambda firmware: dict( name="vm1", @@ -52,6 +52,7 @@ class TestNested: dict(param_name="platform", key="device-model", value="qemu-upstream-uefi"), ), "bios": (), + "bios+dell": (), }[firmware], vdis=[dict(name="vm1 system disk", size="100GiB", device="xvda", userdevice="0")], cd_vbd=dict(device="xvdd", userdevice="3"), @@ -73,7 +74,8 @@ class TestNested: param_mapping={"install_disk": "install_disk", "local_sr": "local_sr", "source_type": "source_type", "version": "iso_version", }) - def test_install(self, vm_booted_with_installer, install_disk, + def test_install(self, answerfile_maybe_tweak_parttable, + vm_booted_with_installer, install_disk, firmware, iso_version, source_type, local_sr): host_vm = vm_booted_with_installer installer.monitor_install(ip=host_vm.ip) @@ -111,7 +113,7 @@ def helper_vm_with_plugged_disk(running_vm, create_vms): "ch821.1", "xs8", "xs70", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) @pytest.mark.continuation_of( lambda version, firmware, local_sr, source_type: [dict( vm="vm1", @@ -123,7 +125,9 @@ def test_tune_firstboot(self, create_vms, helper_vm_with_plugged_disk, firmware, version, machine, local_sr, source_type): helper_vm = helper_vm_with_plugged_disk - helper_vm.ssh(["mount /dev/xvdb1 /mnt"]) + main_part = "/dev/xvdb2" if firmware.endswith("+dell") else "/dev/xvdb1" + + helper_vm.ssh(["mount", main_part, "/mnt"]) try: # hostname logging.info("Setting hostname to %r", machine) @@ -144,7 +148,7 @@ def test_tune_firstboot(self, create_vms, helper_vm_with_plugged_disk, '/mnt/etc/xensource-inventory']) helper_vm.ssh(["grep UUID /mnt/etc/xensource-inventory"]) finally: - helper_vm.ssh(["umount /dev/xvdb1"]) + helper_vm.ssh(["umount", main_part]) def _test_firstboot(self, create_vms, mode, *, machine='DEFAULT'): host_vm = create_vms[0] @@ -303,7 +307,7 @@ def _test_firstboot(self, create_vms, mode, *, machine='DEFAULT'): "ch821.1", "xs8", "xs70", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) @pytest.mark.continuation_of( lambda version, firmware, machine, local_sr, source_type: [ dict(vm="vm1", @@ -333,7 +337,7 @@ def test_boot_inst(self, create_vms, "821.1-821.1", "75-821.1", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) @pytest.mark.continuation_of( lambda mode, firmware, machine, source_type, local_sr: [dict( vm="vm1", @@ -361,7 +365,7 @@ def test_boot_upg(self, create_vms, "83rcnet-83rcnet", "83rcnet-83rcnet-83rcnet", # FIXME "821.1-821.1-821.1", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) @pytest.mark.continuation_of( lambda mode, firmware, source_type, local_sr: [dict( vm="vm1", @@ -391,7 +395,7 @@ def test_boot_rst(self, create_vms, ("821.1", "821.1"), ("75", "821.1"), ]) - @pytest.mark.parametrize("firmware", ("uefi", "bios")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) @pytest.mark.continuation_of( lambda firmware, params, machine, source_type, local_sr: [dict( vm="vm1", @@ -428,7 +432,7 @@ def test_upgrade(self, vm_booted_with_installer, install_disk, ("83rcnet-83rcnet", "83rcnet"), # FIXME ("821.1-821.1", "821.1"), ]) - @pytest.mark.parametrize("firmware", ("uefi", "bios")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) @pytest.mark.continuation_of( lambda firmware, params, local_sr, source_type: [dict( vm="vm1", From 8f9483bc245f514ac32a5d71ba99a9df6e092488 Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Wed, 2 Oct 2024 15:14:46 +0200 Subject: [PATCH 10/10] WIP installation on a disk with a MBR partition --- tests/install/conftest.py | 15 +++++++++++++++ tests/install/test.py | 11 ++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/install/conftest.py b/tests/install/conftest.py index 9a23a086..338273d5 100644 --- a/tests/install/conftest.py +++ b/tests/install/conftest.py @@ -129,6 +129,9 @@ def answerfile_maybe_tweak_parttable(request, answerfile): if firmware.endswith("+dell"): answerfile.top_append(dict(TAG="script", stage="installation-start", type="url", CONTENTS="file:///root/preinstall-utilitypart.sh")) + if firmware.endswith("+mbr"): + answerfile.top_append(dict(TAG="script", stage="installation-start", + type="url", CONTENTS="file:///root/preinstall-mbrparttable.sh")) # Remasters the ISO sepecified by `installer_iso` mark, with: # - network and ssh support activated, and .ssh/authorized_key so tests can @@ -251,6 +254,18 @@ def remastered_iso(installer_iso, answerfile, install_disk): EOP EOF +cat > "$INSTALLIMG/root/preinstall-mbrparttable.sh" <<'EOF' +#!/bin/sh +set -ex + +# Dell utility partition +sgdisk --zap-all /dev/{install_disk} +sfdisk /dev/{install_disk} << 'EOP' +unit: sectors +p1 : start= 2048, size= 32768, Id=83 +EOP +EOF + cat > "$INSTALLIMG/root/postinstall.sh" <<'EOF' #!/bin/sh set -ex diff --git a/tests/install/test.py b/tests/install/test.py index 8620afda..769fda89 100644 --- a/tests/install/test.py +++ b/tests/install/test.py @@ -32,7 +32,7 @@ class TestNested: "xs8", "ch821.1", "xs70", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell", "bios+mbr")) @pytest.mark.vm_definitions( lambda firmware: dict( name="vm1", @@ -53,6 +53,7 @@ class TestNested: ), "bios": (), "bios+dell": (), + "bios+mbr": (), }[firmware], vdis=[dict(name="vm1 system disk", size="100GiB", device="xvda", userdevice="0")], cd_vbd=dict(device="xvdd", userdevice="3"), @@ -113,7 +114,7 @@ def helper_vm_with_plugged_disk(running_vm, create_vms): "ch821.1", "xs8", "xs70", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell", "bios+mbr")) @pytest.mark.continuation_of( lambda version, firmware, local_sr, source_type: [dict( vm="vm1", @@ -307,7 +308,7 @@ def _test_firstboot(self, create_vms, mode, *, machine='DEFAULT'): "ch821.1", "xs8", "xs70", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell", "bios+mbr")) @pytest.mark.continuation_of( lambda version, firmware, machine, local_sr, source_type: [ dict(vm="vm1", @@ -337,7 +338,7 @@ def test_boot_inst(self, create_vms, "821.1-821.1", "75-821.1", )) - @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell", "bios+mbr")) @pytest.mark.continuation_of( lambda mode, firmware, machine, source_type, local_sr: [dict( vm="vm1", @@ -395,7 +396,7 @@ def test_boot_rst(self, create_vms, ("821.1", "821.1"), ("75", "821.1"), ]) - @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell")) + @pytest.mark.parametrize("firmware", ("uefi", "bios", "bios+dell", "bios+mbr")) @pytest.mark.continuation_of( lambda firmware, params, machine, source_type, local_sr: [dict( vm="vm1",