From nobody Mon Feb 9 20:10:42 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1527210227057443.6759407021524; Thu, 24 May 2018 18:03:47 -0700 (PDT) Received: from localhost ([::1]:41313 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fM190-00005P-72 for importer@patchew.org; Thu, 24 May 2018 21:03:46 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:58438) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fM14d-0005pE-07 for qemu-devel@nongnu.org; Thu, 24 May 2018 20:59:17 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1fM14Z-0005DQ-K6 for qemu-devel@nongnu.org; Thu, 24 May 2018 20:59:14 -0400 Received: from mx3-rdu2.redhat.com ([66.187.233.73]:49256 helo=mx1.redhat.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1fM14Z-0005DM-E7 for qemu-devel@nongnu.org; Thu, 24 May 2018 20:59:11 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id E5DD340122A4; Fri, 25 May 2018 00:59:10 +0000 (UTC) Received: from localhost.localdomain.com (ovpn-126-90.rdu2.redhat.com [10.10.126.90]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 5E765200BC62; Fri, 25 May 2018 00:59:10 +0000 (UTC) From: Cleber Rosa To: qemu-devel@nongnu.org Date: Thu, 24 May 2018 20:58:38 -0400 Message-Id: <20180525005839.11556-5-crosa@redhat.com> In-Reply-To: <20180525005839.11556-1-crosa@redhat.com> References: <20180525005839.11556-1-crosa@redhat.com> X-Scanned-By: MIMEDefang 2.78 on 10.11.54.4 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.5]); Fri, 25 May 2018 00:59:10 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.5]); Fri, 25 May 2018 00:59:10 +0000 (UTC) for IP:'10.11.54.4' DOMAIN:'int-mx04.intmail.prod.int.rdu2.redhat.com' HELO:'smtp.corp.redhat.com' FROM:'crosa@redhat.com' RCPT:'' X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 66.187.233.73 Subject: [Qemu-devel] [PATCH 4/5] scripts/qemu.py: introduce set_console() method X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Fam Zheng , Eduardo Habkost , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Amador Pahim , Stefan Hajnoczi , Cleber Rosa Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" The set_console() method is intended to ease higher level use cases that require a console device. The amount of inteligence is limited on purpose, requiring either the device type explicitly, or the existence of a machine (pattern) definition. Because of the console device type selection criteria (by machine type), users should also be able to define that. It'll then be used for both '-machine' and for the console device type selection. Users of the set_console() method will certainly be interested in accessing the console device, and for that a console_socket property has been added. Signed-off-by: Cleber Rosa --- scripts/qemu.py | 97 +++++++++++++++++++++++- scripts/test_qemu.py | 176 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 scripts/test_qemu.py diff --git a/scripts/qemu.py b/scripts/qemu.py index 7813dd45ad..89db5bc6b2 100644 --- a/scripts/qemu.py +++ b/scripts/qemu.py @@ -17,19 +17,41 @@ import logging import os import subprocess import qmp.qmp +import re import shutil +import socket import tempfile =20 =20 LOG =3D logging.getLogger(__name__) =20 =20 +#: Maps machine types to the preferred console device types +CONSOLE_DEV_TYPES =3D { + r'^clipper$': 'isa-serial', + r'^malta': 'isa-serial', + r'^(pc.*|q35.*|isapc)$': 'isa-serial', + r'^(40p|powernv|prep)$': 'isa-serial', + r'^pseries.*': 'spapr-vty', + r'^s390-ccw-virtio.*': 'sclpconsole', + } + + class QEMUMachineError(Exception): """ Exception called when an error in QEMUMachine happens. """ =20 =20 +class QEMUMachineAddDeviceError(QEMUMachineError): + """ + Exception raised when a request to add a device can not be fulfilled + + The failures are caused by limitations, lack of information or conflic= ting + requests on the QEMUMachine methods. This exception does not represent + failures reported by the QEMU binary itself. + """ + class MonitorResponseError(qmp.qmp.QMPError): ''' Represents erroneous QMP monitor reply @@ -91,6 +113,10 @@ class QEMUMachine(object): self._test_dir =3D test_dir self._temp_dir =3D None self._launched =3D False + self._machine =3D None + self._console_device_type =3D None + self._console_address =3D None + self._console_socket =3D None =20 # just in case logging wasn't configured by the main script: logging.basicConfig() @@ -175,9 +201,19 @@ class QEMUMachine(object): self._monitor_address[1]) else: moncdev =3D 'socket,id=3Dmon,path=3D%s' % self._vm_monitor - return ['-chardev', moncdev, + args =3D ['-chardev', moncdev, '-mon', 'chardev=3Dmon,mode=3Dcontrol', '-display', 'none', '-vga', 'none'] + if self._machine is not None: + args.extend(['-machine', self._machine]) + if self._console_device_type is not None: + self._console_address =3D os.path.join(self._temp_dir, + self._name + "-console.so= ck") + chardev =3D ('socket,id=3Dconsole,path=3D%s,server,nowait' % + self._console_address) + device =3D '%s,chardev=3Dconsole' % self._console_device_type + args.extend(['-chardev', chardev, '-device', device]) + return args =20 def _pre_launch(self): self._temp_dir =3D tempfile.mkdtemp(dir=3Dself._test_dir) @@ -202,6 +238,10 @@ class QEMUMachine(object): =20 self._qemu_log_path =3D None =20 + if self._console_socket is not None: + self._console_socket.close() + self._console_socket =3D None + if self._temp_dir is not None: shutil.rmtree(self._temp_dir) self._temp_dir =3D None @@ -365,3 +405,58 @@ class QEMUMachine(object): Adds to the list of extra arguments to be given to the QEMU binary ''' return self._args.extend(args) + + def set_machine(self, machine_type): + ''' + Sets the machine type + + If set, the machine type will be added to the base arguments + of the resulting QEMU command line. + ''' + self._machine =3D machine_type + + def set_console(self, device_type=3DNone): + ''' + Sets the device type for a console device + + If set, the console device and a backing character device will + be added to the base arguments of the resulting QEMU command + line. + + This is a convenience method that will either use the provided + device type, of if not given, it will used the device type set + on CONSOLE_DEV_TYPES. + + The actual setting of command line arguments will be be done at + machine launch time, as it depends on the temporary directory + to be created. + + @param device_type: the device type, such as "isa-serial" + @raises: QEMUMachineAddDeviceError if the device type is not given + and can not be determined. + ''' + if device_type is None: + if self._machine is None: + raise QEMUMachineAddDeviceError("Can not add a console dev= ice:" + " QEMU instance without a " + "defined machine type") + for regex, device in CONSOLE_DEV_TYPES.items(): + if re.match(regex, self._machine): + device_type =3D device + break + if device_type is None: + raise QEMUMachineAddDeviceError("Can not add a console dev= ice:" + " no matching console devi= ce " + "type definition") + self._console_device_type =3D device_type + + @property + def console_socket(self): + """ + Returns a socket connected to the console + """ + if self._console_socket is None: + self._console_socket =3D socket.socket(socket.AF_UNIX, + socket.SOCK_STREAM) + self._console_socket.connect(self._console_address) + return self._console_socket diff --git a/scripts/test_qemu.py b/scripts/test_qemu.py new file mode 100644 index 0000000000..5e016d3751 --- /dev/null +++ b/scripts/test_qemu.py @@ -0,0 +1,176 @@ +import sys +import os +import glob +import unittest +import shutil +import tempfile +import subprocess + +import qemu +import qmp.qmp + + +class QEMUMachineProbeError(Exception): + """ + Exception raised when a probe a fails to be deterministic + """ + + +def get_built_qemu_binaries(root_dir=3DNone): + """ + Attempts to find QEMU binaries in a tree + + If root_dir is None, it will attempt to find the binaries at the + source tree. It's possible to override it by setting the environment + variable QEMU_ROOT_DIR. + """ + if root_dir is None: + src_dir =3D os.path.dirname(os.path.dirname(os.path.abspath(__file= __))) + root_dir =3D os.environ.get("QEMU_ROOT_DIR", src_dir) + binaries =3D glob.glob(os.path.join(root_dir, '*-softmmu/qemu-system-*= ')) + if 'win' in sys.platform: + bin_filter =3D lambda x: x.endswith(".exe") + else: + bin_filter =3D lambda x: not x.endswith(".exe") + return [_ for _ in binaries if bin_filter(_)] + + +def subprocess_dev_null(mode=3D'w'): + """ + A portable null file object suitable for use with the subprocess module + """ + if hasattr(subprocess, 'DEVNULL'): + return subprocess.DEVNULL + else: + return open(os.path.devnull, mode) + + +def qmp_execute(binary_path, qmp_command): + """ + Executes a QMP command on a given QEMU binary + + Useful for one-off execution of QEMU binaries to get runtime + information. + + @param binary_path: path to a QEMU binary + @param qmp_command: the QMP command + @note: passing arguments to the QMP command is not supported at + this time. + """ + try: + tempdir =3D tempfile.mkdtemp() + monitor_socket =3D os.path.join(tempdir, 'monitor.sock') + args =3D [binary_path, '-nodefaults', '-machine', 'none', + '-nographic', '-S', '-qmp', 'unix:%s' % monitor_socket] + monitor =3D qmp.qmp.QEMUMonitorProtocol(monitor_socket, True) + try: + qemu_proc =3D subprocess.Popen(args, + stdin=3Dsubprocess.PIPE, + stdout=3Dsubprocess.PIPE, + stderr=3Dsubprocess_dev_null(), + universal_newlines=3DTrue) + except OSError: + return None + monitor.accept() + res =3D monitor.cmd(qmp_command) + monitor.cmd("quit") + qemu_proc.wait() + monitor.close() + return res.get("return", None) + finally: + shutil.rmtree(tempdir) + + +def qemu_bin_probe_arch(binary_path): + """ + Probes the architecture from the QEMU binary + + @returns: either the probed arch or None + @rtype: str or None + @raises: QEMUMachineProbeError + """ + res =3D qmp_execute(binary_path, "query-target") + if res is None: + raise QEMUMachineProbeError('Failed to probe the QEMU arch by quer= ying' + ' the target of binary "%s"' % binary_= path) + return res.get("arch", None) + + +class QEMU(unittest.TestCase): + + + TEST_ARCH_MACHINE_CONSOLES =3D { + 'alpha': ['clipper'], + 'mips': ['malta'], + 'x86_64': ['isapc', + 'pc', 'pc-0.10', 'pc-0.11', 'pc-0.12', 'pc-0.13', + 'pc-0.14', 'pc-0.15', 'pc-1.0', 'pc-1.1', 'pc-1.2', + 'pc-1.3', + 'pc-i440fx-1.4', 'pc-i440fx-1.5', 'pc-i440fx-1.6', + 'pc-i440fx-1.7', 'pc-i440fx-2.0', 'pc-i440fx-2.1', + 'pc-i440fx-2.10', 'pc-i440fx-2.11', 'pc-i440fx-2.2', + 'pc-i440fx-2.3', 'pc-i440fx-2.4', 'pc-i440fx-2.5', + 'pc-i440fx-2.6', 'pc-i440fx-2.7', 'pc-i440fx-2.8', + 'pc-i440fx-2.9', 'pc-q35-2.10', 'pc-q35-2.11', + 'q35', 'pc-q35-2.4', 'pc-q35-2.5', 'pc-q35-2.6', + 'pc-q35-2.7', 'pc-q35-2.8', 'pc-q35-2.9'], + 'ppc64': ['40p', 'powernv', 'prep', 'pseries', 'pseries-2.1', + 'pseries-2.2', 'pseries-2.3', 'pseries-2.4', 'pseries-2.= 5', + 'pseries-2.6', 'pseries-2.7', 'pseries-2.8', 'pseries-2.= 9', + 'pseries-2.10', 'pseries-2.11', 'pseries-2.12'], + 's390x': ['s390-ccw-virtio', 's390-ccw-virtio-2.4', + 's390-ccw-virtio-2.5', 's390-ccw-virtio-2.6', + 's390-ccw-virtio-2.7', 's390-ccw-virtio-2.8', + 's390-ccw-virtio-2.9', 's390-ccw-virtio-2.10', + 's390-ccw-virtio-2.11', 's390-ccw-virtio-2.12'] + } + + + def test_set_console(self): + for machines in QEMU.TEST_ARCH_MACHINE_CONSOLES.values(): + for machine in machines: + qemu_machine =3D qemu.QEMUMachine('/fake/path/to/binary') + qemu_machine.set_machine(machine) + qemu_machine.set_console() + + def test_set_console_no_machine(self): + qemu_machine =3D qemu.QEMUMachine('/fake/path/to/binary') + self.assertRaises(qemu.QEMUMachineAddDeviceError, + qemu_machine.set_console) + + def test_set_console_no_machine_match(self): + qemu_machine =3D qemu.QEMUMachine('/fake/path/to/binary') + qemu_machine.set_machine('unknown-machine-model') + self.assertRaises(qemu.QEMUMachineAddDeviceError, + qemu_machine.set_console) + + @unittest.skipUnless(get_built_qemu_binaries(), + "Could not find any QEMU binaries built to use on= " + "console check") + def test_set_console_launch(self): + for binary in get_built_qemu_binaries(): + probed_arch =3D qemu_bin_probe_arch(binary) + for machine in QEMU.TEST_ARCH_MACHINE_CONSOLES.get(probed_arch= , []): + qemu_machine =3D qemu.QEMUMachine(binary) + + # the following workarounds are target specific required f= or + # this test. users are of QEMUMachine are expected to dea= l with + # target specific requirements just the same in their own = code + cap_htm_off =3D ('pseries-2.7', 'pseries-2.8', 'pseries-2.= 9', + 'pseries-2.10', 'pseries-2.11', 'pseries-2.= 12') + if probed_arch =3D=3D 'ppc64' and machine in cap_htm_off: + qemu_machine._machine =3D machine # pylint: disable= =3DW0212 + qemu_machine.args.extend(['-machine', + '%s,cap-htm=3Doff' % machine= ]) + elif probed_arch =3D=3D 's390x': + qemu_machine.set_machine(machine) + qemu_machine.args.append('-nodefaults') + elif probed_arch =3D=3D 'mips': + qemu_machine.set_machine(machine) + qemu_machine.args.extend(['-bios', os.path.devnull]) + else: + qemu_machine.set_machine(machine) + + qemu_machine.set_console() + qemu_machine.launch() + qemu_machine.shutdown() --=20 2.17.0