blob: ddf985eccb2d13a6215c2464bb92954084a7186b [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
Achuith Bhandarkarb00bafc2018-09-11 16:30:13 -070012import errno
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -080013import multiprocessing
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070014import os
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +010015import re
Achuith Bhandarkarb00bafc2018-09-11 16:30:13 -070016import socket
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070017
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -080018from chromite.cli.cros import cros_chrome_sdk
19from chromite.lib import cache
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070020from chromite.lib import commandline
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -080021from chromite.lib import constants
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070022from chromite.lib import cros_build_lib
23from chromite.lib import cros_logging as logging
Mike Frysinger10666292018-07-12 01:03:38 -040024from chromite.lib import memoize
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -070025from chromite.lib import osutils
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -080026from chromite.lib import path_util
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070027from chromite.lib import remote_access
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -070028from chromite.lib import retry_util
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070029
30
31class VMError(Exception):
32 """Exception for VM failures."""
33
34 def __init__(self, message):
35 super(VMError, self).__init__()
36 logging.error(message)
37
38
39class VM(object):
40 """Class for managing a VM."""
41
42 SSH_PORT = 9222
Nicolas Norvezf527cdf2018-01-26 11:55:44 -080043 IMAGE_FORMAT = 'raw'
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070044
Achuith Bhandarkar2beae292018-03-26 16:44:53 -070045 def __init__(self, opts):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070046 """Initialize VM.
47
48 Args:
Achuith Bhandarkar2beae292018-03-26 16:44:53 -070049 opts: command line options.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070050 """
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010051 self.qemu_path = opts.qemu_path
Achuith Bhandarkar41259652017-11-14 10:31:02 -080052 self.qemu_bios_path = opts.qemu_bios_path
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -080053 self.qemu_m = opts.qemu_m
54 self.qemu_cpu = opts.qemu_cpu
55 self.qemu_smp = opts.qemu_smp
56 if self.qemu_smp == 0:
57 self.qemu_smp = min(8, multiprocessing.cpu_count)
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010058 self.enable_kvm = opts.enable_kvm
Achuith Bhandarkarf877da22017-09-12 12:27:39 -070059 # We don't need sudo access for software emulation or if /dev/kvm is
60 # writeable.
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010061 self.use_sudo = self.enable_kvm and not os.access('/dev/kvm', os.W_OK)
62 self.display = opts.display
63 self.image_path = opts.image_path
Nicolas Norvezf527cdf2018-01-26 11:55:44 -080064 self.image_format = opts.image_format
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -080065 self.board = opts.board
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010066 self.ssh_port = opts.ssh_port
Achuith Bhandarkar195b9512018-08-15 17:14:32 -070067 self.private_key = opts.private_key
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010068 self.dry_run = opts.dry_run
Achuith Bhandarkar195b9512018-08-15 17:14:32 -070069 # log_level is only set if --log-level or --debug is specified.
70 self.log_level = getattr(opts, 'log_level', None)
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +010071
72 self.start = opts.start
73 self.stop = opts.stop
74 self.cmd = opts.args[1:] if opts.cmd else None
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070075
Achuith Bhandarkar02ef76b2018-04-05 10:47:33 +020076 self.cache_dir = os.path.abspath(opts.cache_dir)
77 assert os.path.isdir(self.cache_dir), "Cache directory doesn't exist"
78
Achuith Bhandarkar2beae292018-03-26 16:44:53 -070079 self.vm_dir = opts.vm_dir
80 if not self.vm_dir:
81 self.vm_dir = os.path.join(osutils.GetGlobalTempDir(),
82 'cros_vm_%d' % self.ssh_port)
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -070083 self._CreateVMDir()
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -070084
85 self.pidfile = os.path.join(self.vm_dir, 'kvm.pid')
86 self.kvm_monitor = os.path.join(self.vm_dir, 'kvm.monitor')
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070087 self.kvm_pipe_in = '%s.in' % self.kvm_monitor # to KVM
88 self.kvm_pipe_out = '%s.out' % self.kvm_monitor # from KVM
89 self.kvm_serial = '%s.serial' % self.kvm_monitor
90
Achuith Bhandarkar65d1a892017-05-08 14:13:12 -070091 self.remote = remote_access.RemoteDevice(remote_access.LOCALHOST,
Achuith Bhandarkar195b9512018-08-15 17:14:32 -070092 port=self.ssh_port,
93 private_key=self.private_key)
Achuith Bhandarkar2beae292018-03-26 16:44:53 -070094 self.device_addr = 'ssh://%s:%d' % (remote_access.LOCALHOST, self.ssh_port)
Achuith Bhandarkar65d1a892017-05-08 14:13:12 -070095
Achuith Bhandarkard8d19292016-05-03 14:32:58 -070096 # TODO(achuith): support nographics, snapshot, mem_path, usb_passthrough,
97 # moblab, etc.
98
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -070099 def _RunCommand(self, *args, **kwargs):
Achuith Bhandarkar665a9b32018-05-17 11:39:19 -0700100 """Use SudoRunCommand or RunCommand as necessary.
101
102 Args:
103 args and kwargs: positional and optional args to RunCommand.
104
105 Returns:
106 cros_build_lib.CommandResult object.
107 """
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -0700108 if self.use_sudo:
109 return cros_build_lib.SudoRunCommand(*args, **kwargs)
110 else:
111 return cros_build_lib.RunCommand(*args, **kwargs)
112
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700113 def _CreateVMDir(self):
114 """Safely create vm_dir."""
Steven Bennettsc57ca0e2018-09-18 09:33:59 -0700115 if not osutils.SafeMakedirs(self.vm_dir):
116 # For security, ensure that vm_dir is not a symlink, and is owned by us.
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700117 error_str = ('VM state dir is misconfigured; please recreate: %s'
118 % self.vm_dir)
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700119 assert os.path.isdir(self.vm_dir), error_str
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700120 assert not os.path.islink(self.vm_dir), error_str
Steven Bennettsc57ca0e2018-09-18 09:33:59 -0700121 assert os.stat(self.vm_dir).st_uid == os.getuid(), error_str
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700122
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700123 def _RmVMDir(self):
124 """Cleanup vm_dir."""
Mike Frysinger97080242017-09-13 01:58:45 -0400125 osutils.RmDir(self.vm_dir, ignore_missing=True, sudo=self.use_sudo)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700126
Achuith Bhandarkar02ef76b2018-04-05 10:47:33 +0200127 def _GetCachePath(self, cache_name):
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700128 """Return path to cache.
129
130 Args:
131 cache_name: Name of cache.
132
133 Returns:
134 File path of cache.
135 """
Achuith Bhandarkar02ef76b2018-04-05 10:47:33 +0200136 return os.path.join(self.cache_dir,
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700137 cros_chrome_sdk.COMMAND_NAME,
138 cache_name)
139
Mike Frysinger10666292018-07-12 01:03:38 -0400140 @memoize.MemoizedSingleCall
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700141 def _SDKVersion(self):
142 """Determine SDK version.
143
144 Check the environment if we're in the SDK shell, and failing that, look at
145 the misc cache.
146
147 Returns:
148 SDK version.
149 """
150 sdk_version = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_VERSION_ENV)
Achuith Bhandarkar4e367c22018-03-27 15:32:48 -0700151 if not sdk_version and self.board:
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700152 misc_cache = cache.DiskCache(self._GetCachePath(
153 cros_chrome_sdk.SDKFetcher.MISC_CACHE))
154 with misc_cache.Lookup((self.board, 'latest')) as ref:
155 if ref.Exists(lock=True):
156 sdk_version = osutils.ReadFile(ref.path).strip()
157 return sdk_version
158
159 def _CachePathForKey(self, key):
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800160 """Get cache path for key.
161
162 Args:
163 key: cache key.
164 """
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700165 tarball_cache = cache.TarballCache(self._GetCachePath(
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800166 cros_chrome_sdk.SDKFetcher.TARBALL_CACHE))
Achuith Bhandarkar4e367c22018-03-27 15:32:48 -0700167 if self.board and self._SDKVersion():
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700168 cache_key = (self.board, self._SDKVersion(), key)
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800169 with tarball_cache.Lookup(cache_key) as ref:
170 if ref.Exists():
171 return ref.path
172 return None
173
Mike Frysinger10666292018-07-12 01:03:38 -0400174 @memoize.MemoizedSingleCall
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +0100175 def QemuVersion(self):
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700176 """Determine QEMU version.
177
178 Returns:
179 QEMU version.
180 """
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +0100181 version_str = self._RunCommand([self.qemu_path, '--version'],
182 capture_output=True).output
183 # version string looks like one of these:
184 # QEMU emulator version 2.0.0 (Debian 2.0.0+dfsg-2ubuntu1.36), Copyright (c)
185 # 2003-2008 Fabrice Bellard
186 #
187 # QEMU emulator version 2.6.0, Copyright (c) 2003-2008 Fabrice Bellard
188 #
189 # qemu-x86_64 version 2.10.1
190 # Copyright (c) 2003-2017 Fabrice Bellard and the QEMU Project developers
191 m = re.search(r"version ([0-9.]+)", version_str)
192 if not m:
193 raise VMError('Unable to determine QEMU version from:\n%s.' % version_str)
194 return m.group(1)
195
196 def _CheckQemuMinVersion(self):
197 """Ensure minimum QEMU version."""
198 min_qemu_version = '2.6.0'
199 logging.info('QEMU version %s', self.QemuVersion())
200 LooseVersion = distutils.version.LooseVersion
201 if LooseVersion(self.QemuVersion()) < LooseVersion(min_qemu_version):
202 raise VMError('QEMU %s is the minimum supported version. You have %s.'
203 % (min_qemu_version, self.QemuVersion()))
204
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800205 def _SetQemuPath(self):
206 """Find a suitable Qemu executable."""
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800207 qemu_exe = 'qemu-system-x86_64'
208 qemu_exe_path = os.path.join('usr/bin', qemu_exe)
209
210 # Check SDK cache.
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800211 if not self.qemu_path:
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700212 qemu_dir = self._CachePathForKey(cros_chrome_sdk.SDKFetcher.QEMU_BIN_KEY)
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800213 if qemu_dir:
214 qemu_path = os.path.join(qemu_dir, qemu_exe_path)
215 if os.path.isfile(qemu_path):
216 self.qemu_path = qemu_path
217
218 # Check chroot.
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800219 if not self.qemu_path:
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800220 qemu_path = os.path.join(
221 constants.SOURCE_ROOT, constants.DEFAULT_CHROOT_DIR, qemu_exe_path)
222 if os.path.isfile(qemu_path):
223 self.qemu_path = qemu_path
224
225 # Check system.
226 if not self.qemu_path:
227 self.qemu_path = osutils.Which(qemu_exe)
228
229 if not self.qemu_path or not os.path.isfile(self.qemu_path):
230 raise VMError('QEMU not found.')
231 logging.debug('QEMU path: %s', self.qemu_path)
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800232 self._CheckQemuMinVersion()
233
234 def _GetBuiltVMImagePath(self):
235 """Get path of a locally built VM image."""
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800236 vm_image_path = os.path.join(constants.SOURCE_ROOT, 'src/build/images',
237 cros_build_lib.GetBoard(self.board),
238 'latest', constants.VM_IMAGE_BIN)
239 return vm_image_path if os.path.isfile(vm_image_path) else None
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800240
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800241 def _GetCacheVMImagePath(self):
242 """Get path of a cached VM image."""
Achuith Bhandarkarf6bccdb2018-03-23 13:12:29 -0700243 cache_path = self._CachePathForKey(constants.VM_IMAGE_TAR)
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800244 if cache_path:
245 vm_image = os.path.join(cache_path, constants.VM_IMAGE_BIN)
246 if os.path.isfile(vm_image):
247 return vm_image
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800248 return None
249
250 def _SetVMImagePath(self):
251 """Detect VM image path in SDK and chroot."""
252 if not self.image_path:
Achuith Bhandarkarfc75f692018-01-10 11:33:09 -0800253 self.image_path = (self._GetCacheVMImagePath() or
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800254 self._GetBuiltVMImagePath())
255 if not self.image_path:
256 raise VMError('No VM image found. Use cros chrome-sdk --download-vm.')
257 if not os.path.isfile(self.image_path):
258 raise VMError('VM image does not exist: %s' % self.image_path)
259 logging.debug('VM image path: %s', self.image_path)
260
Achuith Bhandarkarb00bafc2018-09-11 16:30:13 -0700261 def _WaitForSSHPort(self):
262 """Wait for SSH port to become available."""
263 class _SSHPortInUseError(Exception):
264 """Exception for _CheckSSHPortBusy to throw."""
265
266 def _CheckSSHPortBusy(ssh_port):
267 """Check if the SSH port is in use."""
268 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
269 try:
270 sock.bind((remote_access.LOCALHOST_IP, ssh_port))
271 except socket.error as e:
272 if e.errno == errno.EADDRINUSE:
273 logging.info('SSH port %d in use...', self.ssh_port)
274 raise _SSHPortInUseError()
275 sock.close()
276
277 try:
278 retry_util.RetryException(
279 exception=_SSHPortInUseError,
280 max_retry=7,
281 functor=lambda: _CheckSSHPortBusy(self.ssh_port),
282 sleep=1)
283 except _SSHPortInUseError:
284 raise VMError('SSH port %d in use' % self.ssh_port)
285
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100286 def Run(self):
Achuith Bhandarkar665a9b32018-05-17 11:39:19 -0700287 """Performs an action, one of start, stop, or run a command in the VM."""
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100288 if not self.start and not self.stop and not self.cmd:
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700289 raise VMError('Must specify one of start, stop, or cmd.')
Achuith Bhandarkar665a9b32018-05-17 11:39:19 -0700290
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100291 if self.start:
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700292 self.Start()
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100293 if self.cmd:
Achuith Bhandarkar665a9b32018-05-17 11:39:19 -0700294 if not self.IsRunning():
295 raise VMError('VM not running.')
296 self.RemoteCommand(self.cmd, stream_output=True)
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100297 if self.stop:
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700298 self.Stop()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700299
300 def Start(self):
301 """Start the VM."""
302
303 self.Stop()
Achuith Bhandarkarb00bafc2018-09-11 16:30:13 -0700304 self._WaitForSSHPort()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700305
Achuith Bhandarkar022d69c2016-10-05 14:28:14 -0700306 logging.debug('Start VM')
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800307 self._SetQemuPath()
308 self._SetVMImagePath()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700309
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700310 self._RmVMDir()
311 self._CreateVMDir()
Mike Frysinger97080242017-09-13 01:58:45 -0400312 # Make sure we can read these files later on by creating them as ourselves.
313 osutils.Touch(self.kvm_serial)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700314 for pipe in [self.kvm_pipe_in, self.kvm_pipe_out]:
Mike Frysinger0444f4c2018-08-03 15:12:46 -0400315 os.mkfifo(pipe, 0o600)
Mike Frysinger97080242017-09-13 01:58:45 -0400316 osutils.Touch(self.pidfile)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700317
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800318 qemu_args = [self.qemu_path]
319 if self.qemu_bios_path:
320 if not os.path.isdir(self.qemu_bios_path):
321 raise VMError('Invalid QEMU bios path: %s' % self.qemu_bios_path)
322 qemu_args += ['-L', self.qemu_bios_path]
Achuith Bhandarkar22bfedf2017-11-08 11:59:38 +0100323
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800324 qemu_args += [
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800325 '-m', self.qemu_m, '-smp', str(self.qemu_smp), '-vga', 'virtio',
326 '-daemonize', '-usbdevice', 'tablet',
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800327 '-pidfile', self.pidfile,
328 '-chardev', 'pipe,id=control_pipe,path=%s' % self.kvm_monitor,
329 '-serial', 'file:%s' % self.kvm_serial,
330 '-mon', 'chardev=control_pipe',
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800331 # Append 'check' to warn if the requested CPU is not fully supported.
332 '-cpu', self.qemu_cpu + ',check',
Lepton Wuaa6cca42018-04-19 18:56:29 -0700333 '-device', 'virtio-net,netdev=eth0',
Achuith Bhandarkarb00bafc2018-09-11 16:30:13 -0700334 '-netdev', 'user,id=eth0,net=10.0.2.0/27,hostfwd=tcp:%s:%d-:22'
335 % (remote_access.LOCALHOST_IP, self.ssh_port),
Nicolas Norvezf527cdf2018-01-26 11:55:44 -0800336 '-drive', 'file=%s,index=0,media=disk,cache=unsafe,format=%s'
337 % (self.image_path, self.image_format),
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800338 ]
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700339 if self.enable_kvm:
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800340 qemu_args.append('-enable-kvm')
Achuith Bhandarkarb891adb2016-10-24 18:43:22 -0700341 if not self.display:
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800342 qemu_args.extend(['-display', 'none'])
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700343 logging.info('Pid file: %s', self.pidfile)
344 if not self.dry_run:
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800345 self._RunCommand(qemu_args)
Achuith Bhandarkarbe977482018-02-06 16:44:00 -0800346 else:
347 logging.info(cros_build_lib.CmdToStr(qemu_args))
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700348
349 def _GetVMPid(self):
350 """Get the pid of the VM.
351
352 Returns:
353 pid of the VM.
354 """
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -0700355 if not os.path.exists(self.vm_dir):
356 logging.debug('%s not present.', self.vm_dir)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700357 return 0
358
359 if not os.path.exists(self.pidfile):
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700360 logging.info('%s does not exist.', self.pidfile)
361 return 0
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700362
Mike Frysinger97080242017-09-13 01:58:45 -0400363 pid = osutils.ReadFile(self.pidfile).rstrip()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700364 if not pid.isdigit():
Mike Frysinger97080242017-09-13 01:58:45 -0400365 # Ignore blank/empty files.
366 if pid:
367 logging.error('%s in %s is not a pid.', pid, self.pidfile)
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700368 return 0
369
Achuith Bhandarkar022d69c2016-10-05 14:28:14 -0700370 return int(pid)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700371
372 def IsRunning(self):
373 """Returns True if there's a running VM.
374
375 Returns:
376 True if there's a running VM.
377 """
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700378 pid = self._GetVMPid()
Achuith Bhandarkar022d69c2016-10-05 14:28:14 -0700379 if not pid:
380 return False
381
382 # Make sure the process actually exists.
Mike Frysinger97080242017-09-13 01:58:45 -0400383 return os.path.isdir('/proc/%i' % pid)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700384
385 def Stop(self):
386 """Stop the VM."""
Achuith Bhandarkar022d69c2016-10-05 14:28:14 -0700387 logging.debug('Stop VM')
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700388
389 pid = self._GetVMPid()
Achuith Bhandarkarf4950ba2016-10-11 15:40:07 -0700390 if pid:
391 logging.info('Killing %d.', pid)
392 if not self.dry_run:
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -0700393 self._RunCommand(['kill', '-9', str(pid)], error_code_ok=True)
Achuith Bhandarkarfd5d1852018-03-26 14:50:54 -0700394 self._RmVMDir()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700395
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700396 def _WaitForProcs(self):
397 """Wait for expected processes to launch."""
398 class _TooFewPidsException(Exception):
399 """Exception for _GetRunningPids to throw."""
400
401 def _GetRunningPids(exe, numpids):
402 pids = self.remote.GetRunningPids(exe, full_path=False)
403 logging.info('%s pids: %s', exe, repr(pids))
404 if len(pids) < numpids:
405 raise _TooFewPidsException()
406
407 def _WaitForProc(exe, numpids):
408 try:
409 retry_util.RetryException(
Achuith Bhandarkar0e7b8502017-06-12 15:32:41 -0700410 exception=_TooFewPidsException,
Achuith Bhandarkarb00bafc2018-09-11 16:30:13 -0700411 max_retry=5,
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700412 functor=lambda: _GetRunningPids(exe, numpids),
413 sleep=2)
414 except _TooFewPidsException:
415 raise VMError('_WaitForProcs failed: timed out while waiting for '
416 '%d %s processes to start.' % (numpids, exe))
417
418 # We could also wait for session_manager, nacl_helper, etc, but chrome is
419 # the long pole. We expect the parent, 2 zygotes, gpu-process, renderer.
420 # This could potentially break with Mustash.
421 _WaitForProc('chrome', 5)
422
423 def WaitForBoot(self):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700424 """Wait for the VM to boot up.
425
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700426 Wait for ssh connection to become active, and wait for all expected chrome
427 processes to be launched.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700428 """
Achuith Bhandarkaree3163d2016-10-19 12:58:35 -0700429 if not os.path.exists(self.vm_dir):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700430 self.Start()
431
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700432 try:
433 result = retry_util.RetryException(
Achuith Bhandarkar0e7b8502017-06-12 15:32:41 -0700434 exception=remote_access.SSHConnectionError,
Achuith Bhandarkar2f8352f2017-06-02 12:47:18 -0700435 max_retry=10,
436 functor=lambda: self.RemoteCommand(cmd=['echo']),
437 sleep=5)
438 except remote_access.SSHConnectionError:
439 raise VMError('WaitForBoot timed out trying to connect to VM.')
440
441 if result.returncode != 0:
442 raise VMError('WaitForBoot failed: %s.' % result.error)
443
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100444 # Chrome can take a while to start with software emulation.
445 if not self.enable_kvm:
446 self._WaitForProcs()
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700447
Achuith Bhandarkarbad755b2018-06-09 16:05:01 -0700448 def RemoteCommand(self, cmd, stream_output=False, **kwargs):
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700449 """Run a remote command in the VM.
450
451 Args:
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100452 cmd: command to run.
Achuith Bhandarkarbad755b2018-06-09 16:05:01 -0700453 stream_output: Stream output of long-running commands.
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100454 kwargs: additional args (see documentation for RemoteDevice.RunCommand).
Achuith Bhandarkar665a9b32018-05-17 11:39:19 -0700455
456 Returns:
457 cros_build_lib.CommandResult object.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700458 """
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700459 if not self.dry_run:
Achuith Bhandarkarbad755b2018-06-09 16:05:01 -0700460 kwargs.setdefault('error_code_ok', True)
461 if stream_output:
462 kwargs.setdefault('capture_output', False)
463 else:
464 kwargs.setdefault('combine_stdout_stderr', True)
465 kwargs.setdefault('log_output', True)
466 return self.remote.RunCommand(cmd, debug_level=logging.INFO, **kwargs)
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700467
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100468 @staticmethod
Achuith Bhandarkar2beae292018-03-26 16:44:53 -0700469 def GetParser():
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100470 """Parse a list of args.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700471
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100472 Args:
473 argv: list of command line arguments.
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700474
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100475 Returns:
476 List of parsed opts.
477 """
478 parser = commandline.ArgumentParser(description=__doc__)
479 parser.add_argument('--start', action='store_true', default=False,
480 help='Start the VM.')
481 parser.add_argument('--stop', action='store_true', default=False,
482 help='Stop the VM.')
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700483 parser.add_argument('--image-path', type=str,
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100484 help='Path to VM image to launch with --start.')
Nicolas Norvezf527cdf2018-01-26 11:55:44 -0800485 parser.add_argument('--image-format', default=VM.IMAGE_FORMAT,
486 help='Format of the VM image (raw, qcow2, ...).')
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700487 parser.add_argument('--qemu-path', type=str,
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100488 help='Path of qemu binary to launch with --start.')
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800489 parser.add_argument('--qemu-m', type=str, default='8G',
490 help='Memory argument that will be passed to qemu.')
491 parser.add_argument('--qemu-smp', type=int, default='0',
492 help='SMP argument that will be passed to qemu. (0 '
493 'means auto-detection.)')
Po-Hsien Wangc5b335f2018-02-06 18:36:16 -0800494 # TODO(pwang): replace SandyBridge to Haswell-noTSX once lab machine
495 # running VMTest all migrate to GCE.
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800496 parser.add_argument('--qemu-cpu', type=str,
Po-Hsien Wangc5b335f2018-02-06 18:36:16 -0800497 default='SandyBridge,-invpcid,-tsc-deadline',
Po-Hsien Wang1cec8d12018-01-25 16:36:44 -0800498 help='CPU argument that will be passed to qemu.')
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700499 parser.add_argument('--qemu-bios-path', type=str,
Achuith Bhandarkar41259652017-11-14 10:31:02 -0800500 help='Path of directory with qemu bios files.')
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100501 parser.add_argument('--disable-kvm', dest='enable_kvm',
502 action='store_false', default=True,
503 help='Disable KVM, use software emulation.')
504 parser.add_argument('--no-display', dest='display',
505 action='store_false', default=True,
506 help='Do not display video output.')
507 parser.add_argument('--ssh-port', type=int, default=VM.SSH_PORT,
508 help='ssh port to communicate with VM.')
Achuith Bhandarkar195b9512018-08-15 17:14:32 -0700509 parser.add_argument('--private-key', help='Path to ssh private key.')
Achuith Bhandarkar1297dcf2017-11-21 12:03:48 -0800510 sdk_board_env = os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
511 parser.add_argument('--board', default=sdk_board_env, help='Board to use.')
Achuith Bhandarkar02ef76b2018-04-05 10:47:33 +0200512 parser.add_argument('--cache-dir', type=str,
513 default=path_util.GetCacheDir(),
514 help='Cache directory to use.')
Achuith Bhandarkar46d83872018-04-04 01:49:55 -0700515 parser.add_argument('--vm-dir', type=str,
Achuith Bhandarkar2beae292018-03-26 16:44:53 -0700516 help='Temp VM directory to use.')
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100517 parser.add_argument('--dry-run', action='store_true', default=False,
518 help='dry run for debugging.')
519 parser.add_argument('--cmd', action='store_true', default=False,
520 help='Run a command in the VM.')
521 parser.add_argument('args', nargs=argparse.REMAINDER,
522 help='Command to run in the VM.')
Achuith Bhandarkar2beae292018-03-26 16:44:53 -0700523 return parser
Achuith Bhandarkard8d19292016-05-03 14:32:58 -0700524
525def main(argv):
Achuith Bhandarkar2beae292018-03-26 16:44:53 -0700526 opts = VM.GetParser().parse_args(argv)
527 opts.Freeze()
528
529 vm = VM(opts)
Achuith Bhandarkar2a39adf2017-10-30 10:24:45 +0100530 vm.Run()