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