#!/usr/bin/python # Copyright 2015 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 httplib import logging import os import sys import urllib2 import common try: # Ensure the chromite site-package is installed. from chromite.lib import * except ImportError: import subprocess build_externals_path = os.path.join( os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'utils', 'build_externals.py') subprocess.check_call([build_externals_path, 'chromiterepo']) # Restart the script so python now finds the autotest site-packages. sys.exit(os.execv(__file__, sys.argv)) from autotest_lib.server.hosts import moblab_host from autotest_lib.site_utils import brillo_common _DEFAULT_STAGE_PATH_TEMPLATE = 'aue2e/%(use)s' _DEVSERVER_STAGE_URL_TEMPLATE = ('http://%(moblab)s:%(port)s/stage?' 'local_path=%(stage_dir)s&' 'files=%(stage_files)s') _DEVSERVER_PAYLOAD_URI_TEMPLATE = ('http://%(moblab)s:%(port)s/static/' '%(stage_path)s') _STAGED_PAYLOAD_FILENAME = 'update.gz' _SPEC_GEN_LABEL = 'gen' _TEST_JOB_NAME = 'brillo_update_test' _TEST_NAME = 'autoupdate_EndToEndTest' _DEFAULT_DEVSERVER_PORT = '8080' # Snippet of code that runs on the Moblab and returns the type of a payload # file. Result is either 'delta' or 'full', acordingly. _GET_PAYLOAD_TYPE = """ import update_payload p = update_payload(open('%(payload_file)s')) p.Init() print 'delta' if p.IsDelta() else 'full' """ class PayloadStagingError(brillo_common.BrilloTestError): """A failure that occurred while staging an update payload.""" class PayloadGenerationError(brillo_common.BrilloTestError): """A failure that occurred while generating an update payload.""" def setup_parser(parser): """Add parser options. @param parser: argparse.ArgumentParser of the script. """ parser.add_argument('-t', '--target_payload', metavar='SPEC', required=True, help='Stage a target payload. This can either be a ' 'path to a local payload file, or take the form ' '"%s:DST_IMAGE[:SRC_IMAGE]", in which case a ' 'new payload will get generated from SRC_IMAGE ' '(if given) and DST_IMAGE and staged on the ' 'server. This is a mandatory input.' % _SPEC_GEN_LABEL) parser.add_argument('-s', '--source_payload', metavar='SPEC', help='Stage a source payload. This is an optional ' 'input. See --target_payload for possible values ' 'for SPEC.') brillo_common.setup_test_action_parser(parser) def get_stage_rel_path(stage_file): """Returns the relative stage path for remote file. The relative stage path consists of the last three path components: the file name and the two directory levels that contain it. @param stage_file: Path to the file that is being staged. @return A stage relative path. """ components = [] for i in range(3): stage_file, component = os.path.split(stage_file) components.insert(0, component) return os.path.join(*components) def stage_remote_payload(moblab, devserver_port, tmp_stage_file): """Stages a remote payload on the Moblab's devserver. @param moblab: MoblabHost representing the MobLab being used for testing. @param devserver_port: Externally accessible port to the Moblab devserver. @param tmp_stage_file: Path to the remote payload file to stage. @return URI to use for downloading the staged payload. @raise PayloadStagingError: If we failed to stage the payload. """ # Remove the artifact if previously staged. stage_rel_path = get_stage_rel_path(tmp_stage_file) target_stage_file = os.path.join(moblab_host.MOBLAB_IMAGE_STORAGE, stage_rel_path) moblab.run('rm -f %s && chown moblab:moblab %s' % (target_stage_file, tmp_stage_file)) tmp_stage_dir, stage_file = os.path.split(tmp_stage_file) devserver_host = moblab.web_address.split(':')[0] try: stage_url = _DEVSERVER_STAGE_URL_TEMPLATE % { 'moblab': devserver_host, 'port': devserver_port, 'stage_dir': tmp_stage_dir, 'stage_files': stage_file} res = urllib2.urlopen(stage_url).read() except (urllib2.HTTPError, httplib.HTTPException, urllib2.URLError) as e: raise PayloadStagingError('Unable to stage payload on moblab: %s' % e) else: if res != 'Success': raise PayloadStagingError('Staging failed: %s' % res) logging.debug('Payload is staged on Moblab as %s', stage_rel_path) return _DEVSERVER_PAYLOAD_URI_TEMPLATE % { 'moblab': devserver_host, 'port': _DEFAULT_DEVSERVER_PORT, 'stage_path': os.path.dirname(stage_rel_path)} def stage_local_payload(moblab, devserver_port, tmp_stage_dir, payload): """Stages a local payload on the MobLab's devserver. @param moblab: MoblabHost representing the MobLab being used for testing. @param devserver_port: Externally accessible port to the Moblab devserver. @param tmp_stage_dir: Path of temporary staging directory on the Moblab. @param payload: Path to the local payload file to stage. @return Tuple consisting a payload download URI and the payload type ('delta' or 'full'). @raise PayloadStagingError: If we failed to stage the payload. """ if not os.path.isfile(payload): raise PayloadStagingError('Payload file %s does not exist.' % payload) # Copy the payload file over to the temporary stage directory. tmp_stage_file = os.path.join(tmp_stage_dir, _STAGED_PAYLOAD_FILENAME) moblab.send_file(payload, tmp_stage_file) # Find the payload type. get_payload_type = _GET_PAYLOAD_TYPE % {'payload_file': tmp_stage_file} payload_type = moblab.run('python', stdin=get_payload_type).stdout.strip() # Stage the copied payload. payload_uri = stage_remote_payload(moblab, devserver_port, tmp_stage_file) return payload_uri, payload_type def generate_payload(moblab, devserver_port, tmp_stage_dir, payload_spec): """Generates and stages a payload from local image(s). @param moblab: MoblabHost representing the MobLab being used for testing. @param devserver_port: Externally accessible port to the Moblab devserver. @param tmp_stage_dir: Path of temporary staging directory on the Moblab. @param payload_spec: A string of the form "DST_IMAGE[:SRC_IMAGE]", where DST_IMAGE is a target image and SRC_IMAGE an optional source image. @return Tuple consisting a payload download URI and the payload type ('delta' or 'full'). @raise PayloadGenerationError: If we failed to generate the payload. @raise PayloadStagingError: If we failed to stage the payload. """ parts = payload_spec.split(':', 1) dst_image = parts[0] src_image = parts[1] if len(parts) == 2 else None if not os.path.isfile(dst_image): raise PayloadGenerationError('Target image file %s does not exist.' % dst_image) if src_image and not os.path.isfile(src_image): raise PayloadGenerationError('Source image file %s does not exist.' % src_image) tmp_images_dir = moblab.make_tmp_dir() try: # Copy the images to a temporary location. remote_dst_image = os.path.join(tmp_images_dir, os.path.basename(dst_image)) moblab.send_file(dst_image, remote_dst_image) remote_src_image = None if src_image: remote_src_image = os.path.join(tmp_images_dir, os.path.basename(src_image)) moblab.send_file(src_image, remote_src_image) # Generate the payload into a temporary staging directory. tmp_stage_file = os.path.join(tmp_stage_dir, _STAGED_PAYLOAD_FILENAME) gen_cmd = ['brillo_update_payload', 'generate', '--payload', tmp_stage_file, '--target_image', remote_dst_image] if remote_src_image: payload_type = 'delta' gen_cmd += ['--source_image', remote_src_image] else: payload_type = 'full' moblab.run(' '.join(gen_cmd), stdout_tee=None, stderr_tee=None) finally: moblab.run('rm -rf %s' % tmp_images_dir) # Stage the generated payload. payload_uri = stage_remote_payload(moblab, devserver_port, tmp_stage_file) return payload_uri, payload_type def stage_payload(moblab, devserver_port, tmp_dir, use, payload_spec): """Stages the payload based on a given specification. @param moblab: MoblabHost representing the MobLab being used for testing. @param devserver_port: Externally accessible port to the Moblab devserver. @param tmp_dir: Path of temporary static subdirectory. @param use: String defining the use for the payload, either 'source' or 'target'. @param payload_spec: Either a string of the form "PAYLOAD:DST_IMAGE[:SRC_IMAGE]" describing how to generate a new payload from a target and (optionally) source image; or path to a local payload file. @return Tuple consisting a payload download URI and the payload type ('delta' or 'full'). @raise PayloadGenerationError: If we failed to generate the payload. @raise PayloadStagingError: If we failed to stage the payload. """ tmp_stage_dir = os.path.join( tmp_dir, _DEFAULT_STAGE_PATH_TEMPLATE % {'use': use}) moblab.run('mkdir -p %s && chown -R moblab:moblab %s' % (tmp_stage_dir, tmp_stage_dir)) spec_gen_prefix = _SPEC_GEN_LABEL + ':' if payload_spec.startswith(spec_gen_prefix): return generate_payload(moblab, devserver_port, tmp_stage_dir, payload_spec[len(spec_gen_prefix):]) else: return stage_local_payload(moblab, devserver_port, tmp_stage_dir, payload_spec) def main(args): """The main function.""" args = brillo_common.parse_args( 'Set up Moblab for running Brillo AU end-to-end test, then launch ' 'the test (unless otherwise requested).', setup_parser=setup_parser) moblab, devserver_port = brillo_common.get_moblab_and_devserver_port( args.moblab_host) tmp_dir = moblab.make_tmp_dir(base=moblab_host.MOBLAB_IMAGE_STORAGE) moblab.run('chown -R moblab:moblab %s' % tmp_dir) test_args = {'name': _TEST_JOB_NAME} try: if args.source_payload: payload_uri, _ = stage_payload(moblab, devserver_port, tmp_dir, 'source', args.source_payload) test_args['source_payload_uri'] = payload_uri logging.info('Source payload was staged') payload_uri, payload_type = stage_payload( moblab, devserver_port, tmp_dir, 'target', args.target_payload) test_args['target_payload_uri'] = payload_uri test_args['update_type'] = payload_type logging.info('Target payload was staged') finally: moblab.run('rm -rf %s' % tmp_dir) brillo_common.do_test_action(args, moblab, _TEST_NAME, test_args) if __name__ == '__main__': try: main(sys.argv) sys.exit(0) except brillo_common.BrilloTestError as e: logging.error('Error: %s', e) sys.exit(1)