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.connection = None
191        self.ip = None
192        self.server_ip = None
193
194    def restart(self, params=None):
195        if self.runner.restart(params):
196            self.ip = self.runner.ip
197            self.server_ip = self.runner.server_ip
198            self.connection = SSHControl(ip=self.ip, logfile=self.sshlog)
199        else:
200            raise RuntimError("%s - FAILED to re-start qemu - check the task log and the boot log" % self.pn)
201
202    def run_serial(self, command, timeout=60):
203        return self.runner.run_serial(command, timeout=timeout)
204
205
206class SimpleRemoteTarget(BaseTarget):
207
208    def __init__(self, d):
209        super(SimpleRemoteTarget, self).__init__(d)
210        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.')
211        self.ip = addr.split(":")[0]
212        try:
213            self.port = addr.split(":")[1]
214        except IndexError:
215            self.port = None
216        self.logger.info("Target IP: %s" % self.ip)
217        self.server_ip = d.getVar("TEST_SERVER_IP")
218        if not self.server_ip:
219            try:
220                self.server_ip = subprocess.check_output(['ip', 'route', 'get', self.ip ]).split("\n")[0].split()[-1]
221            except Exception as e:
222                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)
223        self.logger.info("Server IP: %s" % self.server_ip)
224
225    def deploy(self):
226        super(SimpleRemoteTarget, self).deploy()
227
228    def start(self, params=None, ssh=True, extra_bootparams=None):
229        if ssh:
230            self.connection = SSHControl(self.ip, logfile=self.sshlog, port=self.port)
231
232    def stop(self):
233        self.connection = None
234        self.ip = None
235        self.server_ip = None
236