Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ data.py
vm_data.py
/scripts/guests/windows/id_rsa.pub
.envrc
pytest-logs/
64 changes: 64 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
from lib.pool import Pool
from lib.sr import SR
from lib.vm import VM, vm_cache_key_from_def
from lib.unixvm import UnixVM
from lib.windowsvm import WindowsVM
from lib.xo import xo_cli
from lib.serial_console_logger import SerialConsoleLogger

# Import package-scoped fixtures. Although we need to define them in a separate file so that we can
# then import them in individual packages to fix the buggy package scope handling by pytest, we also
Expand Down Expand Up @@ -813,6 +816,67 @@ def session_log_dir():
# postpone directory creation only if some test failed
return log_dir

# Serial console logging fixtures

@pytest.fixture(scope='module')
def unix_vm_with_serial_console(imported_vm: VM, host: Host) -> Generator[UnixVM, None, None]:
vm = imported_vm

if not vm.is_running():
vm.start()
vm.wait_for_os_booted()

# TODO: to be replaced by a knock_port() once PR is merged
wait_for(lambda: not os.system(f"ping -c1 {vm.ip} > /dev/null 2>&1"),
"Wait for host up", timeout_secs=10 * 60, retry_delay_secs=10)
wait_for(lambda: not os.system(f"nc -zw5 {vm.ip} 22"),
"Wait for ssh up on host", timeout_secs=10 * 60, retry_delay_secs=5)

# Auto-detect distro and convert to distro-specific class
vm = UnixVM.from_vm_auto_detect(vm)
logging.info(f"Serial console will be configured for {vm.__class__.__name__}")
vm.configure_serial_console()

# Need a reboot for VM to take into account
vm.reboot()
vm.wait_for_os_booted()

yield vm

@pytest.fixture(scope='session')
def serial_console_logger_session(request, session_log_dir):
logger = SerialConsoleLogger(session_log_dir, scope="session")
yield logger
logger.cleanup()

@pytest.fixture(scope='class')
def serial_console_logger_class(request, session_log_dir):
logger = SerialConsoleLogger(session_log_dir, scope="class")
yield logger
logger.cleanup()

@pytest.fixture(scope='function', autouse=True)
def track_vms_for_session_logger(request, serial_console_logger_session):
_, test_vms = _introspect_test_fixtures(request)
logger = serial_console_logger_session
for vm in test_vms:
try:
logger.add_vm(vm)
except Exception:
pass
yield

@pytest.fixture(scope='function', autouse=True)
def track_vms_for_class_logger(request, serial_console_logger_class):
_, test_vms = _introspect_test_fixtures(request)
logger = serial_console_logger_class
for vm in test_vms:
try:
logger.add_vm(vm)
except Exception:
pass
yield

# Helper function for immediate console capture
def capture_vm_console(vm: VM, log_dir: str) -> str:

Expand Down
3 changes: 2 additions & 1 deletion jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,8 @@
],
"nb_pools": 1,
"params": {},
"paths": ["tests/misc/test_log_collection.py"],
"paths": ["tests/misc/test_log_collection.py",
"tests/misc/test_serial_console_logging.py"],
}
}

Expand Down
18 changes: 18 additions & 0 deletions lib/distros/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

from lib.distros.alma import AlmaVM
from lib.distros.alpine import AlpineVM
from lib.distros.centos import CentOSVM
from lib.distros.debian import DebianVM
from lib.distros.freebsd import FreeBsdVM
from lib.distros.opensuse import OpenSuseVM
from lib.distros.ubuntu import UbuntuVM

__all__ = [
'AlpineVM',
'AlmaVM',
'CentOSVM',
'DebianVM',
'FreeBsdVM',
'OpenSuseVM',
'UbuntuVM',
]
22 changes: 22 additions & 0 deletions lib/distros/alma.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations

from lib.unixvm import UnixVM

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from lib.host import Host


class AlmaVM(UnixVM):

def __init__(self, uuid: str, host: Host):
super().__init__(uuid, host)

