Python queue, 2019-02-22

Python:
 * introduce "python" directory with module namespace
 * log QEMU launch command line on qemu.QEMUMachine
 
 Acceptance Tests:
 * initrd 4GiB+ test
 * migration test
 * multi vm support in test class
 * bump Avocado version and drop "🥑 enable"
 -----BEGIN PGP SIGNATURE-----
 
 iQIcBAABCAAGBQJccE9jAAoJEGV+jTOl8gnzb1cP/j99kGbgfQJA4CftO9eRXdIm
 FKms4Z42n7KPus+/DphgfOXGYaHzPcqJQNguQYHuPlWaM3DWNU0rcFfAi/QdcZC1
 3iYMyQwiRubjnCMN0Ab4k+GhpCPW6fea6GTzyvqha4jNRhCIhx7v54GTDfxWESQp
 nqW40gAONGSG98DdFgubxg1YYqt7zlI9EVogGixe1gO9SVDkMEe7uH8tPCl9mt2m
 VjN7AeP/NTDmidiwu+2LwSpDC0UmpDAsFnxGI6rDcNx8NOnjSHkSHmtxNJ8j2uZz
 9P0ncGui+LfivdQh/yiBgrjTWXEXAx/oHKQCz7r8uJ8f60eYLFtjTHm//2G7lG48
 luLSnNKq/niM4k/vNhBQr0ByqoHHlpmqAjbmYqw7wdvImBbkXN2Gh9kjNs55S8VZ
 Z7wTceC0G7pyM3LCdFnikyCXKoRxLZ3AXQ3YXFN0PgX/IsyHVuBWBGPFkPkLwcRa
 JW3DEmwx/oeTg2MKp7iA3dGTUIarbsjp+R04erMznlLvE+NgmB8ENY8T+qZ6c+NM
 ZNyp1MH2nuTJsYxY3CkVKwPUqNSoaTLkMxvoZW5rKQdtvNinCYZpaeHuBchaHJed
 E63r0+1n9vAMH3PHDrypW5qjcjSDBOHS+8ajhr0jr2r+6grLQKYEP8q+PwubUaMq
 BsS5jOb8gLGC8ESfZxx/
 =dwff
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/cleber/tags/python-next-pull-request' into staging

Python queue, 2019-02-22

Python:
* introduce "python" directory with module namespace
* log QEMU launch command line on qemu.QEMUMachine

Acceptance Tests:
* initrd 4GiB+ test
* migration test
* multi vm support in test class
* bump Avocado version and drop "🥑 enable"

# gpg: Signature made Fri 22 Feb 2019 19:37:07 GMT
# gpg:                using RSA key 657E8D33A5F209F3
# gpg: Good signature from "Cleber Rosa <crosa@redhat.com>" [marginal]
# gpg: WARNING: This key is not certified with sufficiently trusted signatures!
# gpg:          It is not certain that the signature belongs to the owner.
# Primary key fingerprint: 7ABB 96EB 8B46 B94D 5E0F  E9BB 657E 8D33 A5F2 09F3

* remotes/cleber/tags/python-next-pull-request:
  Acceptance tests: expect boot to extract 2GiB+ initrd with linux-v4.16
  Acceptance tests: use linux-3.6 and set vm memory to 4GiB
  tests.acceptance: adds simple migration test
  tests.acceptance: adds multi vm capability for acceptance tests
  scripts/qemu.py: log QEMU launch command line
  Introduce a Python module structure
  Acceptance tests: drop usage of "🥑 enable"

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-03-07 16:16:02 +00:00
commit 6cb4f6db4f
23 changed files with 193 additions and 40 deletions

1
configure vendored
View File

