Home | History | Annotate | Download | only in provision_CheetsUpdate
      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 
      5 import logging
      6 import pipes
      7 import os
      8 import re
      9 import shutil
     10 import subprocess
     11 import tempfile
     12 
     13 from autotest_lib.client.common_lib.cros import dev_server
     14 from autotest_lib.client.common_lib import error
     15 from autotest_lib.server import test
     16 from 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
     20 KERNEL_A_PARTITION = 2
     21 KERNEL_B_PARTITION = 4
     22 
     23 SIMG2IMG_PATH = '/usr/bin/simg2img'
     24 
     25 
     26 class 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_AutoUpdate 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