1# Copyright 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
5import logging
6import os
7import re
8import sys
9
10import common
11from autotest_lib.client.common_lib import error
12from autotest_lib.server import utils
13from autotest_lib.server.hosts import adb_host
14from autotest_lib.utils import emulator_manager
15
16OS_TYPE_EMULATED_BRILLO = 'emulated_brillo'
17OS_TYPE_DEFAULT = OS_TYPE_EMULATED_BRILLO
18BOARD_DEFAULT = "brilloemulator_arm"
19EMULATED_BRILLO_ARTIFACT_FORMAT = (
20    '%(build_target)s-target_files-%(build_id)s.zip')
21EMULATED_BRILLO_DTB_FORMAT = (
22    '%(build_target)s-dtb-%(build_id)s.zip')
23
24
25class EmulatedADBHost(adb_host.ADBHost):
26    """Run an emulator as an ADB device preserving the API and assumptions of
27    ADBHost.
28
29    Currently supported emulators:
30    * Brillo
31        * brilloemulator_arm
32    """
33
34    def _initialize(self, *args, **kwargs):
35        """Intialize an emulator so that existing assumptions that the host is
36        always ready ar satisfied.
37
38        @param args: pass through to ADBHost
39        @param kwargs: pass through to ADBHost
40        """
41        super(EmulatedADBHost, self)._initialize(*args, **kwargs)
42
43        # Verify serial
44        m = re.match('emulator-(\d{4})', self.adb_serial)
45        if not m:
46            raise ValueError('Emulator serial must be in the format '
47                             'emulator-PORT.')
48        self.port = int(m.group(1)) + 1
49
50        # Determine directory for images (needs to be persistent)
51        tmp_dir = self.teststation.get_tmp_dir()
52        self.imagedir = os.path.join(os.path.dirname(tmp_dir), self.adb_serial)
53        self.teststation.run('rm -rf %s' % tmp_dir)
54        self.teststation.run('mkdir -p %s' % self.imagedir)
55
56        # Boot the emulator, if not already booted, since ADBHost assumes the
57        # device is always available
58        self._emulator = emulator_manager.EmulatorManager(
59                self.imagedir, self.port, run=self.teststation.run)
60        self._start_emulator_if_not_started()
61
62
63    def _start_emulator_if_not_started(self):
64        """Boot or reboot the emulator if necessary.
65
66        If the emulator is not started boot the emulator. Otherwise leave it
67        alone. Ensure emulator is running and ready before returning.
68        """
69        host_os = self.get_os_type()
70        board = self.get_board()
71
72        # Check that images exist in imagedir
73        try:
74            self.teststation.run('test -f %s' % os.path.join(self.imagedir,
75                                                            'system.img'))
76
77        # Use default images
78        except error.GenericHostRunError:
79            self.teststation.run('cp %s/* %s/' % (
80                os.path.join('/usr/local/emulator_images', host_os, board),
81                self.imagedir
82            ))
83
84        if not self._emulator.find():
85            self._emulator.start()
86        self.wait_up()
87        self._reset_adbd_connection()
88
89
90    def get_os_type(self):
91        """Determine the OS type from afe_host object or use the default if
92        no os label / no afe_host object.
93
94        @return: os type as str
95        """
96        info = self.host_info_store.get()
97        return info.os or OS_TYPE_DEFAULT
98
99
100    def get_board(self):
101        """Determine the board from afe_host object or use the default if
102        no board label / no afe_host object.
103
104        @return: board as str
105        """
106        info = self.host_info_store.get()
107        return info.board or BOARD_DEFAULT
108
109
110    @staticmethod
111    def check_host(host, timeout=10):
112        """No dynamic host checking. Must be explicit.
113
114        @param host: ignored
115        @param timeout: ignored
116
117        @return: False
118        """
119        return False
120
121
122    def stage_emulator_artifact(self, build_url):
123        """Download required build artifact from the given build_url to a
124        local directory in the machine running the emulator.
125
126        @param build_url: The url to use for downloading Android artifacts.
127                          pattern: http://$devserv/static/branch/target/build_id
128
129        @return: Path to the directory contains image files.
130        """
131        build_info = self.get_build_info_from_build_url(build_url)
132
133        zipped_artifact = EMULATED_BRILLO_ARTIFACT_FORMAT % build_info
134        dtb_artifact = EMULATED_BRILLO_DTB_FORMAT % build_info
135        image_dir = self.teststation.get_tmp_dir()
136
137        try:
138            self.download_file(build_url, zipped_artifact, image_dir,
139                               unzip=True)
140            self.download_file(build_url, dtb_artifact, image_dir,
141                               unzip=True)
142            return image_dir
143        except:
144            self.teststation.run('rm -rf %s' % image_dir)
145            raise
146
147
148    def setup_brillo_emulator(self, build_url, build_local_path=None):
149        """Install the Brillo DUT.
150
151        Following are the steps used here to provision an android device:
152        1. If build_local_path is not set, download the target_files zip, e.g.,
153        brilloemulator_arm-target_files-123456.zip, and unzip it.
154        2. Move the necessary images to a new directory.
155        3. Determine port for ADB from serial.
156        4. Use EmulatorManager to start the emulator.
157
158        @param build_url: The url to use for downloading Android artifacts.
159                          pattern: http://$devserver:###/static/$build
160        @param build_local_path: The path to a local folder that contains the
161                                 image files needed to provision the device.
162                                 Note that the folder is in the machine running
163                                 adb command, rather than the drone.
164
165        @raises AndroidInstallError if any error occurs.
166        """
167        # If the build is not staged in local server yet, clean up the temp
168        # folder used to store image files after the provision is completed.
169        delete_build_folder = bool(not build_local_path)
170
171        try:
172            # Download image files needed for provision to a local directory.
173            if not build_local_path:
174                build_local_path = self.stage_emulator_artifact(build_url)
175
176            # Create directory with required files
177            self.teststation.run('rm -rf %s && mkdir %s' % (self.imagedir,
178                                                            self.imagedir))
179            self.teststation.run('mv %s %s' % (
180                    os.path.join(build_local_path, 'IMAGES', 'system.img'),
181                    os.path.join(self.imagedir, 'system.img')
182            ))
183            self.teststation.run('mv %s %s' % (
184                    os.path.join(build_local_path, 'IMAGES', 'userdata.img'),
185                    os.path.join(self.imagedir, 'userdata.img')
186            ))
187            self.teststation.run('mv %s %s' % (
188                    os.path.join(build_local_path, 'BOOT', 'kernel'),
189                    os.path.join(self.imagedir, 'kernel')
190            ))
191            self.teststation.run('mv %s/*.dtb %s' % (build_local_path,
192                                                     self.imagedir))
193
194            # Start the emulator
195            self._emulator.force_stop()
196            self._start_emulator_if_not_started()
197
198        except Exception as e:
199            logging.error('Install Brillo build failed with error: %s', e)
200            # Re-raise the exception with type of AndroidInstallError.
201            raise (adb_host.AndroidInstallError, sys.exc_info()[1],
202                   sys.exc_info()[2])
203        finally:
204            if delete_build_folder:
205                self.teststation.run('rm -rf %s' % build_local_path)
206                logging.info('Successfully installed Android build staged at '
207                             '%s.', build_url)
208
209
210    def machine_install(self, build_url=None, build_local_path=None, wipe=True,
211                        flash_all=False, os_type=None):
212        """Install the DUT.
213
214        @param build_url: The url to use for downloading Android artifacts.
215                          pattern: http://$devserver:###/static/$build.
216                          If build_url is set to None, the code may try
217                          _parser.options.image to do the installation. If none
218                          of them is set, machine_install will fail.
219        @param build_local_path: The path to a local directory that contains the
220                                 image files needed to provision the device.
221        @param wipe: No-op
222        @param flash_all: No-op
223        @param os_type: OS to install (overrides label).
224
225        @returns A tuple of (image_name, host_attributes). image_name is the
226                 name of image installed, e.g.,
227                 git_mnc-release/shamu-userdebug/1234
228                 host_attributes is a dictionary of (attribute, value), which
229                 can be saved to afe_host_attributes table in database. This
230                 method returns a dictionary with a single entry of
231                 `job_repo_url_[adb_serial]`: devserver_url, where devserver_url
232                 is a url to the build staged on devserver.
233        """
234        os_type = os_type or self.get_os_type()
235        if not build_url and self._parser.options.image:
236            build_url, _ = self.stage_build_for_install(
237                    self._parser.options.image, os_type=os_type)
238        if os_type == OS_TYPE_EMULATED_BRILLO:
239            self.setup_brillo_emulator(
240                    build_url=build_url, build_local_path=build_local_path)
241            self.ensure_adb_mode()
242        else:
243            raise error.InstallError(
244                    'Installation of os type %s is not supported.' %
245                    os_type)
246        return (build_url.split('static/')[-1],
247                {self.job_repo_url_attribute: build_url})
248
249
250    def repair(self):
251        """No-op. No repair procedures for emulated devices.
252        """
253        pass
254
255
256    def verify_software(self):
257        """Verify commands are available on teststation.
258
259        @return: Bool - teststation has necessary software installed.
260        """
261        adb = self.teststation.run('which adb')
262        qemu = self.teststation.run('which qemu-system-arm')
263        unzip = self.teststation.run('which unzip')
264
265        return bool(adb and qemu and unzip)
266
267
268    def fastboot_run(self, command, **kwargs):
269        """No-op, emulators do not support fastboot.
270
271        @param command: command to not execute
272        @param kwargs: additional arguments to ignore
273
274        @return: empty CmdResult object
275        """
276        return utils.CmdResult()
277
278
279    def get_labels(self):
280        """No-op, emulators do not have any detectable labels.
281
282        @return: empty list
283        """
284        return []
285
286
287    def get_platform(self):
288        """@return: emulated_adb
289        """
290        return 'emulated_adb'
291
292