Without this change, the new Avocado v103 fails to find the tests that are based on the LinuxTest class. Suggested-by: Cleber Rosa <crosa@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-ID: <20240830133841.142644-6-thuth@redhat.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
		
			
				
	
	
		
			254 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Test class and utilities for functional Linux-based tests
 | 
						|
#
 | 
						|
# Copyright (c) 2018 Red Hat, Inc.
 | 
						|
#
 | 
						|
# Author:
 | 
						|
#  Cleber Rosa <crosa@redhat.com>
 | 
						|
#
 | 
						|
# This work is licensed under the terms of the GNU GPL, version 2 or
 | 
						|
# later.  See the COPYING file in the top-level directory.
 | 
						|
 | 
						|
import os
 | 
						|
import shutil
 | 
						|
 | 
						|
from avocado.utils import cloudinit, datadrainer, process, vmimage
 | 
						|
 | 
						|
from avocado_qemu import LinuxSSHMixIn
 | 
						|
from avocado_qemu import QemuSystemTest
 | 
						|
 | 
						|
if os.path.islink(os.path.dirname(os.path.dirname(__file__))):
 | 
						|
    # The link to the avocado tests dir in the source code directory
 | 
						|
    lnk = os.path.dirname(os.path.dirname(__file__))
 | 
						|
    #: The QEMU root source directory
 | 
						|
    SOURCE_DIR = os.path.dirname(os.path.dirname(os.readlink(lnk)))
 | 
						|
else:
 | 
						|
    SOURCE_DIR = BUILD_DIR
 | 
						|
 | 
						|
