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