[0] https://github.com/qemu/qemu/blob/master/docs/interop/qmp-spec.txt
Signed-off-by: Saul Wold <saul.w...@windriver.com>
---
meta/classes/testimage.bbclass | 7 ++++
meta/lib/oeqa/core/target/qemu.py | 6 +++
meta/lib/oeqa/core/target/ssh.py | 22 ++++++++--
meta/lib/oeqa/targetcontrol.py | 5 +++
meta/lib/oeqa/utils/dump.py | 20 +++++++++
meta/lib/oeqa/utils/qemurunner.py | 70 ++++++++++++++++++++++++++++++-
6 files changed, 126 insertions(+), 4 deletions(-)
diff --git a/meta/classes/testimage.bbclass b/meta/classes/testimage.bbclass
index e3feef02f8..a274865955 100644
--- a/meta/classes/testimage.bbclass
+++ b/meta/classes/testimage.bbclass
@@ -127,6 +127,12 @@ testimage_dump_host () {
netstat -an
}
+testimage_dump_monitor () {
+ '{"execute":"status"}\n'
+ '{"execute":"query-status"}\n'
+ '{"execute":"query-block"}\n'
+}
+
python do_testimage() {
testimage_main(d)
}
@@ -319,6 +325,7 @@ def testimage_main(d):
target_kwargs['powercontrol_extra_args'] = d.getVar("TEST_POWERCONTROL_EXTRA_ARGS")
or ""
target_kwargs['serialcontrol_cmd'] = d.getVar("TEST_SERIALCONTROL_CMD")
or None
target_kwargs['serialcontrol_extra_args'] =
d.getVar("TEST_SERIALCONTROL_EXTRA_ARGS") or ""
+ target_kwargs['testimage_dump_monitor'] = d.getVar("testimage_dump_monitor") or
""
target_kwargs['testimage_dump_target'] = d.getVar("testimage_dump_target") or
""
def export_ssh_agent(d):
diff --git a/meta/lib/oeqa/core/target/qemu.py
b/meta/lib/oeqa/core/target/qemu.py
index 0f29414df5..a73d82d9af 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
from .ssh import OESSHTarget
from oeqa.utils.qemurunner import QemuRunner
+from oeqa.utils.dump import MonitorDumper
from oeqa.utils.dump import TargetDumper
supported_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic']
@@ -43,6 +44,11 @@ class OEQemuTarget(OESSHTarget):
dump_host_cmds=dump_host_cmds, logger=logger,
serial_ports=serial_ports, boot_patterns =
boot_patterns,
use_ovmf=ovmf)
+ dump_monitor_cmds = kwargs.get("testimage_dump_monitor")
+ self.monitor_dumper = MonitorDumper(dump_monitor_cmds, dump_dir,
self.runner)
+ if self.monitor_dumper:
+ self.monitor_dumper.create_dir("qmp")
+
dump_target_cmds = kwargs.get("testimage_dump_target")
self.target_dumper = TargetDumper(dump_target_cmds, dump_dir,
self.runner)
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..faffc8acdf 100644
--- a/meta/lib/oeqa/core/target/ssh.py
+++ b/meta/lib/oeqa/core/target/ssh.py
@@ -43,6 +43,7 @@ class OESSHTarget(OETarget):
if port:
self.ssh = self.ssh + [ '-p', port ]
self.scp = self.scp + [ '-P', port ]
+ self._monitor_dumper = None
def start(self, **kwargs):
pass
@@ -50,11 +51,20 @@ class OESSHTarget(OETarget):
def stop(self, **kwargs):
pass
+ @property
+ def monitor_dumper(self):
+ return self._monitor_dumper
+
+ @monitor_dumper.setter
+ def monitor_dumper(self, dumper):
+ self._monitor_dumper = dumper
+ self.monitor_dumper.dump_monitor()
+
def _run(self, command, timeout=None, ignore_status=True):
"""
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)
@@ -87,9 +97,15 @@ 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):
+ # for testing right now
+ if self.monitor_dumper:
+ self.monitor_dumper.dump_monitor()
+ if status == 255:
self.target_dumper.dump_target()
+ if self.monitor_dumper:
+ self.monitor_dumper.dump_monitor()
return (status, output)
def copyTo(self, localSrc, remoteDst):
diff --git a/meta/lib/oeqa/targetcontrol.py b/meta/lib/oeqa/targetcontrol.py
index 19f5a4ea7e..0d070531c3 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 = d.getVar("testimage_dump_monitor")
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,9 @@ 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)
+ self.logger.debug("sgw monitor: %s" % self.monitor_dumper)
+
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..4d0357a155 100644
--- a/meta/lib/oeqa/utils/dump.py
+++ b/meta/lib/oeqa/utils/dump.py
@@ -96,3 +96,23 @@ 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:
+ output = self.runner.run_monitor(cmd)
+ self._write_dump("qemu_monitor", (cmd + "\n" + 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: