199 lines
7.7 KiB
Python
199 lines
7.7 KiB
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 logging
|
|
import multiprocessing
|
|
import sys
|
|
|
|
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.cros import provisioner
|
|
from autotest_lib.server.cros.dynamic_suite import constants
|
|
|
|
#Update status
|
|
UPDATE_SUCCESS = 0
|
|
UPDATE_FAILURE = 1
|
|
|
|
def update_dut_worker(updater_obj, dut, image, force):
|
|
"""The method called by multiprocessing worker pool for updating DUT.
|
|
This function is the function which is repeatedly scheduled for each
|
|
DUT through the multiprocessing worker. This has to be defined outside
|
|
the class because it needs to be pickleable.
|
|
|
|
@param updater_obj: An CliqueDUTUpdater object.
|
|
@param dut: DUTObject representing the DUT.
|
|
@param image: The build type and version to install on the host.
|
|
@param force: If False, will only updated the host if it is not
|
|
already running the build. If True, force the
|
|
update regardless, and force a full-reimage.
|
|
|
|
"""
|
|
updater_obj.update_dut(dut_host=dut.host, image=image, force=force)
|
|
|
|
|
|
class CliqueDUTUpdater(object):
|
|
"""CliqueDUTUpdater is responsible for updating all the DUT's in the
|
|
DUT pool to the same release.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initializes the DUT updater for updating the DUT's in the pool."""
|
|
|
|
|
|
@staticmethod
|
|
def _get_board_name_from_host(dut_host):
|
|
"""Get the board name of the remote host.
|
|
|
|
@param host: Host object representing the DUT.
|
|
|
|
@return: A string representing the board of the remote host.
|
|
"""
|
|
try:
|
|
board = dut_host.get_board().replace(constants.BOARD_PREFIX, '')
|
|
except error.AutoservRunError:
|
|
raise error.TestFail(
|
|
'Cannot determine board for host %s' % dut_host.hostname)
|
|
logging.debug('Detected board %s for host %s', board, dut_host.hostname)
|
|
return board
|
|
|
|
@staticmethod
|
|
def _construct_image_label(dut_board, release_version):
|
|
"""Constructs a label combining the board name and release version.
|
|
|
|
@param dut_board: A string representing the board of the remote host.
|
|
@param release_version: A chromeOS release version.
|
|
|
|
@return: A string representing the release version.
|
|
Ex: lumpy-release/R28-3993.0.0
|
|
"""
|
|
# todo(rpius): We should probably make this more flexible to accept
|
|
# images from trybot's, etc.
|
|
return dut_board + '-release/' + release_version
|
|
|
|
@staticmethod
|
|
def _get_update_url(ds_url, image):
|
|
"""Returns the full update URL. """
|
|
config = global_config.global_config
|
|
image_url_pattern = config.get_config_value(
|
|
'CROS', 'image_url_pattern', type=str)
|
|
return image_url_pattern % (ds_url, image)
|
|
|
|
@staticmethod
|
|
def _get_release_version_from_dut(dut_host):
|
|
"""Get release version from the DUT located in lsb-release file.
|
|
|
|
@param dut_host: Host object representing the DUT.
|
|
|
|
@return: A string representing the release version.
|
|
"""
|
|
return dut_host.get_release_version()
|
|
|
|
@staticmethod
|
|
def _get_release_version_from_image(image):
|
|
"""Get release version from the image label.
|
|
|
|
@param image: The build type and version to install on the host.
|
|
|
|
@return: A string representing the release version.
|
|
"""
|
|
return image.split('-')[-1]
|
|
|
|
@staticmethod
|
|
def _get_latest_release_version_from_server(dut_board):
|
|
"""Gets the latest release version for a given board from a dev server.
|
|
|
|
@param dut_board: A string representing the board of the remote host.
|
|
|
|
@return: A string representing the release version.
|
|
"""
|
|
build_target = dut_board + "-release"
|
|
config = global_config.global_config
|
|
server_url_list = config.get_config_value(
|
|
'CROS', 'dev_server', type=list, default=[])
|
|
ds = dev_server.ImageServer(server_url_list[0])
|
|
return ds.get_latest_build_in_server(build_target)
|
|
|
|
def update_dut(self, dut_host, image, force=True):
|
|
"""The method called by to start the upgrade of a single DUT.
|
|
|
|
@param dut_host: Host object representing the DUT.
|
|
@param image: The build type and version to install on the host.
|
|
@param force: If False, will only updated the host if it is not
|
|
already running the build. If True, force the
|
|
update regardless, and force a full-reimage.
|
|
|
|
"""
|
|
logging.debug('Host: %s. Start updating DUT to %s', dut_host, image)
|
|
|
|
# If the host is already on the correct build, we have nothing to do.
|
|
dut_release_version = self._get_release_version_from_dut(dut_host)
|
|
image_release_version = self._get_release_version_from_image(image)
|
|
if not force and dut_release_version == image_release_version:
|
|
logging.info('Host: %s. Already running %s',
|
|
dut_host, image_release_version)
|
|
sys.exit(UPDATE_SUCCESS)
|
|
|
|
try:
|
|
ds = dev_server.ImageServer.resolve(image)
|
|
except dev_server.DevServerException as e:
|
|
error_str = 'Host: ' + dut_host + '. ' + e
|
|
logging.error(error_str)
|
|
sys.exit(UPDATE_FAILURE)
|
|
|
|
url = self._get_update_url(ds.url(), image)
|
|
logging.debug('Host: %s. Installing image from %s', dut_host, url)
|
|
try:
|
|
provisioner.ChromiumOSProvisioner(url,
|
|
host=dut_host).run_provision()
|
|
except error.TestFail as e:
|
|
error_str = 'Host: ' + dut_host + '. ' + e
|
|
logging.error(error_str)
|
|
sys.exit(UPDATE_FAILURE)
|
|
|
|
dut_release_version = self._get_release_version_from_dut(dut_host)
|
|
if dut_release_version != image_release_version:
|
|
error_str = 'Host: ' + dut_host + '. Expected version of ' + \
|
|
image_release_version + ' in DUT, but found ' + \
|
|
dut_release_version + '.'
|
|
logging.error(error_str)
|
|
sys.exit(UPDATE_FAILURE)
|
|
|
|
logging.info('Host: %s. Finished updating DUT to %s', dut_host, image)
|
|
sys.exit(UPDATE_SUCCESS)
|
|
|
|
def update_dut_pool(self, dut_objects, release_version=""):
|
|
"""Updates all the DUT's in the pool to a provided release version.
|
|
|
|
@param dut_objects: An array of DUTObjects corresponding to all the
|
|
DUT's in the DUT pool.
|
|
@param release_version: A chromeOS release version.
|
|
|
|
@return: True if all the DUT's successfully upgraded, False otherwise.
|
|
"""
|
|
tasks = []
|
|
for dut in dut_objects:
|
|
dut_board = self._get_board_name_from_host(dut.host)
|
|
if release_version == "":
|
|
release_version = self._get_latest_release_version_from_server(
|
|
dut_board)
|
|
dut_image = self._construct_image_label(dut_board, release_version)
|
|
# Schedule the update for this DUT to the update process pool.
|
|
task = multiprocessing.Process(
|
|
target=update_dut_worker,
|
|
args=(self, dut, dut_image, False))
|
|
tasks.append(task)
|
|
# Run the updates in parallel.
|
|
for task in tasks:
|
|
task.start()
|
|
for task in tasks:
|
|
task.join()
|
|
|
|
# Check the exit code to determine if the updates were all successful
|
|
# or not.
|
|
for task in tasks:
|
|
if task.exitcode == UPDATE_FAILURE:
|
|
return False
|
|
return True
|