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 subprocess
11import bb
12import logging
13from oeqa.utils.sshcontrol import SSHControl
14from oeqa.utils.qemurunner import QemuRunner
15from oeqa.utils.qemutinyrunner import QemuTinyRunner
16from oeqa.utils.dump import TargetDumper
17from oeqa.utils.dump import MonitorDumper
18from abc import ABCMeta, abstractmethod
19
20class BaseTarget(object, metaclass=ABCMeta):
21
22    supported_image_fstypes = []
23
24    def __init__(self, d, logger):
25        self.connection = None
26        self.ip = None
27        self.server_ip = None
28        self.datetime = d.getVar('DATETIME')
29        self.testdir = d.getVar("TEST_LOG_DIR")
30        self.pn = d.getVar("PN")
31        self.logger = logger
32
33    @abstractmethod
34    def deploy(self):
35
36        self.sshlog = os.path.join(self.testdir, "ssh_target_log.%s" % self.datetime)
37        sshloglink = os.path.join(self.testdir, "ssh_target_log")
38        if os.path.islink(sshloglink):
39            os.unlink(sshloglink)
40        os.symlink(self.sshlog, sshloglink)
41        self.logger.info("SSH log file: %s" % self.sshlog)
42
43    @abstractmethod
44    def start(self, params=None, ssh=True, extra_bootparams=None):
45        pass
46
47    @abstractmethod
48    def stop(self):
49        pass
50
51    @classmethod
52    def get_extra_files(self):
53        return None
54
55    @classmethod
56    def match_image_fstype(self, d, image_fstypes=None):
57        if not image_fstypes:
58            image_fstypes = d.getVar('IMAGE_FSTYPES').split(' ')
59        possible_image_fstypes = [fstype for fstype in self.supported_image_fstypes if fstype in image_fstypes]
60        if possible_image_fstypes:
61            return possible_image_fstypes[0]
62        else:
63            return None
64
65    def get_image_fstype(self, d):
66        image_fstype = self.match_image_fstype(d)
67        if image_fstype:
68            return image_fstype
69        else:
70            bb.fatal("IMAGE_FSTYPES should contain a Target Controller supported image fstype: %s " % ', '.join(map(str, self.supported_image_fstypes)))
71
72    def restart(self, params=None):
73        self.stop()
74        self.start(params)
75
76    def run(self, cmd, timeout=None):
77        return self.connection.run(cmd, timeout)
78
79    def copy_to(self, localpath, remotepath):
80        return self.connection.copy_to(localpath, remotepath)
81
82    def copy_from(self, remotepath, localpath):
83        return self.connection.copy_from(remotepath, localpath)
84
85
86
87class QemuTarget(BaseTarget):
88
89    supported_image_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic']
90
91    def __init__(self, d, logger, image_fstype=None):
92
93        import oe.types
94
95        super(QemuTarget, self).__init__(d, logger)
96
97        self.rootfs = ''
98        self.kernel = ''
99        self.image_fstype = ''
100
101        if d.getVar('FIND_ROOTFS') == '1':
102            self.image_fstype = image_fstype or self.get_image_fstype(d)
103            self.rootfs = os.path.join(d.getVar("DEPLOY_DIR_IMAGE"),  d.getVar("IMAGE_LINK_NAME") + '.' + self.image_fstype)
104            self.kernel = os.path.join(d.getVar("DEPLOY_DIR_IMAGE"), d.getVar("KERNEL_IMAGETYPE", False) + '-' + d.getVar('MACHINE', False) + '.bin')
105        self.qemulog = os.path.join(self.testdir, "qemu_boot_log.%s" % self.datetime)
106        dump_monitor_cmds = d.getVar("testimage_dump_monitor")
107        dump_dir = d.getVar("TESTIMAGE_DUMP_DIR")
108        if not dump_dir:
109            dump_dir = os.path.join(d.getVar('LOG_DIR'), 'runtime-hostdump')
110        use_kvm = oe.types.qemu_use_kvm(d.getVar('QEMU_USE_KVM'), d.getVar('TARGET_ARCH'))
111
112        # Log QemuRunner log output to a file
113        import oe.path
114        bb.utils.mkdirhier(self.testdir)
115        self.qemurunnerlog = os.path.join(self.testdir, 'qemurunner_log.%s' % self.datetime)
116        self.loggerhandler = logging.FileHandler(self.qemurunnerlog)
117        self.loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
118        self.logger.addHandler(self.loggerhandler)
119        oe.path.symlink(os.path.basename(self.qemurunnerlog), os.path.join(self.testdir, 'qemurunner_log'), force=True)
120
121        if d.getVar("DISTRO") == "poky-tiny":
122            self.runner = QemuTinyRunner(machine=d.getVar("MACHINE"),
123                            rootfs=self.rootfs,
124                            tmpdir = d.getVar("TMPDIR"),
125                            deploy_dir_image = d.getVar("DEPLOY_DIR_IMAGE"),
126                            display = d.getVar("BB_ORIGENV", False).getVar("DISPLAY"),
127                            logfile = self.qemulog,
128                            kernel = self.kernel,
129                            boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT")),
130                            tmpfsdir = d.getVar("RUNQEMU_TMPFS_DIR"),
131                            logger = logger)
132        else:
133            self.runner = QemuRunner(machine=d.getVar("MACHINE"),
134                            rootfs=self.rootfs,
135                            tmpdir = d.getVar("TMPDIR"),
136                            deploy_dir_image = d.getVar("DEPLOY_DIR_IMAGE"),
137                            display = d.getVar("BB_ORIGENV", False).getVar("DISPLAY"),
138                            logfile = self.qemulog,
139                            boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT")),
140                            use_kvm = use_kvm,
141                            dump_dir = dump_dir,
142                            logger = logger,
143                            tmpfsdir = d.getVar("RUNQEMU_TMPFS_DIR"),
144                            serial_ports = len(d.getVar("SERIAL_CONSOLES").split()))
145
146        self.monitor_dumper = MonitorDumper(dump_monitor_cmds, dump_dir, self.runner)
147        if (self.monitor_dumper):
148            self.monitor_dumper.create_dir("qmp")
149
150    def deploy(self):
151        bb.utils.mkdirhier(self.testdir)
152
153        qemuloglink = os.path.join(self.testdir, "qemu_boot_log")
154        if os.path.islink(qemuloglink):
155            os.unlink(qemuloglink)
156        os.symlink(self.qemulog, qemuloglink)
157
158        self.logger.info("rootfs file: %s" % self.rootfs)
159        self.logger.info("Qemu log file: %s" % self.qemulog)
160        super(QemuTarget, self).deploy()
161
162    def start(self, params=None, ssh=True, extra_bootparams='', runqemuparams='', launch_cmd='', discard_writes=True):
163        if launch_cmd:
164            start = self.runner.launch(get_ip=ssh, launch_cmd=launch_cmd, qemuparams=params)
165        else:
166            start = self.runner.start(params, get_ip=ssh, extra_bootparams=extra_bootparams, runqemuparams=runqemuparams, discard_writes=discard_writes)
167
168        if start:
169            if ssh:
170                self.ip = self.runner.ip
171                self.server_ip = self.runner.server_ip
172                self.connection = SSHControl(ip=self.ip, logfile=self.sshlog)
173        else:
174            self.stop()
175            if os.path.exists(self.qemulog):
176                with open(self.qemulog, 'r') as f:
177                    bb.error("Qemu log output from %s:\n%s" % (self.qemulog, f.read()))
178            raise RuntimeError("%s - FAILED to start qemu - check the task log and the boot log" % self.pn)
179
180    def check(self):
181        return self.runner.is_alive()
182
183    def stop(self):
184        try:
185            self.runner.stop()
186        except:
187            pass
188        self.logger.removeHandler(self.loggerhandler)
189        self.loggerhandler.close()
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 RuntimeError("%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