xref: /openbmc/openbmc/poky/meta/lib/oeqa/targetcontrol.py (revision c124f4f2e04dca16a428a76c89677328bc7bf908)
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, boot_patterns=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                            boot_patterns = boot_patterns)
146
147        self.monitor_dumper = MonitorDumper(dump_monitor_cmds, dump_dir, self.runner)
148        if (self.monitor_dumper):
149            self.monitor_dumper.create_dir("qmp")
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 RuntimeError("%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