1# Copyright (c) 2016 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Utility to run a Brillo emulator programmatically. 5 6Requires system.img, userdata.img and kernel to be in imagedir. If running an 7arm emulator kernel.dtb (or another dtb file) must also be in imagedir. 8 9WARNING: Processes created by this utility may not die unless 10EmulatorManager.stop is called. Call EmulatorManager.verify_stop to 11confirm process has stopped and port is free. 12""" 13 14import os 15import time 16 17import common 18from autotest_lib.client.common_lib import error 19from autotest_lib.client.common_lib import utils 20 21 22class EmulatorManagerException(Exception): 23 """Bad port, missing artifact or non-existant imagedir.""" 24 pass 25 26 27class EmulatorManager(object): 28 """Manage an instance of a device emulator. 29 30 @param imagedir: directory of emulator images. 31 @param port: Port number for emulator's adbd. Note this port is one higher 32 than the port in the emulator's serial number. 33 @param run: Function used to execute shell commands. 34 """ 35 def __init__(self, imagedir, port, run=utils.run): 36 if not port % 2 or port < 5555 or port > 5585: 37 raise EmulatorManagerException('Port must be an odd number ' 38 'between 5555 and 5585.') 39 try: 40 run('test -f %s' % os.path.join(imagedir, 'system.img')) 41 except error.GenericHostRunError: 42 raise EmulatorManagerException('Image directory must exist and ' 43 'contain emulator images.') 44 45 self.port = port 46 self.imagedir = imagedir 47 self.run = run 48 49 50 def verify_stop(self, timeout_secs=3): 51 """Wait for emulator on our port to stop. 52 53 @param timeout_secs: Max seconds to wait for the emulator to stop. 54 55 @return: Bool - True if emulator stops. 56 """ 57 cycles = 0 58 pid = self.find() 59 while pid: 60 cycles += 1 61 time.sleep(0.1) 62 pid = self.find() 63 if cycles >= timeout_secs*10 and pid: 64 return False 65 return True 66 67 68 def _find_dtb(self): 69 """Detect a dtb file in the image directory 70 71 @return: Path to dtb file or None. 72 """ 73 cmd_result = self.run('find "%s" -name "*.dtb"' % self.imagedir) 74 dtb = cmd_result.stdout.split('\n')[0] 75 return dtb.strip() or None 76 77 78 def start(self): 79 """Start an emulator with the images and port specified. 80 81 If an emulator is already running on the port it will be killed. 82 """ 83 self.force_stop() 84 time.sleep(1) # Wait for port to be free 85 # TODO(jgiorgi): Add support for x86 / x64 emulators 86 args = [ 87 '-dmS', 'emulator-%s' % self.port, 'qemu-system-arm', 88 '-M', 'vexpress-a9', 89 '-m', '1024M', 90 '-kernel', os.path.join(self.imagedir, 'kernel'), 91 '-append', ('"console=ttyAMA0 ro root=/dev/sda ' 92 'androidboot.hardware=qemu qemu=1 rootwait noinitrd ' 93 'init=/init androidboot.selinux=enforcing"'), 94 '-nographic', 95 '-device', 'virtio-scsi-device,id=scsi', 96 '-device', 'scsi-hd,drive=system', 97 '-drive', ('file="%s,if=none,id=system,format=raw"' 98 % os.path.join(self.imagedir, 'system.img')), 99 '-device', 'scsi-hd,drive=userdata', 100 '-drive', ('file="%s,if=none,id=userdata,format=raw"' 101 % os.path.join(self.imagedir, 'userdata.img')), 102 '-redir', 'tcp:%s::5555' % self.port, 103 ] 104 105 # DTB file produced and required for arm but not x86 emulators 106 dtb = self._find_dtb() 107 if dtb: 108 args += ['-dtb', dtb] 109 else: 110 raise EmulatorManagerException('DTB file missing. Required for arm ' 111 'emulators.') 112 113 self.run(' '.join(['screen'] + args)) 114 115 116 def find(self): 117 """Detect the PID of a qemu process running on our port. 118 119 @return: PID or None 120 """ 121 running = self.run('netstat -nlpt').stdout 122 for proc in running.split('\n'): 123 if ':%s' % self.port in proc: 124 process = proc.split()[-1] 125 if '/' in process: # Program identified, we started and can kill 126 return process.split('/')[0] 127 128 129 def stop(self, kill=False): 130 """Send signal to stop emulator process. 131 132 Signal is sent to any running qemu process on our port regardless of how 133 it was started. Silent no-op if no running qemu processes on the port. 134 135 @param kill: Send SIGKILL signal instead of SIGTERM. 136 """ 137 pid = self.find() 138 if pid: 139 cmd = 'kill -9 %s' if kill else 'kill %s' 140 self.run(cmd % pid) 141 142 143 def force_stop(self): 144 """Attempt graceful shutdown, kill if not dead after 3 seconds. 145 """ 146 self.stop() 147 if not self.verify_stop(timeout_secs=3): 148 self.stop(kill=True) 149 if not self.verify_stop(): 150 raise RuntimeError('Emulator running on port %s failed to stop.' 151 % self.port) 152 153