1# 2# Copyright (C) 2013 Intel Corporation 3# 4# SPDX-License-Identifier: MIT 5# 6 7# This module is used by testimage.bbclass for setting up and controlling a target machine. 8 9import os 10import shutil 11import subprocess 12import bb 13import traceback 14import sys 15import logging 16from oeqa.utils.sshcontrol import SSHControl 17from oeqa.utils.qemurunner import QemuRunner 18from oeqa.utils.qemutinyrunner import QemuTinyRunner 19from oeqa.utils.dump import TargetDumper 20from oeqa.controllers.testtargetloader import TestTargetLoader 21from abc import ABCMeta, abstractmethod 22 23class BaseTarget(object, metaclass=ABCMeta): 24 25 supported_image_fstypes = [] 26 27 def __init__(self, d, logger): 28 self.connection = None 29 self.ip = None 30 self.server_ip = None 31 self.datetime = d.getVar('DATETIME') 32 self.testdir = d.getVar("TEST_LOG_DIR") 33 self.pn = d.getVar("PN") 34 self.logger = logger 35 36 @abstractmethod 37 def deploy(self): 38 39 self.sshlog = os.path.join(self.testdir, "ssh_target_log.%s" % self.datetime) 40 sshloglink = os.path.join(self.testdir, "ssh_target_log") 41 if os.path.islink(sshloglink): 42 os.unlink(sshloglink) 43 os.symlink(self.sshlog, sshloglink) 44 self.logger.info("SSH log file: %s" % self.sshlog) 45 46 @abstractmethod 47 def start(self, params=None, ssh=True, extra_bootparams=None): 48 pass 49 50 @abstractmethod 51 def stop(self): 52 pass 53 54 @classmethod 55 def get_extra_files(self): 56 return None 57 58 @classmethod 59 def match_image_fstype(self, d, image_fstypes=None): 60 if not image_fstypes: 61 image_fstypes = d.getVar('IMAGE_FSTYPES').split(' ') 62 possible_image_fstypes = [fstype for fstype in self.supported_image_fstypes if fstype in image_fstypes] 63 if possible_image_fstypes: 64 return possible_image_fstypes[0] 65 else: 66 return None 67 68 def get_image_fstype(self, d): 69 image_fstype = self.match_image_fstype(d) 70 if image_fstype: 71 return image_fstype 72 else: 73 bb.fatal("IMAGE_FSTYPES should contain a Target Controller supported image fstype: %s " % ', '.join(map(str, self.supported_image_fstypes))) 74 75 def restart(self, params=None): 76 self.stop() 77 self.start(params) 78 79 def run(self, cmd, timeout=None): 80 return self.connection.run(cmd, timeout) 81 82 def copy_to(self, localpath, remotepath): 83 return self.connection.copy_to(localpath, remotepath) 84 85 def copy_from(self, remotepath, localpath): 86 return self.connection.copy_from(remotepath, localpath) 87 88 89 90class QemuTarget(BaseTarget): 91 92 supported_image_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic'] 93 94 def __init__(self, d, logger, image_fstype=None): 95 96 import oe.types 97 98 super(QemuTarget, self).__init__(d, logger) 99 100 self.rootfs = '' 101 self.kernel = '' 102 self.image_fstype = '' 103 104 if d.getVar('FIND_ROOTFS') == '1': 105 self.image_fstype = image_fstype or self.get_image_fstype(d) 106 self.rootfs = os.path.join(d.getVar("DEPLOY_DIR_IMAGE"), d.getVar("IMAGE_LINK_NAME") + '.' + self.image_fstype) 107 self.kernel = os.path.join(d.getVar("DEPLOY_DIR_IMAGE"), d.getVar("KERNEL_IMAGETYPE", False) + '-' + d.getVar('MACHINE', False) + '.bin') 108 self.qemulog = os.path.join(self.testdir, "qemu_boot_log.%s" % self.datetime) 109 dump_target_cmds = d.getVar("testimage_dump_target") 110 dump_host_cmds = d.getVar("testimage_dump_host") 111 dump_dir = d.getVar("TESTIMAGE_DUMP_DIR") 112 if not dump_dir: 113 dump_dir = os.path.join(d.getVar('LOG_DIR'), 'runtime-hostdump') 114 use_kvm = oe.types.qemu_use_kvm(d.getVar('QEMU_USE_KVM'), d.getVar('TARGET_ARCH')) 115 116 # Log QemuRunner log output to a file 117 import oe.path 118 bb.utils.mkdirhier(self.testdir) 119 self.qemurunnerlog = os.path.join(self.testdir, 'qemurunner_log.%s' % self.datetime) 120 self.loggerhandler = logging.FileHandler(self.qemurunnerlog) 121 self.loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) 122 self.logger.addHandler(self.loggerhandler) 123 oe.path.symlink(os.path.basename(self.qemurunnerlog), os.path.join(self.testdir, 'qemurunner_log'), force=True) 124 125 if d.getVar("DISTRO") == "poky-tiny": 126 self.runner = QemuTinyRunner(machine=d.getVar("MACHINE"), 127 rootfs=self.rootfs, 128 tmpdir = d.getVar("TMPDIR"), 129 deploy_dir_image = d.getVar("DEPLOY_DIR_IMAGE"), 130 display = d.getVar("BB_ORIGENV", False).getVar("DISPLAY"), 131 logfile = self.qemulog, 132 kernel = self.kernel, 133 boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT")), 134 logger = logger) 135 else: 136 self.runner = QemuRunner(machine=d.getVar("MACHINE"), 137 rootfs=self.rootfs, 138 tmpdir = d.getVar("TMPDIR"), 139 deploy_dir_image = d.getVar("DEPLOY_DIR_IMAGE"), 140 display = d.getVar("BB_ORIGENV", False).getVar("DISPLAY"), 141 logfile = self.qemulog, 142 boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT")), 143 use_kvm = use_kvm, 144 dump_dir = dump_dir, 145 dump_host_cmds = d.getVar("testimage_dump_host"), 146 logger = logger, 147 serial_ports = len(d.getVar("SERIAL_CONSOLES").split())) 148 149 self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner) 150 151 def deploy(self): 152 bb.utils.mkdirhier(self.testdir) 153 154 qemuloglink = os.path.join(self.testdir, "qemu_boot_log") 155 if os.path.islink(qemuloglink): 156 os.unlink(qemuloglink) 157 os.symlink(self.qemulog, qemuloglink) 158 159 self.logger.info("rootfs file: %s" % self.rootfs) 160 self.logger.info("Qemu log file: %s" % self.qemulog) 161 super(QemuTarget, self).deploy() 162 163 def start(self, params=None, ssh=True, extra_bootparams='', runqemuparams='', launch_cmd='', discard_writes=True): 164 if launch_cmd: 165 start = self.runner.launch(get_ip=ssh, launch_cmd=launch_cmd, qemuparams=params) 166 else: 167 start = self.runner.start(params, get_ip=ssh, extra_bootparams=extra_bootparams, runqemuparams=runqemuparams, discard_writes=discard_writes) 168 169 if start: 170 if ssh: 171 self.ip = self.runner.ip 172 self.server_ip = self.runner.server_ip 173 self.connection = SSHControl(ip=self.ip, logfile=self.sshlog) 174 else: 175 self.stop() 176 if os.path.exists(self.qemulog): 177 with open(self.qemulog, 'r') as f: 178 bb.error("Qemu log output from %s:\n%s" % (self.qemulog, f.read())) 179 raise RuntimeError("%s - FAILED to start qemu - check the task log and the boot log" % self.pn) 180 181 def check(self): 182 return self.runner.is_alive() 183 184 def stop(self): 185 try: 186 self.runner.stop() 187 except: 188 pass 189 self.logger.removeHandler(self.loggerhandler) 190 self.loggerhandler.close() 191 self.connection = None 192 self.ip = None 193 self.server_ip = None 194 195 def restart(self, params=None): 196 if self.runner.restart(params): 197 self.ip = self.runner.ip 198 self.server_ip = self.runner.server_ip 199 self.connection = SSHControl(ip=self.ip, logfile=self.sshlog) 200 else: 201 raise RuntimError("%s - FAILED to re-start qemu - check the task log and the boot log" % self.pn) 202 203 def run_serial(self, command, timeout=60): 204 return self.runner.run_serial(command, timeout=timeout) 205 206 207class SimpleRemoteTarget(BaseTarget): 208 209 def __init__(self, d): 210 super(SimpleRemoteTarget, self).__init__(d) 211 addr = d.getVar("TEST_TARGET_IP") or bb.fatal('Please set TEST_TARGET_IP with the IP address of the machine you want to run the tests on.') 212 self.ip = addr.split(":")[0] 213 try: 214 self.port = addr.split(":")[1] 215 except IndexError: 216 self.port = None 217 self.logger.info("Target IP: %s" % self.ip) 218 self.server_ip = d.getVar("TEST_SERVER_IP") 219 if not self.server_ip: 220 try: 221 self.server_ip = subprocess.check_output(['ip', 'route', 'get', self.ip ]).split("\n")[0].split()[-1] 222 except Exception as e: 223 bb.fatal("Failed to determine the host IP address (alternatively you can set TEST_SERVER_IP with the IP address of this machine): %s" % e) 224 self.logger.info("Server IP: %s" % self.server_ip) 225 226 def deploy(self): 227 super(SimpleRemoteTarget, self).deploy() 228 229 def start(self, params=None, ssh=True, extra_bootparams=None): 230 if ssh: 231 self.connection = SSHControl(self.ip, logfile=self.sshlog, port=self.port) 232 233 def stop(self): 234 self.connection = None 235 self.ip = None 236 self.server_ip = None 237