普通文本  |  286行  |  11.47 KB

# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import pipes
import os
import re
import shutil
import subprocess
import tempfile

from autotest_lib.client.common_lib.cros import dev_server
from autotest_lib.client.common_lib import error
from autotest_lib.server import test
from autotest_lib.server import utils

# 2 & 4 are default partitions, and the system boots from one of them.
# Code from chromite/scripts/deploy_chrome.py
KERNEL_A_PARTITION = 2
KERNEL_B_PARTITION = 4

SIMG2IMG_PATH = '/usr/bin/simg2img'


class provision_CheetsUpdate(test.test):
    """
    Update Android build On the target DUT.

    This test is designed for ARC++ Treehugger style CQ to update Android image
    on the DUT.
    """
    version = 1


    def initialize(self):
        self.android_build_path = None
        self.push_to_device_dir_path = None
        self.__build_temp_dir = None


    def download_android_build(self, android_build, ds):
        """
        Download the Android test build from the dev server.

        @param android_build: Android build to test.
        @param ds: Dev server instance for downloading the test build.
        """
        build_filename = self.generate_android_build_filename(android_build)
        logging.info('Generated build name: %s', build_filename)
        branch, target, build_id = (
                utils.parse_launch_control_build(android_build))
        ds.stage_artifacts(target, build_id, branch, artifacts=['zip_images'])
        zip_image = ds.get_staged_file_url(
                build_filename,
                target,
                build_id,
                branch)
        logging.info('Downloading the test build.')
        test_filepath = os.path.join(self.__build_temp_dir, build_filename)
        logging.info('Android test file download path: %s', test_filepath)
        logging.info('Zip image: %s', zip_image)
        # Timeout if Android build downloading takes more than 10 minutes.
        ds.download_file(zip_image, test_filepath, timeout=10)
        if not os.path.exists(test_filepath):
            raise error.TestFail(
                    'Android test build %s download failed' % test_filepath)
        self.android_build_path = test_filepath

    def download_sepolicy(self, android_build, ds):
        """
        Download sepolicy.zip artifact of an Android build.

        @param android_build: Android build to test
        @param ds: Dev server instance for downloading the test build.
        """
        _SEPOLICY_FILENAME = 'sepolicy.zip'
        branch, target, build_id = (
                utils.parse_launch_control_build(android_build))
        try:
            ds.stage_artifacts(target, build_id, branch, artifacts=[_SEPOLICY_FILENAME])
        except dev_server.DevServerException as e:
            # e is DevServerException with response HTML in the message.
            # We can't simply match ArtifactDownloadError by error type.
            # Instead, we could only use string match to determine the server error type.
            if 'ArtifactDownloadError: No artifact found' in str(e):
                self.sepolicy = None
                logging.info(
                        'No artifact sepolicy.zip. Fallback to Android policy only')
                return
            else:
                raise e
        sepolicy_zip_url = ds.get_staged_file_url(
                _SEPOLICY_FILENAME,
                target,
                build_id,
                branch)
        logging.info('Downloading the sepolicy.zip.')
        sepolicy_zip_filepath = os.path.join(self.__build_temp_dir, 'sepolicy.zip')
        ds.download_file(sepolicy_zip_url, sepolicy_zip_filepath, timeout=10)
        if not os.path.exists(sepolicy_zip_filepath):
            raise error.TestFail('Android sepolicy.zip download failed')
        self.sepolicy = sepolicy_zip_filepath


    def download_push_to_device(self, android_build, ds):
        """
        Download and unarchive push_to_device artifact from the dev server.

        @param android_build:
            Android build containing the push_to_device artifact.
        @param ds: Dev server instance for downloading push_to_device.
        """
        logging.info('Downloading push_to_device.zip.')
        branch, target, build_id = (
                utils.parse_launch_control_build(android_build))
        ds.stage_artifacts(
                target, build_id, branch, artifacts=['push_to_device_zip'])
        zip_url = ds.get_staged_file_url(
                'push_to_device.zip', target, build_id, branch)
        zip_filepath = os.path.join(self.__build_temp_dir, 'push_to_device.zip')
        dir_filepath = os.path.join(self.__build_temp_dir, 'push_to_device')
        ds.download_file(zip_url, zip_filepath, timeout=10)
        if not os.path.exists(zip_filepath):
            raise error.TestFail('Failed to download %s' % zip_url)
        logging.info('Unarchiving push_to_device.zip to %s', dir_filepath)
        cmd = ['unzip', zip_filepath, '-d', dir_filepath]
        try:
            subprocess.check_output(cmd, stderr=subprocess.STDOUT)
        except subprocess.CalledProcessError as e:
            raise error.TestFail('unzip failed due to: %s' % e.output)
        self.push_to_device_dir_path = dir_filepath


    def remove_rootfs(self, host):
        """
        Remove rootfs verification on DUT.

        Removing rootfs is required to push a new Android image to DUT.

        @param host: DUT on which rootfs needs to be disabled.
        """
        logging.info('Disabling rootfs on the DUT.')
        cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh --partitions %d '
               '--remove_rootfs_verification --force')
        for partition in (KERNEL_A_PARTITION, KERNEL_B_PARTITION):
            cmd_with_partition = cmd % partition
            logging.info(cmd_with_partition)
            host.run(cmd_with_partition)
        host.reboot()


    def generate_android_build_filename(self, android_build):
        """
        Parse Android build version to generate the build file name.

        @param android_build: android build info with branch and build type.
                              e.g. git_mnc-dr-arc-dev/cheets_arm-user/P3909418
                              e.g. git_mnc-dr-arc-dev/cheets_x86-user/P3909418

        @return Android test file name to download and update on the DUT.
        """
        m = re.findall(r'cheets_\w+|P?\d+$', android_build)
        if m:
            return m[0] + '-img-' + m[1] + '.zip'
        else:
            raise error.TestFail(
                    'Android build arg %s is missing build version info.' %
                    android_build)


    def run_push_to_device(self, host):
        """
        Run push_to_device command to push the test Android build to the DUT.

        @param host: DUT on which the new Android image needs to be pushed.
        """
        cmd = ['python3',
               os.path.join(self.push_to_device_dir_path, 'push_to_device.py'),
               '--use-prebuilt-file',
               self.android_build_path,
               '--simg2img-path',
               SIMG2IMG_PATH,
               '--secilc-path',
               os.path.join(self.push_to_device_dir_path, 'bin', 'secilc'),
               '--mksquashfs-path',
               os.path.join(self.push_to_device_dir_path, 'bin', 'mksquashfs'),
               '--unsquashfs-path',
               os.path.join(self.push_to_device_dir_path, 'bin', 'unsquashfs'),
               '--shift-uid-py-path',
               os.path.join(self.push_to_device_dir_path, 'shift_uid.py'),
               host.hostname,
               '--loglevel',
               'DEBUG']
        if self.sepolicy:
          cmd.extend(['--sepolicy-artifacts-path', self.sepolicy])
        try:
            logging.info('Running push to device:')
            logging.info(
                    '%s',
                    ' '.join(pipes.quote(arg) for arg in cmd))
            output = subprocess.check_output(
                    cmd,
                    stderr=subprocess.STDOUT)
            logging.info(output)
        except subprocess.CalledProcessError as e:
            logging.error(
                    'Error while executing %s',
                    ' '.join(pipes.quote(arg) for arg in cmd))
            logging.error(e.output)
            raise error.TestFail(
                    'Pushing Android test build failed due to: %s' %
                    e.output)


    def run_once(self, host, value=None):
        """
        Installs test ChromeOS version and Android version `value` on `host`.

        This method is invoked by the test control file to start the
        provisioning test.

        @param host: DUT on which the test to be run.
        @param value: contains Android build info to test.
                      git_nyc-arc/cheets_x86-user/3512523
        """
        logging.debug('Start provisioning %s to %s.', host, value)

        if not value:
            raise error.TestFail('No build provided.')

        cheets_prefix = host.host_version_prefix(value)
        info = host.host_info_store.get()
        try:
            host_android_build = info.get_label_value(cheets_prefix)
            logging.info('Cheets build from cheets-version: %s.',
                         host_android_build)
        except:
            # In case the DUT has never run cheets tests before, there might not
            # be cheets build label set.
            host_android_build = None
        # provision_AutoUpdate can update the cheets version and the
        # cheets-version label might not have been updated so checking the
        # cheets version installed on the DUT.
        dut_arc_version = host.get_arc_version()
        logging.info('Cheets build installed on the DUT from lsb-release: %s.',
                     dut_arc_version)
        if dut_arc_version and dut_arc_version in value:
            # Update the cheets version label in case the DUT label and
            # installed cheets version aren't matching.
            if host_android_build != value:
                info.set_version_label(cheets_prefix, value)
                host.host_info_store.commit(info)
            # If the installed cheets version is same as the test version, emitting
            # an INFO line.
            self.job.record('INFO', None, None, 'Host already running %s.' % value)
            return
        else:
            logging.info('Updating ARC++ build from %s to %s.',
                         host_android_build,
                         value)
            self.remove_rootfs(host)
            logging.info('Setting up devserver.')
            ds = dev_server.AndroidBuildServer.resolve(value)
            self.__build_temp_dir = tempfile.mkdtemp()
            self.download_android_build(value, ds)
            self.download_push_to_device(value, ds)
            self.download_sepolicy(value, ds)
            self.run_push_to_device(host)
            info = host.host_info_store.get()
            logging.info('Updating DUT version label: %s:%s', cheets_prefix, value)
            info.clear_version_labels(cheets_prefix)
            info.set_version_label(cheets_prefix, value)
            host.host_info_store.commit(info)


    def cleanup(self):
        if self.android_build_path and os.path.exists(self.android_build_path):
            try:
                logging.info(
                        'Deleting Android build dir at %s',
                        self.__build_temp_dir)
                shutil.rmtree(self.__build_temp_dir)
            except OSError as e:
                raise error.TestFail('%s' % e)