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