class LinuxDistro:
 | 
						|
    """Represents a Linux distribution
 | 
						|
 | 
						|
    Holds information of known distros.
 | 
						|
    """
 | 
						|
    #: A collection of known distros and their respective image checksum
 | 
						|
    KNOWN_DISTROS = {
 | 
						|
        'fedora': {
 | 
						|
            '31': {
 | 
						|
                'x86_64':
 | 
						|
                {'checksum': ('e3c1b309d9203604922d6e255c2c5d09'
 | 
						|
                              '8a309c2d46215d8fc026954f3c5c27a0'),
 | 
						|
                 'pxeboot_url': ('https://archives.fedoraproject.org/'
 | 
						|
                                 'pub/archive/fedora/linux/releases/31/'
 | 
						|
                                 'Everything/x86_64/os/images/pxeboot/'),
 | 
						|
                 'kernel_params': ('root=UUID=b1438b9b-2cab-4065-a99a-'
 | 
						|
                                   '08a96687f73c ro no_timer_check '
 | 
						|
                                   'net.ifnames=0 console=tty1 '
 | 
						|
                                   'console=ttyS0,115200n8'),
 | 
						|
                },
 | 
						|
                'aarch64':
 | 
						|
                {'checksum': ('1e18d9c0cf734940c4b5d5ec592facae'
 | 
						|
                              'd2af0ad0329383d5639c997fdf16fe49'),
 | 
						|
                'pxeboot_url': 'https://archives.fedoraproject.org/'
 | 
						|
                               'pub/archive/fedora/linux/releases/31/'
 | 
						|
                               'Everything/aarch64/os/images/pxeboot/',
 | 
						|
                'kernel_params': ('root=UUID=b6950a44-9f3c-4076-a9c2-'
 | 
						|
                                  '355e8475b0a7 ro earlyprintk=pl011,0x9000000'
 | 
						|
                                  ' ignore_loglevel no_timer_check'
 | 
						|
                                  ' printk.time=1 rd_NO_PLYMOUTH'
 | 
						|
                                  ' console=ttyAMA0'),
 | 
						|
                },
 | 
						|
                'ppc64':
 | 
						|
                {'checksum': ('7c3528b85a3df4b2306e892199a9e1e4'
 | 
						|
                              '3f991c506f2cc390dc4efa2026ad2f58')},
 | 
						|
                's390x':
 | 
						|
                {'checksum': ('4caaab5a434fd4d1079149a072fdc789'
 | 
						|
                              '1e354f834d355069ca982fdcaf5a122d')},
 | 
						|
            },
 | 
						|
            '32': {
 | 
						|
                'aarch64':
 | 
						|
                {'checksum': ('b367755c664a2d7a26955bbfff985855'
 | 
						|
                              'adfa2ca15e908baf15b4b176d68d3967'),
 | 
						|
                'pxeboot_url': ('http://dl.fedoraproject.org/pub/fedora/linux/'
 | 
						|
                                'releases/32/Server/aarch64/os/images/'
 | 
						|
                                'pxeboot/'),
 | 
						|
                'kernel_params': ('root=UUID=3df75b65-be8d-4db4-8655-'
 | 
						|
                                  '14d95c0e90c5 ro no_timer_check net.ifnames=0'
 | 
						|
                                  ' console=tty1 console=ttyS0,115200n8'),
 | 
						|
                },
 | 
						|
            },
 | 
						|
            '33': {
 | 
						|
                'aarch64':
 | 
						|
                {'checksum': ('e7f75cdfd523fe5ac2ca9eeece68edc1'
 | 
						|
                              'a81f386a17f969c1d1c7c87031008a6b'),
 | 
						|
                'pxeboot_url': ('http://dl.fedoraproject.org/pub/fedora/linux/'
 | 
						|
                                'releases/33/Server/aarch64/os/images/'
 | 
						|
                                'pxeboot/'),
 | 
						|
                'kernel_params': ('root=UUID=d20b3ffa-6397-4a63-a734-'
 | 
						|
                                  '1126a0208f8a ro no_timer_check net.ifnames=0'
 | 
						|
                                  ' console=tty1 console=ttyS0,115200n8'
 | 
						|
                                  ' console=tty0'),
 | 
						|
                 },
 | 
						|
            },
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    def __init__(self, name, version, arch):
 | 
						|
        self.name = name
 | 
						|
        self.version = version
 | 
						|
        self.arch = arch
 | 
						|
        try:
 | 
						|
            info = self.KNOWN_DISTROS.get(name).get(version).get(arch)
 | 
						|
        except AttributeError:
 | 
						|
            # Unknown distro
 | 
						|
            info = None
 | 
						|
        self._info = info or {}
 | 
						|
 | 
						|
    @property
 | 
						|
    def checksum(self):
 | 
						|
        """Gets the cloud-image file checksum"""
 | 
						|
        return self._info.get('checksum', None)
 | 
						|
 | 
						|
    @checksum.setter
 | 
						|
    def checksum(self, value):
 | 
						|
        self._info['checksum'] = value
 | 
						|
 | 
						|
    @property
 | 
						|
    def pxeboot_url(self):
 | 
						|
        """Gets the repository url where pxeboot files can be found"""
 | 
						|
        return self._info.get('pxeboot_url', None)
 | 
						|
 | 
						|
    @property
 | 
						|
    def default_kernel_params(self):
 | 
						|
        """Gets the default kernel parameters"""
 | 
						|
        return self._info.get('kernel_params', None)
 | 
						|
 | 
						|
 | 
						|
class LinuxTest(LinuxSSHMixIn, QemuSystemTest):
 | 
						|
    """Facilitates having a cloud-image Linux based available.
 | 
						|
 | 
						|
    For tests that intend to interact with guests, this is a better choice
 | 
						|
    to start with than the more vanilla `QemuSystemTest` class.
 | 
						|
    """
 | 
						|
 | 
						|
    distro = None
 | 
						|
    username = 'root'
 | 
						|
    password = 'password'
 | 
						|
    smp = '2'
 | 
						|
    memory = '1024'
 | 
						|
 | 
						|
    def _set_distro(self):
 | 
						|
        distro_name = self.params.get(
 | 
						|
            'distro',
 | 
						|
            default=self._get_unique_tag_val('distro'))
 | 
						|
        if not distro_name:
 | 
						|
            distro_name = 'fedora'
 | 
						|
 | 
						|
        distro_version = self.params.get(
 | 
						|
            'distro_version',
 | 
						|
            default=self._get_unique_tag_val('distro_version'))
 | 
						|
        if not distro_version:
 | 
						|
            distro_version = '31'
 | 
						|
 | 
						|
        self.distro = LinuxDistro(distro_name, distro_version, self.arch)
 | 
						|
 | 
						|
        # The distro checksum behaves differently than distro name and
 | 
						|
        # version. First, it does not respect a tag with the same
 | 
						|
        # name, given that it's not expected to be used for filtering
 | 
						|
        # (distro name versions are the natural choice).  Second, the
 | 
						|
        # order of precedence is: parameter, attribute and then value
 | 
						|
        # from KNOWN_DISTROS.
 | 
						|
        distro_checksum = self.params.get('distro_checksum',
 | 
						|
                                          default=None)
 | 
						|
        if distro_checksum:
 | 
						|
            self.distro.checksum = distro_checksum
 | 
						|
 | 
						|
    def setUp(self, ssh_pubkey=None, network_device_type='virtio-net'):
 | 
						|
        super().setUp()
 | 
						|
        self.require_netdev('user')
 | 
						|
        self._set_distro()
 | 
						|
        self.vm.add_args('-smp', self.smp)
 | 
						|
        self.vm.add_args('-m', self.memory)
 | 
						|
        # The following network device allows for SSH connections
 | 
						|
        self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22',
 | 
						|
                         '-device', '%s,netdev=vnet' % network_device_type)
 | 
						|
        self.set_up_boot()
 | 
						|
        if ssh_pubkey is None:
 | 
						|
            ssh_pubkey, self.ssh_key = self.set_up_existing_ssh_keys()
 | 
						|
        self.set_up_cloudinit(ssh_pubkey)
 | 
						|
 | 
						|
    def set_up_existing_ssh_keys(self):
 | 
						|
        ssh_public_key = os.path.join(SOURCE_DIR, 'tests', 'keys', 'id_rsa.pub')
 | 
						|
        source_private_key = os.path.join(SOURCE_DIR, 'tests', 'keys', 'id_rsa')
 | 
						|
        ssh_dir = os.path.join(self.workdir, '.ssh')
 | 
						|
        os.mkdir(ssh_dir, mode=0o700)
 | 
						|
        ssh_private_key = os.path.join(ssh_dir,
 | 
						|
                                       os.path.basename(source_private_key))
 | 
						|
        shutil.copyfile(source_private_key, ssh_private_key)
 | 
						|
        os.chmod(ssh_private_key, 0o600)
 | 
						|
        return (ssh_public_key, ssh_private_key)
 | 
						|
 | 
						|
    def download_boot(self):
 | 
						|
        # Set the qemu-img binary.
 | 
						|
        # If none is available, the test will cancel.
 | 
						|
        vmimage.QEMU_IMG = super().get_qemu_img()
 | 
						|
 | 
						|
        self.log.info('Downloading/preparing boot image')
 | 
						|
        # Fedora 31 only provides ppc64le images
 | 
						|
        image_arch = self.arch
 | 
						|
        if self.distro.name == 'fedora':
 | 
						|
            if image_arch == 'ppc64':
 | 
						|
                image_arch = 'ppc64le'
 | 
						|
 | 
						|
        try:
 | 
						|
            boot = vmimage.get(
 | 
						|
                self.distro.name, arch=image_arch, version=self.distro.version,
 | 
						|
                checksum=self.distro.checksum,
 | 
						|
                algorithm='sha256',
 | 
						|
                cache_dir=self.cache_dirs[0],
 | 
						|
                snapshot_dir=self.workdir)
 | 
						|
        except:
 | 
						|
            self.cancel('Failed to download/prepare boot image')
 | 
						|
        return boot.path
 | 
						|
 | 
						|
    def prepare_cloudinit(self, ssh_pubkey=None):
 | 
						|
        self.log.info('Preparing cloudinit image')
 | 
						|
        try:
 | 
						|
            cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso')
 | 
						|
            pubkey_content = None
 | 
						|
            if ssh_pubkey:
 | 
						|
                with open(ssh_pubkey) as pubkey:
 | 
						|
                    pubkey_content = pubkey.read()
 | 
						|
            cloudinit.iso(cloudinit_iso, self.name,
 | 
						|
                          username=self.username,
 | 
						|
                          password=self.password,
 | 
						|
                          # QEMU's hard coded usermode router address
 | 
						|
                          phone_home_host='10.0.2.2',
 | 
						|
                          phone_home_port=self.phone_server.server_port,
 | 
						|
                          authorized_key=pubkey_content)
 | 
						|
        except Exception:
 | 
						|
            self.cancel('Failed to prepare the cloudinit image')
 | 
						|
        return cloudinit_iso
 | 
						|
 | 
						|
    def set_up_boot(self):
 | 
						|
        path = self.download_boot()
 | 
						|
        self.vm.add_args('-drive', 'file=%s' % path)
 | 
						|
 | 
						|
    def set_up_cloudinit(self, ssh_pubkey=None):
 | 
						|
        self.phone_server = cloudinit.PhoneHomeServer(('0.0.0.0', 0),
 | 
						|
                                                      self.name)
 | 
						|
        cloudinit_iso = self.prepare_cloudinit(ssh_pubkey)
 | 
						|
        self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso)
 | 
						|
 | 
						|
    def launch_and_wait(self, set_up_ssh_connection=True):
 | 
						|
        self.vm.set_console()
 | 
						|
        self.vm.launch()
 | 
						|
        console_drainer = datadrainer.LineLogger(self.vm.console_socket.fileno(),
 | 
						|
                                                 logger=self.log.getChild('console'))
 | 
						|
        console_drainer.start()
 | 
						|
        self.log.info('VM launched, waiting for boot confirmation from guest')
 | 
						|
        while not self.phone_server.instance_phoned_back:
 | 
						|
            self.phone_server.handle_request()
 | 
						|
 | 
						|
        if set_up_ssh_connection:
 | 
						|
            self.log.info('Setting up the SSH connection')
 | 
						|
            self.ssh_connect(self.username, self.ssh_key)
 |