Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2fbfdbb
pool: add a log for better understanding of INFO level
ydirson Sep 6, 2024
7c43118
ci: pull additional typing data for mypy
ydirson May 7, 2025
e5ae823
pxe: improve and simplify error logging
ydirson Mar 21, 2025
ebd57d1
Host: on VM cache miss print a key pastable into IMAGE_*EQUIVS
ydirson Sep 6, 2024
2c0db3a
install 1/n: fixture to create VMs from template
ydirson May 7, 2024
341e5b8
install 2/n: insert ISO in host VM
ydirson Aug 9, 2024
d93274b
install 3/n: use iso-remaster to plug an hardcoded answerfile
ydirson Aug 9, 2024
e6285ca
install 4/n: boot and monitor installer
ydirson Aug 9, 2024
39bc80b
install 5/n: make sure host running installer appears in PXE ARP tables
ydirson Jun 25, 2024
4fefd93
install 6/n: install test-pingpxe service on host
ydirson Aug 2, 2024
3039a9d
install 7/n: answerfile generation
ydirson Oct 7, 2024
04d285a
install 8/n: use VM cache to chain tests
ydirson Jun 25, 2024
f564653
install 9/n: add firstboot test
ydirson Jul 25, 2024
3e1f6f2
Image caching: include commit hash in caching key
ydirson Sep 4, 2024
a6ad342
Image caching: allow to declare image equivalence
ydirson Jul 9, 2024
56b0476
install: use xcpng_chained/continuation_of to simplify dependency spec
ydirson Jun 21, 2024
fedccce
remastered_iso: support for unsigned ISOs
ydirson Sep 4, 2024
171d9bb
install: add "version" test parameter and test-sequences
ydirson Aug 26, 2024
00b0d29
Add upgrade test
ydirson Sep 4, 2024
9cf7f1f
install: add a "firmware" parameter
ydirson Oct 9, 2024
feae835
install: add "restore" test using 8.3 ISO
ydirson Sep 5, 2024
9044d30
install/firstboot: check installed version
ydirson Jun 17, 2024
b7a7c72
install: add XS/CH support
ydirson Sep 5, 2024
f721995
install: add installation of xcp-ng 8.0 and 8.1, upgrades to 8.3
ydirson Aug 29, 2024
8b686f7
install: 7.5, 7.6, and XS 7.0
ydirson Jun 14, 2024
c33a13d
install: produce several hosts from single install
ydirson Oct 8, 2024
93ab539
install: adjust host IP, name, UUIDs in firstboot data before booting
ydirson Sep 9, 2024
9751440
Add local_sr parameter to test_install
ydirson Oct 9, 2024
9f486f1
import_vm: add clone:// and clone+start:// URIs
ydirson Jul 15, 2024
625e072
hosts: make setup_host a nested func
ydirson Sep 19, 2024
22ef392
Add support for --nest=... --hosts=cache://...
ydirson Jul 23, 2024
ce0e117
Add support for netinstall
gthvn1 Sep 9, 2024
6a7ce9b
New test: pool_join
ydirson Aug 30, 2024
3ba1176
firstboot: reordering cleanup
ydirson Oct 8, 2024
fcf809b
firstboot: move _upg and _rst tests to group them with upgrade and re…
ydirson May 6, 2025
024e768
install: add 82nightly configuration for CI
ydirson Mar 17, 2025
982d160
install: replace 8.3 prereleases with 8.3.0 as base version for upgrades
ydirson Mar 17, 2025
c707904
install: support the new platform setting for nesting in 8.3+
ydirson Feb 3, 2025
9aa7b9e
pxe.arp_addresses_for: use ARP_SERVER not PXE_CONFIG_SERVER
ydirson Feb 4, 2025
e6985f9
pxe.arp_addresses_for: use iproute2
ydirson Feb 4, 2025
796937a
install: add a first README for automated installs
ydirson Mar 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/code-checkers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
pip install mypy
- name: Create a dummy data.py
run: cp data.py-dist data.py
- name: Check with mypy
run: mypy lib/ tests/
- name: Install additional typing data and check with mypy
run: mypy --install-types --non-interactive lib/ tests/

pyright:
runs-on: ubuntu-latest
steps:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/test-sequences.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Check test-sequences consistency

on: [push]

jobs:
jobs-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v4
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements/base.txt
- name: Create a dummy data.py
run: cp data.py-dist data.py
- name: jobs-check
run: |
FAILURES=""
for seq in $(find -name "*.lst"); do
if ! pytest @$seq --collect-only --quiet; then
FAILURES="$FAILURES $seq"
fi
done
[ -z "$FAILURES" ] || { echo >&2 "ERROR: test sequences failed consistency check: $FAILURES"; exit 1; }
242 changes: 235 additions & 7 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import itertools
import git
import logging
import os
import pytest
import tempfile

