linux-user: Fix erroneous conversion in copy_file_range
[qemu/ar7.git] / tests / vm / basevm.py
blob00f1d5ca8daad1905b4ce6d59b2439d596f93e3a
2 # VM testing base class
4 # Copyright 2017-2019 Red Hat Inc.
6 # Authors:
7 # Fam Zheng <famz@redhat.com>
8 # Gerd Hoffmann <kraxel@redhat.com>
10 # This code is licensed under the GPL version 2 or later. See
11 # the COPYING file in the top-level directory.
14 import os
15 import re
16 import sys
17 import socket
18 import logging
19 import time
20 import datetime
21 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
22 from qemu.accel import kvm_available
23 from qemu.machine import QEMUMachine
24 import subprocess
25 import hashlib
26 import argparse
27 import atexit
28 import tempfile
29 import shutil
30 import multiprocessing
31 import traceback
32 import shlex
34 SSH_KEY_FILE = os.path.join(os.path.dirname(__file__),
35 "..", "keys", "id_rsa")
36 SSH_PUB_KEY_FILE = os.path.join(os.path.dirname(__file__),
37 "..", "keys", "id_rsa.pub")
39 # This is the standard configuration.
40 # Any or all of these can be overridden by
41 # passing in a config argument to the VM constructor.
42 DEFAULT_CONFIG = {
43 'cpu' : "max",
44 'machine' : 'pc',
45 'guest_user' : "qemu",
46 'guest_pass' : "qemupass",
47 'root_user' : "root",
48 'root_pass' : "qemupass",
49 'ssh_key_file' : SSH_KEY_FILE,
50 'ssh_pub_key_file': SSH_PUB_KEY_FILE,
51 'memory' : "4G",
52 'extra_args' : [],
53 'qemu_args' : "",
54 'dns' : "",
55 'ssh_port' : 0,
56 'install_cmds' : "",
57 'boot_dev_type' : "block",
58 'ssh_timeout' : 1,
60 BOOT_DEVICE = {
61 'block' : "-drive file={},if=none,id=drive0,cache=writeback "\
62 "-device virtio-blk,drive=drive0,bootindex=0",
63 'scsi' : "-device virtio-scsi-device,id=scsi "\
64 "-drive file={},format=raw,if=none,id=hd0 "\
65 "-device scsi-hd,drive=hd0,bootindex=0",
67 class BaseVM(object):
69 envvars = [
70 "https_proxy",
71 "http_proxy",
72 "ftp_proxy",
73 "no_proxy",
76 # The script to run in the guest that builds QEMU
77 BUILD_SCRIPT = ""
78 # The guest name, to be overridden by subclasses
79 name = "#base"
80 # The guest architecture, to be overridden by subclasses
81 arch = "#arch"
82 # command to halt the guest, can be overridden by subclasses
83 poweroff = "poweroff"
84 # Time to wait for shutdown to finish.
85 shutdown_timeout_default = 30
86 # enable IPv6 networking
87 ipv6 = True
88 # This is the timeout on the wait for console bytes.
89 socket_timeout = 120
90 # Scale up some timeouts under TCG.
91 # 4 is arbitrary, but greater than 2,
92 # since we found we need to wait more than twice as long.
93 tcg_timeout_multiplier = 4
94 def __init__(self, args, config=None):
95 self._guest = None
96 self._genisoimage = args.genisoimage
97 self._build_path = args.build_path
98 self._efi_aarch64 = args.efi_aarch64
99 # Allow input config to override defaults.
100 self._config = DEFAULT_CONFIG.copy()
101 if config != None:
102 self._config.update(config)
103 self.validate_ssh_keys()
104 self._tmpdir = os.path.realpath(tempfile.mkdtemp(prefix="vm-test-",
105 suffix=".tmp",
106 dir="."))
107 atexit.register(shutil.rmtree, self._tmpdir)
108 # Copy the key files to a temporary directory.
109 # Also chmod the key file to agree with ssh requirements.
110 self._config['ssh_key'] = \
111 open(self._config['ssh_key_file']).read().rstrip()
112 self._config['ssh_pub_key'] = \
113 open(self._config['ssh_pub_key_file']).read().rstrip()
114 self._ssh_tmp_key_file = os.path.join(self._tmpdir, "id_rsa")
115 open(self._ssh_tmp_key_file, "w").write(self._config['ssh_key'])
116 subprocess.check_call(["chmod", "600", self._ssh_tmp_key_file])
118 self._ssh_tmp_pub_key_file = os.path.join(self._tmpdir, "id_rsa.pub")
119 open(self._ssh_tmp_pub_key_file,
120 "w").write(self._config['ssh_pub_key'])
122 self.debug = args.debug
123 self._console_log_path = None
124 if args.log_console:
125 self._console_log_path = \
126 os.path.join(os.path.expanduser("~/.cache/qemu-vm"),
127 "{}.install.log".format(self.name))
128 self._stderr = sys.stderr
129 self._devnull = open(os.devnull, "w")
130 if self.debug:
131 self._stdout = sys.stdout
132 else:
133 self._stdout = self._devnull
134 netdev = "user,id=vnet,hostfwd=:127.0.0.1:{}-:22"
135 self._args = [ \
136 "-nodefaults", "-m", self._config['memory'],
137 "-cpu", self._config['cpu'],
138 "-netdev",
139 netdev.format(self._config['ssh_port']) +
140 (",ipv6=no" if not self.ipv6 else "") +
141 (",dns=" + self._config['dns'] if self._config['dns'] else ""),
142 "-device", "virtio-net-pci,netdev=vnet",
143 "-vnc", "127.0.0.1:0,to=20"]
144 if args.jobs and args.jobs > 1:
145 self._args += ["-smp", "%d" % args.jobs]
146 if kvm_available(self.arch):
147 self._shutdown_timeout = self.shutdown_timeout_default
148 self._args += ["-enable-kvm"]
149 else:
150 logging.info("KVM not available, not using -enable-kvm")
151 self._shutdown_timeout = \
152 self.shutdown_timeout_default * self.tcg_timeout_multiplier
153 self._data_args = []
155 if self._config['qemu_args'] != None:
156 qemu_args = self._config['qemu_args']
157 qemu_args = qemu_args.replace('\n',' ').replace('\r','')
158 # shlex groups quoted arguments together
159 # we need this to keep the quoted args together for when
160 # the QEMU command is issued later.
161 args = shlex.split(qemu_args)
162 self._config['extra_args'] = []
163 for arg in args:
164 if arg:
165 # Preserve quotes around arguments.
166 # shlex above takes them out, so add them in.
167 if " " in arg:
168 arg = '"{}"'.format(arg)
169 self._config['extra_args'].append(arg)
171 def validate_ssh_keys(self):
172 """Check to see if the ssh key files exist."""
173 if 'ssh_key_file' not in self._config or\
174 not os.path.exists(self._config['ssh_key_file']):
175 raise Exception("ssh key file not found.")
176 if 'ssh_pub_key_file' not in self._config or\
177 not os.path.exists(self._config['ssh_pub_key_file']):
178 raise Exception("ssh pub key file not found.")
180 def wait_boot(self, wait_string=None):
181 """Wait for the standard string we expect
182 on completion of a normal boot.
183 The user can also choose to override with an
184 alternate string to wait for."""
185 if wait_string is None:
186 if self.login_prompt is None:
187 raise Exception("self.login_prompt not defined")
188 wait_string = self.login_prompt
189 # Intentionally bump up the default timeout under TCG,
190 # since the console wait below takes longer.
191 timeout = self.socket_timeout
192 if not kvm_available(self.arch):
193 timeout *= 8
194 self.console_init(timeout=timeout)
195 self.console_wait(wait_string)
197 def _download_with_cache(self, url, sha256sum=None, sha512sum=None):
198 def check_sha256sum(fname):
199 if not sha256sum:
200 return True
201 checksum = subprocess.check_output(["sha256sum", fname]).split()[0]
202 return sha256sum == checksum.decode("utf-8")
204 def check_sha512sum(fname):
205 if not sha512sum:
206 return True
207 checksum = subprocess.check_output(["sha512sum", fname]).split()[0]
208 return sha512sum == checksum.decode("utf-8")
210 cache_dir = os.path.expanduser("~/.cache/qemu-vm/download")
211 if not os.path.exists(cache_dir):
212 os.makedirs(cache_dir)
213 fname = os.path.join(cache_dir,
214 hashlib.sha1(url.encode("utf-8")).hexdigest())
215 if os.path.exists(fname) and check_sha256sum(fname) and check_sha512sum(fname):
216 return fname
217 logging.debug("Downloading %s to %s...", url, fname)
218 subprocess.check_call(["wget", "-c", url, "-O", fname + ".download"],
219 stdout=self._stdout, stderr=self._stderr)
220 os.rename(fname + ".download", fname)
221 return fname
223 def _ssh_do(self, user, cmd, check):
224 ssh_cmd = ["ssh",
225 "-t",
226 "-o", "StrictHostKeyChecking=no",
227 "-o", "UserKnownHostsFile=" + os.devnull,
228 "-o",
229 "ConnectTimeout={}".format(self._config["ssh_timeout"]),
230 "-p", self.ssh_port, "-i", self._ssh_tmp_key_file]
231 # If not in debug mode, set ssh to quiet mode to
232 # avoid printing the results of commands.
233 if not self.debug:
234 ssh_cmd.append("-q")
235 for var in self.envvars:
236 ssh_cmd += ['-o', "SendEnv=%s" % var ]
237 assert not isinstance(cmd, str)
238 ssh_cmd += ["%s@127.0.0.1" % user] + list(cmd)
239 logging.debug("ssh_cmd: %s", " ".join(ssh_cmd))
240 r = subprocess.call(ssh_cmd)
241 if check and r != 0:
242 raise Exception("SSH command failed: %s" % cmd)
243 return r
245 def ssh(self, *cmd):
246 return self._ssh_do(self._config["guest_user"], cmd, False)
248 def ssh_root(self, *cmd):
249 return self._ssh_do(self._config["root_user"], cmd, False)
251 def ssh_check(self, *cmd):
252 self._ssh_do(self._config["guest_user"], cmd, True)
254 def ssh_root_check(self, *cmd):
255 self._ssh_do(self._config["root_user"], cmd, True)
257 def build_image(self, img):
258 raise NotImplementedError
260 def exec_qemu_img(self, *args):
261 cmd = [os.environ.get("QEMU_IMG", "qemu-img")]
262 cmd.extend(list(args))
263 subprocess.check_call(cmd)
265 def add_source_dir(self, src_dir):
266 name = "data-" + hashlib.sha1(src_dir.encode("utf-8")).hexdigest()[:5]
267 tarfile = os.path.join(self._tmpdir, name + ".tar")
268 logging.debug("Creating archive %s for src_dir dir: %s", tarfile, src_dir)
269 subprocess.check_call(["./scripts/archive-source.sh", tarfile],
270 cwd=src_dir, stdin=self._devnull,
271 stdout=self._stdout, stderr=self._stderr)
272 self._data_args += ["-drive",
273 "file=%s,if=none,id=%s,cache=writeback,format=raw" % \
274 (tarfile, name),
275 "-device",
276 "virtio-blk,drive=%s,serial=%s,bootindex=1" % (name, name)]
278 def boot(self, img, extra_args=[]):
279 boot_dev = BOOT_DEVICE[self._config['boot_dev_type']]
280 boot_params = boot_dev.format(img)
281 args = self._args + boot_params.split(' ')
282 args += self._data_args + extra_args + self._config['extra_args']
283 logging.debug("QEMU args: %s", " ".join(args))
284 qemu_path = get_qemu_path(self.arch, self._build_path)
286 # Since console_log_path is only set when the user provides the
287 # log_console option, we will set drain_console=True so the
288 # console is always drained.
289 guest = QEMUMachine(binary=qemu_path, args=args,
290 console_log=self._console_log_path,
291 drain_console=True)
292 guest.set_machine(self._config['machine'])
293 guest.set_console()
294 try:
295 guest.launch()
296 except:
297 logging.error("Failed to launch QEMU, command line:")
298 logging.error(" ".join([qemu_path] + args))
299 logging.error("Log:")
300 logging.error(guest.get_log())
301 logging.error("QEMU version >= 2.10 is required")
302 raise
303 atexit.register(self.shutdown)
304 self._guest = guest
305 # Init console so we can start consuming the chars.
306 self.console_init()
307 usernet_info = guest.qmp("human-monitor-command",
308 command_line="info usernet")
309 self.ssh_port = None
310 for l in usernet_info["return"].splitlines():
311 fields = l.split()
312 if "TCP[HOST_FORWARD]" in fields and "22" in fields:
313 self.ssh_port = l.split()[3]
314 if not self.ssh_port:
315 raise Exception("Cannot find ssh port from 'info usernet':\n%s" % \
316 usernet_info)
318 def console_init(self, timeout = None):
319 if timeout == None:
320 timeout = self.socket_timeout
321 vm = self._guest
322 vm.console_socket.settimeout(timeout)
323 self.console_raw_path = os.path.join(vm._temp_dir,
324 vm._name + "-console.raw")
325 self.console_raw_file = open(self.console_raw_path, 'wb')
327 def console_log(self, text):
328 for line in re.split("[\r\n]", text):
329 # filter out terminal escape sequences
330 line = re.sub("\x1b\[[0-9;?]*[a-zA-Z]", "", line)
331 line = re.sub("\x1b\([0-9;?]*[a-zA-Z]", "", line)
332 # replace unprintable chars
333 line = re.sub("\x1b", "<esc>", line)
334 line = re.sub("[\x00-\x1f]", ".", line)
335 line = re.sub("[\x80-\xff]", ".", line)
336 if line == "":
337 continue
338 # log console line
339 sys.stderr.write("con recv: %s\n" % line)
341 def console_wait(self, expect, expectalt = None):
342 vm = self._guest
343 output = ""
344 while True:
345 try:
346 chars = vm.console_socket.recv(1)
347 if self.console_raw_file:
348 self.console_raw_file.write(chars)
349 self.console_raw_file.flush()
350 except socket.timeout:
351 sys.stderr.write("console: *** read timeout ***\n")
352 sys.stderr.write("console: waiting for: '%s'\n" % expect)
353 if not expectalt is None:
354 sys.stderr.write("console: waiting for: '%s' (alt)\n" % expectalt)
355 sys.stderr.write("console: line buffer:\n")
356 sys.stderr.write("\n")
357 self.console_log(output.rstrip())
358 sys.stderr.write("\n")
359 raise
360 output += chars.decode("latin1")
361 if expect in output:
362 break
363 if not expectalt is None and expectalt in output:
364 break
365 if "\r" in output or "\n" in output:
366 lines = re.split("[\r\n]", output)
367 output = lines.pop()
368 if self.debug:
369 self.console_log("\n".join(lines))
370 if self.debug:
371 self.console_log(output)
372 if not expectalt is None and expectalt in output:
373 return False
374 return True
376 def console_consume(self):
377 vm = self._guest
378 output = ""
379 vm.console_socket.setblocking(0)
380 while True:
381 try:
382 chars = vm.console_socket.recv(1)
383 except:
384 break
385 output += chars.decode("latin1")
386 if "\r" in output or "\n" in output:
387 lines = re.split("[\r\n]", output)
388 output = lines.pop()
389 if self.debug:
390 self.console_log("\n".join(lines))
391 if self.debug:
392 self.console_log(output)
393 vm.console_socket.setblocking(1)
395 def console_send(self, command):
396 vm = self._guest
397 if self.debug:
398 logline = re.sub("\n", "<enter>", command)
399 logline = re.sub("[\x00-\x1f]", ".", logline)
400 sys.stderr.write("con send: %s\n" % logline)
401 for char in list(command):
402 vm.console_socket.send(char.encode("utf-8"))
403 time.sleep(0.01)
405 def console_wait_send(self, wait, command):
406 self.console_wait(wait)
407 self.console_send(command)
409 def console_ssh_init(self, prompt, user, pw):
410 sshkey_cmd = "echo '%s' > .ssh/authorized_keys\n" \
411 % self._config['ssh_pub_key'].rstrip()
412 self.console_wait_send("login:", "%s\n" % user)
413 self.console_wait_send("Password:", "%s\n" % pw)
414 self.console_wait_send(prompt, "mkdir .ssh\n")
415 self.console_wait_send(prompt, sshkey_cmd)
416 self.console_wait_send(prompt, "chmod 755 .ssh\n")
417 self.console_wait_send(prompt, "chmod 644 .ssh/authorized_keys\n")
419 def console_sshd_config(self, prompt):
420 self.console_wait(prompt)
421 self.console_send("echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config\n")
422 for var in self.envvars:
423 self.console_wait(prompt)
424 self.console_send("echo 'AcceptEnv %s' >> /etc/ssh/sshd_config\n" % var)
426 def print_step(self, text):
427 sys.stderr.write("### %s ...\n" % text)
429 def wait_ssh(self, wait_root=False, seconds=300, cmd="exit 0"):
430 # Allow more time for VM to boot under TCG.
431 if not kvm_available(self.arch):
432 seconds *= self.tcg_timeout_multiplier
433 starttime = datetime.datetime.now()
434 endtime = starttime + datetime.timedelta(seconds=seconds)
435 cmd_success = False
436 while datetime.datetime.now() < endtime:
437 if wait_root and self.ssh_root(cmd) == 0:
438 cmd_success = True
439 break
440 elif self.ssh(cmd) == 0:
441 cmd_success = True
442 break
443 seconds = (endtime - datetime.datetime.now()).total_seconds()
444 logging.debug("%ds before timeout", seconds)
445 time.sleep(1)
446 if not cmd_success:
447 raise Exception("Timeout while waiting for guest ssh")
449 def shutdown(self):
450 self._guest.shutdown(timeout=self._shutdown_timeout)
452 def wait(self):
453 self._guest.wait(timeout=self._shutdown_timeout)
455 def graceful_shutdown(self):
456 self.ssh_root(self.poweroff)
457 self._guest.wait(timeout=self._shutdown_timeout)
459 def qmp(self, *args, **kwargs):
460 return self._guest.qmp(*args, **kwargs)
462 def gen_cloud_init_iso(self):
463 cidir = self._tmpdir
464 mdata = open(os.path.join(cidir, "meta-data"), "w")
465 name = self.name.replace(".","-")
466 mdata.writelines(["instance-id: {}-vm-0\n".format(name),
467 "local-hostname: {}-guest\n".format(name)])
468 mdata.close()
469 udata = open(os.path.join(cidir, "user-data"), "w")
470 print("guest user:pw {}:{}".format(self._config['guest_user'],
471 self._config['guest_pass']))
472 udata.writelines(["#cloud-config\n",
473 "chpasswd:\n",
474 " list: |\n",
475 " root:%s\n" % self._config['root_pass'],
476 " %s:%s\n" % (self._config['guest_user'],
477 self._config['guest_pass']),
478 " expire: False\n",
479 "users:\n",
480 " - name: %s\n" % self._config['guest_user'],
481 " sudo: ALL=(ALL) NOPASSWD:ALL\n",
482 " ssh-authorized-keys:\n",
483 " - %s\n" % self._config['ssh_pub_key'],
484 " - name: root\n",
485 " ssh-authorized-keys:\n",
486 " - %s\n" % self._config['ssh_pub_key'],
487 "locale: en_US.UTF-8\n"])
488 proxy = os.environ.get("http_proxy")
489 if not proxy is None:
490 udata.writelines(["apt:\n",
491 " proxy: %s" % proxy])
492 udata.close()
493 subprocess.check_call([self._genisoimage, "-output", "cloud-init.iso",
494 "-volid", "cidata", "-joliet", "-rock",
495 "user-data", "meta-data"],
496 cwd=cidir,
497 stdin=self._devnull, stdout=self._stdout,
498 stderr=self._stdout)
499 return os.path.join(cidir, "cloud-init.iso")
501 def get_qemu_path(arch, build_path=None):
502 """Fetch the path to the qemu binary."""
503 # If QEMU environment variable set, it takes precedence
504 if "QEMU" in os.environ:
505 qemu_path = os.environ["QEMU"]
506 elif build_path:
507 qemu_path = os.path.join(build_path, arch + "-softmmu")
508 qemu_path = os.path.join(qemu_path, "qemu-system-" + arch)
509 else:
510 # Default is to use system path for qemu.
511 qemu_path = "qemu-system-" + arch
512 return qemu_path
514 def get_qemu_version(qemu_path):
515 """Get the version number from the current QEMU,
516 and return the major number."""
517 output = subprocess.check_output([qemu_path, '--version'])
518 version_line = output.decode("utf-8")
519 version_num = re.split(' |\(', version_line)[3].split('.')[0]
520 return int(version_num)
522 def parse_config(config, args):
523 """ Parse yaml config and populate our config structure.
524 The yaml config allows the user to override the
525 defaults for VM parameters. In many cases these
526 defaults can be overridden without rebuilding the VM."""
527 if args.config:
528 config_file = args.config
529 elif 'QEMU_CONFIG' in os.environ:
530 config_file = os.environ['QEMU_CONFIG']
531 else:
532 return config
533 if not os.path.exists(config_file):
534 raise Exception("config file {} does not exist".format(config_file))
535 # We gracefully handle importing the yaml module
536 # since it might not be installed.
537 # If we are here it means the user supplied a .yml file,
538 # so if the yaml module is not installed we will exit with error.
539 try:
540 import yaml
541 except ImportError:
542 print("The python3-yaml package is needed "\
543 "to support config.yaml files")
544 # Instead of raising an exception we exit to avoid
545 # a raft of messy (expected) errors to stdout.
546 exit(1)
547 with open(config_file) as f:
548 yaml_dict = yaml.safe_load(f)
550 if 'qemu-conf' in yaml_dict:
551 config.update(yaml_dict['qemu-conf'])
552 else:
553 raise Exception("config file {} is not valid"\
554 " missing qemu-conf".format(config_file))
555 return config
557 def parse_args(vmcls):
559 def get_default_jobs():
560 if multiprocessing.cpu_count() > 1:
561 if kvm_available(vmcls.arch):
562 return multiprocessing.cpu_count() // 2
563 elif os.uname().machine == "x86_64" and \
564 vmcls.arch in ["aarch64", "x86_64", "i386"]:
565 # MTTCG is available on these arches and we can allow
566 # more cores. but only up to a reasonable limit. User
567 # can always override these limits with --jobs.
568 return min(multiprocessing.cpu_count() // 2, 8)
569 else:
570 return 1
572 parser = argparse.ArgumentParser(
573 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
574 description="Utility for provisioning VMs and running builds",
575 epilog="""Remaining arguments are passed to the command.
576 Exit codes: 0 = success, 1 = command line error,
577 2 = environment initialization failed,
578 3 = test command failed""")
579 parser.add_argument("--debug", "-D", action="store_true",
580 help="enable debug output")
581 parser.add_argument("--image", "-i", default="%s.img" % vmcls.name,
582 help="image file name")
583 parser.add_argument("--force", "-f", action="store_true",
584 help="force build image even if image exists")
585 parser.add_argument("--jobs", type=int, default=get_default_jobs(),
586 help="number of virtual CPUs")
587 parser.add_argument("--verbose", "-V", action="store_true",
588 help="Pass V=1 to builds within the guest")
589 parser.add_argument("--build-image", "-b", action="store_true",
590 help="build image")
591 parser.add_argument("--build-qemu",
592 help="build QEMU from source in guest")
593 parser.add_argument("--build-target",
594 help="QEMU build target", default="check")
595 parser.add_argument("--build-path", default=None,
596 help="Path of build directory, "\
597 "for using build tree QEMU binary. ")
598 parser.add_argument("--interactive", "-I", action="store_true",
599 help="Interactively run command")
600 parser.add_argument("--snapshot", "-s", action="store_true",
601 help="run tests with a snapshot")
602 parser.add_argument("--genisoimage", default="genisoimage",
603 help="iso imaging tool")
604 parser.add_argument("--config", "-c", default=None,
605 help="Provide config yaml for configuration. "\
606 "See config_example.yaml for example.")
607 parser.add_argument("--efi-aarch64",
608 default="/usr/share/qemu-efi-aarch64/QEMU_EFI.fd",
609 help="Path to efi image for aarch64 VMs.")
610 parser.add_argument("--log-console", action="store_true",
611 help="Log console to file.")
612 parser.add_argument("commands", nargs="*", help="""Remaining
613 commands after -- are passed to command inside the VM""")
615 return parser.parse_args()
617 def main(vmcls, config=None):
618 try:
619 if config == None:
620 config = DEFAULT_CONFIG
621 args = parse_args(vmcls)
622 if not args.commands and not args.build_qemu and not args.build_image:
623 print("Nothing to do?")
624 return 1
625 config = parse_config(config, args)
626 logging.basicConfig(level=(logging.DEBUG if args.debug
627 else logging.WARN))
628 vm = vmcls(args, config=config)
629 if args.build_image:
630 if os.path.exists(args.image) and not args.force:
631 sys.stderr.writelines(["Image file exists: %s\n" % args.image,
632 "Use --force option to overwrite\n"])
633 return 1
634 return vm.build_image(args.image)
635 if args.build_qemu:
636 vm.add_source_dir(args.build_qemu)
637 cmd = [vm.BUILD_SCRIPT.format(
638 configure_opts = " ".join(args.commands),
639 jobs=int(args.jobs),
640 target=args.build_target,
641 verbose = "V=1" if args.verbose else "")]
642 else:
643 cmd = args.commands
644 img = args.image
645 if args.snapshot:
646 img += ",snapshot=on"
647 vm.boot(img)
648 vm.wait_ssh()
649 except Exception as e:
650 if isinstance(e, SystemExit) and e.code == 0:
651 return 0
652 sys.stderr.write("Failed to prepare guest environment\n")
653 traceback.print_exc()
654 return 2
656 exitcode = 0
657 if vm.ssh(*cmd) != 0:
658 exitcode = 3
659 if args.interactive:
660 vm.ssh()
662 if not args.snapshot:
663 vm.graceful_shutdown()
665 return exitcode