[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH v3 03/10] tests: Add vm test lib
From: |
Kamil Rytarowski |
Subject: |
Re: [Qemu-devel] [PATCH v3 03/10] tests: Add vm test lib |
Date: |
Tue, 22 Aug 2017 14:33:25 +0200 |
User-agent: |
Mozilla/5.0 (X11; NetBSD amd64; rv:52.0) Gecko/20100101 Thunderbird/52.1.0 |
On 22.08.2017 13:19, Philippe Mathieu-Daudé wrote:
> Hi Fam,
>
> On 08/22/2017 01:41 AM, Fam Zheng wrote:
>> This is the common code to implement a "VM test" to
>>
>> 1) Download and initialize a pre-defined VM that has necessary
>> dependencies to build QEMU and SSH access.
>>
>> 2) Archive $SRC_PATH to a .tar file.
>>
>> 3) Boot the VM, and pass the source tar file to the guest.
>>
>> 4) SSH into the VM, untar the source tarball, build from the source.
>>
>> Signed-off-by: Fam Zheng <address@hidden>
>> ---
>> tests/vm/basevm.py | 278
>> +++++++++++++++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 278 insertions(+)
>> create mode 100755 tests/vm/basevm.py
>>
>> diff --git a/tests/vm/basevm.py b/tests/vm/basevm.py
>> new file mode 100755
>> index 0000000000..7eab737aa8
>> --- /dev/null
>> +++ b/tests/vm/basevm.py
>> @@ -0,0 +1,278 @@
>> +#!/usr/bin/env python
>> +#
>> +# VM testing base class
>> +#
>> +# Copyright (C) 2017 Red Hat Inc.
>> +#
>> +# Authors:
>> +# Fam Zheng <address@hidden>
>> +#
>> +# This work is licensed under the terms of the GNU GPL, version 2. See
>> +# the COPYING file in the top-level directory.
>> +#
>> +
>> +import os
>> +import sys
>> +import logging
>> +import time
>> +import datetime
>> +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..",
>> "scripts"))
>> +from qemu import QEMUMachine
>> +import subprocess
>> +import hashlib
>> +import optparse
>> +import atexit
>> +import tempfile
>> +import shutil
>> +import multiprocessing
>> +import traceback
>> +
>> +SSH_KEY = """\
>> +-----BEGIN RSA PRIVATE KEY-----
>> +MIIEowIBAAKCAQEAopAuOlmLV6LVHdFBj8/eeOwI9CqguIJPp7eAQSZvOiB4Ag/R
>> +coEhl/RBbrV5Yc/SmSD4PTpJO/iM10RwliNjDb4a3I8q3sykRJu9c9PI/YsH8WN9
>> ++NH2NjKPtJIcKTu287IM5JYxyB6nDoOzILbTyJ1TDR/xH6qYEfBAyiblggdjcvhA
>> +RTf93QIn39F/xLypXvT1K2O9BJEsnJ8lEUvB2UXhKo/JTfSeZF8wPBeowaP9EONk
>> +7b+nuJOWHGg68Ji6wVi62tjwl2Szch6lxIhZBpnV7QNRKMfYHP6eIyF4pusazzZq
>> +Telsq6xI2ghecWLzb/MF5A+rklsGx2FNuJSAJwIDAQABAoIBAHHi4o/8VZNivz0x
>> +cWXn8erzKV6tUoWQvW85Lj/2RiwJvSlsnYZDkx5af1CpEE2HA/pFT8PNRqsd+MWC
>> +7AEy710cVsM4BYerBFYQaYxwzblaoojo88LSjVPw3h5Z0iLM8+IMVd36nwuc9dpE
>> +R8TecMZ1+U4Tl6BgqkK+9xToZRdPKdjS8L5MoFhGN+xY0vRbbJbGaV9Q0IHxLBkB
>> +rEBV7T1mUynneCHRUQlJQEwJmKpT8MH3IjsUXlG5YvnuuvcQJSNTaW2iDLxuOKp8
>> +cxW8+qL88zpb1D5dppoIu6rlrugN0azSq70ruFJQPc/A8GQrDKoGgRQiagxNY3u+
>> +vHZzXlECgYEA0dKO3gfkSxsDBb94sQwskMScqLhcKhztEa8kPxTx6Yqh+x8/scx3
>> +XhJyOt669P8U1v8a/2Al+s81oZzzfQSzO1Q7gEwSrgBcRMSIoRBUw9uYcy02ngb/
>> +j/ng3DGivfJztjjiSJwb46FHkJ2JR8mF2UisC6UMXk3NgFY/3vWQx78CgYEAxlcG
>> +T3hfSWSmTgKRczMJuHQOX9ULfTBIqwP5VqkkkiavzigGRirzb5lgnmuTSPTpF0LB
>> +XVPjR2M4q+7gzP0Dca3pocrvLEoxjwIKnCbYKnyyvnUoE9qHv4Kr+vDbgWpa2LXG
>> +JbLmE7tgTCIp20jOPPT4xuDvlbzQZBJ5qCQSoZkCgYEAgrotSSihlCnAOFSTXbu4
>> +CHp3IKe8xIBBNENq0eK61kcJpOxTQvOha3sSsJsU4JAM6+cFaxb8kseHIqonCj1j
>> +bhOM/uJmwQJ4el/4wGDsbxriYOBKpyq1D38gGhDS1IW6kk3erl6VAb36WJ/OaGum
>> +eTpN9vNeQWM4Jj2WjdNx4QECgYAwTdd6mU1TmZCrJRL5ZG+0nYc2rbMrnQvFoqUi
>> +BvWiJovggHzur90zy73tNzPaq9Ls2FQxf5G1vCN8NCRJqEEjeYCR59OSDMu/EXc2
>> +CnvQ9SevHOdS1oEDEjcCWZCMFzPi3XpRih1gptzQDe31uuiHjf3cqcGPzTlPdfRt
>> +D8P92QKBgC4UaBvIRwREVJsdZzpIzm224Bpe8LOmA7DeTnjlT0b3lkGiBJ36/Q0p
>> +VhYh/6cjX4/iuIs7gJbGon7B+YPB8scmOi3fj0+nkJAONue1mMfBNkba6qQTc6Y2
>> +5mEKw2/O7/JpND7ucU3OK9plcw/qnrWDgHxl0Iz95+OzUIIagxne
>> +-----END RSA PRIVATE KEY-----
>> +"""
>> +SSH_PUB_KEY = """\
>> +ssh-rsa
>> AAAAB3NzaC1yc2EAAAADAQABAAABAQCikC46WYtXotUd0UGPz9547Aj0KqC4gk+nt4BBJm86IHgCD9FygSGX9EFutXlhz9KZIPg9Okk7+IzXRHCWI2MNvhrcjyrezKREm71z08j9iwfxY3340fY2Mo+0khwpO7bzsgzkljHIHqcOg7MgttPInVMNH/EfqpgR8EDKJuWCB2Ny+EBFN/3dAiff0X/EvKle9PUrY70EkSycnyURS8HZReEqj8lN9J5kXzA8F6jBo/0Q42Ttv6e4k5YcaDrwmLrBWLra2PCXZLNyHqXEiFkGmdXtA1Eox9gc/p4jIXim6xrPNmpN6WyrrEjaCF5xYvNv8wXkD6uSWwbHYU24lIAn
>> qemu-vm-key
>> +"""
>> +
>> +class BaseVM(object):
>> + GUEST_USER = "qemu"
>> + GUEST_PASS = "qemupass"
>> + ROOT_PASS = "qemupass"
>> +
>> + # The script to run in the guest that builds QEMU
>> + BUILD_SCRIPT = ""
>> + # The guest name, to be overridden by subclasses
>> + name = "#base"
>> + def __init__(self, debug=False, vcpus=None):
>> + self._guest = None
>> + self._tmpdir = tempfile.mkdtemp(prefix="qemu-vm-")
>> + atexit.register(shutil.rmtree, self._tmpdir)
>> +
>> + self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa")
>> + open(self._ssh_key_file, "w").write(SSH_KEY)
>> + subprocess.check_call(["chmod", "600", self._ssh_key_file])
>> +
>> + self._ssh_pub_key_file = os.path.join(self._tmpdir,
>> "id_rsa.pub")
>> + open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY)
>> +
>> + self.debug = debug
>> + self._stderr = sys.stderr
>> + self._devnull = open("/dev/null", "w")
>> + if self.debug:
>> + self._stdout = sys.stdout
>> + else:
>> + self._stdout = self._devnull
>> + self._args = [ \
>> + "-nodefaults", "-enable-kvm", "-m", "2G",
Can we make -enable-kvm optional? This is currently Linux specific.
>> + "-cpu", "host",
>> + "-netdev", "user,id=vnet,hostfwd=:0.0.0.0:0-:22",
>> + "-device", "virtio-net-pci,netdev=vnet",
>> + "-vnc", ":0,to=20",
>> + "-serial", "file:%s" % os.path.join(self._tmpdir,
>> "serial.out")]
>> + if vcpus:
>> + self._args += ["-smp", str(vcpus)]
>> +
>> + self._data_args = []
>> +
>> + def _download_with_cache(self, url, sha256sum=None):
>> + def check_sha256sum(fname):
>> + if not sha256sum:
>> + return True
>> + checksum = subprocess.check_output(["sha256sum",
>> fname]).split()[0]
>> + return sha256sum == checksum
>> +
>> + cache_dir = os.path.expanduser("~/.cache/qemu-vm/download")
>> + if not os.path.exists(cache_dir):
>> + os.makedirs(cache_dir)
>> + fname = os.path.join(cache_dir, hashlib.sha1(url).hexdigest())
>> + if os.path.exists(fname) and check_sha256sum(fname):
>> + return fname
>> + logging.debug("Downloading %s to %s...", url, fname)
>> + subprocess.check_call(["wget", "-c", url, "-O", fname +
>> ".download"],
>> + stdout=self._stdout, stderr=self._stderr)
>> + os.rename(fname + ".download", fname)
>> + return fname
>> +
>> + def _ssh_do(self, user, cmd, check, interactive=False):
>> + ssh_cmd = ["ssh", "-q",
>> + "-o", "StrictHostKeyChecking=no",
>> + "-o", "UserKnownHostsFile=/dev/null",
>> + "-o", "ConnectTimeout=1",
>> + "-p", self.ssh_port, "-i", self._ssh_key_file]
>> + if interactive:
>> + ssh_cmd += ['-t']
>> + assert not isinstance(cmd, str)
>> + ssh_cmd += ["address@hidden" % user] + list(cmd)
>> + logging.debug("ssh_cmd: %s", " ".join(ssh_cmd))
>> + r = subprocess.call(ssh_cmd,
>> + stdin=sys.stdin if interactive else
>> self._devnull,
>> + stdout=sys.stdout if interactive else
>> self._stdout,
>> + stderr=sys.stderr if interactive else
>> self._stderr)
>> + if check and r != 0:
>> + raise Exception("SSH command failed: %s" % cmd)
>> + return r
>> +
>> + def ssh(self, *cmd):
>> + return self._ssh_do(self.GUEST_USER, cmd, False)
>> +
>> + def ssh_interactive(self, *cmd):
>> + return self._ssh_do(self.GUEST_USER, cmd, False, True)
>> +
>> + def ssh_root(self, *cmd):
>> + return self._ssh_do("root", cmd, False)
>> +
>> + def ssh_check(self, *cmd):
>> + self._ssh_do(self.GUEST_USER, cmd, True)
>> +
>> + def ssh_root_check(self, *cmd):
>> + self._ssh_do("root", cmd, True)
>> +
>> + def build_image(self, img):
>> + raise NotImplementedError
>> +
>> + def add_source_dir(self, data_dir):
>> + name = "data-" + hashlib.sha1(data_dir).hexdigest()[:5]
>> + tarfile = os.path.join(self._tmpdir, name + ".tar")
>> + logging.debug("Creating archive %s for data dir: %s",
>> tarfile, data_dir)
>> + subprocess.check_call(["tar", "--exclude-vcs",
>
> --exclude-vcs-ignores will process .gitignore,
> you can also list with 'git ls-files',
> or generate tarball with scripts/make-release
>
It might not be a problem here, but --exclude-vcs isn't portable. My
basesystem (NetBSD 8.99.1) version from bsdtar (libarchive) 3.3.2dev
does not support it. The solution to this is to have a fallback to gtar
or make the tar call portable.
>> + "--exclude=tests/vm/*.img",
>> + "--exclude=tests/vm/*.img.*",
>> + "--exclude=*.d",
>> + "--exclude=*.o",
>> + "--exclude=docker-src.*",
>> + "-cf", tarfile, '.'], cwd=data_dir,
>> + stdin=self._devnull, stdout=self._stdout)
>> + self._data_args += ["-drive",
>> +
>> "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
>> + (tarfile, name),
>> + "-device",
>> +
>> "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
>
> what about mounting readonly via '-device virtio-9p-pci' (not using
> tarball)?
>
>> +
>> + def boot(self, img, extra_args=[]):
>> + args = self._args + [
>> + "-device", "VGA",
>> + "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img,
>> + "-device", "virtio-blk,drive=drive0,bootindex=0"]
>> + args += self._data_args + extra_args
>> + logging.debug("QEMU args: %s", " ".join(args))
>> + guest = QEMUMachine(binary=os.environ.get("QEMU",
>> "qemu-system-x86_64"),
>> + args=args)
>> + guest.launch()
>> + atexit.register(self.shutdown)
>> + self._guest = guest
>> + usernet_info = guest.qmp("human-monitor-command",
>> + command_line="info usernet")
>> + self.ssh_port = None
>> + for l in usernet_info["return"].splitlines():
>> + fields = l.split()
>> + if "TCP[HOST_FORWARD]" in fields and "22" in fields:
>> + self.ssh_port = l.split()[3]
>> + if not self.ssh_port:
>> + raise Exception("Cannot find ssh port from 'info
>> usernet':\n%s" % \
>> + usernet_info)
>> +
>> + def wait_ssh(self, seconds=120):
>> + starttime = datetime.datetime.now()
>> + guest_up = False
>> + while (datetime.datetime.now() - starttime).total_seconds() <
>> seconds:
>> + if self.ssh("exit 0") == 0:
>> + guest_up = True
>> + break
>> + time.sleep(1)
>> + if not guest_up:
>> + raise TimeoutError("Timeout while waiting for guest ssh")
>> +
>> + def shutdown(self):
>> + self._guest.shutdown()
>> +
>> + def wait(self):
>> + self._guest.wait()
>> +
>> + def qmp(self, *args, **kwargs):
>> + return self._guest.qmp(*args, **kwargs)
>> +
>> +def parse_args(vm_name):
>> + parser = optparse.OptionParser(description="""
>> + VM test utility. Exit codes: 0 = success, 1 = command line
>> error, 2 = environment initialization failed, 3 = test command failed""")
>> + parser.add_option("--debug", "-D", action="store_true",
>> + help="enable debug output")
>> + parser.add_option("--image", "-i", default="%s.img" % vm_name,
>> + help="image file name")
>> + parser.add_option("--force", "-f", action="store_true",
>> + help="force build image even if image exists")
>> + parser.add_option("--jobs", type=int,
>> default=multiprocessing.cpu_count(),
>> + help="number of virtual CPUs")
>> + parser.add_option("--build-image", "-b", action="store_true",
>> + help="build image")
>> + parser.add_option("--build-qemu",
>> + help="build QEMU from source in guest")
>> + parser.add_option("--interactive", "-I", action="store_true",
>> + help="Interactively run command")
>> + parser.disable_interspersed_args()
>> + return parser.parse_args()
>> +
>> +def main(vmcls):
>> + try:
>> + args, argv = parse_args(vmcls.name)
>> + if not argv and not args.build_qemu and not args.build_image:
>> + print "Nothing to do?"
>> + return 1
>> + if args.debug:
>> + logging.getLogger().setLevel(logging.DEBUG)
>> + vm = vmcls(debug=args.debug, vcpus=args.jobs)
>> + if args.build_image:
>> + if os.path.exists(args.image) and not args.force:
>> + sys.stderr.writelines(["Image file exists: %s\n" %
>> args.image,
>> + "Use --force option to
>> overwrite\n"])
>> + return 1
>> + return vm.build_image(args.image)
>> + if args.build_qemu:
>> + vm.add_source_dir(args.build_qemu)
>> + cmd = [vm.BUILD_SCRIPT.format(
>> + configure_opts = " ".join(argv),
>> + jobs=args.jobs)]
>> + else:
>> + cmd = argv
>> + vm.boot(args.image + ",snapshot=on")
>> + vm.wait_ssh()
>> + except Exception as e:
>> + if isinstance(e, SystemExit) and e.code == 0:
>> + return 0
>> + sys.stderr.write("Failed to prepare guest environment\n")
>> + traceback.print_exc()
>> + return 2
>> +
>> + if args.interactive:
>> + if vm.ssh_interactive(*cmd) == 0:
>> + return 0
>> + vm.ssh_interactive()
>> + return 3
>> + else:
>> + if vm.ssh(*cmd) != 0:
>> + return 3
>>
>
signature.asc
Description: OpenPGP digital signature
[Qemu-devel] [PATCH v3 04/10] tests: Add ubuntu.i386 image, Fam Zheng, 2017/08/22
[Qemu-devel] [PATCH v3 05/10] tests: Add FreeBSD image, Fam Zheng, 2017/08/22
[Qemu-devel] [PATCH v3 06/10] tests: Add NetBSD image, Fam Zheng, 2017/08/22