1*816d4201SThomas Huth# Test class and utilities for functional Linux-based tests 2*816d4201SThomas Huth# 3*816d4201SThomas Huth# Copyright (c) 2018 Red Hat, Inc. 4*816d4201SThomas Huth# 5*816d4201SThomas Huth# Author: 6*816d4201SThomas Huth# Cleber Rosa <crosa@redhat.com> 7*816d4201SThomas Huth# 8*816d4201SThomas Huth# This work is licensed under the terms of the GNU GPL, version 2 or 9*816d4201SThomas Huth# later. See the COPYING file in the top-level directory. 10*816d4201SThomas Huth 11*816d4201SThomas Huthimport os 12*816d4201SThomas Huthimport shutil 13*816d4201SThomas Huth 14*816d4201SThomas Huthfrom avocado.utils import cloudinit, datadrainer, process, vmimage 15*816d4201SThomas Huth 16*816d4201SThomas Huthfrom . import LinuxSSHMixIn 17*816d4201SThomas Huthfrom . import QemuSystemTest 18*816d4201SThomas Huth 19*816d4201SThomas Huthif os.path.islink(os.path.dirname(os.path.dirname(__file__))): 20*816d4201SThomas Huth # The link to the avocado tests dir in the source code directory 21*816d4201SThomas Huth lnk = os.path.dirname(os.path.dirname(__file__)) 22*816d4201SThomas Huth #: The QEMU root source directory 23*816d4201SThomas Huth SOURCE_DIR = os.path.dirname(os.path.dirname(os.readlink(lnk))) 24*816d4201SThomas Huthelse: 25*816d4201SThomas Huth SOURCE_DIR = BUILD_DIR 26*816d4201SThomas Huth 27*816d4201SThomas Huthclass LinuxDistro: 28*816d4201SThomas Huth """Represents a Linux distribution 29*816d4201SThomas Huth 30*816d4201SThomas Huth Holds information of known distros. 31*816d4201SThomas Huth """ 32*816d4201SThomas Huth #: A collection of known distros and their respective image checksum 33*816d4201SThomas Huth KNOWN_DISTROS = { 34*816d4201SThomas Huth 'fedora': { 35*816d4201SThomas Huth '31': { 36*816d4201SThomas Huth 'x86_64': 37*816d4201SThomas Huth {'checksum': ('e3c1b309d9203604922d6e255c2c5d09' 38*816d4201SThomas Huth '8a309c2d46215d8fc026954f3c5c27a0'), 39*816d4201SThomas Huth 'pxeboot_url': ('https://archives.fedoraproject.org/' 40*816d4201SThomas Huth 'pub/archive/fedora/linux/releases/31/' 41*816d4201SThomas Huth 'Everything/x86_64/os/images/pxeboot/'), 42*816d4201SThomas Huth 'kernel_params': ('root=UUID=b1438b9b-2cab-4065-a99a-' 43*816d4201SThomas Huth '08a96687f73c ro no_timer_check ' 44*816d4201SThomas Huth 'net.ifnames=0 console=tty1 ' 45*816d4201SThomas Huth 'console=ttyS0,115200n8'), 46*816d4201SThomas Huth }, 47*816d4201SThomas Huth 'aarch64': 48*816d4201SThomas Huth {'checksum': ('1e18d9c0cf734940c4b5d5ec592facae' 49*816d4201SThomas Huth 'd2af0ad0329383d5639c997fdf16fe49'), 50*816d4201SThomas Huth 'pxeboot_url': 'https://archives.fedoraproject.org/' 51*816d4201SThomas Huth 'pub/archive/fedora/linux/releases/31/' 52*816d4201SThomas Huth 'Everything/aarch64/os/images/pxeboot/', 53*816d4201SThomas Huth 'kernel_params': ('root=UUID=b6950a44-9f3c-4076-a9c2-' 54*816d4201SThomas Huth '355e8475b0a7 ro earlyprintk=pl011,0x9000000' 55*816d4201SThomas Huth ' ignore_loglevel no_timer_check' 56*816d4201SThomas Huth ' printk.time=1 rd_NO_PLYMOUTH' 57*816d4201SThomas Huth ' console=ttyAMA0'), 58*816d4201SThomas Huth }, 59*816d4201SThomas Huth 'ppc64': 60*816d4201SThomas Huth {'checksum': ('7c3528b85a3df4b2306e892199a9e1e4' 61*816d4201SThomas Huth '3f991c506f2cc390dc4efa2026ad2f58')}, 62*816d4201SThomas Huth 's390x': 63*816d4201SThomas Huth {'checksum': ('4caaab5a434fd4d1079149a072fdc789' 64*816d4201SThomas Huth '1e354f834d355069ca982fdcaf5a122d')}, 65*816d4201SThomas Huth }, 66*816d4201SThomas Huth '32': { 67*816d4201SThomas Huth 'aarch64': 68*816d4201SThomas Huth {'checksum': ('b367755c664a2d7a26955bbfff985855' 69*816d4201SThomas Huth 'adfa2ca15e908baf15b4b176d68d3967'), 70*816d4201SThomas Huth 'pxeboot_url': ('http://dl.fedoraproject.org/pub/fedora/linux/' 71*816d4201SThomas Huth 'releases/32/Server/aarch64/os/images/' 72*816d4201SThomas Huth 'pxeboot/'), 73*816d4201SThomas Huth 'kernel_params': ('root=UUID=3df75b65-be8d-4db4-8655-' 74*816d4201SThomas Huth '14d95c0e90c5 ro no_timer_check net.ifnames=0' 75*816d4201SThomas Huth ' console=tty1 console=ttyS0,115200n8'), 76*816d4201SThomas Huth }, 77*816d4201SThomas Huth }, 78*816d4201SThomas Huth '33': { 79*816d4201SThomas Huth 'aarch64': 80*816d4201SThomas Huth {'checksum': ('e7f75cdfd523fe5ac2ca9eeece68edc1' 81*816d4201SThomas Huth 'a81f386a17f969c1d1c7c87031008a6b'), 82*816d4201SThomas Huth 'pxeboot_url': ('http://dl.fedoraproject.org/pub/fedora/linux/' 83*816d4201SThomas Huth 'releases/33/Server/aarch64/os/images/' 84*816d4201SThomas Huth 'pxeboot/'), 85*816d4201SThomas Huth 'kernel_params': ('root=UUID=d20b3ffa-6397-4a63-a734-' 86*816d4201SThomas Huth '1126a0208f8a ro no_timer_check net.ifnames=0' 87*816d4201SThomas Huth ' console=tty1 console=ttyS0,115200n8' 88*816d4201SThomas Huth ' console=tty0'), 89*816d4201SThomas Huth }, 90*816d4201SThomas Huth }, 91*816d4201SThomas Huth } 92*816d4201SThomas Huth } 93*816d4201SThomas Huth 94*816d4201SThomas Huth def __init__(self, name, version, arch): 95*816d4201SThomas Huth self.name = name 96*816d4201SThomas Huth self.version = version 97*816d4201SThomas Huth self.arch = arch 98*816d4201SThomas Huth try: 99*816d4201SThomas Huth info = self.KNOWN_DISTROS.get(name).get(version).get(arch) 100*816d4201SThomas Huth except AttributeError: 101*816d4201SThomas Huth # Unknown distro 102*816d4201SThomas Huth info = None 103*816d4201SThomas Huth self._info = info or {} 104*816d4201SThomas Huth 105*816d4201SThomas Huth @property 106*816d4201SThomas Huth def checksum(self): 107*816d4201SThomas Huth """Gets the cloud-image file checksum""" 108*816d4201SThomas Huth return self._info.get('checksum', None) 109*816d4201SThomas Huth 110*816d4201SThomas Huth @checksum.setter 111*816d4201SThomas Huth def checksum(self, value): 112*816d4201SThomas Huth self._info['checksum'] = value 113*816d4201SThomas Huth 114*816d4201SThomas Huth @property 115*816d4201SThomas Huth def pxeboot_url(self): 116*816d4201SThomas Huth """Gets the repository url where pxeboot files can be found""" 117*816d4201SThomas Huth return self._info.get('pxeboot_url', None) 118*816d4201SThomas Huth 119*816d4201SThomas Huth @property 120*816d4201SThomas Huth def default_kernel_params(self): 121*816d4201SThomas Huth """Gets the default kernel parameters""" 122*816d4201SThomas Huth return self._info.get('kernel_params', None) 123*816d4201SThomas Huth 124*816d4201SThomas Huth 125*816d4201SThomas Huthclass LinuxTest(LinuxSSHMixIn, QemuSystemTest): 126*816d4201SThomas Huth """Facilitates having a cloud-image Linux based available. 127*816d4201SThomas Huth 128*816d4201SThomas Huth For tests that intend to interact with guests, this is a better choice 129*816d4201SThomas Huth to start with than the more vanilla `QemuSystemTest` class. 130*816d4201SThomas Huth """ 131*816d4201SThomas Huth 132*816d4201SThomas Huth distro = None 133*816d4201SThomas Huth username = 'root' 134*816d4201SThomas Huth password = 'password' 135*816d4201SThomas Huth smp = '2' 136*816d4201SThomas Huth memory = '1024' 137*816d4201SThomas Huth 138*816d4201SThomas Huth def _set_distro(self): 139*816d4201SThomas Huth distro_name = self.params.get( 140*816d4201SThomas Huth 'distro', 141*816d4201SThomas Huth default=self._get_unique_tag_val('distro')) 142*816d4201SThomas Huth if not distro_name: 143*816d4201SThomas Huth distro_name = 'fedora' 144*816d4201SThomas Huth 145*816d4201SThomas Huth distro_version = self.params.get( 146*816d4201SThomas Huth 'distro_version', 147*816d4201SThomas Huth default=self._get_unique_tag_val('distro_version')) 148*816d4201SThomas Huth if not distro_version: 149*816d4201SThomas Huth distro_version = '31' 150*816d4201SThomas Huth 151*816d4201SThomas Huth self.distro = LinuxDistro(distro_name, distro_version, self.arch) 152*816d4201SThomas Huth 153*816d4201SThomas Huth # The distro checksum behaves differently than distro name and 154*816d4201SThomas Huth # version. First, it does not respect a tag with the same 155*816d4201SThomas Huth # name, given that it's not expected to be used for filtering 156*816d4201SThomas Huth # (distro name versions are the natural choice). Second, the 157*816d4201SThomas Huth # order of precedence is: parameter, attribute and then value 158*816d4201SThomas Huth # from KNOWN_DISTROS. 159*816d4201SThomas Huth distro_checksum = self.params.get('distro_checksum', 160*816d4201SThomas Huth default=None) 161*816d4201SThomas Huth if distro_checksum: 162*816d4201SThomas Huth self.distro.checksum = distro_checksum 163*816d4201SThomas Huth 164*816d4201SThomas Huth def setUp(self, ssh_pubkey=None, network_device_type='virtio-net'): 165*816d4201SThomas Huth super().setUp() 166*816d4201SThomas Huth self.require_netdev('user') 167*816d4201SThomas Huth self._set_distro() 168*816d4201SThomas Huth self.vm.add_args('-smp', self.smp) 169*816d4201SThomas Huth self.vm.add_args('-m', self.memory) 170*816d4201SThomas Huth # The following network device allows for SSH connections 171*816d4201SThomas Huth self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22', 172*816d4201SThomas Huth '-device', '%s,netdev=vnet' % network_device_type) 173*816d4201SThomas Huth self.set_up_boot() 174*816d4201SThomas Huth if ssh_pubkey is None: 175*816d4201SThomas Huth ssh_pubkey, self.ssh_key = self.set_up_existing_ssh_keys() 176*816d4201SThomas Huth self.set_up_cloudinit(ssh_pubkey) 177*816d4201SThomas Huth 178*816d4201SThomas Huth def set_up_existing_ssh_keys(self): 179*816d4201SThomas Huth ssh_public_key = os.path.join(SOURCE_DIR, 'tests', 'keys', 'id_rsa.pub') 180*816d4201SThomas Huth source_private_key = os.path.join(SOURCE_DIR, 'tests', 'keys', 'id_rsa') 181*816d4201SThomas Huth ssh_dir = os.path.join(self.workdir, '.ssh') 182*816d4201SThomas Huth os.mkdir(ssh_dir, mode=0o700) 183*816d4201SThomas Huth ssh_private_key = os.path.join(ssh_dir, 184*816d4201SThomas Huth os.path.basename(source_private_key)) 185*816d4201SThomas Huth shutil.copyfile(source_private_key, ssh_private_key) 186*816d4201SThomas Huth os.chmod(ssh_private_key, 0o600) 187*816d4201SThomas Huth return (ssh_public_key, ssh_private_key) 188*816d4201SThomas Huth 189*816d4201SThomas Huth def download_boot(self): 190*816d4201SThomas Huth # Set the qemu-img binary. 191*816d4201SThomas Huth # If none is available, the test will cancel. 192*816d4201SThomas Huth vmimage.QEMU_IMG = super().get_qemu_img() 193*816d4201SThomas Huth 194*816d4201SThomas Huth self.log.info('Downloading/preparing boot image') 195*816d4201SThomas Huth # Fedora 31 only provides ppc64le images 196*816d4201SThomas Huth image_arch = self.arch 197*816d4201SThomas Huth if self.distro.name == 'fedora': 198*816d4201SThomas Huth if image_arch == 'ppc64': 199*816d4201SThomas Huth image_arch = 'ppc64le' 200*816d4201SThomas Huth 201*816d4201SThomas Huth try: 202*816d4201SThomas Huth boot = vmimage.get( 203*816d4201SThomas Huth self.distro.name, arch=image_arch, version=self.distro.version, 204*816d4201SThomas Huth checksum=self.distro.checksum, 205*816d4201SThomas Huth algorithm='sha256', 206*816d4201SThomas Huth cache_dir=self.cache_dirs[0], 207*816d4201SThomas Huth snapshot_dir=self.workdir) 208*816d4201SThomas Huth except: 209*816d4201SThomas Huth self.cancel('Failed to download/prepare boot image') 210*816d4201SThomas Huth return boot.path 211*816d4201SThomas Huth 212*816d4201SThomas Huth def prepare_cloudinit(self, ssh_pubkey=None): 213*816d4201SThomas Huth self.log.info('Preparing cloudinit image') 214*816d4201SThomas Huth try: 215*816d4201SThomas Huth cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso') 216*816d4201SThomas Huth pubkey_content = None 217*816d4201SThomas Huth if ssh_pubkey: 218*816d4201SThomas Huth with open(ssh_pubkey) as pubkey: 219*816d4201SThomas Huth pubkey_content = pubkey.read() 220*816d4201SThomas Huth cloudinit.iso(cloudinit_iso, self.name, 221*816d4201SThomas Huth username=self.username, 222*816d4201SThomas Huth password=self.password, 223*816d4201SThomas Huth # QEMU's hard coded usermode router address 224*816d4201SThomas Huth phone_home_host='10.0.2.2', 225*816d4201SThomas Huth phone_home_port=self.phone_server.server_port, 226*816d4201SThomas Huth authorized_key=pubkey_content) 227*816d4201SThomas Huth except Exception: 228*816d4201SThomas Huth self.cancel('Failed to prepare the cloudinit image') 229*816d4201SThomas Huth return cloudinit_iso 230*816d4201SThomas Huth 231*816d4201SThomas Huth def set_up_boot(self): 232*816d4201SThomas Huth path = self.download_boot() 233*816d4201SThomas Huth self.vm.add_args('-drive', 'file=%s' % path) 234*816d4201SThomas Huth 235*816d4201SThomas Huth def set_up_cloudinit(self, ssh_pubkey=None): 236*816d4201SThomas Huth self.phone_server = cloudinit.PhoneHomeServer(('0.0.0.0', 0), 237*816d4201SThomas Huth self.name) 238*816d4201SThomas Huth cloudinit_iso = self.prepare_cloudinit(ssh_pubkey) 239*816d4201SThomas Huth self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso) 240*816d4201SThomas Huth 241*816d4201SThomas Huth def launch_and_wait(self, set_up_ssh_connection=True): 242*816d4201SThomas Huth self.vm.set_console() 243*816d4201SThomas Huth self.vm.launch() 244*816d4201SThomas Huth console_drainer = datadrainer.LineLogger(self.vm.console_socket.fileno(), 245*816d4201SThomas Huth logger=self.log.getChild('console')) 246*816d4201SThomas Huth console_drainer.start() 247*816d4201SThomas Huth self.log.info('VM launched, waiting for boot confirmation from guest') 248*816d4201SThomas Huth while not self.phone_server.instance_phoned_back: 249*816d4201SThomas Huth self.phone_server.handle_request() 250*816d4201SThomas Huth 251*816d4201SThomas Huth if set_up_ssh_connection: 252*816d4201SThomas Huth self.log.info('Setting up the SSH connection') 253*816d4201SThomas Huth self.ssh_connect(self.username, self.ssh_key) 254