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