Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automated install tests #226

Open
wants to merge 44 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
abd28d8
get_vdi_sr_uuid: fix intermediate-style call
ydirson Sep 6, 2024
8cd3d87
pool: add a log for better understanding of INFO level
ydirson Sep 6, 2024
d249b24
Host: on VM cache miss print a key pastable into IMAGE_*EQUIVS
ydirson Sep 6, 2024
cac89ad
install 1/n: fixture to create VMs from template
ydirson May 7, 2024
afb5eca
install 2/n: insert ISO in host VM
ydirson Aug 9, 2024
827be31
install 3/n: use iso-remaster to plug an hardcoded answerfile
ydirson Aug 9, 2024
f78847c
install 4/n: boot and monitor installer
ydirson Aug 9, 2024
0c5abe9
install 5/n: make sure host running installer appears in PXE ARP tables
ydirson Jun 25, 2024
2718537
install 6/n: install test-pingpxe service on host
ydirson Aug 2, 2024
4ea952c
install 7/n: answerfile generation
ydirson Oct 7, 2024
70d5be9
install 8/n: use VM cache to chain tests
ydirson Jun 25, 2024
169eca2
install 9/n: add firstboot test
ydirson Jul 25, 2024
d269e95
Image caching: include commit hash in caching key
ydirson Sep 4, 2024
ab91ea4
Image caching: allow to declare image equivalence
ydirson Jul 9, 2024
0d3aeb9
install: use xcpng_chained/continuation_of to simplify dependency spec
ydirson Jun 21, 2024
0e88ab1
remastered_iso: support for unsigned ISOs
ydirson Sep 4, 2024
68418be
install: add "version" test parameter and test-sequences
ydirson Aug 26, 2024
6694b80
Add upgrade test
ydirson Sep 4, 2024
956ef5e
install: add a "firmware" parameter
ydirson Oct 9, 2024
cad6d5a
install: add "restore" test using 8.3 ISO
ydirson Sep 5, 2024
f720446
install/firstboot: check installed version
ydirson Jun 17, 2024
fdc86e4
install: add XS/CH support
ydirson Sep 5, 2024
05bd2c8
install: add installation of xcp-ng 8.0 and 8.1, upgrades to 8.3
ydirson Aug 29, 2024
6d8be9a
install: 7.5, 7.6, and XS 7.0
ydirson Jun 14, 2024
1281265
install: produce several hosts from single install
ydirson Oct 8, 2024
1349fb5
install: adjust host IP, name, UUIDs in firstboot data before booting
ydirson Sep 9, 2024
7a6b454
Add local_sr parameter to test_install
ydirson Oct 9, 2024
559277e
import_vm: add clone:// and clone+start:// URIs
ydirson Jul 15, 2024
5211c04
hosts: make setup_host a nested func
ydirson Sep 19, 2024
6735aac
Add support for --nest=... --hosts=cache://...
ydirson Jul 23, 2024
3e3c746
Add support for netinstall
gthvn1 Sep 9, 2024
aeef1d4
New test: pool_join
ydirson Aug 30, 2024
ab1f2c2
firstboot: reordering cleanup
ydirson Oct 8, 2024
2354c4e
firstboot: move _upg and _rst tests to group them with upgrade and re…
ydirson Oct 9, 2024
04f5059
fixup! install: add installation of xcp-ng 8.0 and 8.1, upgrades to 8.3
ydirson Oct 10, 2024
85d49bf
fixup! install/firstboot: check installed version
ydirson Oct 10, 2024
b1d7b31
fixup! install/firstboot: check installed version
ydirson Oct 10, 2024
d8a1ccf
fixup! install 7/n: answerfile generation
ydirson Oct 17, 2024
d0fe790
fixup! install: 7.5, 7.6, and XS 7.0
ydirson Oct 18, 2024
030331a
fixup! install 7/n: answerfile generation
ydirson Oct 18, 2024
3055797
fixup! install 7/n: answerfile generation
ydirson Oct 18, 2024
7e29333
fixup! install: adjust host IP, name, UUIDs in firstboot data before …
ydirson Oct 18, 2024
746d21a
fixup! install 1/n: fixture to create VMs from template
ydirson Oct 21, 2024
58fa844
fixup! install 2/n: insert ISO in host VM
ydirson Oct 21, 2024
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
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; }
241 changes: 234 additions & 7 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import itertools
import git
import logging
import os
import pytest
import tempfile

from packaging import version

import lib.config as global_config

from lib import pxe
from lib.common import callable_marker, shortened_nodeid
from lib.common import wait_for, vm_image, is_uuid
from lib.common import prefix_object_name
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 @@ -29,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 @@ -136,22 +148,71 @@ 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://"):
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 @@ -375,6 +436,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 prefix_object_name(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=_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