From: "Saul Wold" <Saul.Wold@windriver.com>
To: openembedded-core@lists.openembedded.org
Subject: Re: [OE-core] [PATCH] [WIP] qemurunner.py: qemu as client
Date: Tue, 10 Nov 2020 20:19:53 -0800 [thread overview]
Message-ID: <426872d7-910b-eca0-9fbe-b4be69d1ba59@windriver.com> (raw)
In-Reply-To: <164658B07603FC62.32470@lists.openembedded.org>
Oops, need to double check what 000* is going to actual send, please
ignore this and the following older qemu_runner patch.
Sau!
On 11/10/20 8:10 PM, Saul Wold wrote:
> Signed-off-by: Saul Wold <saul.wold@windriver.com>
> ---
> meta/lib/oeqa/core/target/ssh.py | 9 ++--
> meta/lib/oeqa/runtime/cases/network.py | 28 +++++++++++
> meta/lib/oeqa/runtime/files/kill_net | 5 ++
> meta/lib/oeqa/targetcontrol.py | 3 ++
> meta/lib/oeqa/utils/dump.py | 22 ++++++++
> meta/lib/oeqa/utils/qemurunner.py | 70 +++++++++++++++++++++++++-
> 6 files changed, 133 insertions(+), 4 deletions(-)
> create mode 100644 meta/lib/oeqa/runtime/cases/network.py
> create mode 100644 meta/lib/oeqa/runtime/files/kill_net
>
> diff --git a/meta/lib/oeqa/core/target/ssh.py b/meta/lib/oeqa/core/target/ssh.py
> index 461448dbc5..c744b90719 100644
> --- a/meta/lib/oeqa/core/target/ssh.py
> +++ b/meta/lib/oeqa/core/target/ssh.py
> @@ -54,7 +54,7 @@ class OESSHTarget(OETarget):
> """
> Runs command in target using SSHProcess.
> """
> - self.logger.debug("[Running]$ %s" % " ".join(command))
> + self.logger.debug("sgw-[Running]$ %s" % " ".join(command))
>
> starttime = time.time()
> status, output = SSHCall(command, self.logger, timeout)
> @@ -77,6 +77,7 @@ class OESSHTarget(OETarget):
> 0: No timeout, runs until return.
> """
> targetCmd = 'export PATH=/usr/sbin:/sbin:/usr/bin:/bin; %s' % command
> + self.ip = "192.168.7.5"
> sshCmd = self.ssh + [self.ip, targetCmd]
>
> if timeout:
> @@ -87,8 +88,10 @@ class OESSHTarget(OETarget):
> processTimeout = self.timeout
>
> status, output = self._run(sshCmd, processTimeout, True)
> - self.logger.debug('Command: %s\nOutput: %s\n' % (command, output))
> - if (status == 255) and (('No route to host') in output):
> + self.logger.debug('Command: %s\nStatus: %d Output: %s\n' % (command, status, output))
> +# if (status == 255) and (('No route to host') in output):
> + if status == 255:
> + self.target_dumper.dump_target()
> self.target_dumper.dump_target()
> return (status, output)
>
> diff --git a/meta/lib/oeqa/runtime/cases/network.py b/meta/lib/oeqa/runtime/cases/network.py
> new file mode 100644
> index 0000000000..49fcac133a
> --- /dev/null
> +++ b/meta/lib/oeqa/runtime/cases/network.py
> @@ -0,0 +1,28 @@
> +#
> +# SPDX-License-Identifier: MIT
> +#
> +
> +from oeqa.runtime.case import OERuntimeTestCase
> +from oeqa.core.decorator.depends import OETestDepends
> +from oeqa.runtime.decorator.package import OEHasPackage
> +
> +class NetworkTest(OERuntimeTestCase):
> +
> + @classmethod
> + def setUp(cls):
> + src = os.path.join(cls.tc.runtime_files_dir, 'kill_net')
> + dst = '/home/root/kill_net'
> + cls.tc.target.copyTo(src, dst)
> +
> +
> + @OETestDepends(['ssh.SSHTest.test_ssh'])
> + def test_network_check(self):
> + (status, output) = self.target.run('/sbin/ip a')
> + msg = 'Failed to run "ip a". Output: %s' % output
> + self.assertEqual(status, 0, msg=msg)
> +
> + @OETestDepends(['network.NetworkTest.test_network_check'])
> + def test_run_kill_net(self):
> + (status, output) = self.target.run('/bin/bash /home/root/kill_net > results 2>&1 &')
> + msg = 'Failed to run "kill_net". Output: %s' % output
> + self.assertEqual(status, 0, msg=msg)
> diff --git a/meta/lib/oeqa/runtime/files/kill_net b/meta/lib/oeqa/runtime/files/kill_net
> new file mode 100644
> index 0000000000..981eec816d
> --- /dev/null
> +++ b/meta/lib/oeqa/runtime/files/kill_net
> @@ -0,0 +1,5 @@
> +#!/bin/bash
> +
> +ip a
> +sleep 1
> +ip link set eth0 down
> diff --git a/meta/lib/oeqa/targetcontrol.py b/meta/lib/oeqa/targetcontrol.py
> index 19f5a4ea7e..8cdd984179 100644
> --- a/meta/lib/oeqa/targetcontrol.py
> +++ b/meta/lib/oeqa/targetcontrol.py
> @@ -17,6 +17,7 @@ from oeqa.utils.sshcontrol import SSHControl
> from oeqa.utils.qemurunner import QemuRunner
> from oeqa.utils.qemutinyrunner import QemuTinyRunner
> from oeqa.utils.dump import TargetDumper
> +from oeqa.utils.dump import MonitorDumper
> from oeqa.controllers.testtargetloader import TestTargetLoader
> from abc import ABCMeta, abstractmethod
>
> @@ -108,6 +109,7 @@ class QemuTarget(BaseTarget):
> self.qemulog = os.path.join(self.testdir, "qemu_boot_log.%s" % self.datetime)
> dump_target_cmds = d.getVar("testimage_dump_target")
> dump_host_cmds = d.getVar("testimage_dump_host")
> + dump_monitor_cmds = ['{"execute":"query-status"}\n', '{"execute":"query-status"}\n']
> dump_dir = d.getVar("TESTIMAGE_DUMP_DIR")
> if not dump_dir:
> dump_dir = os.path.join(d.getVar('LOG_DIR'), 'runtime-hostdump')
> @@ -147,6 +149,7 @@ class QemuTarget(BaseTarget):
> serial_ports = len(d.getVar("SERIAL_CONSOLES").split()))
>
> self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner)
> + self.monitor_dumper = MonitorDumper(dump_monitor_cmds, dump_dir, self.runner)
>
> def deploy(self):
> bb.utils.mkdirhier(self.testdir)
> diff --git a/meta/lib/oeqa/utils/dump.py b/meta/lib/oeqa/utils/dump.py
> index 09a44329e0..058d1e46b1 100644
> --- a/meta/lib/oeqa/utils/dump.py
> +++ b/meta/lib/oeqa/utils/dump.py
> @@ -96,3 +96,25 @@ class TargetDumper(BaseDumper):
> except:
> print("Tried to dump info from target but "
> "serial console failed")
> + print("Failed CMD: %s" % (cmd))
> +
> +class MonitorDumper(BaseDumper):
> + """ Class to get dumps via the Qemu Monitor, it only works with QemuRunner """
> +
> + def __init__(self, cmds, parent_dir, runner):
> + super(MonitorDumper, self).__init__(cmds, parent_dir)
> + self.runner = runner
> +
> + def dump_monitor(self, dump_dir=""):
> + if dump_dir:
> + self.dump_dir = dump_dir
> + for cmd in self.cmds:
> + try:
> + print("dump_target: %s" % cmd)
> + output = self.runner.run_monitor(cmd)
> + print("result: %s" % (output))
> + self._write_dump(cmd.split()[0], output)
> + except:
> + print("Failed to dump montor data")
> + print("Failed CMD: %s" % (cmd))
> +
> diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py
> index 77ec939ad7..0ca0d78470 100644
> --- a/meta/lib/oeqa/utils/qemurunner.py
> +++ b/meta/lib/oeqa/utils/qemurunner.py
> @@ -20,6 +20,7 @@ import string
> import threading
> import codecs
> import logging
> +from contextlib import closing
> from oeqa.utils.dump import HostDumper
> from collections import defaultdict
>
> @@ -84,6 +85,12 @@ class QemuRunner:
> default_boot_patterns['send_login_user'] = 'root\n'
> default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#"
> default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#"
> + monitor_cmds = defaultdict(str)
> + monitor_cmds['qmp_cap'] = '{"execute":"qmp_capabilities","arguments":{"enable":["oob"]}}\n'
> + monitor_cmds['cont'] = '{"execute":"cont"}\n'
> + monitor_cmds['quit'] = '{"execute":"quit"}\n'
> + monitor_cmds['preconfig'] = '{"execute":"x-exit-preconfig"}\n'
> + self.monitor_cmds = monitor_cmds
>
> # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n"
> for pattern in accepted_patterns:
> @@ -168,10 +175,17 @@ class QemuRunner:
> return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env)
>
> def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None):
> + # Find a free socket port that can be used by the QEMU Monitor console
> + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
> + s.bind(('', 0))
> + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
> + qmp_port = s.getsockname()[1]
> +
> try:
> if self.serial_ports >= 2:
> self.threadsock, threadport = self.create_socket()
> self.server_socket, self.serverport = self.create_socket()
> +
> except socket.error as msg:
> self.logger.error("Failed to create listening socket: %s" % msg[1])
> return False
> @@ -185,6 +199,9 @@ class QemuRunner:
> if os.path.exists(self.qemu_pidfile):
> os.remove(self.qemu_pidfile)
> self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1}"'.format(bootparams, self.qemu_pidfile)
> + qemuparams += ' -S -qmp tcp:localhost:%s,server,wait' % (qmp_port)
> + qemuparams += ' -monitor tcp:localhost:4444,server,nowait'
> +
> if qemuparams:
> self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'
>
> @@ -250,6 +267,28 @@ class QemuRunner:
>
> if self.runqemu_exited:
> return False
> +
> + # Create the client socket for the QEMU Monitor Control Socket
> + # This will allow us to read status from Qemu if the the process
> + # is still alive
> + try:
> + self.monitor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
> + self.monitor_socket.connect(("127.0.0.1", qmp_port))
> + self.monitor_socket.setblocking(False)
> +
> + except socket.error as msg:
> + self.logger.error("Failed to connect qemu monitor socket: %s" % msg[1])
> + return False
> +
> + # Run an empty command to get the initial connection details, then
> + # send the qmp_capabilities command, this is required to initialize
> + # the monitor console
> + mon_output = self.run_monitor("")
> + self.logger.debug("Monitor: %s" % mon_output)
> + mon_output = self.run_monitor(self.monitor_cmds['qmp_cap'], timeout=120)
> + self.logger.debug("Monitor: %s" % mon_output)
> + mon_output = self.run_monitor(self.monitor_cmds['cont'], timeout=120)
> + self.logger.debug("Monitor: %s" % mon_output)
>
> if not self.is_alive():
> self.logger.error("Qemu pid didn't appear in %s seconds (%s)" %
> @@ -338,6 +377,7 @@ class QemuRunner:
> reachedlogin = False
> stopread = False
> qemusock = None
> + monsock = None
> bootlog = b''
> data = b''
> while time.time() < endtime and not stopread:
> @@ -376,7 +416,6 @@ class QemuRunner:
> sock.close()
> stopread = True
>
> -
> if not reachedlogin:
> if time.time() >= endtime:
> self.logger.warning("Target didn't reach login banner in %d seconds (%s)" %
> @@ -437,6 +476,9 @@ class QemuRunner:
> self.runqemu.stdout.close()
> self.runqemu_exited = True
>
> + if hasattr(self, 'monitor_socket') and self.monitor_socket:
> + self.monitor_socket.close()
> + self.monitor_socket = None
> if hasattr(self, 'server_socket') and self.server_socket:
> self.server_socket.close()
> self.server_socket = None
> @@ -495,6 +537,32 @@ class QemuRunner:
> return True
> return False
>
> + def run_monitor(self, command, timeout=60):
> + data = ''
> + self.monitor_socket.sendall(command.encode('utf-8'))
> + start = time.time()
> + end = start + timeout
> + while True:
> + now = time.time()
> + if now >= end:
> + data += "<<< run_monitor(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout
> + break
> + try:
> + sread, _, _ = select.select([self.monitor_socket],[],[], end - now)
> + except InterruptedError:
> + continue
> + if sread:
> + answer = self.monitor_socket.recv(1024)
> + if answer:
> + data += answer.decode('utf-8')
> + if data.rfind('\r\n') != -1:
> + break;
> + else:
> + raise Exception("No data on monitor socket")
> +
> + if data:
> + return (str(data))
> +
> def run_serial(self, command, raw=False, timeout=60):
> # We assume target system have echo to get command status
> if not raw:
>
>
>
>
>
--
Sau!
prev parent reply other threads:[~2020-11-11 4:19 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-11-11 4:10 [WIP v2 0/1] Add Qemu Monitor Support Saul Wold
2020-11-11 4:10 ` [WIP v2 1/1] qemurunner: add support for qmp cmds Saul Wold
2020-11-11 14:35 ` [OE-core] " Joshua Watt
2020-11-11 14:54 ` Joshua Watt
2020-11-11 4:10 ` [PATCH] [WIP] qemurunner.py: qemu as client Saul Wold
2020-11-11 4:10 ` [PATCH] qemurunner: add support for qmp cmds Saul Wold
[not found] ` <164658B07603FC62.32470@lists.openembedded.org>
2020-11-11 4:19 ` Saul Wold [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=426872d7-910b-eca0-9fbe-b4be69d1ba59@windriver.com \
--to=saul.wold@windriver.com \
--cc=openembedded-core@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox