autoupdater.py revision 1f288ee1967599a36af5afc0b7f37b5f96ee616c
1# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import httplib 6import logging 7import re 8import socket 9import urlparse 10 11from autotest_lib.client.common_lib import error 12from autotest_lib.client.cros import constants as chromeos_constants 13 14# TODO(dalecurtis): HACK to bootstrap stateful updater until crosbug.com/8960 is 15# fixed. 16LOCAL_STATEFULDEV_UPDATER = ('/home/chromeos-test/chromeos-src/chromeos/src' 17 '/platform/dev/stateful_update') 18STATEFULDEV_UPDATER = '/tmp/stateful_update' 19UPDATER_BIN = '/usr/bin/update_engine_client' 20UPDATER_IDLE = 'UPDATE_STATUS_IDLE' 21UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT' 22UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed' 23 24 25class ChromiumOSError(error.InstallError): 26 """Generic error for ChromiumOS-specific exceptions.""" 27 pass 28 29 30def url_to_version(update_url): 31 # The ChromiumOS updater respects the last element in the path as 32 # the requested version. Parse it out. 33 return urlparse.urlparse(update_url).path.split('/')[-1] 34 35 36class ChromiumOSUpdater(): 37 def __init__(self, host=None, update_url=None): 38 self.host = host 39 self.update_url = update_url 40 self.update_version = url_to_version(update_url) 41 42 43 def check_update_status(self): 44 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1', 45 '| grep CURRENT_OP']) 46 update_status = self._run(update_status_cmd) 47 return update_status.stdout.strip().split('=')[-1] 48 49 50 def reset_update_engine(self): 51 logging.info('Resetting update-engine.') 52 self._run('rm -f %s' % UPDATED_MARKER) 53 try: 54 self._run('initctl stop update-engine') 55 except error.AutoservRunError, e: 56 logging.warn('Stopping update-engine service failed. Already dead?') 57 self._run('initctl start update-engine') 58 # May need to wait if service becomes slow to restart. 59 if self.check_update_status() != UPDATER_IDLE: 60 raise ChromiumOSError('%s is not in an installable state' % 61 self.host.hostname) 62 63 64 def _run(self, cmd, *args, **kwargs): 65 return self.host.run(cmd, *args, **kwargs) 66 67 68 def rootdev(self): 69 return self._run('rootdev').stdout.strip() 70 71 72 def revert_boot_partition(self): 73 part = self.rootdev() 74 logging.warn('Reverting update; Boot partition will be %s', part) 75 return self._run('/postinst %s 2>&1' % part) 76 77 78 def run_update(self): 79 if not self.update_url: 80 return False 81 82 # Check that devserver is accepting connections (from autoserv's host) 83 # If we can't talk to it, the machine host probably can't either. 84 auserver_host = urlparse.urlparse(self.update_url)[1] 85 try: 86 httplib.HTTPConnection(auserver_host).connect() 87 except socket.error: 88 raise ChromiumOSError('Update server at %s not available' % 89 auserver_host) 90 91 logging.info('Installing from %s to: %s' % (self.update_url, 92 self.host.hostname)) 93 # Reset update_engine's state & check that update_engine is idle. 94 self.reset_update_engine() 95 96 # Run autoupdate command. This tells the autoupdate process on 97 # the host to look for an update at a specific URL and version 98 # string. 99 autoupdate_cmd = ' '.join([UPDATER_BIN, 100 '--update', 101 '--omaha_url=%s' % self.update_url, 102 ' 2>&1']) 103 logging.info(autoupdate_cmd) 104 try: 105 self._run(autoupdate_cmd, timeout=900) 106 except error.AutoservRunError, e: 107 # Either a runtime error occurred on the host, or 108 # update_engine_client exited with > 0. 109 raise ChromiumOSError('update_engine failed on %s' % 110 self.host.hostname) 111 112 # Check that the installer completed as expected. 113 status = self.check_update_status() 114 if status != UPDATER_NEED_REBOOT: 115 raise ChromiumOSError('update-engine error on %s: ' 116 '"%s" from update-engine' % 117 (self.host.hostname, status)) 118 119 # Attempt dev & test tools update (which don't live on the 120 # rootfs). This must succeed so that the newly installed host 121 # is testable after we run the autoupdater. 122 statefuldev_url = self.update_url.replace('update', 'static/archive') 123 124 # TODO(dalecurtis): HACK to bootstrap stateful updater until 125 # crosbug.com/8960 is fixed. 126 self.host.send_file(LOCAL_STATEFULDEV_UPDATER, STATEFULDEV_UPDATER, 127 delete_dest=True) 128 statefuldev_cmd = [STATEFULDEV_UPDATER, statefuldev_url] 129 130 # TODO(dalecurtis): HACK necessary until R10 builds are out of testing. 131 if int(self.update_version.split('.')[1]) > 10: 132 statefuldev_cmd.append('--stateful_change=clean') 133 134 statefuldev_cmd.append('2>&1') 135 statefuldev_cmd = ' '.join(statefuldev_cmd) 136 137 logging.info(statefuldev_cmd) 138 try: 139 self._run(statefuldev_cmd, timeout=600) 140 except error.AutoservRunError, e: 141 # TODO(seano): If statefuldev update failed, we must mark 142 # the update as failed, and keep the same rootfs after 143 # reboot. 144 self.revert_boot_partition() 145 raise ChromiumOSError('stateful_update failed on %s.' % 146 self.host.hostname) 147 return True 148 149 150 def check_version(self): 151 booted_version = self.get_build_id() 152 if not booted_version: 153 booted_version = self.get_dev_build_id() 154 if not booted_version in self.update_version: 155 logging.error('Expected Chromium OS version: %s.' 156 'Found Chromium OS %s', 157 self.update_version, booted_version) 158 raise ChromiumOSError('Updater failed on host %s' % 159 self.host.hostname) 160 else: 161 return True 162 163 164 def get_build_id(self): 165 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that 166 matches the build ID.""" 167 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION' 168 ' /etc/lsb-release').stdout 169 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION=' 170 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)') 171 version_match = re.match(build_re, version) 172 if version_match: 173 version, build_id, builder = version_match.groups() 174 build_match = re.match(r'.*: (\d+)', builder) 175 if build_match: 176 builder_num = '-b%s' % build_match.group(1) 177 else: 178 builder_num = '' 179 return '%s-r%s%s' % (version, build_id, builder_num) 180 181 182 def get_dev_build_id(self): 183 """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release.""" 184 return self._run('grep CHROMEOS_RELEASE_VERSION' 185 ' /etc/lsb-release').stdout.split('=')[1].strip() 186