blob: 7f5deb6e5184ec1a75778948fcbfa7e9b8301a84 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Achuith Bhandarkard8d19292016-05-03 14:32:58 -07002# Copyright 2016 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Mike Frysinger666566c2016-09-21 00:00:21 -04006"""Script for VM Management."""
Achuith Bhandarkard8d19292016-05-03 14:32:58 -07007
8from __future__ import print_function
9
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010010import argparse
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +010011import distutils.version
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -080012import multiprocessing
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070013import os
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +010014import re
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070015
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -080016from chromite.cli.cros import cros_chrome_sdk
17from chromite.lib import cache
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070018from chromite.lib import commandline
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -080019from chromite.lib import constants
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070020from chromite.lib import cros_build_lib
21from chromite.lib import cros_logging as logging
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -070022from chromite.lib import osutils
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -080023from chromite.lib import path_util
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070024from chromite.lib import remote_access
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -070025from chromite.lib import retry_util
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070026
27
28class VMError(Exception):
29 """Exception for VM failures."""
30
31 def __init__(self, message):
32 super(VMError, self).__init__()
33 logging.error(message)
34
35
36class VM(object):
37 """Class for managing a VM."""
38
39 SSH_PORT = 9222
Nicolas Norvezf527cdf2018-01-26 11:55:44 -080040 IMAGE_FORMAT = 'raw'
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070041
Achuith Bhandarkar2beae292018-03-26 16:44:53 -070042 def __init__(self, opts):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070043 """Initialize VM.
44
45 Args:
Achuith Bhandarkar2beae292018-03-26 16:44:53 -070046 opts: command line options.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070047 """
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010048 self.qemu_path = opts.qemu_path
Achuith Bhandarkar41259652017-11-14 10:31:02 -080049 self.qemu_bios_path = opts.qemu_bios_path
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -080050 self.qemu_m = opts.qemu_m
51 self.qemu_cpu = opts.qemu_cpu
52 self.qemu_smp = opts.qemu_smp
53 if self.qemu_smp == 0:
54 self.qemu_smp = min(8, multiprocessing.cpu_count)
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010055 self.enable_kvm = opts.enable_kvm
Achuith Bhandarkarf877da22017-09-12 12:27:39 -070056 # We don't need sudo access for software emulation or if /dev/kvm is
57 # writeable.
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010058 self.use_sudo = self.enable_kvm and not os.access('/dev/kvm', os.W_OK)
59 self.display = opts.display
60 self.image_path = opts.image_path
Nicolas Norvezf527cdf2018-01-26 11:55:44 -080061 self.image_format = opts.image_format
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -080062 self.board = opts.board
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010063 self.ssh_port = opts.ssh_port
64 self.dry_run = opts.dry_run
65
66 self.start = opts.start
67 self.stop = opts.stop
68 self.cmd = opts.args[1:] if opts.cmd else None
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070069
Achuith Bhandarkar2beae292018-03-26 16:44:53 -070070 self.vm_dir = opts.vm_dir
71 if not self.vm_dir:
72 self.vm_dir = os.path.join(osutils.GetGlobalTempDir(),
73 'cros_vm_%d' % self.ssh_port)
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -070074 self._CreateVMDir()
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -070075
76 self.pidfile = os.path.join(self.vm_dir, 'kvm.pid')
77 self.kvm_monitor = os.path.join(self.vm_dir, 'kvm.monitor')
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070078 self.kvm_pipe_in = '%s.in' % self.kvm_monitor # to KVM
79 self.kvm_pipe_out = '%s.out' % self.kvm_monitor # from KVM
80 self.kvm_serial = '%s.serial' % self.kvm_monitor
81
Achuith Bhandarkar65d1a892017-05-08 14:13:12 -070082 self.remote = remote_access.RemoteDevice(remote_access.LOCALHOST,
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010083 port=self.ssh_port)
Achuith Bhandarkar2beae292018-03-26 16:44:53 -070084 self.device_addr = 'ssh://%s:%d' % (remote_access.LOCALHOST, self.ssh_port)
Achuith Bhandarkar65d1a892017-05-08 14:13:12 -070085
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070086 # TODO(achuith): support nographics, snapshot, mem_path, usb_passthrough,
87 # moblab, etc.
88
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -070089 def _RunCommand(self, *args, **kwargs):
90 """Use SudoRunCommand or RunCommand as necessary."""
91 if self.use_sudo:
92 return cros_build_lib.SudoRunCommand(*args, **kwargs)
93 else:
94 return cros_build_lib.RunCommand(*args, **kwargs)
95
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -070096 def _CreateVMDir(self):
97 """Safely create vm_dir."""
98 if not osutils.SafeMakedirs(self.vm_dir, sudo=self.use_sudo):
99 # For security, ensure that vm_dir is not a symlink, and is owned by us or
100 # by root.
101 error_str = ('VM state dir is misconfigured; please recreate: %s'
102 % self.vm_dir)
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700103 assert os.path.isdir(self.vm_dir), error_str
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700104 assert not os.path.islink(self.vm_dir), error_str
105 st_uid = os.stat(self.vm_dir).st_uid
106 assert st_uid == 0 or st_uid == os.getuid(), error_str
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700107
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700108 def _RmVMDir(self):
109 """Cleanup vm_dir."""
Mike Frysinger97080242017-09-13 01:58:45 -0400110 osutils.RmDir(self.vm_dir, ignore_missing=True, sudo=self.use_sudo)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700111
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700112 @staticmethod
113 def _GetCachePath(cache_name):
114 """Return path to cache.
115
116 Args:
117 cache_name: Name of cache.
118
119 Returns:
120 File path of cache.
121 """
122 return os.path.join(path_util.GetCacheDir(),
123 cros_chrome_sdk.COMMAND_NAME,
124 cache_name)
125
126 @cros_build_lib.MemoizedSingleCall
127 def _SDKVersion(self):
128 """Determine SDK version.
129
130 Check the environment if we're in the SDK shell, and failing that, look at
131 the misc cache.
132
133 Returns:
134 SDK version.
135 """
136 sdk_version = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_VERSION_ENV)
Achuith Bhandarkar4e367c22018-03-27 15:32:48 -0700137 if not sdk_version and self.board:
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700138 misc_cache = cache.DiskCache(self._GetCachePath(
139 cros_chrome_sdk.SDKFetcher.MISC_CACHE))
140 with misc_cache.Lookup((self.board, 'latest')) as ref:
141 if ref.Exists(lock=True):
142 sdk_version = osutils.ReadFile(ref.path).strip()
143 return sdk_version
144
145 def _CachePathForKey(self, key):
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800146 """Get cache path for key.
147
148 Args:
149 key: cache key.
150 """
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700151 tarball_cache = cache.TarballCache(self._GetCachePath(
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800152 cros_chrome_sdk.SDKFetcher.TARBALL_CACHE))
Achuith Bhandarkar4e367c22018-03-27 15:32:48 -0700153 if self.board and self._SDKVersion():
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700154 cache_key = (self.board, self._SDKVersion(), key)
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800155 with tarball_cache.Lookup(cache_key) as ref:
156 if ref.Exists():
157 return ref.path
158 return None
159
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +0100160 @cros_build_lib.MemoizedSingleCall
161 def QemuVersion(self):
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700162 """Determine QEMU version.
163
164 Returns:
165 QEMU version.
166 """
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +0100167 version_str = self._RunCommand([self.qemu_path, '--version'],
168 capture_output=True).output
169 # version string looks like one of these:
170 # QEMU emulator version 2.0.0 (Debian 2.0.0+dfsg-2ubuntu1.36), Copyright (c)
171 # 2003-2008 Fabrice Bellard
172 #
173 # QEMU emulator version 2.6.0, Copyright (c) 2003-2008 Fabrice Bellard
174 #
175 # qemu-x86_64 version 2.10.1
176 # Copyright (c) 2003-2017 Fabrice Bellard and the QEMU Project developers
177 m = re.search(r"version ([0-9.]+)", version_str)
178 if not m:
179 raise VMError('Unable to determine QEMU version from:\n%s.' % version_str)
180 return m.group(1)
181
182 def _CheckQemuMinVersion(self):
183 """Ensure minimum QEMU version."""
184 min_qemu_version = '2.6.0'
185 logging.info('QEMU version %s', self.QemuVersion())
186 LooseVersion = distutils.version.LooseVersion
187 if LooseVersion(self.QemuVersion()) < LooseVersion(min_qemu_version):
188 raise VMError('QEMU %s is the minimum supported version. You have %s.'
189 % (min_qemu_version, self.QemuVersion()))
190
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800191 def _SetQemuPath(self):
192 """Find a suitable Qemu executable."""
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800193 qemu_exe = 'qemu-system-x86_64'
194 qemu_exe_path = os.path.join('usr/bin', qemu_exe)
195
196 # Check SDK cache.
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800197 if not self.qemu_path:
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700198 qemu_dir = self._CachePathForKey(cros_chrome_sdk.SDKFetcher.QEMU_BIN_KEY)
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800199 if qemu_dir:
200 qemu_path = os.path.join(qemu_dir, qemu_exe_path)
201 if os.path.isfile(qemu_path):
202 self.qemu_path = qemu_path
203
204 # Check chroot.
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800205 if not self.qemu_path:
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800206 qemu_path = os.path.join(
207 constants.SOURCE_ROOT, constants.DEFAULT_CHROOT_DIR, qemu_exe_path)
208 if os.path.isfile(qemu_path):
209 self.qemu_path = qemu_path
210
211 # Check system.
212 if not self.qemu_path:
213 self.qemu_path = osutils.Which(qemu_exe)
214
215 if not self.qemu_path or not os.path.isfile(self.qemu_path):
216 raise VMError('QEMU not found.')
217 logging.debug('QEMU path: %s', self.qemu_path)
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800218 self._CheckQemuMinVersion()
219
220 def _GetBuiltVMImagePath(self):
221 """Get path of a locally built VM image."""
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800222 vm_image_path = os.path.join(constants.SOURCE_ROOT, 'src/build/images',
223 cros_build_lib.GetBoard(self.board),
224 'latest', constants.VM_IMAGE_BIN)
225 return vm_image_path if os.path.isfile(vm_image_path) else None
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800226
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800227 def _GetCacheVMImagePath(self):
228 """Get path of a cached VM image."""
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700229 cache_path = self._CachePathForKey(constants.VM_IMAGE_TAR)
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800230 if cache_path:
231 vm_image = os.path.join(cache_path, constants.VM_IMAGE_BIN)
232 if os.path.isfile(vm_image):
233 return vm_image
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800234 return None
235
236 def _SetVMImagePath(self):
237 """Detect VM image path in SDK and chroot."""
238 if not self.image_path:
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800239 self.image_path = (self._GetCacheVMImagePath() or
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800240 self._GetBuiltVMImagePath())
241 if not self.image_path:
242 raise VMError('No VM image found. Use cros chrome-sdk --download-vm.')
243 if not os.path.isfile(self.image_path):
244 raise VMError('VM image does not exist: %s' % self.image_path)
245 logging.debug('VM image path: %s', self.image_path)
246
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100247 def Run(self):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700248 """Performs an action, one of start, stop, or run a command in the VM.
249
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700250 Returns:
251 cmd output.
252 """
253
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100254 if not self.start and not self.stop and not self.cmd:
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700255 raise VMError('Must specify one of start, stop, or cmd.')
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100256 if self.start:
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700257 self.Start()
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100258 if self.cmd:
259 return self.RemoteCommand(self.cmd)
260 if self.stop:
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700261 self.Stop()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700262
263 def Start(self):
264 """Start the VM."""
265
266 self.Stop()
267
Achuith Bhandarkar022d69c2016-10-05 14:28:14 -0700268 logging.debug('Start VM')
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800269 self._SetQemuPath()
270 self._SetVMImagePath()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700271
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700272 self._RmVMDir()
273 self._CreateVMDir()
Mike Frysinger97080242017-09-13 01:58:45 -0400274 # Make sure we can read these files later on by creating them as ourselves.
275 osutils.Touch(self.kvm_serial)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700276 for pipe in [self.kvm_pipe_in, self.kvm_pipe_out]:
277 os.mkfifo(pipe, 0600)
Mike Frysinger97080242017-09-13 01:58:45 -0400278 osutils.Touch(self.pidfile)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700279
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800280 qemu_args = [self.qemu_path]
281 if self.qemu_bios_path:
282 if not os.path.isdir(self.qemu_bios_path):
283 raise VMError('Invalid QEMU bios path: %s' % self.qemu_bios_path)
284 qemu_args += ['-L', self.qemu_bios_path]
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +0100285
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800286 qemu_args += [
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800287 '-m', self.qemu_m, '-smp', str(self.qemu_smp), '-vga', 'virtio',
288 '-daemonize', '-usbdevice', 'tablet',
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800289 '-pidfile', self.pidfile,
290 '-chardev', 'pipe,id=control_pipe,path=%s' % self.kvm_monitor,
291 '-serial', 'file:%s' % self.kvm_serial,
292 '-mon', 'chardev=control_pipe',
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800293 # Append 'check' to warn if the requested CPU is not fully supported.
294 '-cpu', self.qemu_cpu + ',check',
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800295 # Qemu-vlans are used by qemu to separate out network traffic on the
296 # slirp network bridge. qemu forwards traffic on a slirp vlan to all
297 # ports conected on that vlan. By default, slirp ports are on vlan
298 # 0. We explicitly set a vlan here so that another qemu VM using
299 # slirp doesn't conflict with our network traffic.
300 '-net', 'nic,model=virtio,vlan=%d' % self.ssh_port,
301 '-net', 'user,hostfwd=tcp:127.0.0.1:%d-:22,vlan=%d'
302 % (self.ssh_port, self.ssh_port),
Nicolas Norvezf527cdf2018-01-26 11:55:44 -0800303 '-drive', 'file=%s,index=0,media=disk,cache=unsafe,format=%s'
304 % (self.image_path, self.image_format),
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800305 ]
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700306 if self.enable_kvm:
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800307 qemu_args.append('-enable-kvm')
Achuith Bhandarkarb891adb2016-10-24 18:43:22 -0700308 if not self.display:
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800309 qemu_args.extend(['-display', 'none'])
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700310 logging.info('Pid file: %s', self.pidfile)
311 if not self.dry_run:
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800312 self._RunCommand(qemu_args)
Achuith Bhandarkarbe977482018-02-06 16:44:00 -0800313 else:
314 logging.info(cros_build_lib.CmdToStr(qemu_args))
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700315
316 def _GetVMPid(self):
317 """Get the pid of the VM.
318
319 Returns:
320 pid of the VM.
321 """
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -0700322 if not os.path.exists(self.vm_dir):
323 logging.debug('%s not present.', self.vm_dir)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700324 return 0
325
326 if not os.path.exists(self.pidfile):
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700327 logging.info('%s does not exist.', self.pidfile)
328 return 0
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700329
Mike Frysinger97080242017-09-13 01:58:45 -0400330 pid = osutils.ReadFile(self.pidfile).rstrip()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700331 if not pid.isdigit():
Mike Frysinger97080242017-09-13 01:58:45 -0400332 # Ignore blank/empty files.
333 if pid:
334 logging.error('%s in %s is not a pid.', pid, self.pidfile)
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700335 return 0
336
Achuith Bhandarkar022d69c2016-10-05 14:28:14 -0700337 return int(pid)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700338
339 def IsRunning(self):
340 """Returns True if there's a running VM.
341
342 Returns:
343 True if there's a running VM.
344 """
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700345 pid = self._GetVMPid()
Achuith Bhandarkar022d69c2016-10-05 14:28:14 -0700346 if not pid:
347 return False
348
349 # Make sure the process actually exists.
Mike Frysinger97080242017-09-13 01:58:45 -0400350 return os.path.isdir('/proc/%i' % pid)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700351
352 def Stop(self):
353 """Stop the VM."""
Achuith Bhandarkar022d69c2016-10-05 14:28:14 -0700354 logging.debug('Stop VM')
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700355
356 pid = self._GetVMPid()
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700357 if pid:
358 logging.info('Killing %d.', pid)
359 if not self.dry_run:
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -0700360 self._RunCommand(['kill', '-9', str(pid)], error_code_ok=True)
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700361 self._RmVMDir()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700362
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700363 def _WaitForProcs(self):
364 """Wait for expected processes to launch."""
365 class _TooFewPidsException(Exception):
366 """Exception for _GetRunningPids to throw."""
367
368 def _GetRunningPids(exe, numpids):
369 pids = self.remote.GetRunningPids(exe, full_path=False)
370 logging.info('%s pids: %s', exe, repr(pids))
371 if len(pids) < numpids:
372 raise _TooFewPidsException()
373
374 def _WaitForProc(exe, numpids):
375 try:
376 retry_util.RetryException(
Achuith Bhandarkar0e7b8502017-06-12 15:32:41 -0700377 exception=_TooFewPidsException,
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700378 max_retry=20,
379 functor=lambda: _GetRunningPids(exe, numpids),
380 sleep=2)
381 except _TooFewPidsException:
382 raise VMError('_WaitForProcs failed: timed out while waiting for '
383 '%d %s processes to start.' % (numpids, exe))
384
385 # We could also wait for session_manager, nacl_helper, etc, but chrome is
386 # the long pole. We expect the parent, 2 zygotes, gpu-process, renderer.
387 # This could potentially break with Mustash.
388 _WaitForProc('chrome', 5)
389
390 def WaitForBoot(self):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700391 """Wait for the VM to boot up.
392
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700393 Wait for ssh connection to become active, and wait for all expected chrome
394 processes to be launched.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700395 """
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -0700396 if not os.path.exists(self.vm_dir):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700397 self.Start()
398
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700399 try:
400 result = retry_util.RetryException(
Achuith Bhandarkar0e7b8502017-06-12 15:32:41 -0700401 exception=remote_access.SSHConnectionError,
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700402 max_retry=10,
403 functor=lambda: self.RemoteCommand(cmd=['echo']),
404 sleep=5)
405 except remote_access.SSHConnectionError:
406 raise VMError('WaitForBoot timed out trying to connect to VM.')
407
408 if result.returncode != 0:
409 raise VMError('WaitForBoot failed: %s.' % result.error)
410
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100411 # Chrome can take a while to start with software emulation.
412 if not self.enable_kvm:
413 self._WaitForProcs()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700414
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100415 def RemoteCommand(self, cmd, **kwargs):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700416 """Run a remote command in the VM.
417
418 Args:
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100419 cmd: command to run.
420 kwargs: additional args (see documentation for RemoteDevice.RunCommand).
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700421 """
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700422 if not self.dry_run:
Achuith Bhandarkar65d1a892017-05-08 14:13:12 -0700423 return self.remote.RunCommand(cmd, debug_level=logging.INFO,
424 combine_stdout_stderr=True,
425 log_output=True,
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100426 error_code_ok=True,
427 **kwargs)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700428
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100429 @staticmethod
Achuith Bhandarkar2beae292018-03-26 16:44:53 -0700430 def GetParser():
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100431 """Parse a list of args.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700432
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100433 Args:
434 argv: list of command line arguments.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700435
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100436 Returns:
437 List of parsed opts.
438 """
439 parser = commandline.ArgumentParser(description=__doc__)
440 parser.add_argument('--start', action='store_true', default=False,
441 help='Start the VM.')
442 parser.add_argument('--stop', action='store_true', default=False,
443 help='Stop the VM.')
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700444 parser.add_argument('--image-path', type=str,
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100445 help='Path to VM image to launch with --start.')
Nicolas Norvezf527cdf2018-01-26 11:55:44 -0800446 parser.add_argument('--image-format', default=VM.IMAGE_FORMAT,
447 help='Format of the VM image (raw, qcow2, ...).')
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700448 parser.add_argument('--qemu-path', type=str,
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100449 help='Path of qemu binary to launch with --start.')
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800450 parser.add_argument('--qemu-m', type=str, default='8G',
451 help='Memory argument that will be passed to qemu.')
452 parser.add_argument('--qemu-smp', type=int, default='0',
453 help='SMP argument that will be passed to qemu. (0 '
454 'means auto-detection.)')
Po-Hsien Wangc5b335f2018-02-06 18:36:16 -0800455 # TODO(pwang): replace SandyBridge to Haswell-noTSX once lab machine
456 # running VMTest all migrate to GCE.
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800457 parser.add_argument('--qemu-cpu', type=str,
Po-Hsien Wangc5b335f2018-02-06 18:36:16 -0800458 default='SandyBridge,-invpcid,-tsc-deadline',
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800459 help='CPU argument that will be passed to qemu.')
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700460 parser.add_argument('--qemu-bios-path', type=str,
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800461 help='Path of directory with qemu bios files.')
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100462 parser.add_argument('--disable-kvm', dest='enable_kvm',
463 action='store_false', default=True,
464 help='Disable KVM, use software emulation.')
465 parser.add_argument('--no-display', dest='display',
466 action='store_false', default=True,
467 help='Do not display video output.')
468 parser.add_argument('--ssh-port', type=int, default=VM.SSH_PORT,
469 help='ssh port to communicate with VM.')
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800470 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
471 parser.add_argument('--board', default=sdk_board_env, help='Board to use.')
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700472 parser.add_argument('--vm-dir', type=str,
Achuith Bhandarkar2beae292018-03-26 16:44:53 -0700473 help='Temp VM directory to use.')
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100474 parser.add_argument('--dry-run', action='store_true', default=False,
475 help='dry run for debugging.')
476 parser.add_argument('--cmd', action='store_true', default=False,
477 help='Run a command in the VM.')
478 parser.add_argument('args', nargs=argparse.REMAINDER,
479 help='Command to run in the VM.')
Achuith Bhandarkar2beae292018-03-26 16:44:53 -0700480 return parser
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700481
482def main(argv):
Achuith Bhandarkar2beae292018-03-26 16:44:53 -0700483 opts = VM.GetParser().parse_args(argv)
484 opts.Freeze()
485
486 vm = VM(opts)
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100487 vm.Run()