• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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