295 lines
12 KiB
Python
Executable file
295 lines
12 KiB
Python
Executable file
#!/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)
|