qemu with hax to log dma reads & writes jcs.org/2018/11/12/vfio

tests: Add vm test lib

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 <famz@redhat.com>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>

+262
+262
tests/vm/basevm.py
··· 1 + #!/usr/bin/env python 2 + # 3 + # VM testing base class 4 + # 5 + # Copyright 2017 Red Hat Inc. 6 + # 7 + # Authors: 8 + # Fam Zheng <famz@redhat.com> 9 + # 10 + # This code is licensed under the GPL version 2 or later. See 11 + # the COPYING file in the top-level directory. 12 + # 13 + 14 + import os 15 + import sys 16 + import logging 17 + import time 18 + import datetime 19 + sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "scripts")) 20 + from qemu import QEMUMachine 21 + import subprocess 22 + import hashlib 23 + import optparse 24 + import atexit 25 + import tempfile 26 + import shutil 27 + import multiprocessing 28 + import traceback 29 + 30 + SSH_KEY = open(os.path.join(os.path.dirname(__file__), 31 + "..", "keys", "id_rsa")).read() 32 + SSH_PUB_KEY = open(os.path.join(os.path.dirname(__file__), 33 + "..", "keys", "id_rsa.pub")).read() 34 + 35 + class BaseVM(object): 36 + GUEST_USER = "qemu" 37 + GUEST_PASS = "qemupass" 38 + ROOT_PASS = "qemupass" 39 + 40 + # The script to run in the guest that builds QEMU 41 + BUILD_SCRIPT = "" 42 + # The guest name, to be overridden by subclasses 43 + name = "#base" 44 + def __init__(self, debug=False, vcpus=None): 45 + self._guest = None 46 + self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-", 47 + suffix=".tmp", 48 + dir=".")) 49 + atexit.register(shutil.rmtree, self._tmpdir) 50 + 51 + self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa") 52 + open(self._ssh_key_file, "w").write(SSH_KEY) 53 + subprocess.check_call(["chmod", "600", self._ssh_key_file]) 54 + 55 + self._ssh_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub") 56 + open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY) 57 + 58 + self.debug = debug 59 + self._stderr = sys.stderr 60 + self._devnull = open(os.devnull, "w") 61 + if self.debug: 62 + self._stdout = sys.stdout 63 + else: 64 + self._stdout = self._devnull 65 + self._args = [ \ 66 + "-nodefaults", "-m", "2G", 67 + "-cpu", "host", 68 + "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22", 69 + "-device", "virtio-net-pci,netdev=vnet", 70 + "-vnc", "127.0.0.1:0,to=20", 71 + "-serial", "file:%s" % os.path.join(self._tmpdir, "serial.out")] 72 + if vcpus: 73 + self._args += ["-smp", str(vcpus)] 74 + if os.access("/dev/kvm", os.R_OK | os.W_OK): 75 + self._args += ["-enable-kvm"] 76 + else: 77 + logging.info("KVM not available, not using -enable-kvm") 78 + self._data_args = [] 79 + 80 + def _download_with_cache(self, url, sha256sum=None): 81 + def check_sha256sum(fname): 82 + if not sha256sum: 83 + return True 84 + checksum = subprocess.check_output(["sha256sum", fname]).split()[0] 85 + return sha256sum == checksum 86 + 87 + cache_dir = os.path.expanduser("~/.cache/qemu-vm/download") 88 + if not os.path.exists(cache_dir): 89 + os.makedirs(cache_dir) 90 + fname = os.path.join(cache_dir, hashlib.sha1(url).hexdigest()) 91 + if os.path.exists(fname) and check_sha256sum(fname): 92 + return fname 93 + logging.debug("Downloading %s to %s...", url, fname) 94 + subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"], 95 + stdout=self._stdout, stderr=self._stderr) 96 + os.rename(fname + ".download", fname) 97 + return fname 98 + 99 + def _ssh_do(self, user, cmd, check, interactive=False): 100 + ssh_cmd = ["ssh", "-q", 101 + "-o", "StrictHostKeyChecking=no", 102 + "-o", "UserKnownHostsFile=" + os.devnull, 103 + "-o", "ConnectTimeout=1", 104 + "-p", self.ssh_port, "-i", self._ssh_key_file] 105 + if interactive: 106 + ssh_cmd += ['-t'] 107 + assert not isinstance(cmd, str) 108 + ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd) 109 + logging.debug("ssh_cmd: %s", " ".join(ssh_cmd)) 110 + r = subprocess.call(ssh_cmd, 111 + stdin=sys.stdin if interactive else self._devnull, 112 + stdout=sys.stdout if interactive else self._stdout, 113 + stderr=sys.stderr if interactive else self._stderr) 114 + if check and r != 0: 115 + raise Exception("SSH command failed: %s" % cmd) 116 + return r 117 + 118 + def ssh(self, *cmd): 119 + return self._ssh_do(self.GUEST_USER, cmd, False) 120 + 121 + def ssh_interactive(self, *cmd): 122 + return self._ssh_do(self.GUEST_USER, cmd, False, True) 123 + 124 + def ssh_root(self, *cmd): 125 + return self._ssh_do("root", cmd, False) 126 + 127 + def ssh_check(self, *cmd): 128 + self._ssh_do(self.GUEST_USER, cmd, True) 129 + 130 + def ssh_root_check(self, *cmd): 131 + self._ssh_do("root", cmd, True) 132 + 133 + def build_image(self, img): 134 + raise NotImplementedError 135 + 136 + def add_source_dir(self, src_dir): 137 + name = "data-" + hashlib.sha1(src_dir).hexdigest()[:5] 138 + tarfile = os.path.join(self._tmpdir, name + ".tar") 139 + logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir) 140 + subprocess.check_call(["./scripts/archive-source.sh", tarfile], 141 + cwd=src_dir, stdin=self._devnull, 142 + stdout=self._stdout, stderr=self._stderr) 143 + self._data_args += ["-drive", 144 + "file=%s,if=none,id=%s,cache=writeback,format=raw" % \ 145 + (tarfile, name), 146 + "-device", 147 + "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)] 148 + 149 + def boot(self, img, extra_args=[]): 150 + args = self._args + [ 151 + "-device", "VGA", 152 + "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img, 153 + "-device", "virtio-blk,drive=drive0,bootindex=0"] 154 + args += self._data_args + extra_args 155 + logging.debug("QEMU args: %s", " ".join(args)) 156 + qemu_bin = os.environ.get("QEMU", "qemu-system-x86_64") 157 + guest = QEMUMachine(binary=qemu_bin, args=args) 158 + try: 159 + guest.launch() 160 + except: 161 + logging.error("Failed to launch QEMU, command line:") 162 + logging.error(" ".join([qemu_bin] + args)) 163 + logging.error("Log:") 164 + logging.error(guest.get_log()) 165 + logging.error("QEMU version >= 2.10 is required") 166 + raise 167 + atexit.register(self.shutdown) 168 + self._guest = guest 169 + usernet_info = guest.qmp("human-monitor-command", 170 + command_line="info usernet") 171 + self.ssh_port = None 172 + for l in usernet_info["return"].splitlines(): 173 + fields = l.split() 174 + if "TCP[HOST_FORWARD]" in fields and "22" in fields: 175 + self.ssh_port = l.split()[3] 176 + if not self.ssh_port: 177 + raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \ 178 + usernet_info) 179 + 180 + def wait_ssh(self, seconds=120): 181 + starttime = datetime.datetime.now() 182 + guest_up = False 183 + while (datetime.datetime.now() - starttime).total_seconds() < seconds: 184 + if self.ssh("exit 0") == 0: 185 + guest_up = True 186 + break 187 + time.sleep(1) 188 + if not guest_up: 189 + raise Exception("Timeout while waiting for guest ssh") 190 + 191 + def shutdown(self): 192 + self._guest.shutdown() 193 + 194 + def wait(self): 195 + self._guest.wait() 196 + 197 + def qmp(self, *args, **kwargs): 198 + return self._guest.qmp(*args, **kwargs) 199 + 200 + def parse_args(vm_name): 201 + parser = optparse.OptionParser( 202 + description="VM test utility. Exit codes: " 203 + "0 = success, " 204 + "1 = command line error, " 205 + "2 = environment initialization failed, " 206 + "3 = test command failed") 207 + parser.add_option("--debug", "-D", action="store_true", 208 + help="enable debug output") 209 + parser.add_option("--image", "-i", default="%s.img" % vm_name, 210 + help="image file name") 211 + parser.add_option("--force", "-f", action="store_true", 212 + help="force build image even if image exists") 213 + parser.add_option("--jobs", type=int, default=multiprocessing.cpu_count() / 2, 214 + help="number of virtual CPUs") 215 + parser.add_option("--build-image", "-b", action="store_true", 216 + help="build image") 217 + parser.add_option("--build-qemu", 218 + help="build QEMU from source in guest") 219 + parser.add_option("--interactive", "-I", action="store_true", 220 + help="Interactively run command") 221 + parser.disable_interspersed_args() 222 + return parser.parse_args() 223 + 224 + def main(vmcls): 225 + try: 226 + args, argv = parse_args(vmcls.name) 227 + if not argv and not args.build_qemu and not args.build_image: 228 + print "Nothing to do?" 229 + return 1 230 + if args.debug: 231 + logging.getLogger().setLevel(logging.DEBUG) 232 + vm = vmcls(debug=args.debug, vcpus=args.jobs) 233 + if args.build_image: 234 + if os.path.exists(args.image) and not args.force: 235 + sys.stderr.writelines(["Image file exists: %s\n" % args.image, 236 + "Use --force option to overwrite\n"]) 237 + return 1 238 + return vm.build_image(args.image) 239 + if args.build_qemu: 240 + vm.add_source_dir(args.build_qemu) 241 + cmd = [vm.BUILD_SCRIPT.format( 242 + configure_opts = " ".join(argv), 243 + jobs=args.jobs)] 244 + else: 245 + cmd = argv 246 + vm.boot(args.image + ",snapshot=on") 247 + vm.wait_ssh() 248 + except Exception as e: 249 + if isinstance(e, SystemExit) and e.code == 0: 250 + return 0 251 + sys.stderr.write("Failed to prepare guest environment\n") 252 + traceback.print_exc() 253 + return 2 254 + 255 + if args.interactive: 256 + if vm.ssh_interactive(*cmd) == 0: 257 + return 0 258 + vm.ssh_interactive() 259 + return 3 260 + else: 261 + if vm.ssh(*cmd) != 0: 262 + return 3