def configure_serial_console(self):
self.ssh('export F=/etc/default/grub ; '
'[ -f ${F} ] && '
' ( grep -q " console=ttyS" ${F} || '
' sed -i \'/^GRUB_CMDLINE_LINUX=/ { s/[ ]*quiet[ ]*/ / ; s/"$/ console=ttyS,115200"/ }\' ${F} ) && '
' grub2-mkconfig -o /boot/grub2/grub.cfg',
check=False)
34 changes: 34 additions & 0 deletions lib/distros/alpine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

from lib.unixvm import UnixVM

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from lib.host import Host


class AlpineVM(UnixVM):

def __init__(self, uuid: str, host: 'Host'):
super().__init__(uuid, host)

def configure_serial_console(self):

# Try extlinux.conf first (common in Alpine)
self.ssh('export F=/boot/extlinux.conf ; '
'[ -f ${F} ] && '
' ( grep -q "APPEND.*console=ttyS" ${F} || '
' sed -i "/APPEND.*root=/ { s/$/ console=ttyS,115200/; s/ quiet / /}" ${F} )',
check=False)

# Also try GRUB (Alpine UEFI - alpine-mini)
self.ssh(
'export F=/etc/default/grub ; '
'[ -f ${F} ] && '
' ( grep -q " console=ttyS" ${F} || '
' sed -i \'/^GRUB_CMDLINE_LINUX_DEFAULT/ { s/ quiet / /; '
' s/ *"$/ console=ttyS,115200"/ }\' ${F} ) && '
' grub-mkconfig -o /boot/grub/grub.cfg',
check=False
)
18 changes: 18 additions & 0 deletions lib/distros/centos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from lib.unixvm import UnixVM

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from lib.host import Host


class CentOSVM(UnixVM):

def __init__(self, uuid: str, host: 'Host'):
super().__init__(uuid, host)

def configure_serial_console(self):
self.ssh('sed -i "/^[[:space:]]*kernel / { s/ *quiet */ /; s/$/ console=ttyS,115200/ }" /boot/grub/grub.conf',
check=False)
19 changes: 19 additions & 0 deletions lib/distros/debian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from lib.unixvm import UnixVM

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from lib.host import Host


class DebianVM(UnixVM):

def __init__(self, uuid: str, host: 'Host'):
super().__init__(uuid, host)

def configure_serial_console(self):
self.ssh('echo GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS,115200" > /etc/default/grub.d/console.cfg &&'
' grub-mkconfig -o /boot/grub/grub.cfg',
check=False)
20 changes: 20 additions & 0 deletions lib/distros/freebsd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from __future__ import annotations

import logging

from lib.unixvm import UnixVM

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from lib.host import Host


class FreeBsdVM(UnixVM):

def __init__(self, uuid: str, host: 'Host'):
super().__init__(uuid, host)

def configure_serial_console(self):
""" TODO: Implement FreeBSD serial console configuration. """
logging.warning("Serial console configuration for FreeBSD not yet implemented")
23 changes: 23 additions & 0 deletions lib/distros/opensuse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from __future__ import annotations

from lib.unixvm import UnixVM

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from lib.host import Host


class OpenSuseVM(UnixVM):

def __init__(self, uuid: str, host: 'Host'):
super().__init__(uuid, host)

def configure_serial_console(self):
self.ssh('export F=/etc/default/grub ; '
'[ -f ${F} ] && '
' ( grep -q " console=ttyS" ${F} || '
' sed -i \'/^GRUB_CMDLINE_LINUX_DEFAULT/ { '
' s/ *quiet */ /; s/ *splash=silent */ /; s/ *"$/ console=ttyS,115200"/ }\' ${F} ) && '
' grub2-mkconfig -o /boot/grub2/grub.cfg',
check=False)
19 changes: 19 additions & 0 deletions lib/distros/ubuntu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from lib.unixvm import UnixVM

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from lib.host import Host


class UbuntuVM(UnixVM):

def __init__(self, uuid: str, host: 'Host'):
super().__init__(uuid, host)

def configure_serial_console(self):
self.ssh('echo GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS,115200" > /etc/default/grub.d/console.cfg &&'
' update-grub',
check=False)
Loading