• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 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 pipes
7import os
8import re
9import shutil
10import subprocess
11import tempfile
12
13from autotest_lib.client.common_lib.cros import dev_server
14from autotest_lib.client.common_lib import error
15from autotest_lib.server import test
16from autotest_lib.server import utils
17
18# 2 & 4 are default partitions, and the system boots from one of them.
19# Code from chromite/scripts/deploy_chrome.py
20KERNEL_A_PARTITION = 2
21KERNEL_B_PARTITION = 4
22
23SIMG2IMG_PATH = '/usr/bin/simg2img'
24
25
26class provision_CheetsUpdate(test.test):
27    """
28    Update Android build On the target DUT.
29
30    This test is designed for ARC++ Treehugger style CQ to update Android image
31    on the DUT.
32    """
33    version = 1
34
35
36    def initialize(self):
37        self.android_build_path = None
38        self.push_to_device_dir_path = None
39        self.__build_temp_dir = None
40
41
42    def download_android_build(self, android_build, ds):
43        """
44        Download the Android test build from the dev server.
45
46        @param android_build: Android build to test.
47        @param ds: Dev server instance for downloading the test build.
48        """
49        build_filename = self.generate_android_build_filename(android_build)
50        logging.info('Generated build name: %s', build_filename)
51        branch, target, build_id = (
52                utils.parse_launch_control_build(android_build))
53        ds.stage_artifacts(target, build_id, branch, artifacts=['zip_images'])
54        zip_image = ds.get_staged_file_url(
55                build_filename,
56                target,
57                build_id,
58                branch)
59        logging.info('Downloading the test build.')
60        test_filepath = os.path.join(self.__build_temp_dir, build_filename)
61        logging.info('Android test file download path: %s', test_filepath)
62        logging.info('Zip image: %s', zip_image)
63        # Timeout if Android build downloading takes more than 10 minutes.
64        ds.download_file(zip_image, test_filepath, timeout=10)
65        if not os.path.exists(test_filepath):
66            raise error.TestFail(
67                    'Android test build %s download failed' % test_filepath)
68        self.android_build_path = test_filepath
69
70    def download_sepolicy(self, android_build, ds):
71        """
72        Download sepolicy.zip artifact of an Android build.
73
74        @param android_build: Android build to test
75        @param ds: Dev server instance for downloading the test build.
76        """
77        _SEPOLICY_FILENAME = 'sepolicy.zip'
78        branch, target, build_id = (
79                utils.parse_launch_control_build(android_build))
80        try:
81            ds.stage_artifacts(target, build_id, branch, artifacts=[_SEPOLICY_FILENAME])
82        except dev_server.DevServerException as e:
83            # e is DevServerException with response HTML in the message.
84            # We can't simply match ArtifactDownloadError by error type.
85            # Instead, we could only use string match to determine the server error type.
86            if 'ArtifactDownloadError: No artifact found' in str(e):
87                self.sepolicy = None
88                logging.info(
89                        'No artifact sepolicy.zip. Fallback to Android policy only')
90                return
91            else:
92                raise e
93        sepolicy_zip_url = ds.get_staged_file_url(
94                _SEPOLICY_FILENAME,
95                target,
96                build_id,
97                branch)
98        logging.info('Downloading the sepolicy.zip.')
99        sepolicy_zip_filepath = os.path.join(self.__build_temp_dir, 'sepolicy.zip')
100        ds.download_file(sepolicy_zip_url, sepolicy_zip_filepath, timeout=10)
101        if not os.path.exists(sepolicy_zip_filepath):
102            raise error.TestFail('Android sepolicy.zip download failed')
103        self.sepolicy = sepolicy_zip_filepath
104
105
106    def download_push_to_device(self, android_build, ds):
107        """
108        Download and unarchive push_to_device artifact from the dev server.
109
110        @param android_build:
111            Android build containing the push_to_device artifact.
112        @param ds: Dev server instance for downloading push_to_device.
113        """
114        logging.info('Downloading push_to_device.zip.')
115        branch, target, build_id = (
116                utils.parse_launch_control_build(android_build))
117        ds.stage_artifacts(
118                target, build_id, branch, artifacts=['push_to_device_zip'])
119        zip_url = ds.get_staged_file_url(
120                'push_to_device.zip', target, build_id, branch)
121        zip_filepath = os.path.join(self.__build_temp_dir, 'push_to_device.zip')
122        dir_filepath = os.path.join(self.__build_temp_dir, 'push_to_device')
123        ds.download_file(zip_url, zip_filepath, timeout=10)
124        if not os.path.exists(zip_filepath):
125            raise error.TestFail('Failed to download %s' % zip_url)
126        logging.info('Unarchiving push_to_device.zip to %s', dir_filepath)
127        cmd = ['unzip', zip_filepath, '-d', dir_filepath]
128        try:
129            subprocess.check_output(cmd, stderr=subprocess.STDOUT)
130        except subprocess.CalledProcessError as e:
131            raise error.TestFail('unzip failed due to: %s' % e.output)
132        self.push_to_device_dir_path = dir_filepath
133
134
135    def remove_rootfs(self, host):
136        """
137        Remove rootfs verification on DUT.
138
139        Removing rootfs is required to push a new Android image to DUT.
140
141        @param host: DUT on which rootfs needs to be disabled.
142        """
143        logging.info('Disabling rootfs on the DUT.')
144        cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
145               '--remove_rootfs_verification --force')
146        for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
147            cmd_with_partition = cmd % partition
148            logging.info(cmd_with_partition)
149            host.run(cmd_with_partition)
150        host.reboot()
151
152
153    def generate_android_build_filename(self, android_build):
154        """
155        Parse Android build version to generate the build file name.
156
157        @param android_build: android build info with branch and build type.
158                              e.g. git_mnc-dr-arc-dev/cheets_arm-user/P3909418
159                              e.g. git_mnc-dr-arc-dev/cheets_x86-user/P3909418
160
161        @return Android test file name to download and update on the DUT.
162        """
163        m = re.findall(r'cheets_\w+|P?\d+$', android_build)
164        if m:
165            return m[0] + '-img-' + m[1] + '.zip'
166        else:
167            raise error.TestFail(
168                    'Android build arg %s is missing build version info.' %
169                    android_build)
170
171
172    def run_push_to_device(self, host):
173        """
174        Run push_to_device command to push the test Android build to the DUT.
175
176        @param host: DUT on which the new Android image needs to be pushed.
177        """
178        cmd = ['python3',
179               os.path.join(self.push_to_device_dir_path, 'push_to_device.py'),
180               '--use-prebuilt-file',
181               self.android_build_path,
182               '--simg2img-path',
183               SIMG2IMG_PATH,
184               '--secilc-path',
185               os.path.join(self.push_to_device_dir_path, 'bin', 'secilc'),
186               '--mksquashfs-path',
187               os.path.join(self.push_to_device_dir_path, 'bin', 'mksquashfs'),
188               '--unsquashfs-path',
189               os.path.join(self.push_to_device_dir_path, 'bin', 'unsquashfs'),
190               '--shift-uid-py-path',
191               os.path.join(self.push_to_device_dir_path, 'shift_uid.py'),
192               host.hostname,
193               '--loglevel',
194               'DEBUG']
195        if self.sepolicy:
196            cmd.extend(['--sepolicy-artifacts-path', self.sepolicy])
197        try:
198            logging.info('Running push to device:')
199            logging.info(
200                    '%s',
201                    ' '.join(pipes.quote(arg) for arg in cmd))
202            output = subprocess.check_output(
203                    cmd,
204                    stderr=subprocess.STDOUT)
205            logging.info(output)
206        except subprocess.CalledProcessError as e:
207            logging.error(
208                    'Error while executing %s',
209                    ' '.join(pipes.quote(arg) for arg in cmd))
210            logging.error(e.output)
211            raise error.TestFail(
212                    'Pushing Android test build failed due to: %s' %
213                    e.output)
214
215
216    def run_once(self, host, value=None):
217        """
218        Installs test ChromeOS version and Android version `value` on `host`.
219
220        This method is invoked by the test control file to start the
221        provisioning test.
222
223        @param host: DUT on which the test to be run.
224        @param value: contains Android build info to test.
225                      git_nyc-arc/cheets_x86-user/3512523
226        """
227        logging.debug('Start provisioning %s to %s.', host, value)
228
229        if not value:
230            raise error.TestFail('No build provided.')
231
232        cheets_prefix = host.host_version_prefix(value)
233        info = host.host_info_store.get()
234        try:
235            host_android_build = info.get_label_value(cheets_prefix)
236            logging.info('Cheets build from cheets-version: %s.',
237                         host_android_build)
238        except:
239            # In case the DUT has never run cheets tests before, there might not
240            # be cheets build label set.
241            host_android_build = None
242        # provision_QuickProvision can update the cheets version and the
243        # cheets-version label might not have been updated so checking the
244        # cheets version installed on the DUT.
245        dut_arc_version = host.get_arc_version()
246        logging.info('Cheets build installed on the DUT from lsb-release: %s.',
247                     dut_arc_version)
248        if dut_arc_version and dut_arc_version in value:
249            # Update the cheets version label in case the DUT label and
250            # installed cheets version aren't matching.
251            if host_android_build != value:
252                info.set_version_label(cheets_prefix, value)
253                host.host_info_store.commit(info)
254            # If the installed cheets version is same as the test version, emitting
255            # an INFO line.
256            self.job.record('INFO', None, None, 'Host already running %s.' % value)
257            return
258        else:
259            logging.info('Updating ARC++ build from %s to %s.',
260                         host_android_build,
261                         value)
262            self.remove_rootfs(host)
263            logging.info('Setting up devserver.')
264            ds = dev_server.AndroidBuildServer.resolve(value)
265            self.__build_temp_dir = tempfile.mkdtemp()
266            self.download_android_build(value, ds)
267            self.download_push_to_device(value, ds)
268            self.download_sepolicy(value, ds)
269            self.run_push_to_device(host)
270            info = host.host_info_store.get()
271            logging.info('Updating DUT version label: %s:%s', cheets_prefix, value)
272            info.clear_version_labels(cheets_prefix)
273            info.set_version_label(cheets_prefix, value)
274            host.host_info_store.commit(info)
275
276
277    def cleanup(self):
278        if self.android_build_path and os.path.exists(self.android_build_path):
279            try:
280                logging.info(
281                        'Deleting Android build dir at %s',
282                        self.__build_temp_dir)
283                shutil.rmtree(self.__build_temp_dir)
284            except OSError as e:
285                raise error.TestFail('%s' % e)
286