blob: a80f07a4867bbee16f3843d124249d2288180a35 [file] [log] [blame]
# Copyright 2021 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import base64
import json
import logging
import os
import random
import re
import requests
import stat
import string
import tempfile
import zipfile
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import utils as common_utils
from autotest_lib.client.common_lib.cros import dev_server
from autotest_lib.server.cros import filesystem_util
from distutils import version
# Shell command to force unmount a mount point if it is mounted
FORCED_UMOUNT_DIR_IF_MOUNTPOINT_CMD = (
'if mountpoint -q %(dir)s; then umount -l %(dir)s; fi')
# Shell command to set exec and suid flags
SET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s'
# Shell command to send SIGHUP to dbus daemon
DBUS_RELOAD_COMMAND = 'killall -HUP dbus-daemon'
# Shell command to restore SELinux context of sideloaded files
RESTORECON_COMMAND = 'restorecon -R %s'
# Lacros artifact path mask
_LACROS_PATH_MASK = 'gs://chrome-unsigned/desktop-5c0tCh/{version}/{variant}/lacros.zip'
# Architecture to Lacros variant dictionary
_ARCH_LACROS_VARIANT_DICT = {
'arm': 'lacros-arm32',
'arm64': 'lacros-arm64',
'i386': 'lacros64',
'x86_64': 'lacros64'
}
# Architecture to Lacros platform dictionary
_ARCH_LACROS_PLATFORM_DICT = {
'arm': 'lacros_arm32',
'arm64': 'lacros_arm64',
'i386': 'lacros',
'x86_64': 'lacros'
}
def _gen_run_env_dict(**kwargs):
"""Helper to generate the config for command execution on drone."""
res = {
'stdout_tee':
utils.TEE_TO_LOGS,
'stderr_tee':
utils.TEE_TO_LOGS,
'stdout_level':
logging.INFO,
'stderr_level':
logging.DEBUG,
'extra_paths': [
#TODO(b/307657497): Path for cipd/vpython3 on LXC
# container. Remove it once CFT migration is done.
'/opt/infra-tools',
# The default path for cipd/vpython3 on CFT container.
'/opt/browser-tools',
],
}
res.update(**kwargs)
return res
def extract_from_image(host, image_name, dest_dir):
"""
Extracts contents of an image to a directory.
@param host: The DUT to execute the command on
@param image_name: Name of image
@param dest_dir: directory where contents of image will be placed.
"""
if not host.path_exists('/var/lib/imageloader/%s' % image_name):
raise Exception('Image %s not found on host %s' % (image_name, host))
image_mount_point = '/tmp/image_%s' % _gen_random_str(8)
# Create directories from scratch
host.run(['rm', '-rf', dest_dir])
host.run(['mkdir', '-p', '--mode', '0755', dest_dir, image_mount_point])
try:
# Mount image and copy content to the destination directory
host.run([
'imageloader', '--mount',
'--mount_component=%s' % image_name,
'--mount_point=%s' % image_mount_point
])
host.run(['cp', '-r', '%s/*' % image_mount_point, '%s/' % dest_dir])
except Exception as e:
raise Exception(
'Error extracting content from image %s on host %s ' %
(image_name, host), e)
finally:
# Unmount image and remove the temporary directory
host.run([
'imageloader', '--unmount',
'--mount_point=%s' % image_mount_point
])
host.run(['rm', '-rf', image_mount_point])
def _gen_random_str(length):
"""
Generate random string
@param length: Length of the string
@return random string of specified length
"""
return ''.join(
[random.choice(string.hexdigits) for _ in range(length)])
def _stop_chrome_if_necessary(host):
"""
Stops chrome if it is running.
@param host: The DUT to execute the command on
@return True if chrome was stopped. False otherwise.
"""
status = host.run_output('status ui')
if 'start' in status:
return host.run('stop ui', ignore_status=True).exit_status == 0
return False
def _mount_lacros(host, chrome_dir, lacros_mount_point):
"""
Mounts lacros chrome to a mount point
A mutation of _mount_chrome, but lacros does not require
mount command. We only move it to the specified path.
@param host: The DUT to execute the command on
@param chrome_dir: directory where the lacros binary and artifacts
was provisioned.
@param chrome_mount_point: The path that lacros is expected to exist.
"""
host.run(['rm', '-rf', lacros_mount_point])
host.run(['mkdir', '-p', '--mode', '0755', lacros_mount_point])
host.run(['mv', '%s/*' % chrome_dir, '%s/' % lacros_mount_point])
def _log_chrome_version(host):
"""
Log the chrome version.
@param host: The DUT to execute the command on
"""
host.run(['/opt/google/chrome/chrome', '--version'])
def _mount_chrome(host, chrome_dir, chrome_mount_point):
"""
Mounts chrome to a mount point
@param host: The DUT to execute the command on
@param chrome_dir: directory where the chrome binary and artifacts
will be placed.
@param chrome_mount_point: Chrome mount point
"""
logging.debug('Before mounting chrome on host: %s', host)
_log_chrome_version(host)
chrome_stopped = _stop_chrome_if_necessary(host)
_umount_chrome(host, chrome_mount_point)
# Mount chrome to the desired chrome directory
# Upon restart, this version of chrome will be used instead.
host.run(['mount', '--rbind', chrome_dir, chrome_mount_point])
# Chrome needs partition to have exec and suid flags set
host.run(SET_MOUNT_FLAGS_CMD % chrome_mount_point)
# Restore SELinux context of sideloaded files.
host.run(RESTORECON_COMMAND % chrome_mount_point)
# Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't
# pick up major changes (bus type, logging, etc.), but all we care about is
# getting the latest policy from /opt/google/chrome/dbus so that Chrome will
# be authorized to take ownership of its service names.
host.run(DBUS_RELOAD_COMMAND, ignore_status=True)
if chrome_stopped:
host.run('start ui', ignore_status=True)
logging.debug('After mounting chrome on host: %s', host)
_log_chrome_version(host)
def _umount_lacros(host, lacros_mount_point):
"""
Unmounts lacros
Because lacros does not require "mount", so we just remove its
path. See _mount_lacros.
@param host: The DUT to execute the command on
@param lacros_mount_point: See _mount_lacros.
"""
host.run(['rm', '-rf', lacros_mount_point])
def _umount_chrome(host, chrome_mount_point):
"""
Unmounts chrome
@param host: The DUT to execute the command on
@param chrome_mount_point: Chrome mount point
"""
logging.debug('Before unmounting chrome on host: %s', host)
_log_chrome_version(host)
chrome_stopped = _stop_chrome_if_necessary(host)
# Unmount chrome. Upon restart, the default version of chrome
# under the root partition will be used.
try:
host.run(FORCED_UMOUNT_DIR_IF_MOUNTPOINT_CMD %
{'dir': chrome_mount_point})
except Exception as e:
raise Exception('Exception during cleanup on host %s' % host, e)
if chrome_stopped:
host.run('start ui', ignore_status=True)
logging.info('After unmounting chrome on host: %s', host)
_log_chrome_version(host)
def setup_host(host, chrome_dir, chrome_mount_point, is_cros_chrome=True):
"""
Performs setup on host.
Mounts chrome to point to the version provisioned by TLS.
The provisioning mechanism of chrome from the chrome builder is
based on Lacros Tast Test on Skylab (go/lacros-tast-on-skylab).
The lacros image provisioned by TLS contains the chrome binary
and artifacts.
@param host: The DUT to execute the command on
@param chrome_dir: directory where the chrome binary and artifacts
will be placed.
@param chrome_mount_point: Chrome mount point
@is_cros_chrome: Mount cros chrome or lacros. True by default.
"""
logging.info('Setting up host:%s', host)
try:
extract_from_image(host, 'lacros', chrome_dir)
if chrome_mount_point:
_mount = _mount_chrome if is_cros_chrome else _mount_lacros
_mount(host, '%s/out/Release' % chrome_dir, chrome_mount_point)
except Exception as e:
raise Exception(
'Exception while mounting %s on host %s' %
(chrome_mount_point, host), e)
def cleanup_host(host, chrome_dir, chrome_mount_point, is_cros_chrome=True):
"""
Umounts chrome and performs cleanup.
@param host: The DUT to execute the command on
@param chrome_dir: directory where the chrome binary and artifacts
is placed.
@param chrome_mount_point: Chrome mount point
@is_cros_chrome: Umount cros chrome or lacros. True by default.
"""
logging.info('Cleaning up host: %s', host)
try:
if chrome_mount_point:
_umount = _umount_chrome if is_cros_chrome else _umount_lacros
_umount(host, chrome_mount_point)
host.run(['rm', '-rf', chrome_dir])
except Exception as e:
raise Exception('Exception during cleanup on host %s' % host, e)
def get_tast_expr_from_file(host, args_dict, results_dir, base_path=None):
"""
Gets Tast expression from argument dictionary using a file.
If the tast_expr_file and tast_expr_key are in the dictionary returns the
tast expression from the file. If either/both args are not in the dict,
None is returned.
tast_expr_file expects a file containing a json dictionary which it will
then use tast_expr_key to pull the tast_expr.
The tast_expr_file is a json file containing a dictionary of names to tast
expressions like:
{
'default': '("group:mainline" && "dep:lacros" && !informational)',
'tast_disabled_tests_from_lacros_example': '("group:mainline" && "dep:lacros" && !informational && !"name:lacros.Basic")'
}
@param host: Host having the provisioned lacros image with the file
@param args_dict: Argument dictionary
@param results_dir: Where to store the tast_expr_file from the dut
@param base_path: Base path of the provisioned folder
"""
tast_expr_file_name = args_dict.get('tast_expr_file')
tast_expr_key = args_dict.get('tast_expr_key')
if tast_expr_file_name and tast_expr_key:
if base_path:
tast_expr_file_name = os.path.join(base_path, tast_expr_file_name)
# Get the tast expr file from the provisioned lacros folder
if not host.path_exists(tast_expr_file_name):
raise Exception(
'tast_expr_file: %s could not be found on the dut' %
tast_expr_file_name)
local_file_name = os.path.join(results_dir,
os.path.basename(tast_expr_file_name))
st = os.stat(results_dir)
os.chmod(results_dir, st.st_mode | stat.S_IWRITE)
host.get_file(tast_expr_file_name, local_file_name, delete_dest=True)
return _get_tast_expr_from_json_file(local_file_name, tast_expr_key)
elif tast_expr_file_name or tast_expr_file_name:
raise Exception('Missing tast_expr_file or tast_expr_key')
return None
def get_tast_expr_from_local_file(args_dict, base_path=None):
"""
Gets Tast expression from argument dictionary using a file.
If the tast_expr_file and tast_expr_key are in the dictionary returns the
tast expression from the file. If either/both args are not in the dict,
None is returned.
tast_expr_file expects a file containing a json dictionary which it will
then use tast_expr_key to pull the tast_expr.
The tast_expr_file is a json file containing a dictionary of names to tast
expressions like:
{
'default': '("group:mainline" && "dep:lacros" && !informational)',
'tast_disabled_tests_from_lacros_example': '("group:mainline" && "dep:lacros" && !informational && !"name:lacros.Basic")'
}
@param args_dict: Argument dictionary
@param results_dir: Where to store the tast_expr_file from the dut
@param base_path: Base path of the provisioned folder
"""
tast_expr_file_name = args_dict.get('tast_expr_file')
tast_expr_key = args_dict.get('tast_expr_key')
if tast_expr_file_name and tast_expr_key:
if base_path:
tast_expr_file_name = os.path.join(base_path, tast_expr_file_name)
return _get_tast_expr_from_json_file(tast_expr_file_name,
tast_expr_key)
elif tast_expr_file_name or tast_expr_file_name:
raise Exception('Missing tast_expr_file or tast_expr_key')
return None
def _get_tast_expr_from_json_file(tast_expr_file_name, tast_expr_key):
"""
Gets Tast expression from argument dictionary using a file.
@param tast_expr_file_name: A json file containing a dictionary of names to tast
expressions.
@param tast_expr_key: key to pull the tast expression.
@return: tast expression.
"""
with open(tast_expr_file_name) as tast_expr_file:
expr_dict = json.load(tast_expr_file)
expr = expr_dict.get(tast_expr_key)
# If both args were provided, the entry is expected in the file
if not expr:
raise Exception('tast_expr_key: %s could not be found' %
tast_expr_key)
logging.info('tast_expr retrieved from:%s', tast_expr_file)
return expr
def get_test_args(args_dict, expr_key):
"""Extract an arg or decode its b64 hash from args_dict."""
expr = args_dict.get(expr_key)
if expr:
return expr
expr_b64 = args_dict.get('{}_b64'.format(expr_key))
if expr_b64:
return base64.b64decode(expr_b64).decode()
return None
def get_tast_expr(args_dict):
"""
Gets Tast expression from argument dictionary.
Users have options of using tast_expr or tast_expr_b64 in dictionary.
tast_expr_b64 expects a base64 encoded tast_expr, for instance:
tast_expr = '("group:mainline" && "dep:lacros")'
tast_expr_b64 = base64.b64encode(s.encode('utf-8')).decode('ascii')
@param args_dict: Argument dictionary
"""
exception_msg = """
Tast expression is unspecified: set tast_expr or tast_expr_b64 in --args.
Example: test_that --args="tast_expr=lacros.Basic"
If the expression contains spaces, consider transforming it to
base64 and passing it via tast_expr_b64 flag.
Example:
In Python:
tast_expr = '("group:mainline" && "dep:lacros")'
# Yields "KCJncm91cDptYWlubGluZSIgJiYgImRlcDpsYWNyb3MiKQ=="
tast_expr_b64 = base64.b64encode(s.encode("utf-8")).decode("ascii")
Then in Autotest CLI:
test_that --args="tast_expr_b64=KCJncm91cDptYWlubGluZSIgJiYgImRlcDpsYWNyb3MiKQ=="
More details at go/lacros-on-skylab.
"""
res = get_test_args(args_dict, 'tast_expr')
assert res is not None, exception_msg
return res
def _lookup_lacros_variant(arch):
"""
Looks up the Lacros variant for the hardware architecture.
@param arch: Hardware architecture of the machine
@return: Lacros variant. e.g. lacros-arm32, lacros-arm64, lacros64
"""
if arch not in _ARCH_LACROS_VARIANT_DICT:
raise Exception(
'Failed to find Lacros variant due to unknown architecture: %s' % arch)
return _ARCH_LACROS_VARIANT_DICT[arch]
def _lookup_lacros_platform(arch):
"""
Looks up the Lacros platform for the hardware architecture.
@param arch: Hardware architecture of the machine
@return: Lacros platform. e.g. 'lacros_arm32', 'lacros_arm64', 'lacros'
"""
if arch not in _ARCH_LACROS_PLATFORM_DICT:
raise Exception(
'Failed to find Lacros platform due to unknown architecture: %s' % arch)
return _ARCH_LACROS_PLATFORM_DICT[arch]
def _lookup_lacros_path(host, channel):
"""
Looks up the Lacros artifact path.
@param host: The DUT to execute the command on
@param channel: The lacros channel. e.g. 'stable','beta','dev'
@return: Lacros variant. e.g. 'lacros-arm32', 'lacros64'
"""
arch = utils.get_arch_userspace(host.run)
variant = _lookup_lacros_variant(arch)
platform = _lookup_lacros_platform(arch)
logging.info('Host uses Lacros variant: %s platform: %s',
variant, platform)
version = _lookup_lacros_latest_version(channel, platform)
logging.info('Latest Lacros version for channel %s platform %s : %s',
channel, platform, version)
gs_path = _LACROS_PATH_MASK.format(version=version, variant=variant)
return gs_path, version, variant
def _lookup_lacros_latest_version(channel, platform):
"""
Looks up the latest Lacros version for a channel.
@param channel: Lacros channel. e.g. 'stable','beta','dev'
@param platform: Lacros platform. e.g. 'lacros_arm32', 'lacros_arm64', 'lacros'
@return: Latest Lacros version
"""
# Retrieve latest version of all channels
api_url = 'https://versionhistory.googleapis.com/v1/chrome/platforms/%s/channels/all/versions/all/releases?filter=endtime=none' % platform
try:
res = requests.get(api_url)
except requests.exceptions.RequestException as e:
raise Exception('Failed when call versionhistory api.') from e
logging.debug('Lacros version history lookup for platform %s: %s',
platform, res.text)
release_prefix = 'chrome/platforms/%s/channels/%s/' % (platform, channel)
json_object = json.loads(res.text)
versions = [r['version'] for r in json_object['releases']
if r['name'].startswith(release_prefix)]
if len(versions) < 1:
raise Exception(
'Failed to extract latest version for channel %s from json: %s' % (channel, res.text))
if len(versions) > 1:
logging.info(
"VersionHistory API returns more than 1 version: %s", versions)
# key function to turn version string into list of integers so that
# entries can be compared and sorted
# E.g. "104.0.5112.86" to [104,0,5112,86]
def key_func(version):
ret = list(map(int, version.split('.')))
return ret
sorted_versions = sorted(versions, key=key_func, reverse=True)
return sorted_versions[0]
def deploy_lacros(host,
channel=None,
gs_path=None,
lacros_dir='/usr/local/lacros-chrome',
args_dict={}):
"""
Deploys Lacros to DUT.
Users can either specify channel or gs_path.
@param channel: The Lacros channel. e.g. 'stable','beta','dev'
@param gs_path: The GCS path of the Lacros artifacts.
@param lacros_dir: The directory for Lacros artifacts.
@param args_dict: Additional argument dictionary.
"""
if not lacros_dir:
raise Exception('Failed to specify Lacros directory.')
logging.info('deploy_lacros to host: %s channel: %s gs_path: %s onto %s',
host, channel, gs_path, lacros_dir)
lacros_version = None
if gs_path:
pass
elif channel:
# lookup lacros artifact path based on channel
gs_path, lacros_version, _ = _lookup_lacros_path(host, channel)
else:
raise Exception(
'Please specify either channel or gs_path to locate Lacros artifacts.')
# Check lacros version skew
ash_version, _ = host.get_chrome_version()
if not is_lacros_version_skew_valid(lacros_version, ash_version):
raise Exception('Lacros version skew is invalid for Lacros:%s Ash:%s' %
(lacros_version, ash_version))
else:
logging.debug('Lacros version skew is valid for Lacros:%s Ash:%s',
lacros_version, ash_version)
# Create directories from scratch
tmp_dir = '/tmp/lacros_%s' % _gen_random_str(8)
host.run(['rm', '-rf', lacros_dir])
host.run(['mkdir', '-p', '--mode', '0755', lacros_dir, tmp_dir])
try:
# Download Lacros zip archive from Cache Server.
zip_path = download_gs_to_host(host, gs_path, tmp_dir,
args_dict.get('cache_endpoint'))
# unzip file to Lacros directory
host.run(['unzip', zip_path, '-d', lacros_dir])
# if user specifies Lacros artifacts through lacros_gcs_path, lacros_version
# is retrieved directly from metadata.json
if not lacros_version:
result = host.run(
['jq', '-r', "'.content.version'", os.path.join(lacros_dir, 'metadata.json')])
if result.exit_status != 0 or result.stderr:
raise Exception(
'Error getting Lacros version from metadata.json: %s' % result.stderr)
lacros_version = result.stdout.rstrip()
if not re.match(r'^\d*\.\d*\.\d*\.\d*$', lacros_version):
raise Exception(
'Incorrect Lacros version format: %s' % lacros_version)
except Exception as e:
raise Exception('Error extracting content from %s to %s' %
(gs_path, lacros_dir)) from e
finally:
host.run(['rm', '-rf', tmp_dir])
# Write ash_version and lacros_version into keyval to be included in RDB
keyvals = {}
keyvals['ash_version'] = ash_version
keyvals['lacros_version'] = lacros_version
logging.info('deploy_lacros successful. ash_version: %s lacros_version: %s',
ash_version, lacros_version)
utils.write_keyval(host.job.resultdir, keyvals)
def is_lacros_version_skew_valid(lacros_version_str, ash_version_str):
"""
Returns whether Lacros and Ash are within valid supported skews by comparing
versions based on the version skew policy.
Ash and Lacros version is in the format of "(major).(minor).(build).(patch)".
@param lacros_version_str: Lacros version number in string. e.g. 120.0.6051.2
@param ash_version_str: Ash version number in string. e.g. 120.0.6051.2
@return: True if version skew is valid. False otherwise.
"""
lacros_version = version.LooseVersion(lacros_version_str)
ash_version = version.LooseVersion(ash_version_str)
# Lacros should not be older, ignoring the patch level.
versions_to_compare = 3
if lacros_version.version[:
versions_to_compare] < ash_version.version[:
versions_to_compare]:
return False
# Lacros should be within 2 major version of Ash.
max_major_version_skew = 2
if int(lacros_version.version[0]) > int(
ash_version.version[0]) + max_major_version_skew:
return False
return True
def chromite_deploy_chrome(host, gs_path, archive_type, **kwargs):
"""
Deploy chrome onto DUT using chromite.
Chromite is expected to be packaged in the chrome archive.
@param host: The DUT to execute the command on
@param gs_path: The GCS file of the chrome archive.
@param archive_type: The type of archive. e.g. chrome, lacros
@return: Directory on drone server that contains the unarchived chrome contents
"""
if not gs_path:
raise Exception('gs_path is required')
chrome_dir = tempfile.mkdtemp()
# Download artifacts onto drone server and unarchive to a temp directory.
with tempfile.TemporaryDirectory() as tmp_archive_dir:
archive_file_path = download_gs(gs_path, tmp_archive_dir)
if os.path.basename(archive_file_path).endswith(".zip"):
with zipfile.ZipFile(archive_file_path, 'r') as zip_ref:
zip_ref.extractall(chrome_dir)
elif os.path.basename(archive_file_path).endswith(".squash"):
unsquashfs(archive_file_path, chrome_dir, **kwargs)
else:
raise Exception('Unsupported file extension: %s' %
archive_file_path)
# change file permissions to allow for script execution
cmd = ['chmod', '-R', '755', chrome_dir]
try:
common_utils.run(cmd, **_gen_run_env_dict())
except error.CmdError as e:
raise Exception('Error changing file permissions', e)
# Deploy chrome with chromite
logging.info('Before deploy_chrome')
_log_chrome_version(host)
# Changing current working directory to allow chromite to be properly located by wrapper scripts.
chromite_dir = os.path.join(chrome_dir, 'third_party/chromite')
if not os.path.isdir(chromite_dir):
raise Exception(
'chromite is not packaged in the lacros_gcs_path archive')
kill_proc_timeout = 180
deploy_chrome_timeout = 600
deploy_chrome_bin = os.path.join(chromite_dir, 'bin', 'deploy_chrome')
vpython_spec = os.path.join(chrome_dir, '.vpython3')
cmd = [
'vpython3',
'-vpython-spec',
vpython_spec,
deploy_chrome_bin,
]
# In CFT SSH session is built on a SSH proxy by the host.
# Chromite may not detect the reboot and later hit timeout
# error. As a workaround remove the rootfs verification
# and reboot prior to the chrome deployment.
if kwargs.get('is_cft'):
filesystem_util.make_rootfs_writable(host)
cmd = [
'python3',
deploy_chrome_bin,
'--noremove-rootfs-verification',
]
if archive_type == 'chrome':
board = _remove_prefix(host.get_board(), 'board:')
_deploy_with_retry(
cmd + [
'--force',
'--build-dir',
os.path.join(chrome_dir, 'out/Release/'),
'--process-timeout',
str(kill_proc_timeout),
'--device',
host.host_port,
'--board',
board,
'--mount',
'--nostrip',
], deploy_chrome_timeout,
'Error occurred executing chromite.deploy_chrome for Chrome')
# During the transition phase, since not all the builders have Lacros packaged,
# if the archive also contains lacros_clang, lacros will be deployed. If not,
# a warning will be given.
lacros_dir = os.path.join(chrome_dir, 'out/Release/lacros_clang/')
if os.path.exists(lacros_dir):
_deploy_with_retry(
cmd + [
'--force',
'--build-dir',
lacros_dir,
'--process-timeout',
str(kill_proc_timeout),
'--device',
host.host_port,
'--lacros',
'--nostrip',
'--skip-modifying-config-file',
], deploy_chrome_timeout,
'Error occurred executing chromite.deploy_chrome for Lacros'
)
elif archive_type == 'lacros':
_deploy_with_retry(
cmd + [
'--build-dir',
os.path.join(chrome_dir, 'out/Release/'),
'--process-timeout',
str(kill_proc_timeout),
'--device',
host.host_port,
'--lacros',
'--nostrip',
'--force',
'--skip-modifying-config-file',
], deploy_chrome_timeout,
'Error occurred executing chromite.deploy_chrome for Lacros')
else:
raise Exception('Unknown archive_type:%s' % archive_type)
logging.info('After deploy_chrome')
_log_chrome_version(host)
return chrome_dir
def _remove_prefix(text, prefix):
return text[text.startswith(prefix) and len(prefix):]
def download_gs_to_host(host, gs_path, dest_dir, cache_endpoint):
"""
Download GCS file to host.
@param host: The DUT to execute the command on
@param gs_path: The GCS file path.
@param dest_dir: The directory where the file is copied to.
@param cache_endpoint: Cache server endpoint.
@return: The path of the downloaded file on host
"""
archive_url, bucket, image, file_name = _parse_info_from_gs_path(gs_path)
download_url = _stage_artifacts_on_dev_server(host.hostname,
cache_endpoint, archive_url,
bucket, image, file_name)
try:
# Download gs file from Cache Server.
file_path = os.path.join(dest_dir, file_name)
host.run(['curl', download_url, '--output', file_path])
except Exception as e:
raise Exception('Error downloading cache server content %s to %s' %
(download_url, dest_dir)) from e
return file_path
def download_gs(gs_path, dest_dir):
"""
Download GCS file to drone server.
@param gs_path: The GCS file path.
@param dest_dir: The directory where the file is copied to.
@return: The path of the downloaded file
"""
_, _, _, file_name = _parse_info_from_gs_path(gs_path)
try:
# Download gs file from Cache Server.
cmd = [
'BOTO_CONFIG=', 'gsutil', '-o',
'Credentials:gs_service_key_file=/creds/service_accounts/skylab-drone.json',
'cp', gs_path, dest_dir
]
file_path = os.path.join(dest_dir, file_name)
common_utils.run(cmd, timeout=1200, **_gen_run_env_dict())
except Exception as e:
raise Exception('Error downloading with gsutil from %s to %s' %
(gs_path, dest_dir)) from e
return file_path
def _stage_artifacts_on_dev_server(hostname, cache_endpoint, archive_url,
bucket, image, file_name):
# Stage artifact onto Cache Server
logging.info('cache_endpoint: %s', cache_endpoint)
if cache_endpoint:
# CFT handles the Cache Server lookup outside of Tauto.
ds = dev_server.ImageServer('http://%s' % cache_endpoint)
else:
# For Non-CFT, Tauto handles Cache Server lookup.
ds = dev_server.ImageServer.resolve(image, hostname)
try:
ds.stage_artifacts(image=image,
archive_url=archive_url,
files=[file_name])
download_url = ds.get_staged_file_url(
file_name, image) + '?gs_bucket={bucket}'.format(bucket=bucket)
except Exception as e:
raise Exception('Failed to stage image on Cache Server', e)
return download_url
def _parse_info_from_gs_path(gs_path):
# expects gs path format to be gs://{bucket}/{path}/{zipfile}
matches = re.match('(gs://(.*?)/(.*))/(.*)', gs_path)
if len(matches.groups()) != 4:
raise Exception('Failed to extract required parts from gs_path: %s' %
gs_path)
archive_url, bucket, image, file_name = matches.groups()
return archive_url, bucket, image, file_name
def _deploy_with_retry(cmd, timeout, err_msg, retries=2):
"""Helper to retry deploy chrome via chromite."""
cmd.extend(['--log-level', 'debug'])
attempt = 0
while attempt < retries:
try:
return common_utils.run(cmd,
timeout=timeout,
**_gen_run_env_dict())
except error.CmdError as e:
logging.info(err_msg)
attempt += 1
if attempt < retries:
continue
raise e
def unsquashfs(file_path, dest_dir, **kwargs):
"""
Unarchive squashfs file into a directory.
@param file_path: The path for squashfs archive.
@param dest_dir: The directory where the file is copied to.
@return a CmdResult object or None if the command timed out and
ignore_timeout is True. See common_lib.utils.run().
"""
def _run(cmd, err_msg, **kwargs):
try:
return common_utils.run(cmd, **_gen_run_env_dict(**kwargs))
except error.CmdError as e:
raise Exception(err_msg, ex)
if kwargs.get('is_cft'):
return _run(['unsquashfs', '-f', '-d', dest_dir, file_path],
f'Error running unsquashfs on {file_path}')
# Download artifacts onto drone server and unzip to a temp directory.
with tempfile.TemporaryDirectory() as tmp_squashfs_dir:
# create ensure-file for squashfs
ensure_file_path = os.path.join(tmp_squashfs_dir, 'ensure_file.txt')
with open(ensure_file_path, 'w') as f:
f.write('infra/3pp/tools/squashfs/linux-amd64 latest\n')
f.write('infra/3pp/static_libs/libzstd/linux-amd64 latest')
# download squashfs from cipd
cmd = [
'cipd', 'ensure', '-ensure-file', ensure_file_path, '-root',
tmp_squashfs_dir
]
_run(cmd, 'Error downloading squashfs from CIPD')
# unsquashfs archive into destination directory
return _run([
os.path.join(tmp_squashfs_dir, 'squashfs-tools', 'unsquashfs'),
'-f',
'-d',
dest_dir,
file_path,
],
f'Error running unsquashfs on {file_path}',
env={
'LD_LIBRARY_PATH':
os.path.join(tmp_squashfs_dir, 'lib'),
})