All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cleber Rosa <crosa@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Eduardo Habkost" <ehabkost@redhat.com>,
	"Philippe Mathieu-Daudé" <f4bug@amsat.org>,
	"Stefan Hajnoczi" <stefanha@redhat.com>,
	"Fam Zheng" <famz@redhat.com>, "Amador Pahim" <amador@pahim.org>,
	"Cleber Rosa" <crosa@redhat.com>
Subject: [Qemu-devel] [PATCH 4/5] scripts/qemu.py: introduce set_console() method
Date: Thu, 24 May 2018 20:58:38 -0400	[thread overview]
Message-ID: <20180525005839.11556-5-crosa@redhat.com> (raw)
In-Reply-To: <20180525005839.11556-1-crosa@redhat.com>

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 <crosa@redhat.com>
---
 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
 
 
 LOG = logging.getLogger(__name__)
 
 
+#: Maps machine types to the preferred console device types
+CONSOLE_DEV_TYPES = {
+    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.
     """
 
 
+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 conflicting
+    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 = test_dir
         self._temp_dir = None
         self._launched = False
+        self._machine = None
+        self._console_device_type = None
+        self._console_address = None
+        self._console_socket = None
 
         # 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 = 'socket,id=mon,path=%s' % self._vm_monitor
-        return ['-chardev', moncdev,
+        args = ['-chardev', moncdev,
                 '-mon', 'chardev=mon,mode=control',
                 '-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 = os.path.join(self._temp_dir,
+                                                 self._name + "-console.sock")
+            chardev = ('socket,id=console,path=%s,server,nowait' %
+                       self._console_address)
+            device = '%s,chardev=console' % self._console_device_type
+            args.extend(['-chardev', chardev, '-device', device])
+        return args
 
     def _pre_launch(self):
         self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
@@ -202,6 +238,10 @@ class QEMUMachine(object):
 
         self._qemu_log_path = None
 
+        if self._console_socket is not None:
+            self._console_socket.close()
+            self._console_socket = None
+
         if self._temp_dir is not None:
             shutil.rmtree(self._temp_dir)
             self._temp_dir = 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 = machine_type
+
+    def set_console(self, device_type=None):
+        '''
+        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 device:"
+                                                " QEMU instance without a "
+                                                "defined machine type")
+            for regex, device in CONSOLE_DEV_TYPES.items():
+                if re.match(regex, self._machine):
+                    device_type = device
+                    break
+            if device_type is None:
+                raise QEMUMachineAddDeviceError("Can not add a console device:"
+                                                " no matching console device "
+                                                "type definition")
+        self._console_device_type = device_type
+
+    @property
+    def console_socket(self):
+        """
+        Returns a socket connected to the console
+        """
+        if self._console_socket is None:
+            self._console_socket = 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=None):
+    """
+    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 = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+        root_dir = os.environ.get("QEMU_ROOT_DIR", src_dir)
+    binaries = glob.glob(os.path.join(root_dir, '*-softmmu/qemu-system-*'))
+    if 'win' in sys.platform:
+        bin_filter = lambda x: x.endswith(".exe")
+    else:
+        bin_filter = lambda x: not x.endswith(".exe")
+    return [_ for _ in binaries if bin_filter(_)]
+
+
+def subprocess_dev_null(mode='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 = tempfile.mkdtemp()
+        monitor_socket = os.path.join(tempdir, 'monitor.sock')
+        args = [binary_path, '-nodefaults', '-machine', 'none',
+                '-nographic', '-S', '-qmp', 'unix:%s' % monitor_socket]
+        monitor = qmp.qmp.QEMUMonitorProtocol(monitor_socket, True)
+        try:
+            qemu_proc = subprocess.Popen(args,
+                                         stdin=subprocess.PIPE,
+                                         stdout=subprocess.PIPE,
+                                         stderr=subprocess_dev_null(),
+                                         universal_newlines=True)
+        except OSError:
+            return None
+        monitor.accept()
+        res = 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 = qmp_execute(binary_path, "query-target")
+    if res is None:
+        raise QEMUMachineProbeError('Failed to probe the QEMU arch by querying'
+                                    ' the target of binary "%s"' % binary_path)
+    return res.get("arch", None)
+
+
+class QEMU(unittest.TestCase):
+
+
+    TEST_ARCH_MACHINE_CONSOLES = {
+        '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 = qemu.QEMUMachine('/fake/path/to/binary')
+                qemu_machine.set_machine(machine)
+                qemu_machine.set_console()
+
+    def test_set_console_no_machine(self):
+        qemu_machine = qemu.QEMUMachine('/fake/path/to/binary')
+        self.assertRaises(qemu.QEMUMachineAddDeviceError,
+                          qemu_machine.set_console)
+
+    def test_set_console_no_machine_match(self):
+        qemu_machine = 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 = qemu_bin_probe_arch(binary)
+            for machine in QEMU.TEST_ARCH_MACHINE_CONSOLES.get(probed_arch, []):
+                qemu_machine = qemu.QEMUMachine(binary)
+
+                # the following workarounds are target specific required for
+                # this test.  users are of QEMUMachine are expected to deal with
+                # target specific requirements just the same in their own code
+                cap_htm_off = ('pseries-2.7', 'pseries-2.8', 'pseries-2.9',
+                               'pseries-2.10', 'pseries-2.11', 'pseries-2.12')
+                if probed_arch == 'ppc64' and machine in cap_htm_off:
+                    qemu_machine._machine = machine   # pylint: disable=W0212
+                    qemu_machine.args.extend(['-machine',
+                                              '%s,cap-htm=off' % machine])
+                elif probed_arch == 's390x':
+                    qemu_machine.set_machine(machine)
+                    qemu_machine.args.append('-nodefaults')
+                elif probed_arch == '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()
-- 
2.17.0

  parent reply	other threads:[~2018-05-25  0:59 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-05-25  0:58 [Qemu-devel] [PATCH 0/5] Acceptance/functional tests Cleber Rosa
2018-05-25  0:58 ` [Qemu-devel] [PATCH 1/5] Add functional/acceptance tests infrastructure Cleber Rosa
2018-05-25  5:33   ` Fam Zheng
2018-05-25 16:15     ` Cleber Rosa
2018-05-25  0:58 ` [Qemu-devel] [PATCH 2/5] scripts/qemu.py: allow adding to the list of extra arguments Cleber Rosa
2018-05-25  0:58 ` [Qemu-devel] [PATCH 3/5] Acceptance tests: add quick VNC tests Cleber Rosa
2018-05-25  5:37   ` Fam Zheng
2018-05-25 16:27     ` Cleber Rosa
2018-05-29 14:32       ` Eduardo Habkost
2018-05-29 14:59         ` Cleber Rosa
2018-05-29 17:52           ` Eduardo Habkost
2018-05-29 19:36             ` Cleber Rosa
2018-05-25  0:58 ` Cleber Rosa [this message]
2018-05-25  5:47   ` [Qemu-devel] [PATCH 4/5] scripts/qemu.py: introduce set_console() method Fam Zheng
2018-05-25 16:57     ` Cleber Rosa
2018-05-29 19:06       ` Eduardo Habkost
2018-05-29 19:31         ` Cleber Rosa
2018-05-25  0:58 ` [Qemu-devel] [PATCH 5/5] Acceptance tests: add Linux kernel boot and console checking test Cleber Rosa
2018-05-25  1:14 ` [Qemu-devel] [PATCH 0/5] Acceptance/functional tests Cleber Rosa
2018-05-25  6:08 ` Fam Zheng
2018-05-25 17:04   ` Cleber Rosa

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=20180525005839.11556-5-crosa@redhat.com \
    --to=crosa@redhat.com \
    --cc=amador@pahim.org \
    --cc=ehabkost@redhat.com \
    --cc=f4bug@amsat.org \
    --cc=famz@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.