1ff2ebff0SFam Zheng# 2ff2ebff0SFam Zheng# VM testing base class 3ff2ebff0SFam Zheng# 48dd38334SGerd Hoffmann# Copyright 2017-2019 Red Hat Inc. 5ff2ebff0SFam Zheng# 6ff2ebff0SFam Zheng# Authors: 7ff2ebff0SFam Zheng# Fam Zheng <famz@redhat.com> 88dd38334SGerd Hoffmann# Gerd Hoffmann <kraxel@redhat.com> 9ff2ebff0SFam Zheng# 10ff2ebff0SFam Zheng# This code is licensed under the GPL version 2 or later. See 11ff2ebff0SFam Zheng# the COPYING file in the top-level directory. 12ff2ebff0SFam Zheng# 13ff2ebff0SFam Zheng 14ff2ebff0SFam Zhengimport os 158dd38334SGerd Hoffmannimport re 16ff2ebff0SFam Zhengimport sys 178dd38334SGerd Hoffmannimport socket 18ff2ebff0SFam Zhengimport logging 19ff2ebff0SFam Zhengimport time 20ff2ebff0SFam Zhengimport datetime 218f8fd9edSCleber Rosasys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) 228b272e00SWainer dos Santos Moschettafrom qemu.accel import kvm_available 23abf0bf99SJohn Snowfrom qemu.machine import QEMUMachine 24ff2ebff0SFam Zhengimport subprocess 25ff2ebff0SFam Zhengimport hashlib 26ff2ebff0SFam Zhengimport optparse 27ff2ebff0SFam Zhengimport atexit 28ff2ebff0SFam Zhengimport tempfile 29ff2ebff0SFam Zhengimport shutil 30ff2ebff0SFam Zhengimport multiprocessing 31ff2ebff0SFam Zhengimport traceback 32ff2ebff0SFam Zheng 33ff2ebff0SFam ZhengSSH_KEY = open(os.path.join(os.path.dirname(__file__), 34ff2ebff0SFam Zheng "..", "keys", "id_rsa")).read() 35ff2ebff0SFam ZhengSSH_PUB_KEY = open(os.path.join(os.path.dirname(__file__), 36ff2ebff0SFam Zheng "..", "keys", "id_rsa.pub")).read() 37ff2ebff0SFam Zheng 38ff2ebff0SFam Zhengclass BaseVM(object): 39ff2ebff0SFam Zheng GUEST_USER = "qemu" 40ff2ebff0SFam Zheng GUEST_PASS = "qemupass" 41ff2ebff0SFam Zheng ROOT_PASS = "qemupass" 42ff2ebff0SFam Zheng 43b08ba163SGerd Hoffmann envvars = [ 44b08ba163SGerd Hoffmann "https_proxy", 45b08ba163SGerd Hoffmann "http_proxy", 46b08ba163SGerd Hoffmann "ftp_proxy", 47b08ba163SGerd Hoffmann "no_proxy", 48b08ba163SGerd Hoffmann ] 49b08ba163SGerd Hoffmann 50ff2ebff0SFam Zheng # The script to run in the guest that builds QEMU 51ff2ebff0SFam Zheng BUILD_SCRIPT = "" 52ff2ebff0SFam Zheng # The guest name, to be overridden by subclasses 53ff2ebff0SFam Zheng name = "#base" 5431719c37SPhilippe Mathieu-Daudé # The guest architecture, to be overridden by subclasses 5531719c37SPhilippe Mathieu-Daudé arch = "#arch" 56b3f94b2fSGerd Hoffmann # command to halt the guest, can be overridden by subclasses 57b3f94b2fSGerd Hoffmann poweroff = "poweroff" 585b790481SEduardo Habkost # enable IPv6 networking 595b790481SEduardo Habkost ipv6 = True 60c9de3935SRobert Foley # Scale up some timeouts under TCG. 61c9de3935SRobert Foley # 4 is arbitrary, but greater than 2, 62c9de3935SRobert Foley # since we found we need to wait more than twice as long. 63c9de3935SRobert Foley tcg_ssh_timeout_multiplier = 4 64*1f335d18SRobert Foley def __init__(self, args): 65ff2ebff0SFam Zheng self._guest = None 66*1f335d18SRobert Foley self._genisoimage = args.genisoimage 67*1f335d18SRobert Foley self._build_path = args.build_path 68ff2ebff0SFam Zheng self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-", 69ff2ebff0SFam Zheng suffix=".tmp", 70ff2ebff0SFam Zheng dir=".")) 71ff2ebff0SFam Zheng atexit.register(shutil.rmtree, self._tmpdir) 72ff2ebff0SFam Zheng 73ff2ebff0SFam Zheng self._ssh_key_file = os.path.join(self._tmpdir, "id_rsa") 74ff2ebff0SFam Zheng open(self._ssh_key_file, "w").write(SSH_KEY) 75ff2ebff0SFam Zheng subprocess.check_call(["chmod", "600", self._ssh_key_file]) 76ff2ebff0SFam Zheng 77ff2ebff0SFam Zheng self._ssh_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub") 78ff2ebff0SFam Zheng open(self._ssh_pub_key_file, "w").write(SSH_PUB_KEY) 79ff2ebff0SFam Zheng 80*1f335d18SRobert Foley self.debug = args.debug 81ff2ebff0SFam Zheng self._stderr = sys.stderr 82ff2ebff0SFam Zheng self._devnull = open(os.devnull, "w") 83ff2ebff0SFam Zheng if self.debug: 84ff2ebff0SFam Zheng self._stdout = sys.stdout 85ff2ebff0SFam Zheng else: 86ff2ebff0SFam Zheng self._stdout = self._devnull 87ff2ebff0SFam Zheng self._args = [ \ 88eb2712f5SPeter Maydell "-nodefaults", "-m", "4G", 89b33bd859SPeter Maydell "-cpu", "max", 905b790481SEduardo Habkost "-netdev", "user,id=vnet,hostfwd=:127.0.0.1:0-:22" + 915b790481SEduardo Habkost (",ipv6=no" if not self.ipv6 else ""), 92ff2ebff0SFam Zheng "-device", "virtio-net-pci,netdev=vnet", 938dd38334SGerd Hoffmann "-vnc", "127.0.0.1:0,to=20"] 94*1f335d18SRobert Foley if args.jobs and args.jobs > 1: 95*1f335d18SRobert Foley self._args += ["-smp", "%d" % args.jobs] 9671531bb5SPhilippe Mathieu-Daudé if kvm_available(self.arch): 97ff2ebff0SFam Zheng self._args += ["-enable-kvm"] 98ff2ebff0SFam Zheng else: 99ff2ebff0SFam Zheng logging.info("KVM not available, not using -enable-kvm") 100ff2ebff0SFam Zheng self._data_args = [] 101ff2ebff0SFam Zheng 1025b4b4865SAlex Bennée def _download_with_cache(self, url, sha256sum=None, sha512sum=None): 103ff2ebff0SFam Zheng def check_sha256sum(fname): 104ff2ebff0SFam Zheng if not sha256sum: 105ff2ebff0SFam Zheng return True 106ff2ebff0SFam Zheng checksum = subprocess.check_output(["sha256sum", fname]).split()[0] 1073ace9be6SGerd Hoffmann return sha256sum == checksum.decode("utf-8") 108ff2ebff0SFam Zheng 1095b4b4865SAlex Bennée def check_sha512sum(fname): 1105b4b4865SAlex Bennée if not sha512sum: 1115b4b4865SAlex Bennée return True 1125b4b4865SAlex Bennée checksum = subprocess.check_output(["sha512sum", fname]).split()[0] 1135b4b4865SAlex Bennée return sha512sum == checksum.decode("utf-8") 1145b4b4865SAlex Bennée 115ff2ebff0SFam Zheng cache_dir = os.path.expanduser("~/.cache/qemu-vm/download") 116ff2ebff0SFam Zheng if not os.path.exists(cache_dir): 117ff2ebff0SFam Zheng os.makedirs(cache_dir) 1183ace9be6SGerd Hoffmann fname = os.path.join(cache_dir, 1193ace9be6SGerd Hoffmann hashlib.sha1(url.encode("utf-8")).hexdigest()) 1205b4b4865SAlex Bennée if os.path.exists(fname) and check_sha256sum(fname) and check_sha512sum(fname): 121ff2ebff0SFam Zheng return fname 122ff2ebff0SFam Zheng logging.debug("Downloading %s to %s...", url, fname) 123ff2ebff0SFam Zheng subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"], 124ff2ebff0SFam Zheng stdout=self._stdout, stderr=self._stderr) 125ff2ebff0SFam Zheng os.rename(fname + ".download", fname) 126ff2ebff0SFam Zheng return fname 127ff2ebff0SFam Zheng 128796471e9SGerd Hoffmann def _ssh_do(self, user, cmd, check): 12989adc5b9SRobert Foley ssh_cmd = ["ssh", 13089adc5b9SRobert Foley "-t", 131ff2ebff0SFam Zheng "-o", "StrictHostKeyChecking=no", 132ff2ebff0SFam Zheng "-o", "UserKnownHostsFile=" + os.devnull, 133ff2ebff0SFam Zheng "-o", "ConnectTimeout=1", 134ff2ebff0SFam Zheng "-p", self.ssh_port, "-i", self._ssh_key_file] 13589adc5b9SRobert Foley # If not in debug mode, set ssh to quiet mode to 13689adc5b9SRobert Foley # avoid printing the results of commands. 13789adc5b9SRobert Foley if not self.debug: 13889adc5b9SRobert Foley ssh_cmd.append("-q") 139b08ba163SGerd Hoffmann for var in self.envvars: 140b08ba163SGerd Hoffmann ssh_cmd += ['-o', "SendEnv=%s" % var ] 141ff2ebff0SFam Zheng assert not isinstance(cmd, str) 142ff2ebff0SFam Zheng ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd) 143ff2ebff0SFam Zheng logging.debug("ssh_cmd: %s", " ".join(ssh_cmd)) 144726c9a3bSFam Zheng r = subprocess.call(ssh_cmd) 145ff2ebff0SFam Zheng if check and r != 0: 146ff2ebff0SFam Zheng raise Exception("SSH command failed: %s" % cmd) 147ff2ebff0SFam Zheng return r 148ff2ebff0SFam Zheng 149ff2ebff0SFam Zheng def ssh(self, *cmd): 150ff2ebff0SFam Zheng return self._ssh_do(self.GUEST_USER, cmd, False) 151ff2ebff0SFam Zheng 152ff2ebff0SFam Zheng def ssh_root(self, *cmd): 153ff2ebff0SFam Zheng return self._ssh_do("root", cmd, False) 154ff2ebff0SFam Zheng 155ff2ebff0SFam Zheng def ssh_check(self, *cmd): 156ff2ebff0SFam Zheng self._ssh_do(self.GUEST_USER, cmd, True) 157ff2ebff0SFam Zheng 158ff2ebff0SFam Zheng def ssh_root_check(self, *cmd): 159ff2ebff0SFam Zheng self._ssh_do("root", cmd, True) 160ff2ebff0SFam Zheng 161ff2ebff0SFam Zheng def build_image(self, img): 162ff2ebff0SFam Zheng raise NotImplementedError 163ff2ebff0SFam Zheng 1641e48931cSWainer dos Santos Moschetta def exec_qemu_img(self, *args): 1651e48931cSWainer dos Santos Moschetta cmd = [os.environ.get("QEMU_IMG", "qemu-img")] 1661e48931cSWainer dos Santos Moschetta cmd.extend(list(args)) 1671e48931cSWainer dos Santos Moschetta subprocess.check_call(cmd) 1681e48931cSWainer dos Santos Moschetta 169ff2ebff0SFam Zheng def add_source_dir(self, src_dir): 1703ace9be6SGerd Hoffmann name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5] 171ff2ebff0SFam Zheng tarfile = os.path.join(self._tmpdir, name + ".tar") 172ff2ebff0SFam Zheng logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir) 173ff2ebff0SFam Zheng subprocess.check_call(["./scripts/archive-source.sh", tarfile], 174ff2ebff0SFam Zheng cwd=src_dir, stdin=self._devnull, 175ff2ebff0SFam Zheng stdout=self._stdout, stderr=self._stderr) 176ff2ebff0SFam Zheng self._data_args += ["-drive", 177ff2ebff0SFam Zheng "file=%s,if=none,id=%s,cache=writeback,format=raw" % \ 178ff2ebff0SFam Zheng (tarfile, name), 179ff2ebff0SFam Zheng "-device", 180ff2ebff0SFam Zheng "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)] 181ff2ebff0SFam Zheng 182ff2ebff0SFam Zheng def boot(self, img, extra_args=[]): 183ff2ebff0SFam Zheng args = self._args + [ 184ff2ebff0SFam Zheng "-drive", "file=%s,if=none,id=drive0,cache=writeback" % img, 185ff2ebff0SFam Zheng "-device", "virtio-blk,drive=drive0,bootindex=0"] 186ff2ebff0SFam Zheng args += self._data_args + extra_args 187ff2ebff0SFam Zheng logging.debug("QEMU args: %s", " ".join(args)) 188e56c4504SRobert Foley qemu_path = get_qemu_path(self.arch, self._build_path) 189e56c4504SRobert Foley guest = QEMUMachine(binary=qemu_path, args=args) 1908dd38334SGerd Hoffmann guest.set_machine('pc') 1918dd38334SGerd Hoffmann guest.set_console() 192ff2ebff0SFam Zheng try: 193ff2ebff0SFam Zheng guest.launch() 194ff2ebff0SFam Zheng except: 195ff2ebff0SFam Zheng logging.error("Failed to launch QEMU, command line:") 196e56c4504SRobert Foley logging.error(" ".join([qemu_path] + args)) 197ff2ebff0SFam Zheng logging.error("Log:") 198ff2ebff0SFam Zheng logging.error(guest.get_log()) 199ff2ebff0SFam Zheng logging.error("QEMU version >= 2.10 is required") 200ff2ebff0SFam Zheng raise 201ff2ebff0SFam Zheng atexit.register(self.shutdown) 202ff2ebff0SFam Zheng self._guest = guest 203ff2ebff0SFam Zheng usernet_info = guest.qmp("human-monitor-command", 204ff2ebff0SFam Zheng command_line="info usernet") 205ff2ebff0SFam Zheng self.ssh_port = None 206ff2ebff0SFam Zheng for l in usernet_info["return"].splitlines(): 207ff2ebff0SFam Zheng fields = l.split() 208ff2ebff0SFam Zheng if "TCP[HOST_FORWARD]" in fields and "22" in fields: 209ff2ebff0SFam Zheng self.ssh_port = l.split()[3] 210ff2ebff0SFam Zheng if not self.ssh_port: 211ff2ebff0SFam Zheng raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \ 212ff2ebff0SFam Zheng usernet_info) 213ff2ebff0SFam Zheng 2148dd38334SGerd Hoffmann def console_init(self, timeout = 120): 2158dd38334SGerd Hoffmann vm = self._guest 2168dd38334SGerd Hoffmann vm.console_socket.settimeout(timeout) 217698a64f9SGerd Hoffmann self.console_raw_path = os.path.join(vm._temp_dir, 218698a64f9SGerd Hoffmann vm._name + "-console.raw") 219698a64f9SGerd Hoffmann self.console_raw_file = open(self.console_raw_path, 'wb') 2208dd38334SGerd Hoffmann 2218dd38334SGerd Hoffmann def console_log(self, text): 2228dd38334SGerd Hoffmann for line in re.split("[\r\n]", text): 2238dd38334SGerd Hoffmann # filter out terminal escape sequences 2248dd38334SGerd Hoffmann line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line) 2258dd38334SGerd Hoffmann line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line) 2268dd38334SGerd Hoffmann # replace unprintable chars 2278dd38334SGerd Hoffmann line = re.sub("\x1b", "<esc>", line) 2288dd38334SGerd Hoffmann line = re.sub("[\x00-\x1f]", ".", line) 2298dd38334SGerd Hoffmann line = re.sub("[\x80-\xff]", ".", line) 2308dd38334SGerd Hoffmann if line == "": 2318dd38334SGerd Hoffmann continue 2328dd38334SGerd Hoffmann # log console line 2338dd38334SGerd Hoffmann sys.stderr.write("con recv: %s\n" % line) 2348dd38334SGerd Hoffmann 23560136e06SGerd Hoffmann def console_wait(self, expect, expectalt = None): 2368dd38334SGerd Hoffmann vm = self._guest 2378dd38334SGerd Hoffmann output = "" 2388dd38334SGerd Hoffmann while True: 2398dd38334SGerd Hoffmann try: 2408dd38334SGerd Hoffmann chars = vm.console_socket.recv(1) 241698a64f9SGerd Hoffmann if self.console_raw_file: 242698a64f9SGerd Hoffmann self.console_raw_file.write(chars) 243698a64f9SGerd Hoffmann self.console_raw_file.flush() 2448dd38334SGerd Hoffmann except socket.timeout: 2458dd38334SGerd Hoffmann sys.stderr.write("console: *** read timeout ***\n") 2468dd38334SGerd Hoffmann sys.stderr.write("console: waiting for: '%s'\n" % expect) 24760136e06SGerd Hoffmann if not expectalt is None: 24860136e06SGerd Hoffmann sys.stderr.write("console: waiting for: '%s' (alt)\n" % expectalt) 2498dd38334SGerd Hoffmann sys.stderr.write("console: line buffer:\n") 2508dd38334SGerd Hoffmann sys.stderr.write("\n") 2518dd38334SGerd Hoffmann self.console_log(output.rstrip()) 2528dd38334SGerd Hoffmann sys.stderr.write("\n") 2538dd38334SGerd Hoffmann raise 2548dd38334SGerd Hoffmann output += chars.decode("latin1") 2558dd38334SGerd Hoffmann if expect in output: 2568dd38334SGerd Hoffmann break 25760136e06SGerd Hoffmann if not expectalt is None and expectalt in output: 25860136e06SGerd Hoffmann break 2598dd38334SGerd Hoffmann if "\r" in output or "\n" in output: 2608dd38334SGerd Hoffmann lines = re.split("[\r\n]", output) 2618dd38334SGerd Hoffmann output = lines.pop() 2628dd38334SGerd Hoffmann if self.debug: 2638dd38334SGerd Hoffmann self.console_log("\n".join(lines)) 2648dd38334SGerd Hoffmann if self.debug: 2658dd38334SGerd Hoffmann self.console_log(output) 26660136e06SGerd Hoffmann if not expectalt is None and expectalt in output: 26760136e06SGerd Hoffmann return False 26860136e06SGerd Hoffmann return True 2698dd38334SGerd Hoffmann 2706c4f0416SGerd Hoffmann def console_consume(self): 2716c4f0416SGerd Hoffmann vm = self._guest 2726c4f0416SGerd Hoffmann output = "" 2736c4f0416SGerd Hoffmann vm.console_socket.setblocking(0) 2746c4f0416SGerd Hoffmann while True: 2756c4f0416SGerd Hoffmann try: 2766c4f0416SGerd Hoffmann chars = vm.console_socket.recv(1) 2776c4f0416SGerd Hoffmann except: 2786c4f0416SGerd Hoffmann break 2796c4f0416SGerd Hoffmann output += chars.decode("latin1") 2806c4f0416SGerd Hoffmann if "\r" in output or "\n" in output: 2816c4f0416SGerd Hoffmann lines = re.split("[\r\n]", output) 2826c4f0416SGerd Hoffmann output = lines.pop() 2836c4f0416SGerd Hoffmann if self.debug: 2846c4f0416SGerd Hoffmann self.console_log("\n".join(lines)) 2856c4f0416SGerd Hoffmann if self.debug: 2866c4f0416SGerd Hoffmann self.console_log(output) 2876c4f0416SGerd Hoffmann vm.console_socket.setblocking(1) 2886c4f0416SGerd Hoffmann 2898dd38334SGerd Hoffmann def console_send(self, command): 2908dd38334SGerd Hoffmann vm = self._guest 2918dd38334SGerd Hoffmann if self.debug: 2928dd38334SGerd Hoffmann logline = re.sub("\n", "<enter>", command) 2938dd38334SGerd Hoffmann logline = re.sub("[\x00-\x1f]", ".", logline) 2948dd38334SGerd Hoffmann sys.stderr.write("con send: %s\n" % logline) 2958dd38334SGerd Hoffmann for char in list(command): 2968dd38334SGerd Hoffmann vm.console_socket.send(char.encode("utf-8")) 2978dd38334SGerd Hoffmann time.sleep(0.01) 2988dd38334SGerd Hoffmann 2998dd38334SGerd Hoffmann def console_wait_send(self, wait, command): 3008dd38334SGerd Hoffmann self.console_wait(wait) 3018dd38334SGerd Hoffmann self.console_send(command) 3028dd38334SGerd Hoffmann 3038dd38334SGerd Hoffmann def console_ssh_init(self, prompt, user, pw): 3048dd38334SGerd Hoffmann sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" % SSH_PUB_KEY.rstrip() 3058dd38334SGerd Hoffmann self.console_wait_send("login:", "%s\n" % user) 3068dd38334SGerd Hoffmann self.console_wait_send("Password:", "%s\n" % pw) 3078dd38334SGerd Hoffmann self.console_wait_send(prompt, "mkdir .ssh\n") 3088dd38334SGerd Hoffmann self.console_wait_send(prompt, sshkey_cmd) 3098dd38334SGerd Hoffmann self.console_wait_send(prompt, "chmod 755 .ssh\n") 3108dd38334SGerd Hoffmann self.console_wait_send(prompt, "chmod 644 .ssh/authorized_keys\n") 3118dd38334SGerd Hoffmann 3128dd38334SGerd Hoffmann def console_sshd_config(self, prompt): 3138dd38334SGerd Hoffmann self.console_wait(prompt) 3148dd38334SGerd Hoffmann self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n") 3158dd38334SGerd Hoffmann for var in self.envvars: 3168dd38334SGerd Hoffmann self.console_wait(prompt) 3178dd38334SGerd Hoffmann self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var) 3188dd38334SGerd Hoffmann 3198dd38334SGerd Hoffmann def print_step(self, text): 3208dd38334SGerd Hoffmann sys.stderr.write("### %s ...\n" % text) 3218dd38334SGerd Hoffmann 3226ee982c9SRobert Foley def wait_ssh(self, wait_root=False, seconds=300, cmd="exit 0"): 323c9de3935SRobert Foley # Allow more time for VM to boot under TCG. 324c9de3935SRobert Foley if not kvm_available(self.arch): 325c9de3935SRobert Foley seconds *= self.tcg_ssh_timeout_multiplier 326ff2ebff0SFam Zheng starttime = datetime.datetime.now() 327f5d3d218SPhilippe Mathieu-Daudé endtime = starttime + datetime.timedelta(seconds=seconds) 3286ee982c9SRobert Foley cmd_success = False 329f5d3d218SPhilippe Mathieu-Daudé while datetime.datetime.now() < endtime: 3306ee982c9SRobert Foley if wait_root and self.ssh_root(cmd) == 0: 3316ee982c9SRobert Foley cmd_success = True 332fbb3aa29SRobert Foley break 3336ee982c9SRobert Foley elif self.ssh(cmd) == 0: 3346ee982c9SRobert Foley cmd_success = True 335ff2ebff0SFam Zheng break 336f5d3d218SPhilippe Mathieu-Daudé seconds = (endtime - datetime.datetime.now()).total_seconds() 337f5d3d218SPhilippe Mathieu-Daudé logging.debug("%ds before timeout", seconds) 338ff2ebff0SFam Zheng time.sleep(1) 3396ee982c9SRobert Foley if not cmd_success: 340ff2ebff0SFam Zheng raise Exception("Timeout while waiting for guest ssh") 341ff2ebff0SFam Zheng 342ff2ebff0SFam Zheng def shutdown(self): 343ff2ebff0SFam Zheng self._guest.shutdown() 344ff2ebff0SFam Zheng 345ff2ebff0SFam Zheng def wait(self): 346ff2ebff0SFam Zheng self._guest.wait() 347ff2ebff0SFam Zheng 348b3f94b2fSGerd Hoffmann def graceful_shutdown(self): 349b3f94b2fSGerd Hoffmann self.ssh_root(self.poweroff) 350b3f94b2fSGerd Hoffmann self._guest.wait() 351b3f94b2fSGerd Hoffmann 352ff2ebff0SFam Zheng def qmp(self, *args, **kwargs): 353ff2ebff0SFam Zheng return self._guest.qmp(*args, **kwargs) 354ff2ebff0SFam Zheng 355b081986cSRobert Foley def gen_cloud_init_iso(self): 356b081986cSRobert Foley cidir = self._tmpdir 357b081986cSRobert Foley mdata = open(os.path.join(cidir, "meta-data"), "w") 358b081986cSRobert Foley name = self.name.replace(".","-") 359b081986cSRobert Foley mdata.writelines(["instance-id: {}-vm-0\n".format(name), 360b081986cSRobert Foley "local-hostname: {}-guest\n".format(name)]) 361b081986cSRobert Foley mdata.close() 362b081986cSRobert Foley udata = open(os.path.join(cidir, "user-data"), "w") 363f01454adSAlex Bennée print("guest user:pw {}:{}".format(self.GUEST_USER, 364f01454adSAlex Bennée self.GUEST_PASS)) 365b081986cSRobert Foley udata.writelines(["#cloud-config\n", 366b081986cSRobert Foley "chpasswd:\n", 367b081986cSRobert Foley " list: |\n", 368f01454adSAlex Bennée " root:%s\n" % self.ROOT_PASS, 369f01454adSAlex Bennée " %s:%s\n" % (self.GUEST_USER, 370f01454adSAlex Bennée self.GUEST_PASS), 371b081986cSRobert Foley " expire: False\n", 372b081986cSRobert Foley "users:\n", 373f01454adSAlex Bennée " - name: %s\n" % self.GUEST_USER, 374b081986cSRobert Foley " sudo: ALL=(ALL) NOPASSWD:ALL\n", 375b081986cSRobert Foley " ssh-authorized-keys:\n", 376f01454adSAlex Bennée " - %s\n" % SSH_PUB_KEY, 377b081986cSRobert Foley " - name: root\n", 378b081986cSRobert Foley " ssh-authorized-keys:\n", 379f01454adSAlex Bennée " - %s\n" % SSH_PUB_KEY, 380b081986cSRobert Foley "locale: en_US.UTF-8\n"]) 381b081986cSRobert Foley proxy = os.environ.get("http_proxy") 382b081986cSRobert Foley if not proxy is None: 383b081986cSRobert Foley udata.writelines(["apt:\n", 384b081986cSRobert Foley " proxy: %s" % proxy]) 385b081986cSRobert Foley udata.close() 38692fecad3SAlex Bennée subprocess.check_call([self._genisoimage, "-output", "cloud-init.iso", 387b081986cSRobert Foley "-volid", "cidata", "-joliet", "-rock", 388b081986cSRobert Foley "user-data", "meta-data"], 389b081986cSRobert Foley cwd=cidir, 390b081986cSRobert Foley stdin=self._devnull, stdout=self._stdout, 391b081986cSRobert Foley stderr=self._stdout) 392b081986cSRobert Foley 393b081986cSRobert Foley return os.path.join(cidir, "cloud-init.iso") 394b081986cSRobert Foley 395e56c4504SRobert Foleydef get_qemu_path(arch, build_path=None): 396e56c4504SRobert Foley """Fetch the path to the qemu binary.""" 397e56c4504SRobert Foley # If QEMU environment variable set, it takes precedence 398e56c4504SRobert Foley if "QEMU" in os.environ: 399e56c4504SRobert Foley qemu_path = os.environ["QEMU"] 400e56c4504SRobert Foley elif build_path: 401e56c4504SRobert Foley qemu_path = os.path.join(build_path, arch + "-softmmu") 402e56c4504SRobert Foley qemu_path = os.path.join(qemu_path, "qemu-system-" + arch) 403e56c4504SRobert Foley else: 404e56c4504SRobert Foley # Default is to use system path for qemu. 405e56c4504SRobert Foley qemu_path = "qemu-system-" + arch 406e56c4504SRobert Foley return qemu_path 407e56c4504SRobert Foley 40863a24c5eSPhilippe Mathieu-Daudédef parse_args(vmcls): 4098a6e007eSPhilippe Mathieu-Daudé 4108a6e007eSPhilippe Mathieu-Daudé def get_default_jobs(): 41163a24c5eSPhilippe Mathieu-Daudé if kvm_available(vmcls.arch): 4123ad3e36eSWainer dos Santos Moschetta return multiprocessing.cpu_count() // 2 4138a6e007eSPhilippe Mathieu-Daudé else: 4148a6e007eSPhilippe Mathieu-Daudé return 1 4158a6e007eSPhilippe Mathieu-Daudé 416ff2ebff0SFam Zheng parser = optparse.OptionParser( 417ff2ebff0SFam Zheng description="VM test utility. Exit codes: " 418ff2ebff0SFam Zheng "0 = success, " 419ff2ebff0SFam Zheng "1 = command line error, " 420ff2ebff0SFam Zheng "2 = environment initialization failed, " 421ff2ebff0SFam Zheng "3 = test command failed") 422ff2ebff0SFam Zheng parser.add_option("--debug", "-D", action="store_true", 423ff2ebff0SFam Zheng help="enable debug output") 42463a24c5eSPhilippe Mathieu-Daudé parser.add_option("--image", "-i", default="%s.img" % vmcls.name, 425ff2ebff0SFam Zheng help="image file name") 426ff2ebff0SFam Zheng parser.add_option("--force", "-f", action="store_true", 427ff2ebff0SFam Zheng help="force build image even if image exists") 4288a6e007eSPhilippe Mathieu-Daudé parser.add_option("--jobs", type=int, default=get_default_jobs(), 429ff2ebff0SFam Zheng help="number of virtual CPUs") 43041e3340aSPeter Maydell parser.add_option("--verbose", "-V", action="store_true", 43141e3340aSPeter Maydell help="Pass V=1 to builds within the guest") 432ff2ebff0SFam Zheng parser.add_option("--build-image", "-b", action="store_true", 433ff2ebff0SFam Zheng help="build image") 434ff2ebff0SFam Zheng parser.add_option("--build-qemu", 435ff2ebff0SFam Zheng help="build QEMU from source in guest") 4365c2ec9b6SAlex Bennée parser.add_option("--build-target", 4375c2ec9b6SAlex Bennée help="QEMU build target", default="check") 438e56c4504SRobert Foley parser.add_option("--build-path", default=None, 439e56c4504SRobert Foley help="Path of build directory, "\ 440e56c4504SRobert Foley "for using build tree QEMU binary. ") 441ff2ebff0SFam Zheng parser.add_option("--interactive", "-I", action="store_true", 442ff2ebff0SFam Zheng help="Interactively run command") 443983c2a77SFam Zheng parser.add_option("--snapshot", "-s", action="store_true", 444983c2a77SFam Zheng help="run tests with a snapshot") 44592fecad3SAlex Bennée parser.add_option("--genisoimage", default="genisoimage", 44692fecad3SAlex Bennée help="iso imaging tool") 447ff2ebff0SFam Zheng parser.disable_interspersed_args() 448ff2ebff0SFam Zheng return parser.parse_args() 449ff2ebff0SFam Zheng 450ff2ebff0SFam Zhengdef main(vmcls): 451ff2ebff0SFam Zheng try: 45263a24c5eSPhilippe Mathieu-Daudé args, argv = parse_args(vmcls) 453ff2ebff0SFam Zheng if not argv and not args.build_qemu and not args.build_image: 454f03868bdSEduardo Habkost print("Nothing to do?") 455ff2ebff0SFam Zheng return 1 456fb3b4e6dSEduardo Habkost logging.basicConfig(level=(logging.DEBUG if args.debug 457fb3b4e6dSEduardo Habkost else logging.WARN)) 458*1f335d18SRobert Foley vm = vmcls(args) 459ff2ebff0SFam Zheng if args.build_image: 460ff2ebff0SFam Zheng if os.path.exists(args.image) and not args.force: 461ff2ebff0SFam Zheng sys.stderr.writelines(["Image file exists: %s\n" % args.image, 462ff2ebff0SFam Zheng "Use --force option to overwrite\n"]) 463ff2ebff0SFam Zheng return 1 464ff2ebff0SFam Zheng return vm.build_image(args.image) 465ff2ebff0SFam Zheng if args.build_qemu: 466ff2ebff0SFam Zheng vm.add_source_dir(args.build_qemu) 467ff2ebff0SFam Zheng cmd = [vm.BUILD_SCRIPT.format( 468ff2ebff0SFam Zheng configure_opts = " ".join(argv), 4693ace9be6SGerd Hoffmann jobs=int(args.jobs), 4705c2ec9b6SAlex Bennée target=args.build_target, 47141e3340aSPeter Maydell verbose = "V=1" if args.verbose else "")] 472ff2ebff0SFam Zheng else: 473ff2ebff0SFam Zheng cmd = argv 474983c2a77SFam Zheng img = args.image 475983c2a77SFam Zheng if args.snapshot: 476983c2a77SFam Zheng img += ",snapshot=on" 477983c2a77SFam Zheng vm.boot(img) 478ff2ebff0SFam Zheng vm.wait_ssh() 479ff2ebff0SFam Zheng except Exception as e: 480ff2ebff0SFam Zheng if isinstance(e, SystemExit) and e.code == 0: 481ff2ebff0SFam Zheng return 0 482ff2ebff0SFam Zheng sys.stderr.write("Failed to prepare guest environment\n") 483ff2ebff0SFam Zheng traceback.print_exc() 484ff2ebff0SFam Zheng return 2 485ff2ebff0SFam Zheng 486b3f94b2fSGerd Hoffmann exitcode = 0 487ff2ebff0SFam Zheng if vm.ssh(*cmd) != 0: 488b3f94b2fSGerd Hoffmann exitcode = 3 489bcc388dfSAlex Bennée if args.interactive: 490b3f94b2fSGerd Hoffmann vm.ssh() 491b3f94b2fSGerd Hoffmann 492b3f94b2fSGerd Hoffmann if not args.snapshot: 493b3f94b2fSGerd Hoffmann vm.graceful_shutdown() 494b3f94b2fSGerd Hoffmann 495b3f94b2fSGerd Hoffmann return exitcode 496