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