Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import httplib | 5 import httplib |
| 6 import logging | 6 import logging |
| 7 import re | 7 import re |
| 8 import socket | 8 import socket |
| 9 import urlparse | 9 import urlparse |
| 10 | 10 |
| 11 from autotest_lib.client.bin import site_utils | |
| 12 from autotest_lib.client.common_lib import error | 11 from autotest_lib.client.common_lib import error |
| 13 | 12 |
| 14 | |
| 15 STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update' | 13 STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update' |
| 16 UPDATER_BIN = '/usr/bin/update_engine_client' | 14 UPDATER_BIN = '/usr/bin/update_engine_client' |
| 17 UPDATER_IDLE = 'UPDATE_STATUS_IDLE' | 15 UPDATER_IDLE = 'UPDATE_STATUS_IDLE' |
| 18 UPDATER_NEED_REBOOT = 'UPDATED_NEED_REBOOT' | 16 UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT' |
| 19 | 17 |
| 20 | 18 |
| 21 class ChromiumOSError(error.InstallError): | 19 class ChromiumOSError(error.InstallError): |
| 22 """Generic error for ChromiumOS-specific exceptions.""" | 20 """Generic error for ChromiumOS-specific exceptions.""" |
| 23 pass | 21 pass |
| 24 | 22 |
| 25 | 23 |
| 26 def url_to_version(update_url): | 24 def url_to_version(update_url): |
| 27 # The ChromiumOS updater respects the last element in the path as | 25 # The ChromiumOS updater respects the last element in the path as |
| 28 # the requested version. Parse it out. | 26 # the requested version. Parse it out. |
| 29 return urlparse.urlparse(update_url).path.split('/')[-1] | 27 return urlparse.urlparse(update_url).path.split('/')[-1] |
| 30 | 28 |
| 31 | 29 |
| 32 class ChromiumOSUpdater(): | 30 class ChromiumOSUpdater(): |
| 33 def __init__(self, host=None, update_url=None): | 31 def __init__(self, host=None, update_url=None): |
| 34 self.host = host | 32 self.host = host |
| 35 self.update_url = update_url | 33 self.update_url = update_url |
| 36 self.update_version = url_to_version(update_url) | 34 self.update_version = url_to_version(update_url) |
| 37 | 35 |
| 36 | |
| 38 def check_update_status(self): | 37 def check_update_status(self): |
| 39 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1', | 38 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1', |
| 40 '| grep CURRENT_OP']) | 39 '| grep CURRENT_OP']) |
| 41 update_status = self._run(update_status_cmd) | 40 update_status = self._run(update_status_cmd) |
| 42 return update_status.stdout.strip().split('=')[-1] | 41 return update_status.stdout.strip().split('=')[-1] |
| 43 | 42 |
| 43 | |
| 44 def reset_update_engine(self): | |
| 45 self._run('initctl stop update-engine') | |
|
petkov
2010/08/23 19:20:31
Does this throw an exception if update-engine is n
| |
| 46 self._run('rm -f /tmp/update_engine_autoupdate_completed') | |
| 47 self._run('initctl start update-engine') | |
| 48 # May need to wait if service becomes slow to restart. | |
| 49 if self.check_update_status() != UPDATER_IDLE: | |
| 50 raise ChromiumOSError('%s is not in an installable state' % | |
| 51 self.host.hostname) | |
| 52 | |
| 53 | |
| 44 def _run(self, cmd, *args, **kwargs): | 54 def _run(self, cmd, *args, **kwargs): |
| 45 return self.host.run(cmd, *args, **kwargs) | 55 return self.host.run(cmd, *args, **kwargs) |
| 46 | 56 |
| 57 | |
| 47 def run_update(self): | 58 def run_update(self): |
| 48 # TODO(seano): Retrieve update_engine.log from target host. | 59 # TODO(seano): Retrieve update_engine.log from target host. |
| 49 if not self.update_url: | 60 if not self.update_url: |
| 50 return False | 61 return False |
| 51 | 62 |
| 52 # Check that devserver is accepting connections (from autoserv's host) | 63 # Check that devserver is accepting connections (from autoserv's host) |
| 53 # If we can't talk to it, the machine host probably can't either. | 64 # If we can't talk to it, the machine host probably can't either. |
| 54 auserver_host = urlparse.urlparse(self.update_url)[1] | 65 auserver_host = urlparse.urlparse(self.update_url)[1] |
| 55 try: | 66 try: |
| 56 httplib.HTTPConnection(auserver_host).connect() | 67 httplib.HTTPConnection(auserver_host).connect() |
| 57 except socket.error: | 68 except socket.error: |
| 58 raise ChromiumOSError('Update server at %s not available' % | 69 raise ChromiumOSError('Update server at %s not available' % |
| 59 auserver_host) | 70 auserver_host) |
| 60 | 71 |
| 61 logging.info('Installing from %s to: %s' % (self.update_url, | 72 logging.info('Installing from %s to: %s' % (self.update_url, |
| 62 self.host.hostname)) | 73 self.host.hostname)) |
| 63 # If we find the system an updated-but-not-rebooted state, | 74 # If we find the system an updated-but-not-rebooted state, |
| 64 # that's probably bad and we shouldn't trust that the previous | 75 # that's probably bad and we shouldn't trust that the previous |
| 65 # update left the machine in a good state. Reset update_engine's | 76 # update left the machine in a good state. Reset update_engine's |
| 66 # state & ensure that update_engine is idle. | 77 # state & ensure that update_engine is idle. |
| 67 if self.check_update_status() != UPDATER_IDLE: | 78 if self.check_update_status() != UPDATER_IDLE: |
|
petkov
2010/08/23 19:20:31
This may throw an exception if update-engine is no
| |
| 68 self._run('initctl stop update-engine') | 79 self.reset_update_engine() |
| 69 self._run('rm -f /tmp/update_engine_autoupdate_completed') | 80 |
| 70 self._run('initctl start update-engine') | 81 # Run autoupdate command. This tells the autoupdate process on |
| 71 # May need to wait if service becomes slow to restart. | 82 # the host to look for an update at a specific URL and version |
| 72 if self.check_update_status() != UPDATER_IDLE: | 83 # string. |
| 73 raise ChromiumOSError('%s is not in an installable state' % | 84 autoupdate_cmd = ' '.join([UPDATER_BIN, |
| 85 '--update', | |
| 86 '--omaha_url=%s' % self.update_url, | |
| 87 '--app_version ForcedUpdate', | |
|
petkov
2010/08/23 19:20:31
FYI, currently --update implies --app_version=Forc
| |
| 88 ' 2>&1']) | |
| 89 logging.info(autoupdate_cmd) | |
| 90 try: | |
| 91 self._run(autoupdate_cmd, timeout=900) | |
| 92 except error.AutoservRunError, e: | |
| 93 # Either a runtime error occurred on the host, or | |
| 94 # update_engine_client exited with > 0. | |
| 95 raise ChromiumOSError('update_engine failed on %s' % | |
| 74 self.host.hostname) | 96 self.host.hostname) |
| 75 | 97 |
| 76 # First, attempt dev & test tools update (which don't live on | 98 # Check that the installer completed as expected. |
| 77 # the rootfs). This must succeed so that the newly installed | 99 status = self.check_update_status() |
| 78 # host is testable after we run the autoupdater. | 100 if status != UPDATER_NEED_REBOOT: |
| 101 # TODO(seano): should we aggressively reset update-engine here? | |
|
petkov
2010/08/23 19:20:31
It might be good to somehow log /var/log/update_en
| |
| 102 raise ChromiumOSError('update-engine error on %s: ' | |
| 103 '"%s" from update-engine' % | |
| 104 (self.host.hostname, status)) | |
| 105 | |
| 106 # Attempt dev & test tools update (which don't live on the | |
| 107 # rootfs). This must succeed so that the newly installed host | |
| 108 # is testable after we run the autoupdater. | |
| 79 statefuldev_url = self.update_url.replace('update', 'static/archive') | 109 statefuldev_url = self.update_url.replace('update', 'static/archive') |
| 80 | 110 |
| 81 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url, | 111 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url, |
| 82 '2>&1']) | 112 '2>&1']) |
| 83 logging.info(statefuldev_cmd) | 113 logging.info(statefuldev_cmd) |
| 84 try: | 114 try: |
| 85 self._run(statefuldev_cmd, timeout=1200) | 115 self._run(statefuldev_cmd, timeout=600) |
| 86 except error.AutoservRunError, e: | 116 except error.AutoservRunError, e: |
| 117 # TODO(seano): If statefuldev update failed, we must mark | |
| 118 # the update as failed, and keep the same rootfs after | |
| 119 # reboot. | |
| 87 raise ChromiumOSError('stateful_update failed on %s' % | 120 raise ChromiumOSError('stateful_update failed on %s' % |
| 88 self.host.hostname) | 121 self.host.hostname) |
| 89 | |
| 90 # Run autoupdate command. This tells the autoupdate process on | |
| 91 # the host to look for an update at a specific URL and version | |
| 92 # string. | |
| 93 autoupdate_cmd = ' '.join([UPDATER_BIN, | |
| 94 '--omaha_url=%s' % self.update_url, | |
| 95 '--app_version ForcedUpdate']) | |
| 96 logging.info(autoupdate_cmd) | |
| 97 try: | |
| 98 self._run(autoupdate_cmd, timeout=60) | |
| 99 except error.AutoservRunError, e: | |
| 100 raise ChromiumOSError('unable to run updater on %s' % | |
| 101 self.host.hostname) | |
| 102 | |
| 103 # Check that the installer completed as expected. | |
| 104 def update_successful(): | |
| 105 status = self.check_update_status() | |
| 106 if status == UPDATER_IDLE: | |
| 107 raise ChromiumOSError('update-engine error on %s' % | |
| 108 self.host.hostname) | |
| 109 else: | |
| 110 return 'UPDATED_NEED_REBOOT' in status | |
| 111 | |
| 112 site_utils.poll_for_condition(update_successful, | |
| 113 ChromiumOSError('Updater failed'), | |
| 114 900, 10) | |
| 115 return True | 122 return True |
| 116 | 123 |
| 117 | 124 |
| 118 def check_version(self): | 125 def check_version(self): |
| 119 booted_version = self.get_build_id() | 126 booted_version = self.get_build_id() |
| 120 if booted_version != self.update_version: | 127 if booted_version != self.update_version: |
| 121 logging.error('Expected Chromium OS version: %s.' | 128 logging.error('Expected Chromium OS version: %s.' |
| 122 'Found Chromium OS %s', | 129 'Found Chromium OS %s', |
| 123 (self.update_version, booted_version)) | 130 (self.update_version, booted_version)) |
| 124 raise ChromiumOSError('Updater failed on host %s' % | 131 raise ChromiumOSError('Updater failed on host %s' % |
| 125 self.host.hostname) | 132 self.host.hostname) |
| 126 else: | 133 else: |
| 127 return True | 134 return True |
| 128 | 135 |
| 136 | |
| 129 def get_build_id(self): | 137 def get_build_id(self): |
| 130 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that | 138 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that |
| 131 matches the build ID.""" | 139 matches the build ID.""" |
| 132 # TODO(seano): handle dev build naming schemes. | 140 # TODO(seano): handle dev build naming schemes. |
| 133 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION' | 141 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION' |
| 134 ' /etc/lsb-release').stdout | 142 ' /etc/lsb-release').stdout |
| 135 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION=' | 143 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION=' |
| 136 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)') | 144 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)') |
| 137 version_match = re.match(build_re, version) | 145 version_match = re.match(build_re, version) |
| 138 if not version_match: | 146 if not version_match: |
| 139 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"', | 147 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"', |
| 140 self.host.hostname, version) | 148 self.host.hostname, version) |
| 141 version, build_id, builder = version_match.groups() | 149 version, build_id, builder = version_match.groups() |
| 142 # Continuous builds have an extra "builder number" on the end. | 150 # Continuous builds have an extra "builder number" on the end. |
| 143 # Report it if this looks like one. | 151 # Report it if this looks like one. |
| 144 build_match = re.match(r'.*: (\d+)', builder) | 152 build_match = re.match(r'.*: (\d+)', builder) |
| 145 if build_match: | 153 if build_match: |
| 146 builder_num = '-b%s' % build_match.group(1) | 154 builder_num = '-b%s' % build_match.group(1) |
| 147 else: | 155 else: |
| 148 builder_num = '' | 156 builder_num = '' |
| 149 return '%s-r%s%s' % (version, build_id, builder_num) | 157 return '%s-r%s%s' % (version, build_id, builder_num) |
| OLD | NEW |