blob: e84c79156c7dbb7f7c6435ca04e639555e28dc87 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright (c) 2014 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.
"""Module for umpire client update utilities."""
import collections
import logging
import gzip
import StringIO
import urllib2
import factory_common # pylint: disable=W0611
from cros.factory.umpire.client import umpire_client
UpdateInfo = collections.namedtuple('UpdateInfo',
['needs_update', 'md5sum', 'url', 'scheme'])
"""Information about update of a component.
Properties:
needs_update: True if an update is needed, False otherwise.
md5sum: The new md5sum of component on the server.
update_url: The url of component on the server.
Note that for device factory toolkit this is the root directory
of device, so the factory directory is <update_url>/usr/local/factory/.
scheme: update scheme provided by Umpire server, e.g. 'http', 'rsync'.
"""
class UmpireClientGetUpdateException(Exception):
"""Exception for Umpire client get_update utils."""
pass
def GetUpdateForComponents(proxy, components):
"""Gets update information of components from Umpire server.
Args:
proxy: An UmpireServerProxy that connects to Umpire server.
components: A list of component names. They must be in
umpire_client.COMPONENT_KEYS.
Returns:
A dict containing mapping from component name to UpdateInfo.
Raises:
UmpireClientGetUpdateException: If components contains invalid keys.
"""
invalid_keys = set(components) - umpire_client.COMPONENT_KEYS
if invalid_keys:
raise UmpireClientGetUpdateException(
'Invalid keys in components: %r' % invalid_keys)
update_dict = proxy.GetUpdate(
umpire_client.UmpireClientInfo().GetDUTInfoComponents())
logging.info('update_dict: %r', update_dict)
ret = dict([key,
UpdateInfo(needs_update=update_dict[key]['needs_update'],
md5sum=update_dict[key]['md5sum'],
url=update_dict[key]['url'],
scheme=update_dict[key]['scheme'])]
for key in components)
return ret
def NeedImageUpdate(proxy):
"""Checks if device need to update test or release image.
Args:
proxy: An UmpireServerProxy that connects to Umpire server.
Returns:
True if device needs to update image.
"""
update_info = GetUpdateForComponents(proxy, ['rootfs_test', 'rootfs_release'])
logging.info('Update info for image: %r', update_info)
return (update_info['rootfs_test'].needs_update or
update_info['rootfs_release'].needs_update)
def GetUpdateForDeviceFactoryToolkit(proxy):
"""Gets update information for device factory toolkit.
Args:
proxy: An UmpireServerProxy that connects to Umpire server.
Returns:
A UpdateInfo for device factory toolkit.
"""
return GetUpdateForComponents(
proxy, ['device_factory_toolkit'])['device_factory_toolkit']
def GetUpdateForHWID(proxy):
"""Gets HWID update from Umpire server.
The user of this method is get_hwid_updater in cros.factory.test.shopfloor.
For backward compatibility, this method returns the content of HWID shell
ball file to hide the difference between GetHWIDUpdater XMLRPC call in
shopfloor v1, v2 and GetUpdate mechanism in Umpire.
Args:
proxy: An UmpireServerProxy that connects to Umpire server.
Returns:
None if there is no HWID update. Otherwise, return unzipped HWID update
bundle file content.
"""
update_info = GetUpdateForComponents(
proxy, ['hwid'])['hwid']
if not update_info.needs_update:
return None
if update_info.scheme != 'http':
raise UmpireClientGetUpdateException(
'HWID update scheme %s other than http'
' is not supported.' % update_info.scheme)
return Download(update_info.url)
def Download(url, unzip=True):
"""Downloads a gzip file and returns the content.
Args:
url: The url of the gzip file.
unzip: Whether to unzip the file.
Returns:
The downloaded content, or the unzipped content if unzip is True.
"""
file_content = urllib2.urlopen(url).read()
if unzip:
string_io = StringIO.StringIO(file_content)
content = None
with gzip.GzipFile(fileobj=string_io) as f:
content = f.read()
return content
else:
return file_content
def GetUpdateForFirmware(proxy):
"""Gets firmware update from Umpire server.
The user of this method is get_firmware_updater in
cros.factory.test.shopfloor.
This method returns the content of unzipped firmware.gz file, that is,
chromeos-firmwareupdate.
Args:
proxy: An UmpireServerProxy that connects to Umpire server.
Returns:
None if there is no firmware update. Otherwise, return unzipped
chromeos-firmwareupdate file content.
"""
update_info_firmware = GetUpdateForComponents(
proxy, ['firmware_ec', 'firmware_bios', 'firmware_pd'])
logging.info('Update info for firmware: %r', update_info_firmware)
if (update_info_firmware['firmware_ec'].needs_update or
update_info_firmware['firmware_bios'].needs_update or
update_info_firmware['firmware_pd'].needs_update):
logging.info('Need firmware update from Umpire')
else:
return None
if (len(set([update_info_firmware['firmware_ec'].md5sum,
update_info_firmware['firmware_bios'].md5sum,
update_info_firmware['firmware_pd'].md5sum])) != 1):
raise UmpireClientGetUpdateException(
'Md5sum for ec ,bios, and pd firmware updater are different:'
' ec: %s, bios: %s, pd: %s',
update_info_firmware['firmware_ec'].md5sum,
update_info_firmware['firmware_bios'].md5sum,
update_info_firmware['firmware_pd'].md5sum)
update_info = update_info_firmware['firmware_ec']
if update_info.scheme != 'http':
raise UmpireClientGetUpdateException(
('Firmware update scheme %s other than http is not supported.'
% update_info.scheme))
return Download(update_info.url)
def GetUpdateForNetbootFirmware(proxy):
"""Gets update information for netboot firmware.
Args:
proxy: An UmpireServerProxy that connects to Umpire server.
Returns:
None if there is no netboot firmware update. Otherwise, return the
downloaded netboot firmware.
"""
try:
update_info = GetUpdateForComponents(
proxy, ['netboot_firmware'])['netboot_firmware']
except KeyError:
raise RuntimeError('factory bundle does not contain netboot_firmware')
if update_info.needs_update:
return Download(update_info.url, unzip=False)
else:
return None