diff options
-rw-r--r-- | meta/classes/testimage.bbclass | 6 | ||||
-rw-r--r-- | meta/lib/oeqa/core/target/qemu.py | 6 | ||||
-rw-r--r-- | meta/lib/oeqa/core/target/ssh.py | 17 | ||||
-rw-r--r-- | meta/lib/oeqa/targetcontrol.py | 3 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/dump.py | 32 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/qemurunner.py | 70 | ||||
-rw-r--r-- | meta/recipes-devtools/qemu/qemu.inc | 2 |
7 files changed, 129 insertions, 7 deletions
diff --git a/meta/classes/testimage.bbclass b/meta/classes/testimage.bbclass index e613759503..43de9d4d76 100644 --- a/meta/classes/testimage.bbclass +++ b/meta/classes/testimage.bbclass | |||
@@ -127,6 +127,11 @@ testimage_dump_host () { | |||
127 | netstat -an | 127 | netstat -an |
128 | } | 128 | } |
129 | 129 | ||
130 | testimage_dump_monitor () { | ||
131 | query-status | ||
132 | query-block | ||
133 | } | ||
134 | |||
130 | python do_testimage() { | 135 | python do_testimage() { |
131 | testimage_main(d) | 136 | testimage_main(d) |
132 | } | 137 | } |
@@ -320,6 +325,7 @@ def testimage_main(d): | |||
320 | target_kwargs['powercontrol_extra_args'] = d.getVar("TEST_POWERCONTROL_EXTRA_ARGS") or "" | 325 | target_kwargs['powercontrol_extra_args'] = d.getVar("TEST_POWERCONTROL_EXTRA_ARGS") or "" |
321 | target_kwargs['serialcontrol_cmd'] = d.getVar("TEST_SERIALCONTROL_CMD") or None | 326 | target_kwargs['serialcontrol_cmd'] = d.getVar("TEST_SERIALCONTROL_CMD") or None |
322 | target_kwargs['serialcontrol_extra_args'] = d.getVar("TEST_SERIALCONTROL_EXTRA_ARGS") or "" | 327 | target_kwargs['serialcontrol_extra_args'] = d.getVar("TEST_SERIALCONTROL_EXTRA_ARGS") or "" |
328 | target_kwargs['testimage_dump_monitor'] = d.getVar("testimage_dump_monitor") or "" | ||
323 | target_kwargs['testimage_dump_target'] = d.getVar("testimage_dump_target") or "" | 329 | target_kwargs['testimage_dump_target'] = d.getVar("testimage_dump_target") or "" |
324 | 330 | ||
325 | def export_ssh_agent(d): | 331 | def export_ssh_agent(d): |
diff --git a/meta/lib/oeqa/core/target/qemu.py b/meta/lib/oeqa/core/target/qemu.py index 792efca1f8..4a5df4a9a8 100644 --- a/meta/lib/oeqa/core/target/qemu.py +++ b/meta/lib/oeqa/core/target/qemu.py | |||
@@ -12,6 +12,7 @@ from collections import defaultdict | |||
12 | 12 | ||
13 | from .ssh import OESSHTarget | 13 | from .ssh import OESSHTarget |
14 | from oeqa.utils.qemurunner import QemuRunner | 14 | from oeqa.utils.qemurunner import QemuRunner |
15 | from oeqa.utils.dump import MonitorDumper | ||
15 | from oeqa.utils.dump import TargetDumper | 16 | from oeqa.utils.dump import TargetDumper |
16 | 17 | ||
17 | supported_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic'] | 18 | supported_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic'] |
@@ -43,6 +44,11 @@ class OEQemuTarget(OESSHTarget): | |||
43 | dump_host_cmds=dump_host_cmds, logger=logger, | 44 | dump_host_cmds=dump_host_cmds, logger=logger, |
44 | serial_ports=serial_ports, boot_patterns = boot_patterns, | 45 | serial_ports=serial_ports, boot_patterns = boot_patterns, |
45 | use_ovmf=ovmf, tmpfsdir=tmpfsdir) | 46 | use_ovmf=ovmf, tmpfsdir=tmpfsdir) |
47 | dump_monitor_cmds = kwargs.get("testimage_dump_monitor") | ||
48 | self.monitor_dumper = MonitorDumper(dump_monitor_cmds, dump_dir, self.runner) | ||
49 | if self.monitor_dumper: | ||
50 | self.monitor_dumper.create_dir("qmp") | ||
51 | |||
46 | dump_target_cmds = kwargs.get("testimage_dump_target") | 52 | dump_target_cmds = kwargs.get("testimage_dump_target") |
47 | self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner) | 53 | self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner) |
48 | self.target_dumper.create_dir("qemu") | 54 | self.target_dumper.create_dir("qemu") |
diff --git a/meta/lib/oeqa/core/target/ssh.py b/meta/lib/oeqa/core/target/ssh.py index 461448dbc5..923a223b25 100644 --- a/meta/lib/oeqa/core/target/ssh.py +++ b/meta/lib/oeqa/core/target/ssh.py | |||
@@ -43,6 +43,7 @@ class OESSHTarget(OETarget): | |||
43 | if port: | 43 | if port: |
44 | self.ssh = self.ssh + [ '-p', port ] | 44 | self.ssh = self.ssh + [ '-p', port ] |
45 | self.scp = self.scp + [ '-P', port ] | 45 | self.scp = self.scp + [ '-P', port ] |
46 | self._monitor_dumper = None | ||
46 | 47 | ||
47 | def start(self, **kwargs): | 48 | def start(self, **kwargs): |
48 | pass | 49 | pass |
@@ -50,6 +51,15 @@ class OESSHTarget(OETarget): | |||
50 | def stop(self, **kwargs): | 51 | def stop(self, **kwargs): |
51 | pass | 52 | pass |
52 | 53 | ||
54 | @property | ||
55 | def monitor_dumper(self): | ||
56 | return self._monitor_dumper | ||
57 | |||
58 | @monitor_dumper.setter | ||
59 | def monitor_dumper(self, dumper): | ||
60 | self._monitor_dumper = dumper | ||
61 | self.monitor_dumper.dump_monitor() | ||
62 | |||
53 | def _run(self, command, timeout=None, ignore_status=True): | 63 | def _run(self, command, timeout=None, ignore_status=True): |
54 | """ | 64 | """ |
55 | Runs command in target using SSHProcess. | 65 | Runs command in target using SSHProcess. |
@@ -87,9 +97,14 @@ class OESSHTarget(OETarget): | |||
87 | processTimeout = self.timeout | 97 | processTimeout = self.timeout |
88 | 98 | ||
89 | status, output = self._run(sshCmd, processTimeout, True) | 99 | status, output = self._run(sshCmd, processTimeout, True) |
90 | self.logger.debug('Command: %s\nOutput: %s\n' % (command, output)) | 100 | self.logger.debug('Command: %s\nStatus: %d Output: %s\n' % (command, status, output)) |
91 | if (status == 255) and (('No route to host') in output): | 101 | if (status == 255) and (('No route to host') in output): |
102 | if self.monitor_dumper: | ||
103 | self.monitor_dumper.dump_monitor() | ||
104 | if status == 255: | ||
92 | self.target_dumper.dump_target() | 105 | self.target_dumper.dump_target() |
106 | if self.monitor_dumper: | ||
107 | self.monitor_dumper.dump_monitor() | ||
93 | return (status, output) | 108 | return (status, output) |
94 | 109 | ||
95 | def copyTo(self, localSrc, remoteDst): | 110 | def copyTo(self, localSrc, remoteDst): |
diff --git a/meta/lib/oeqa/targetcontrol.py b/meta/lib/oeqa/targetcontrol.py index 12057f855a..005ebaa7f3 100644 --- a/meta/lib/oeqa/targetcontrol.py +++ b/meta/lib/oeqa/targetcontrol.py | |||
@@ -17,6 +17,7 @@ from oeqa.utils.sshcontrol import SSHControl | |||
17 | from oeqa.utils.qemurunner import QemuRunner | 17 | from oeqa.utils.qemurunner import QemuRunner |
18 | from oeqa.utils.qemutinyrunner import QemuTinyRunner | 18 | from oeqa.utils.qemutinyrunner import QemuTinyRunner |
19 | from oeqa.utils.dump import TargetDumper | 19 | from oeqa.utils.dump import TargetDumper |
20 | from oeqa.utils.dump import MonitorDumper | ||
20 | from oeqa.controllers.testtargetloader import TestTargetLoader | 21 | from oeqa.controllers.testtargetloader import TestTargetLoader |
21 | from abc import ABCMeta, abstractmethod | 22 | from abc import ABCMeta, abstractmethod |
22 | 23 | ||
@@ -108,6 +109,7 @@ class QemuTarget(BaseTarget): | |||
108 | self.qemulog = os.path.join(self.testdir, "qemu_boot_log.%s" % self.datetime) | 109 | self.qemulog = os.path.join(self.testdir, "qemu_boot_log.%s" % self.datetime) |
109 | dump_target_cmds = d.getVar("testimage_dump_target") | 110 | dump_target_cmds = d.getVar("testimage_dump_target") |
110 | dump_host_cmds = d.getVar("testimage_dump_host") | 111 | dump_host_cmds = d.getVar("testimage_dump_host") |
112 | dump_monitor_cmds = d.getVar("testimage_dump_monitor") | ||
111 | dump_dir = d.getVar("TESTIMAGE_DUMP_DIR") | 113 | dump_dir = d.getVar("TESTIMAGE_DUMP_DIR") |
112 | if not dump_dir: | 114 | if not dump_dir: |
113 | dump_dir = os.path.join(d.getVar('LOG_DIR'), 'runtime-hostdump') | 115 | dump_dir = os.path.join(d.getVar('LOG_DIR'), 'runtime-hostdump') |
@@ -149,6 +151,7 @@ class QemuTarget(BaseTarget): | |||
149 | serial_ports = len(d.getVar("SERIAL_CONSOLES").split())) | 151 | serial_ports = len(d.getVar("SERIAL_CONSOLES").split())) |
150 | 152 | ||
151 | self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner) | 153 | self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner) |
154 | self.monitor_dumper = MonitorDumper(dump_monitor_cmds, dump_dir, self.runner) | ||
152 | 155 | ||
153 | def deploy(self): | 156 | def deploy(self): |
154 | bb.utils.mkdirhier(self.testdir) | 157 | bb.utils.mkdirhier(self.testdir) |
diff --git a/meta/lib/oeqa/utils/dump.py b/meta/lib/oeqa/utils/dump.py index 09a44329e0..843e19fe8a 100644 --- a/meta/lib/oeqa/utils/dump.py +++ b/meta/lib/oeqa/utils/dump.py | |||
@@ -4,6 +4,7 @@ | |||
4 | 4 | ||
5 | import os | 5 | import os |
6 | import sys | 6 | import sys |
7 | import json | ||
7 | import errno | 8 | import errno |
8 | import datetime | 9 | import datetime |
9 | import itertools | 10 | import itertools |
@@ -51,6 +52,8 @@ class BaseDumper(object): | |||
51 | prefix = "host" | 52 | prefix = "host" |
52 | elif isinstance(self, TargetDumper): | 53 | elif isinstance(self, TargetDumper): |
53 | prefix = "target" | 54 | prefix = "target" |
55 | elif isinstance(self, MonitorDumper): | ||
56 | prefix = "qmp" | ||
54 | else: | 57 | else: |
55 | prefix = "unknown" | 58 | prefix = "unknown" |
56 | for i in itertools.count(): | 59 | for i in itertools.count(): |
@@ -58,9 +61,12 @@ class BaseDumper(object): | |||
58 | fullname = os.path.join(self.dump_dir, filename) | 61 | fullname = os.path.join(self.dump_dir, filename) |
59 | if not os.path.exists(fullname): | 62 | if not os.path.exists(fullname): |
60 | break | 63 | break |
61 | with open(fullname, 'w') as dump_file: | 64 | if isinstance(self, MonitorDumper): |
62 | dump_file.write(output) | 65 | with open(fullname, 'w') as json_file: |
63 | 66 | json.dump(output, json_file, indent=4) | |
67 | else: | ||
68 | with open(fullname, 'w') as dump_file: | ||
69 | dump_file.write(output) | ||
64 | 70 | ||
65 | class HostDumper(BaseDumper): | 71 | class HostDumper(BaseDumper): |
66 | """ Class to get dumps from the host running the tests """ | 72 | """ Class to get dumps from the host running the tests """ |
@@ -96,3 +102,23 @@ class TargetDumper(BaseDumper): | |||
96 | except: | 102 | except: |
97 | print("Tried to dump info from target but " | 103 | print("Tried to dump info from target but " |
98 | "serial console failed") | 104 | "serial console failed") |
105 | print("Failed CMD: %s" % (cmd)) | ||
106 | |||
107 | class MonitorDumper(BaseDumper): | ||
108 | """ Class to get dumps via the Qemu Monitor, it only works with QemuRunner """ | ||
109 | |||
110 | def __init__(self, cmds, parent_dir, runner): | ||
111 | super(MonitorDumper, self).__init__(cmds, parent_dir) | ||
112 | self.runner = runner | ||
113 | |||
114 | def dump_monitor(self, dump_dir=""): | ||
115 | if self.runner is None: | ||
116 | return | ||
117 | if dump_dir: | ||
118 | self.dump_dir = dump_dir | ||
119 | for cmd in self.cmds: | ||
120 | try: | ||
121 | output = self.runner.run_monitor(cmd) | ||
122 | self._write_dump(cmd, output) | ||
123 | except: | ||
124 | print("Failed to dump QMP CMD: %s" % (cmd)) | ||
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py index 278904ba0b..f6e1007288 100644 --- a/meta/lib/oeqa/utils/qemurunner.py +++ b/meta/lib/oeqa/utils/qemurunner.py | |||
@@ -20,8 +20,10 @@ import string | |||
20 | import threading | 20 | import threading |
21 | import codecs | 21 | import codecs |
22 | import logging | 22 | import logging |
23 | import tempfile | ||
23 | from oeqa.utils.dump import HostDumper | 24 | from oeqa.utils.dump import HostDumper |
24 | from collections import defaultdict | 25 | from collections import defaultdict |
26 | import importlib | ||
25 | 27 | ||
26 | # Get Unicode non printable control chars | 28 | # Get Unicode non printable control chars |
27 | control_range = list(range(0,32))+list(range(127,160)) | 29 | control_range = list(range(0,32))+list(range(127,160)) |
@@ -172,6 +174,21 @@ class QemuRunner: | |||
172 | return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env) | 174 | return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env) |
173 | 175 | ||
174 | def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None): | 176 | def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None): |
177 | # use logfile to determine the recipe-sysroot-native path and | ||
178 | # then add in the site-packages path components and add that | ||
179 | # to the python sys.path so qmp.py can be found. | ||
180 | python_path = os.path.dirname(os.path.dirname(self.logfile)) | ||
181 | python_path += "/recipe-sysroot-native/usr/lib/python3.9/site-packages" | ||
182 | sys.path.append(python_path) | ||
183 | importlib.invalidate_caches() | ||
184 | try: | ||
185 | qmp = importlib.import_module("qmp") | ||
186 | except: | ||
187 | self.logger.error("qemurunner: qmp.py missing, please ensure it's installed") | ||
188 | return False | ||
189 | qmp_port = self.tmpdir + "/." + next(tempfile._get_candidate_names()) | ||
190 | qmp_param = ' -S -qmp unix:%s,server,wait' % (qmp_port) | ||
191 | |||
175 | try: | 192 | try: |
176 | if self.serial_ports >= 2: | 193 | if self.serial_ports >= 2: |
177 | self.threadsock, threadport = self.create_socket() | 194 | self.threadsock, threadport = self.create_socket() |
@@ -188,7 +205,8 @@ class QemuRunner: | |||
188 | # and analyze descendents in order to determine it. | 205 | # and analyze descendents in order to determine it. |
189 | if os.path.exists(self.qemu_pidfile): | 206 | if os.path.exists(self.qemu_pidfile): |
190 | os.remove(self.qemu_pidfile) | 207 | os.remove(self.qemu_pidfile) |
191 | self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1}"'.format(bootparams, self.qemu_pidfile) | 208 | self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1} {2}"'.format(bootparams, self.qemu_pidfile, qmp_param) |
209 | |||
192 | if qemuparams: | 210 | if qemuparams: |
193 | self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' | 211 | self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' |
194 | 212 | ||
@@ -242,6 +260,7 @@ class QemuRunner: | |||
242 | while not self.is_alive() and time.time() < endtime: | 260 | while not self.is_alive() and time.time() < endtime: |
243 | if self.runqemu.poll(): | 261 | if self.runqemu.poll(): |
244 | if self.runqemu_exited: | 262 | if self.runqemu_exited: |
263 | self.logger.warning("runqemu during is_alive() test") | ||
245 | return False | 264 | return False |
246 | if self.runqemu.returncode: | 265 | if self.runqemu.returncode: |
247 | # No point waiting any longer | 266 | # No point waiting any longer |
@@ -253,9 +272,51 @@ class QemuRunner: | |||
253 | time.sleep(0.5) | 272 | time.sleep(0.5) |
254 | 273 | ||
255 | if self.runqemu_exited: | 274 | if self.runqemu_exited: |
275 | self.logger.warning("runqemu after timeout") | ||
276 | return False | ||
277 | |||
278 | if self.runqemu.returncode: | ||
279 | self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode) | ||
256 | return False | 280 | return False |
257 | 281 | ||
258 | if not self.is_alive(): | 282 | if not self.is_alive(): |
283 | self.logger.warning('is_alive() failed later') | ||
284 | return False | ||
285 | |||
286 | # Create the client socket for the QEMU Monitor Control Socket | ||
287 | # This will allow us to read status from Qemu if the the process | ||
288 | # is still alive | ||
289 | self.logger.debug("QMP Initializing to %s" % (qmp_port)) | ||
290 | try: | ||
291 | self.qmp = qmp.QEMUMonitorProtocol(qmp_port) | ||
292 | except OSError as msg: | ||
293 | self.logger.warning("Failed to initialize qemu monitor socket: %s File: %s" % (msg, msg.filename)) | ||
294 | return False | ||
295 | |||
296 | self.logger.debug("QMP Connecting to %s" % (qmp_port)) | ||
297 | if not os.path.exists(qmp_port) and self.is_alive(): | ||
298 | self.logger.debug("QMP Port does not exist waiting for it to be created") | ||
299 | endtime = time.time() + self.runqemutime | ||
300 | while not os.path.exists(qmp_port) and self.is_alive() and time.time() < endtime: | ||
301 | self.logger.warning("QMP port does not exist yet!") | ||
302 | time.sleep(0.5) | ||
303 | if not os.path.exists(qmp_port) and self.is_alive(): | ||
304 | self.logger.warning("QMP Port still does not exist but QEMU is alive") | ||
305 | return False | ||
306 | |||
307 | try: | ||
308 | self.qmp.connect() | ||
309 | except OSError as msg: | ||
310 | self.logger.warning("Failed to connect qemu monitor socket: %s File: %s" % (msg, msg.filename)) | ||
311 | return False | ||
312 | except qmp.QMPConnectError as msg: | ||
313 | self.logger.warning("Failed to communicate with qemu monitor: %s" % (msg)) | ||
314 | return False | ||
315 | |||
316 | # Release the qemu porcess to continue running | ||
317 | self.run_monitor('cont') | ||
318 | |||
319 | if not self.is_alive(): | ||
259 | self.logger.error("Qemu pid didn't appear in %s seconds (%s)" % | 320 | self.logger.error("Qemu pid didn't appear in %s seconds (%s)" % |
260 | (self.runqemutime, time.strftime("%D %H:%M:%S"))) | 321 | (self.runqemutime, time.strftime("%D %H:%M:%S"))) |
261 | 322 | ||
@@ -380,7 +441,6 @@ class QemuRunner: | |||
380 | sock.close() | 441 | sock.close() |
381 | stopread = True | 442 | stopread = True |
382 | 443 | ||
383 | |||
384 | if not reachedlogin: | 444 | if not reachedlogin: |
385 | if time.time() >= endtime: | 445 | if time.time() >= endtime: |
386 | self.logger.warning("Target didn't reach login banner in %d seconds (%s)" % | 446 | self.logger.warning("Target didn't reach login banner in %d seconds (%s)" % |
@@ -441,6 +501,9 @@ class QemuRunner: | |||
441 | self.runqemu.stdout.close() | 501 | self.runqemu.stdout.close() |
442 | self.runqemu_exited = True | 502 | self.runqemu_exited = True |
443 | 503 | ||
504 | if hasattr(self, 'qmp') and self.qmp: | ||
505 | self.qmp.close() | ||
506 | self.qmp = None | ||
444 | if hasattr(self, 'server_socket') and self.server_socket: | 507 | if hasattr(self, 'server_socket') and self.server_socket: |
445 | self.server_socket.close() | 508 | self.server_socket.close() |
446 | self.server_socket = None | 509 | self.server_socket = None |
@@ -499,6 +562,9 @@ class QemuRunner: | |||
499 | return True | 562 | return True |
500 | return False | 563 | return False |
501 | 564 | ||
565 | def run_monitor(self, command, timeout=60): | ||
566 | return self.qmp.cmd(command) | ||
567 | |||
502 | def run_serial(self, command, raw=False, timeout=60): | 568 | def run_serial(self, command, raw=False, timeout=60): |
503 | # We assume target system have echo to get command status | 569 | # We assume target system have echo to get command status |
504 | if not raw: | 570 | if not raw: |
diff --git a/meta/recipes-devtools/qemu/qemu.inc b/meta/recipes-devtools/qemu/qemu.inc index 5797bbecf2..46b784241e 100644 --- a/meta/recipes-devtools/qemu/qemu.inc +++ b/meta/recipes-devtools/qemu/qemu.inc | |||
@@ -30,7 +30,7 @@ SRC_URI = "https://download.qemu.org/${BPN}-${PV}.tar.xz \ | |||
30 | file://mmap2.patch \ | 30 | file://mmap2.patch \ |
31 | file://determinism.patch \ | 31 | file://determinism.patch \ |
32 | file://0001-tests-meson.build-use-relative-path-to-refer-to-file.patch \ | 32 | file://0001-tests-meson.build-use-relative-path-to-refer-to-file.patch \ |
33 | file://CVE-2021-20203.patch \ | 33 | file://CVE-2021-20203.patch \ |
34 | file://CVE-2020-35517_1.patch \ | 34 | file://CVE-2020-35517_1.patch \ |
35 | file://CVE-2020-35517_2.patch \ | 35 | file://CVE-2020-35517_2.patch \ |
36 | file://CVE-2020-35517_3.patch \ | 36 | file://CVE-2020-35517_3.patch \ |