| 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 chromeos_constants |
| 11 from autotest_lib.client.common_lib import error | 12 from autotest_lib.client.common_lib import error |
| 12 | 13 |
| 13 STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update' | 14 STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update' |
| 14 UPDATER_BIN = '/usr/bin/update_engine_client' | 15 UPDATER_BIN = '/usr/bin/update_engine_client' |
| 15 UPDATER_IDLE = 'UPDATE_STATUS_IDLE' | 16 UPDATER_IDLE = 'UPDATE_STATUS_IDLE' |
| 16 UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT' | 17 UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT' |
| 17 | 18 |
| 18 | 19 |
| 19 class ChromiumOSError(error.InstallError): | 20 class ChromiumOSError(error.InstallError): |
| 20 """Generic error for ChromiumOS-specific exceptions.""" | 21 """Generic error for ChromiumOS-specific exceptions.""" |
| (...skipping 14 matching lines...) Expand all Loading... |
| 35 | 36 |
| 36 | 37 |
| 37 def check_update_status(self): | 38 def check_update_status(self): |
| 38 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1', | 39 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1', |
| 39 '| grep CURRENT_OP']) | 40 '| grep CURRENT_OP']) |
| 40 update_status = self._run(update_status_cmd) | 41 update_status = self._run(update_status_cmd) |
| 41 return update_status.stdout.strip().split('=')[-1] | 42 return update_status.stdout.strip().split('=')[-1] |
| 42 | 43 |
| 43 | 44 |
| 44 def reset_update_engine(self): | 45 def reset_update_engine(self): |
| 45 self._run('initctl stop update-engine') | 46 logging.info('Resetting update-engine.') |
| 46 self._run('rm -f /tmp/update_engine_autoupdate_completed') | 47 self._run('rm -f /tmp/update_engine_autoupdate_completed') |
| 48 try: |
| 49 self._run('initctl stop update-engine') |
| 50 except error.AutoservRunError, e: |
| 51 logging.warn('Stopping update-engine service failed. Already dead?') |
| 47 self._run('initctl start update-engine') | 52 self._run('initctl start update-engine') |
| 48 # May need to wait if service becomes slow to restart. | 53 # May need to wait if service becomes slow to restart. |
| 49 if self.check_update_status() != UPDATER_IDLE: | 54 if self.check_update_status() != UPDATER_IDLE: |
| 50 raise ChromiumOSError('%s is not in an installable state' % | 55 raise ChromiumOSError('%s is not in an installable state' % |
| 51 self.host.hostname) | 56 self.host.hostname) |
| 52 | 57 |
| 53 | 58 |
| 54 def _run(self, cmd, *args, **kwargs): | 59 def _run(self, cmd, *args, **kwargs): |
| 55 return self.host.run(cmd, *args, **kwargs) | 60 return self.host.run(cmd, *args, **kwargs) |
| 56 | 61 |
| 57 | 62 |
| 63 def rootdev(self): |
| 64 return self._run('rootdev').stdout.strip() |
| 65 |
| 66 |
| 67 def revert_boot_partition(self): |
| 68 part = self.rootdev() |
| 69 logging.warn('Reverting update; Boot partition will be %s', part) |
| 70 return self._run('/postinst %s 2>&1' % part) |
| 71 |
| 72 |
| 58 def run_update(self): | 73 def run_update(self): |
| 59 # TODO(seano): Retrieve update_engine.log from target host. | |
| 60 if not self.update_url: | 74 if not self.update_url: |
| 61 return False | 75 return False |
| 62 | 76 |
| 63 # Check that devserver is accepting connections (from autoserv's host) | 77 # Check that devserver is accepting connections (from autoserv's host) |
| 64 # If we can't talk to it, the machine host probably can't either. | 78 # If we can't talk to it, the machine host probably can't either. |
| 65 auserver_host = urlparse.urlparse(self.update_url)[1] | 79 auserver_host = urlparse.urlparse(self.update_url)[1] |
| 66 try: | 80 try: |
| 67 httplib.HTTPConnection(auserver_host).connect() | 81 httplib.HTTPConnection(auserver_host).connect() |
| 68 except socket.error: | 82 except socket.error: |
| 69 raise ChromiumOSError('Update server at %s not available' % | 83 raise ChromiumOSError('Update server at %s not available' % |
| 70 auserver_host) | 84 auserver_host) |
| 71 | 85 |
| 72 logging.info('Installing from %s to: %s' % (self.update_url, | 86 logging.info('Installing from %s to: %s' % (self.update_url, |
| 73 self.host.hostname)) | 87 self.host.hostname)) |
| 74 # If we find the system an updated-but-not-rebooted state, | 88 # Reset update_engine's state & check that update_engine is idle. |
| 75 # that's probably bad and we shouldn't trust that the previous | 89 self.reset_update_engine() |
| 76 # update left the machine in a good state. Reset update_engine's | |
| 77 # state & ensure that update_engine is idle. | |
| 78 if self.check_update_status() != UPDATER_IDLE: | |
| 79 self.reset_update_engine() | |
| 80 | 90 |
| 81 # Run autoupdate command. This tells the autoupdate process on | 91 # Run autoupdate command. This tells the autoupdate process on |
| 82 # the host to look for an update at a specific URL and version | 92 # the host to look for an update at a specific URL and version |
| 83 # string. | 93 # string. |
| 84 autoupdate_cmd = ' '.join([UPDATER_BIN, | 94 autoupdate_cmd = ' '.join([UPDATER_BIN, |
| 85 '--update', | 95 '--update', |
| 86 '--omaha_url=%s' % self.update_url, | 96 '--omaha_url=%s' % self.update_url, |
| 87 '--app_version ForcedUpdate', | |
| 88 ' 2>&1']) | 97 ' 2>&1']) |
| 89 logging.info(autoupdate_cmd) | 98 logging.info(autoupdate_cmd) |
| 90 try: | 99 try: |
| 91 self._run(autoupdate_cmd, timeout=900) | 100 self._run(autoupdate_cmd, timeout=900) |
| 92 except error.AutoservRunError, e: | 101 except error.AutoservRunError, e: |
| 93 # Either a runtime error occurred on the host, or | 102 # Either a runtime error occurred on the host, or |
| 94 # update_engine_client exited with > 0. | 103 # update_engine_client exited with > 0. |
| 95 raise ChromiumOSError('update_engine failed on %s' % | 104 raise ChromiumOSError('update_engine failed on %s' % |
| 96 self.host.hostname) | 105 self.host.hostname) |
| 97 | 106 |
| 98 # Check that the installer completed as expected. | 107 # Check that the installer completed as expected. |
| 99 status = self.check_update_status() | 108 status = self.check_update_status() |
| 100 if status != UPDATER_NEED_REBOOT: | 109 if status != UPDATER_NEED_REBOOT: |
| 101 # TODO(seano): should we aggressively reset update-engine here? | |
| 102 raise ChromiumOSError('update-engine error on %s: ' | 110 raise ChromiumOSError('update-engine error on %s: ' |
| 103 '"%s" from update-engine' % | 111 '"%s" from update-engine' % |
| 104 (self.host.hostname, status)) | 112 (self.host.hostname, status)) |
| 105 | 113 |
| 106 # Attempt dev & test tools update (which don't live on the | 114 # Attempt dev & test tools update (which don't live on the |
| 107 # rootfs). This must succeed so that the newly installed host | 115 # rootfs). This must succeed so that the newly installed host |
| 108 # is testable after we run the autoupdater. | 116 # is testable after we run the autoupdater. |
| 109 statefuldev_url = self.update_url.replace('update', 'static/archive') | 117 statefuldev_url = self.update_url.replace('update', 'static/archive') |
| 110 | 118 |
| 111 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url, | 119 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url, |
| 112 '2>&1']) | 120 '2>&1']) |
| 113 logging.info(statefuldev_cmd) | 121 logging.info(statefuldev_cmd) |
| 114 try: | 122 try: |
| 115 self._run(statefuldev_cmd, timeout=600) | 123 self._run(statefuldev_cmd, timeout=600) |
| 116 except error.AutoservRunError, e: | 124 except error.AutoservRunError, e: |
| 117 # TODO(seano): If statefuldev update failed, we must mark | 125 # TODO(seano): If statefuldev update failed, we must mark |
| 118 # the update as failed, and keep the same rootfs after | 126 # the update as failed, and keep the same rootfs after |
| 119 # reboot. | 127 # reboot. |
| 120 raise ChromiumOSError('stateful_update failed on %s' % | 128 self.revert_boot_partition() |
| 129 raise ChromiumOSError('stateful_update failed on %s.' % |
| 121 self.host.hostname) | 130 self.host.hostname) |
| 122 return True | 131 return True |
| 123 | 132 |
| 124 | 133 |
| 125 def check_version(self): | 134 def check_version(self): |
| 126 booted_version = self.get_build_id() | 135 booted_version = self.get_build_id() |
| 127 if booted_version != self.update_version: | 136 if not booted_version in self.update_version: |
| 128 logging.error('Expected Chromium OS version: %s.' | 137 logging.error('Expected Chromium OS version: %s.' |
| 129 'Found Chromium OS %s', | 138 'Found Chromium OS %s', |
| 130 (self.update_version, booted_version)) | 139 self.update_version, booted_version) |
| 131 raise ChromiumOSError('Updater failed on host %s' % | 140 raise ChromiumOSError('Updater failed on host %s' % |
| 132 self.host.hostname) | 141 self.host.hostname) |
| 133 else: | 142 else: |
| 134 return True | 143 return True |
| 135 | 144 |
| 136 | 145 |
| 137 def get_build_id(self): | 146 def get_build_id(self): |
| 138 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that | 147 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that |
| 139 matches the build ID.""" | 148 matches the build ID.""" |
| 140 # TODO(seano): handle dev build naming schemes. | 149 # TODO(seano): handle dev build naming schemes. |
| 141 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION' | 150 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION' |
| 142 ' /etc/lsb-release').stdout | 151 ' /etc/lsb-release').stdout |
| 143 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION=' | 152 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION=' |
| 144 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)') | 153 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)') |
| 145 version_match = re.match(build_re, version) | 154 version_match = re.match(build_re, version) |
| 146 if not version_match: | 155 if not version_match: |
| 147 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"', | 156 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"', |
| 148 self.host.hostname, version) | 157 self.host.hostname, version) |
| 149 version, build_id, builder = version_match.groups() | 158 version, build_id, builder = version_match.groups() |
| 150 # Continuous builds have an extra "builder number" on the end. | |
| 151 # Report it if this looks like one. | |
| 152 build_match = re.match(r'.*: (\d+)', builder) | 159 build_match = re.match(r'.*: (\d+)', builder) |
| 153 if build_match: | 160 if build_match: |
| 154 builder_num = '-b%s' % build_match.group(1) | 161 builder_num = '-b%s' % build_match.group(1) |
| 155 else: | 162 else: |
| 156 builder_num = '' | 163 builder_num = '' |
| 157 return '%s-r%s%s' % (version, build_id, builder_num) | 164 return '%s-r%s%s' % (version, build_id, builder_num) |
| OLD | NEW |