Expand All @@ -8,11 +10,14 @@

import lib.config as global_config

from lib import pxe
from lib.common import callable_marker, shortened_nodeid, prefix_object_name
from lib.common import wait_for, vm_image, is_uuid
from lib.common import setup_formatted_and_mounted_disk, teardown_formatted_and_mounted_disk
from lib.netutil import is_ipv6
from lib.pool import Pool
from lib.vm import VM
from lib.sr import SR
from lib.vm import VM, vm_cache_key_from_def
from lib.xo import xo_cli

# Import package-scoped fixtures. Although we need to define them in a separate file so that we can
Expand All @@ -30,6 +35,12 @@
# pytest hooks

def pytest_addoption(parser):
parser.addoption(
"--nest",
action="store",
default=None,
help="XCP-ng or XS master of pool to use for nesting hosts under test",
)
parser.addoption(
"--hosts",
action="append",
Expand Down Expand Up @@ -137,22 +148,73 @@ def pytest_runtest_makereport(item, call):

# fixtures

def setup_host(hostname_or_ip):
pool = Pool(hostname_or_ip)
h = pool.master
return h

@pytest.fixture(scope='session')
def hosts(pytestconfig):
nested_list = []

def setup_host(hostname_or_ip, *, config=None):
host_vm = None
if hostname_or_ip.startswith("cache://"):
if config is None:
raise RuntimeError("setup_host: a cache:// host requires --nest")
nest_hostname = config.getoption("nest")
if not nest_hostname:
pytest.fail("--hosts=cache://... requires --nest parameter")
nest = Pool(nest_hostname).master

protocol, rest = hostname_or_ip.split(":", 1)
host_vm = nest.import_vm(f"clone:{rest}", nest.main_sr_uuid(),
use_cache=True)
nested_list.append(host_vm)

vif = host_vm.vifs()[0]
mac_address = vif.param_get('MAC')
logging.info("Nested host has MAC %s", mac_address)

host_vm.start()
wait_for(host_vm.is_running, "Wait for nested host VM running")

# catch host-vm IP address
wait_for(lambda: pxe.arp_addresses_for(mac_address),
"Wait for DHCP server to see nested host in ARP tables",
timeout_secs=10 * 60)
ips = pxe.arp_addresses_for(mac_address)
logging.info("Nested host has IPs %s", ips)
assert len(ips) == 1
host_vm.ip = ips[0]

wait_for(lambda: not os.system(f"nc -zw5 {host_vm.ip} 22"),
"Wait for ssh up on nested host", retry_delay_secs=5)

hostname_or_ip = host_vm.ip

pool = Pool(hostname_or_ip)
h = pool.master
return h

def cleanup_hosts():
for vm in nested_list:
logging.info("Destroying nested host VM %s", vm.uuid)
vm.destroy(verify=True)

# a list of master hosts, each from a different pool
hosts_args = pytestconfig.getoption("hosts")
hosts_split = [hostlist.split(',') for hostlist in hosts_args]
hostname_list = list(itertools.chain(*hosts_split))
host_list = [setup_host(hostname_or_ip) for hostname_or_ip in hostname_list]

try:
host_list = [setup_host(hostname_or_ip, config=pytestconfig)
for hostname_or_ip in hostname_list]
except Exception:
cleanup_hosts()
raise

if not host_list:
pytest.fail("This test requires at least one --hosts parameter")
yield host_list

cleanup_hosts()

@pytest.fixture(scope='session')
def registered_xo_cli():
# The fixture is not responsible for establishing the connection.
Expand Down Expand Up @@ -410,6 +472,172 @@ def imported_vm(host, vm_ref):
logging.info("<< Destroy VM")
vm.destroy(verify=True)

@pytest.fixture(scope="session")
def tests_git_revision():
"""
Get the git revision string for this tests repo.

Use of this fixture means impacted tests cannot run unless all
modifications are commited.
"""
test_repo = git.Repo(".")
assert not test_repo.is_dirty(), "test repo must not be dirty"
yield test_repo.head.commit.hexsha

@pytest.fixture(scope="function")
def create_vms(request, host, tests_git_revision):
"""
Returns list of VM objects created from `vm_definitions` marker.

`vm_definitions` marker test author to specify one or more VMs, by
giving for each VM one `dict`, or a callable taking fixtures as
arguments and returning such a `dict`.

Mandatory keys:
- `name`: name of the VM to create (str)
- `template`: name (or UUID) of template to use (str)

Optional keys: see example below

Example:
-------
> @pytest.mark.vm_definitions(
> dict(name="vm1", template="Other install media"),
> dict(name="vm2",
> template="CentOS 7",
> params=(
> dict(param_name="memory-static-max", value="4GiB"),
> dict(param_name="HVM-boot-params", key="order", value="dcn"),
> ),
> vdis=[dict(name="vm 2 system disk",
> size="100GiB",
> device="xvda",
> userdevice="0",
> )],
> cd_vbd=dict(device="xvdd", userdevice="3"),
> vifs=(dict(index=0, network_name=NETWORKS["MGMT"]),
> dict(index=1, network_uuid=NETWORKS["MYNET_UUID"]),
> ),
> ))
> def test_foo(create_vms):
> ...

Example:
-------
> @pytest.mark.dependency(depends=["test_foo"])
> @pytest.mark.vm_definitions(dict(name="vm1", image_test="test_foo", image_vm="vm2"))
> def test_bar(create_vms):
> ...

"""
marker = request.node.get_closest_marker("vm_definitions")
if marker is None:
raise Exception("No vm_definitions marker specified.")

vm_defs = []
for vm_def in marker.args:
vm_def = callable_marker(vm_def, request)
assert "name" in vm_def
assert "template" in vm_def or "image_test" in vm_def
if "template" in vm_def:
assert "image_test" not in vm_def
# FIXME should check optional vdis contents
# FIXME should check for extra args
vm_defs.append(vm_def)

try:
vms = []
vdis = []
vbds = []
for vm_def in vm_defs:
if "template" in vm_def:
_create_vm(request, vm_def, host, vms, vdis, vbds)
elif "image_test" in vm_def:
_vm_from_cache(request, vm_def, host, vms, tests_git_revision)
yield vms

# request.node is an "item" because this fixture has "function" scope
report = request.node.stash.get(PHASE_REPORT_KEY, None)
if report is None:
# user interruption during setup
logging.warning("test setup result not available: not exporting VMs")
elif report["setup"].failed:
logging.warning("setting up a test failed or skipped: not exporting VMs")
elif ("call" not in report) or report["call"].failed:
logging.warning("executing test failed or skipped: not exporting VMs")
else:
# record this state
for vm_def, vm in zip(vm_defs, vms):
nodeid = shortened_nodeid(request.node.nodeid)
vm.save_to_cache(f"{nodeid}-{vm_def['name']}-{tests_git_revision}")

except Exception:
logging.error("exception caught...")
raise

finally:
for vbd in vbds:
logging.info("<< Destroy VBD %s", vbd.uuid)
vbd.destroy()
for vdi in vdis:
logging.info("<< Destroy VDI %s", vdi.uuid)
vdi.destroy()
for vm in vms:
logging.info("<< Destroy VM %s", vm.uuid)
vm.destroy(verify=True)

def _vm_name(request, vm_def):
return f"{vm_def['name']} in {request.node.nodeid}"

def _create_vm(request, vm_def, host, vms, vdis, vbds):
vm_name = _vm_name(request, vm_def)
vm_template = vm_def["template"]

logging.info("Installing VM %r from template %r", vm_name, vm_template)

vm = host.vm_from_template(vm_name, vm_template)

# VM is now created, make sure we clean it up on any subsequent failure
vms.append(vm)

if "vdis" in vm_def:
for vdi_def in vm_def["vdis"]:
sr = SR(host.main_sr_uuid(), host.pool)
vdi = sr.create_vdi(vdi_def["name"], vdi_def["size"])
vdis.append(vdi)
# connect to VM
vbd = vm.create_vbd(vdi_def["device"], vdi.uuid)
vbds.append(vbd)
vbd.param_set(param_name="userdevice", value=vdi_def["userdevice"])

if "cd_vbd" in vm_def:
vm.create_cd_vbd(**vm_def["cd_vbd"])

if "vifs" in vm_def:
for vif_def in vm_def["vifs"]:
vm.create_vif(vif_def["index"],
network_uuid=vif_def.get("network_uuid", None),
network_name=vif_def.get("network_name", None))

if "params" in vm_def:
for param_def in vm_def["params"]:
logging.info("Setting param %s", param_def)
vm.param_set(**param_def)

def _vm_from_cache(request, vm_def, host, vms, tests_hexsha):
base_vm = host.cached_vm(vm_cache_key_from_def(vm_def, request.node.nodeid, tests_hexsha),
sr_uuid=host.main_sr_uuid())
if base_vm is None:
raise RuntimeError("No cache found")

# Clone the VM before running tests, so that the original VM remains untouched
logging.info("Cloning VM from cache")
vm = base_vm.clone(name=prefix_object_name(_vm_name(request, vm_def)))
# Remove the description, which may contain a cache identifier
vm.param_set('name-description', "")

vms.append(vm)

@pytest.fixture(scope="module")
def started_vm(imported_vm):
vm = imported_vm
Expand Down
Loading
Loading