@ -7674,6 +7674,7 @@ LINKS="$LINKS pc-bios/qemu-icon.bmp"
LINKS="$LINKS .gdbinit scripts" # scripts needed by relative path in .gdbinit LINKS="$LINKS .gdbinit scripts" # scripts needed by relative path in .gdbinit
LINKS="$LINKS tests/acceptance tests/data" LINKS="$LINKS tests/acceptance tests/data"
LINKS="$LINKS tests/qemu-iotests/check" LINKS="$LINKS tests/qemu-iotests/check"
LINKS="$LINKS python"
for bios_file in \ for bios_file in \
$source_path/pc-bios/*.bin \ $source_path/pc-bios/*.bin \
$source_path/pc-bios/*.lid \ $source_path/pc-bios/*.lid \

View File

@ -600,7 +600,6 @@ the ``avocado_qemu.Test`` class. Here's a simple usage example:
class Version(Test): class Version(Test):
""" """
:avocado: enable
:avocado: tags=quick :avocado: tags=quick
""" """
def test_qmp_human_info_version(self): def test_qmp_human_info_version(self):
@ -634,7 +633,46 @@ instance, available at ``self.vm``. Because many tests will tweak the
QEMU command line, launching the QEMUMachine (by using ``self.vm.launch()``) QEMU command line, launching the QEMUMachine (by using ``self.vm.launch()``)
is left to the test writer. is left to the test writer.
At test "tear down", ``avocado_qemu.Test`` handles the QEMUMachine The base test class has also support for tests with more than one
QEMUMachine. The way to get machines is through the ``self.get_vm()``
method which will return a QEMUMachine instance. The ``self.get_vm()``
method accepts arguments that will be passed to the QEMUMachine creation
and also an optional `name` attribute so you can identify a specific
machine and get it more than once through the tests methods. A simple
and hypothetical example follows:
.. code::
from avocado_qemu import Test
class MultipleMachines(Test):
"""
:avocado: enable
"""
def test_multiple_machines(self):
first_machine = self.get_vm()
second_machine = self.get_vm()
self.get_vm(name='third_machine').launch()
first_machine.launch()
second_machine.launch()
first_res = first_machine.command(
'human-monitor-command',
command_line='info version')
second_res = second_machine.command(
'human-monitor-command',
command_line='info version')
third_res = self.get_vm(name='third_machine').command(
'human-monitor-command',
command_line='info version')
self.assertEquals(first_res, second_res, third_res)
At test "tear down", ``avocado_qemu.Test`` handles all the QEMUMachines
shutdown. shutdown.
QEMUMachine QEMUMachine

View File

@ -16,12 +16,13 @@ import errno
import logging import logging
import os import os
import subprocess import subprocess
import qmp.qmp
import re import re
import shutil import shutil
import socket import socket
import tempfile import tempfile
from . import qmp
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -66,7 +67,7 @@ class QEMUMachineAddDeviceError(QEMUMachineError):
failures reported by the QEMU binary itself. failures reported by the QEMU binary itself.
""" """
class MonitorResponseError(qmp.qmp.QMPError): class MonitorResponseError(qmp.QMPError):
""" """
Represents erroneous QMP monitor reply Represents erroneous QMP monitor reply
""" """
@ -266,8 +267,8 @@ class QEMUMachine(object):
self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log") self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
self._qemu_log_file = open(self._qemu_log_path, 'wb') self._qemu_log_file = open(self._qemu_log_path, 'wb')
self._qmp = qmp.qmp.QEMUMonitorProtocol(self._vm_monitor, self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor,
server=True) server=True)
def _post_launch(self): def _post_launch(self):
self._qmp.accept() self._qmp.accept()
@ -319,6 +320,7 @@ class QEMUMachine(object):
self._pre_launch() self._pre_launch()
self._qemu_full_args = (self._wrapper + [self._binary] + self._qemu_full_args = (self._wrapper + [self._binary] +
self._base_args() + self._args) self._base_args() + self._args)
LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
self._popen = subprocess.Popen(self._qemu_full_args, self._popen = subprocess.Popen(self._qemu_full_args,
stdin=devnull, stdin=devnull,
stdout=self._qemu_log_file, stdout=self._qemu_log_file,
@ -383,7 +385,7 @@ class QEMUMachine(object):
""" """
reply = self.qmp(cmd, conv_keys, **args) reply = self.qmp(cmd, conv_keys, **args)
if reply is None: if reply is None:
raise qmp.qmp.QMPError("Monitor is closed") raise qmp.QMPError("Monitor is closed")
if "error" in reply: if "error" in reply:
raise MonitorResponseError(reply) raise MonitorResponseError(reply)
return reply["return"] return reply["return"]

View File

@ -13,7 +13,8 @@
import socket import socket
import os import os
import qemu
from . import QEMUMachine
class QEMUQtestProtocol(object): class QEMUQtestProtocol(object):
@ -79,7 +80,7 @@ class QEMUQtestProtocol(object):
self._sock.settimeout(timeout) self._sock.settimeout(timeout)
class QEMUQtestMachine(qemu.QEMUMachine): class QEMUQtestMachine(QEMUMachine):
'''A QEMU VM''' '''A QEMU VM'''
def __init__(self, binary, args=None, name=None, test_dir="/var/tmp", def __init__(self, binary, args=None, name=None, test_dir="/var/tmp",

View File

@ -25,6 +25,7 @@ check for crashes and unexpected errors.
""" """
from __future__ import print_function from __future__ import print_function
import os
import sys import sys
import glob import glob
import logging import logging
@ -34,6 +35,7 @@ import random
import argparse import argparse
from itertools import chain from itertools import chain
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python'))
from qemu import QEMUMachine from qemu import QEMUMachine
logger = logging.getLogger('device-crash-test') logger = logging.getLogger('device-crash-test')

View File

@ -37,10 +37,13 @@
# #
from __future__ import print_function from __future__ import print_function
import os
import sys
import base64 import base64
import random import random
import qmp sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu import qmp
class QemuGuestAgent(qmp.QEMUMonitorProtocol): class QemuGuestAgent(qmp.QEMUMonitorProtocol):

View File

@ -66,7 +66,6 @@
# sent to QEMU, which is useful for debugging and documentation generation. # sent to QEMU, which is useful for debugging and documentation generation.
from __future__ import print_function from __future__ import print_function
import qmp
import json import json
import ast import ast
import readline import readline
@ -76,6 +75,9 @@ import errno
import atexit import atexit
import shlex import shlex
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu import qmp
class QMPCompleter(list): class QMPCompleter(list):
def complete(self, text, state): def complete(self, text, state):
for cmd in self: for cmd in self:

View File

@ -23,6 +23,8 @@ import sys
import subprocess import subprocess
import json import json
from graphviz import Digraph from graphviz import Digraph
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python'))
from qemu import MonitorResponseError from qemu import MonitorResponseError

View File

@ -10,12 +10,12 @@
import os import os
import sys import sys
import uuid
import avocado import avocado
SRC_ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) SRC_ROOT_DIR = os.path.join(os.path.dirname(__file__), '..', '..', '..')
SRC_ROOT_DIR = os.path.abspath(os.path.dirname(SRC_ROOT_DIR)) sys.path.append(os.path.join(SRC_ROOT_DIR, 'python'))
sys.path.append(os.path.join(SRC_ROOT_DIR, 'scripts'))
from qemu import QEMUMachine from qemu import QEMUMachine
@ -42,13 +42,29 @@ def pick_default_qemu_bin():
class Test(avocado.Test): class Test(avocado.Test):
def setUp(self): def setUp(self):
self.vm = None self._vms = {}
self.qemu_bin = self.params.get('qemu_bin', self.qemu_bin = self.params.get('qemu_bin',
default=pick_default_qemu_bin()) default=pick_default_qemu_bin())
if self.qemu_bin is None: if self.qemu_bin is None:
self.cancel("No QEMU binary defined or found in the source tree") self.cancel("No QEMU binary defined or found in the source tree")
self.vm = QEMUMachine(self.qemu_bin)
def _new_vm(self, *args):
vm = QEMUMachine(self.qemu_bin)
if args:
vm.add_args(*args)
return vm
@property
def vm(self):
return self.get_vm(name='default')
def get_vm(self, *args, name=None):
if not name:
name = str(uuid.uuid4())
if self._vms.get(name) is None:
self._vms[name] = self._new_vm(*args)
return self._vms[name]
def tearDown(self): def tearDown(self):
if self.vm is not None: for vm in self._vms.values():
self.vm.shutdown() vm.shutdown()

View File

@ -18,7 +18,6 @@ class BootLinuxConsole(Test):
Boots a x86_64 Linux kernel and checks that the console is operational Boots a x86_64 Linux kernel and checks that the console is operational
and the kernel command line is properly passed from QEMU to the kernel and the kernel command line is properly passed from QEMU to the kernel
:avocado: enable
:avocado: tags=x86_64 :avocado: tags=x86_64
""" """

View File

@ -8,6 +8,7 @@
# This work is licensed under the terms of the GNU GPL, version 2 or # This work is licensed under the terms of the GNU GPL, version 2 or
# later. See the COPYING file in the top-level directory. # later. See the COPYING file in the top-level directory.
import logging
import tempfile import tempfile
from avocado.utils.process import run from avocado.utils.process import run
@ -18,20 +19,21 @@ class LinuxInitrd(Test):
""" """
Checks QEMU evaluates correctly the initrd file passed as -initrd option. Checks QEMU evaluates correctly the initrd file passed as -initrd option.
:avocado: enable
:avocado: tags=x86_64 :avocado: tags=x86_64
""" """
timeout = 60 timeout = 300
def test_with_2gib_file_should_exit_error_msg(self): def test_with_2gib_file_should_exit_error_msg_with_linux_v3_6(self):
""" """
Pretends to boot QEMU with an initrd file with size of 2GiB Pretends to boot QEMU with an initrd file with size of 2GiB
and expect it exits with error message. and expect it exits with error message.
Fedora-18 shipped with linux-3.6 which have not supported xloadflags
cannot support more than 2GiB initrd.
""" """
kernel_url = ('https://mirrors.kernel.org/fedora/releases/28/' kernel_url = ('https://archives.fedoraproject.org/pub/archive/fedora/li'
'Everything/x86_64/os/images/pxeboot/vmlinuz') 'nux/releases/18/Fedora/x86_64/os/images/pxeboot/vmlinuz')
kernel_hash = '238e083e114c48200f80d889f7e32eeb2793e02a' kernel_hash = '41464f68efe42b9991250bed86c7081d2ccdbb21'
kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash) kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
max_size = 2 * (1024 ** 3) - 1 max_size = 2 * (1024 ** 3) - 1
@ -39,10 +41,44 @@ class LinuxInitrd(Test):
initrd.seek(max_size) initrd.seek(max_size)
initrd.write(b'\0') initrd.write(b'\0')
initrd.flush() initrd.flush()
cmd = "%s -kernel %s -initrd %s" % (self.qemu_bin, kernel_path, cmd = "%s -kernel %s -initrd %s -m 4096" % (
initrd.name) self.qemu_bin, kernel_path, initrd.name)
res = run(cmd, ignore_status=True) res = run(cmd, ignore_status=True)
self.assertEqual(res.exit_status, 1) self.assertEqual(res.exit_status, 1)
expected_msg = r'.*initrd is too large.*max: \d+, need %s.*' % ( expected_msg = r'.*initrd is too large.*max: \d+, need %s.*' % (
max_size + 1) max_size + 1)
self.assertRegex(res.stderr_text, expected_msg) self.assertRegex(res.stderr_text, expected_msg)
def test_with_2gib_file_should_work_with_linux_v4_16(self):
"""
QEMU has supported up to 4 GiB initrd for recent kernel
Expect guest can reach 'Unpacking initramfs...'
"""
kernel_url = ('https://mirrors.kernel.org/fedora/releases/28/'
'Everything/x86_64/os/images/pxeboot/vmlinuz')
kernel_hash = '238e083e114c48200f80d889f7e32eeb2793e02a'
kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
max_size = 2 * (1024 ** 3) + 1
with tempfile.NamedTemporaryFile() as initrd:
initrd.seek(max_size)
initrd.write(b'\0')
initrd.flush()
self.vm.set_machine('pc')
self.vm.set_console()
kernel_command_line = 'console=ttyS0'
self.vm.add_args('-kernel', kernel_path,
'-append', kernel_command_line,
'-initrd', initrd.name,
'-m', '5120')
self.vm.launch()
console = self.vm.console_socket.makefile()
console_logger = logging.getLogger('console')
while True:
msg = console.readline()
console_logger.debug(msg.strip())
if 'Unpacking initramfs...' in msg:
break
if 'Kernel panic - not syncing' in msg:
self.fail("Kernel panic reached")

View File

@ -0,0 +1,53 @@
# Migration test
#
# Copyright (c) 2019 Red Hat, Inc.
#
# Authors:
# Cleber Rosa <crosa@redhat.com>
# Caio Carrara <ccarrara@redhat.com>
#
# This work is licensed under the terms of the GNU GPL, version 2 or
# later. See the COPYING file in the top-level directory.
from avocado_qemu import Test
from avocado.utils import network
from avocado.utils import wait
class Migration(Test):
"""
:avocado: enable
"""
timeout = 10
@staticmethod
def migration_finished(vm):
return vm.command('query-migrate')['status'] in ('completed', 'failed')
def _get_free_port(self):
port = network.find_free_port()
if port is None:
self.cancel('Failed to find a free port')
return port
def test_migration_with_tcp_localhost(self):
source_vm = self.get_vm()
dest_uri = 'tcp:localhost:%u' % self._get_free_port()
dest_vm = self.get_vm('-incoming', dest_uri)
dest_vm.launch()
source_vm.launch()
source_vm.qmp('migrate', uri=dest_uri)
wait.wait_for(
self.migration_finished,
timeout=self.timeout,
step=0.1,
args=(source_vm,)
)
self.assertEqual(dest_vm.command('query-migrate')['status'], 'completed')
self.assertEqual(source_vm.command('query-migrate')['status'], 'completed')
self.assertEqual(dest_vm.command('query-status')['status'], 'running')
self.assertEqual(source_vm.command('query-status')['status'], 'postmigrate')

View File

@ -14,7 +14,6 @@ from avocado_qemu import Test
class Version(Test): class Version(Test):
""" """
:avocado: enable
:avocado: tags=quick :avocado: tags=quick
""" """
def test_qmp_human_info_version(self): def test_qmp_human_info_version(self):

View File

@ -11,7 +11,7 @@ Check compatibility of virtio device types
import sys import sys
import os import os
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "scripts")) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu import QEMUMachine from qemu import QEMUMachine
from avocado_qemu import Test from avocado_qemu import Test
@ -61,7 +61,6 @@ class VirtioVersionCheck(Test):
same device tree created by `disable-modern` and same device tree created by `disable-modern` and
`disable-legacy`. `disable-legacy`.
:avocado: enable
:avocado: tags=x86_64 :avocado: tags=x86_64
""" """

View File

@ -13,7 +13,6 @@ from avocado_qemu import Test
class Vnc(Test): class Vnc(Test):
""" """
:avocado: enable
:avocado: tags=vnc,quick :avocado: tags=vnc,quick
""" """
def test_no_vnc(self): def test_no_vnc(self):

View File

@ -24,13 +24,14 @@ import re
import sys import sys
import time import time
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'scripts'))
import qemu
import qmp.qmp
from guestperf.progress import Progress, ProgressStats from guestperf.progress import Progress, ProgressStats
from guestperf.report import Report from guestperf.report import Report
from guestperf.timings import TimingRecord, Timings from guestperf.timings import TimingRecord, Timings
sys.path.append(os.path.join(os.path.dirname(__file__),
'..', '..', '..', 'python'))
import qemu
class Engine(object): class Engine(object):

View File

@ -23,7 +23,7 @@ import os
import iotests import iotests
from iotests import qemu_img_create, qemu_io, file_path, log from iotests import qemu_img_create, qemu_io, file_path, log
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu import QEMUMachine from qemu import QEMUMachine

View File

@ -23,7 +23,7 @@ import os
import iotests import iotests
from iotests import log from iotests import log
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu import QEMUMachine from qemu import QEMUMachine

View File

@ -32,8 +32,8 @@ import atexit
import io import io
from collections import OrderedDict from collections import OrderedDict
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
import qtest from qemu import qtest
# This will not work if arguments contain spaces but is necessary if we # This will not work if arguments contain spaces but is necessary if we

View File

@ -1,4 +1,4 @@
# Add Python module requirements, one per line, to be installed # Add Python module requirements, one per line, to be installed
# in the tests/venv Python virtual environment. For more info, # in the tests/venv Python virtual environment. For more info,
# refer to: https://pip.pypa.io/en/stable/user_guide/#id1 # refer to: https://pip.pypa.io/en/stable/user_guide/#id1
avocado-framework==65.0 avocado-framework==68.0

View File

@ -17,7 +17,7 @@ import sys
import logging import logging
import time import time
import datetime import datetime
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "scripts")) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu import QEMUMachine, kvm_available from qemu import QEMUMachine, kvm_available
import subprocess import subprocess
import hashlib import hashlib