blob: f23a2848d399361266d05e297e828fcdf699bddc [file] [log] [blame]
# Copyright (c) 2010 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 re
import socket
import urlparse
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import constants as chromeos_constants
# TODO(dalecurtis): HACK to bootstrap stateful updater until crosbug.com/8960 is
# fixed.
LOCAL_STATEFULDEV_UPDATER = ('/home/chromeos-test/chromeos-src/chromeos/src'
'/platform/dev/stateful_update')
STATEFULDEV_UPDATER = '/tmp/stateful_update'
UPDATER_BIN = '/usr/bin/update_engine_client'
UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
class ChromiumOSError(error.InstallError):
"""Generic error for ChromiumOS-specific exceptions."""
pass
def url_to_version(update_url):
# The ChromiumOS updater respects the last element in the path as
# the requested version. Parse it out.
return urlparse.urlparse(update_url).path.split('/')[-1]
class ChromiumOSUpdater():
def __init__(self, host=None, update_url=None):
self.host = host
self.update_url = update_url
self.update_version = url_to_version(update_url)
def check_update_status(self):
update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1',
'| grep CURRENT_OP'])
update_status = self._run(update_status_cmd)
return update_status.stdout.strip().split('=')[-1]
def reset_update_engine(self):
logging.info('Resetting update-engine.')
self._run('rm -f %s' % UPDATED_MARKER)
try:
self._run('initctl stop update-engine')
except error.AutoservRunError, e:
logging.warn('Stopping update-engine service failed. Already dead?')
self._run('initctl start update-engine')
# May need to wait if service becomes slow to restart.
if self.check_update_status() != UPDATER_IDLE:
raise ChromiumOSError('%s is not in an installable state' %
self.host.hostname)
def _run(self, cmd, *args, **kwargs):
return self.host.run(cmd, *args, **kwargs)
def rootdev(self):
return self._run('rootdev').stdout.strip()
def revert_boot_partition(self):
part = self.rootdev()
logging.warn('Reverting update; Boot partition will be %s', part)
return self._run('/postinst %s 2>&1' % part)
def run_update(self):
if not self.update_url:
return False
# Check that devserver is accepting connections (from autoserv's host)
# If we can't talk to it, the machine host probably can't either.
auserver_host = urlparse.urlparse(self.update_url)[1]
try:
httplib.HTTPConnection(auserver_host).connect()
except socket.error:
raise ChromiumOSError('Update server at %s not available' %
auserver_host)
logging.info('Installing from %s to: %s' % (self.update_url,
self.host.hostname))
# Reset update_engine's state & check that update_engine is idle.
self.reset_update_engine()
# Run autoupdate command. This tells the autoupdate process on
# the host to look for an update at a specific URL and version
# string.
autoupdate_cmd = ' '.join([UPDATER_BIN,
'--update',
'--omaha_url=%s' % self.update_url,
' 2>&1'])
logging.info(autoupdate_cmd)
try:
self._run(autoupdate_cmd, timeout=900)
except error.AutoservRunError, e:
# Either a runtime error occurred on the host, or
# update_engine_client exited with > 0.
raise ChromiumOSError('update_engine failed on %s' %
self.host.hostname)
# Check that the installer completed as expected.
status = self.check_update_status()
if status != UPDATER_NEED_REBOOT:
raise ChromiumOSError('update-engine error on %s: '
'"%s" from update-engine' %
(self.host.hostname, status))
# Attempt dev & test tools update (which don't live on the
# rootfs). This must succeed so that the newly installed host
# is testable after we run the autoupdater.
statefuldev_url = self.update_url.replace('update', 'static/archive')
# TODO(dalecurtis): HACK to bootstrap stateful updater until
# crosbug.com/8960 is fixed.
self.host.send_file(LOCAL_STATEFULDEV_UPDATER, STATEFULDEV_UPDATER,
delete_dest=True)
statefuldev_cmd = [STATEFULDEV_UPDATER, statefuldev_url]
# TODO(dalecurtis): HACK necessary until R10 builds are out of testing.
if int(self.update_version.split('.')[1]) > 10:
statefuldev_cmd.append('--stateful_change=clean')
statefuldev_cmd.append('2>&1')
statefuldev_cmd = ' '.join(statefuldev_cmd)
logging.info(statefuldev_cmd)
try:
self._run(statefuldev_cmd, timeout=600)
except error.AutoservRunError, e:
# TODO(seano): If statefuldev update failed, we must mark
# the update as failed, and keep the same rootfs after
# reboot.
self.revert_boot_partition()
raise ChromiumOSError('stateful_update failed on %s.' %
self.host.hostname)
return True
def check_version(self):
booted_version = self.get_build_id()
if not booted_version:
booted_version = self.get_dev_build_id()
if not booted_version in self.update_version:
logging.error('Expected Chromium OS version: %s.'
'Found Chromium OS %s',
self.update_version, booted_version)
raise ChromiumOSError('Updater failed on host %s' %
self.host.hostname)
else:
return True
def get_build_id(self):
"""Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that
matches the build ID."""
version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION'
' /etc/lsb-release').stdout
build_re = (r'CHROMEOS_RELEASE_DESCRIPTION='
'(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)')
version_match = re.match(build_re, version)
if version_match:
version, build_id, builder = version_match.groups()
build_match = re.match(r'.*: (\d+)', builder)
if build_match:
builder_num = '-b%s' % build_match.group(1)
else:
builder_num = ''
return '%s-r%s%s' % (version, build_id, builder_num)
def get_dev_build_id(self):
"""Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release."""
return self._run('grep CHROMEOS_RELEASE_VERSION'
' /etc/lsb-release').stdout.split('=')[1].strip()