162 lines
6.7 KiB
Python
162 lines
6.7 KiB
Python
# Copyright (c) 2013 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 re
|
|
import sys
|
|
import time
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib import global_config
|
|
from autotest_lib.client.common_lib.cros import dev_server
|
|
from autotest_lib.server import afe_utils
|
|
from autotest_lib.server import test
|
|
from autotest_lib.server import utils
|
|
from autotest_lib.server.cros import provision
|
|
from autotest_lib.server.cros import provisioner
|
|
|
|
try:
|
|
from chromite.lib import metrics
|
|
except ImportError:
|
|
metrics = utils.metrics_mock
|
|
|
|
_CONFIG = global_config.global_config
|
|
# pylint: disable-msg=E1120
|
|
_IMAGE_URL_PATTERN = _CONFIG.get_config_value('CROS',
|
|
'image_url_pattern',
|
|
type=str)
|
|
|
|
|
|
def _metric_name(base_name):
|
|
return 'chromeos/autotest/provision/' + base_name
|
|
|
|
|
|
def _get_build_metrics_fields(build_name):
|
|
try:
|
|
return utils.ParseBuildName(build_name)[0:2]
|
|
except utils.ParseBuildNameException:
|
|
logging.warning(
|
|
'Unable to parse build name %s for metrics. '
|
|
'Continuing anyway.', build_name)
|
|
return ('', '')
|
|
|
|
|
|
def _emit_updater_metrics(name_prefix, build_name, failure_reason, duration,
|
|
fields):
|
|
# reset_after=True is required for String gauges events to ensure that
|
|
# the metrics are not repeatedly emitted until the server restarts.
|
|
metrics.String(_metric_name(name_prefix + '_build_by_devserver_dut'),
|
|
reset_after=True).set(build_name, fields=fields)
|
|
if failure_reason:
|
|
metrics.String(_metric_name(name_prefix +
|
|
'_failure_reason_by_devserver_dut'),
|
|
reset_after=True).set(failure_reason, fields=fields)
|
|
metrics.SecondsDistribution(
|
|
_metric_name(name_prefix + '_duration_by_devserver_dut')).add(
|
|
duration, fields=fields)
|
|
|
|
|
|
def _emit_provision_metrics(update_url, dut_host_name, exception, duration):
|
|
# The following is high cardinality, but sparse.
|
|
# Each DUT is of a single board type, and likely build type.
|
|
#
|
|
# TODO(jrbarnette) The devserver-triggered provisioning code
|
|
# includes retries in certain cases. For that reason, the metrics
|
|
# distinguish 'provision' metrics which summarizes across all
|
|
# retries, and 'auto_update' which summarizes an individual update
|
|
# attempt. ChromiumOSProvisioner doesn't do retries, so we just report
|
|
# the same information twice. We should replace the metrics with
|
|
# something better tailored to the current implementation.
|
|
build_name = provisioner.url_to_image_name(update_url)
|
|
board, build_type = _get_build_metrics_fields(build_name)
|
|
fields = {
|
|
'board': board,
|
|
'build_type': build_type,
|
|
'dut_host_name': dut_host_name,
|
|
'dev_server': dev_server.get_resolved_hostname(update_url),
|
|
'success': not exception,
|
|
}
|
|
failure_reason = provisioner.get_update_failure_reason(exception)
|
|
_emit_updater_metrics('provision', build_name, failure_reason, duration,
|
|
fields)
|
|
fields['attempt'] = 1
|
|
_emit_updater_metrics('auto_update', build_name, failure_reason, duration,
|
|
fields)
|
|
|
|
|
|
class provision_QuickProvision(test.test):
|
|
"""A test that can provision a machine to the correct ChromeOS version."""
|
|
version = 1
|
|
|
|
def initialize(self, host, value, is_test_na=False):
|
|
"""Initialize.
|
|
|
|
@param host: The host object to update to |value|.
|
|
@param value: The build type and version to install on the host.
|
|
@param is_test_na: boolean, if True, will simply skip the test
|
|
and emit TestNAError. The control file
|
|
determines whether the test should be skipped
|
|
and passes the decision via this argument. Note
|
|
we can't raise TestNAError in control file as it won't
|
|
be caught and handled properly.
|
|
"""
|
|
if is_test_na:
|
|
raise error.TestNAError(
|
|
'Test not available for test_that. chroot detected, '
|
|
'you are probably using test_that.')
|
|
# We check value in initialize so that it fails faster.
|
|
if not value:
|
|
raise error.TestFail('No build version specified.')
|
|
|
|
def run_once(self, host, value):
|
|
"""The method called by the control file to start the test.
|
|
|
|
@param host: The host object to update to |value|.
|
|
@param value: The host object to provision with a build corresponding
|
|
to |value|.
|
|
"""
|
|
with_cheets = False
|
|
logging.debug('Start provisioning %s to %s.', host, value)
|
|
if value.endswith(provision.CHEETS_SUFFIX):
|
|
image = re.sub(provision.CHEETS_SUFFIX + '$', '', value)
|
|
with_cheets = True
|
|
else:
|
|
image = value
|
|
|
|
# If the host is already on the correct build, we have nothing to do.
|
|
# Note that this means we're not doing any sort of stateful-only
|
|
# update, and that we're relying more on cleanup to do cleanup.
|
|
info = host.host_info_store.get()
|
|
if info.build == value:
|
|
# We can't raise a TestNA, as would make sense, as that makes
|
|
# job.run_test return False as if the job failed. However, it'd
|
|
# still be nice to get this into the status.log, so we manually
|
|
# emit an INFO line instead.
|
|
self.job.record('INFO', None, None,
|
|
'Host already running %s' % value)
|
|
return
|
|
|
|
try:
|
|
ds = dev_server.ImageServer.resolve(image, host.hostname)
|
|
except dev_server.DevServerException as e:
|
|
raise error.TestFail, str(e), sys.exc_info()[2]
|
|
|
|
url = _IMAGE_URL_PATTERN % (ds.url(), image)
|
|
|
|
logging.debug('Installing image from URL: %s', url)
|
|
start_time = time.time()
|
|
failure = None
|
|
try:
|
|
afe_utils.machine_install_and_update_labels(host,
|
|
url,
|
|
with_cheets,
|
|
staging_server=ds)
|
|
except BaseException as e:
|
|
failure = e
|
|
raise
|
|
finally:
|
|
_emit_provision_metrics(url, host.hostname, failure,
|
|
time.time() - start_time)
|
|
logging.debug('Finished provisioning %s to %s', host, value)
|