Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 1 | # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import httplib |
| 6 | import logging |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 7 | import multiprocessing |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 8 | import os |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 9 | import re |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 10 | import urlparse |
| 11 | |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 12 | from autotest_lib.client.common_lib import error, global_config |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 13 | |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 14 | # Local stateful update path is relative to the CrOS source directory. |
| 15 | LOCAL_STATEFUL_UPDATE_PATH = 'src/platform/dev/stateful_update' |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 16 | LOCAL_CHROOT_STATEFUL_UPDATE_PATH = '/usr/bin/stateful_update' |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 17 | REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update' |
| 18 | STATEFUL_UPDATE = '/tmp/stateful_update' |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 19 | UPDATER_BIN = '/usr/bin/update_engine_client' |
| 20 | UPDATER_IDLE = 'UPDATE_STATUS_IDLE' |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 21 | UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT' |
Darin Petkov | 7d57299 | 2010-09-23 10:11:05 -0700 | [diff] [blame] | 22 | UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed' |
Dale Curtis | 1e97318 | 2011-07-12 18:21:36 -0700 | [diff] [blame] | 23 | UPDATER_LOGS = '/var/log/messages /var/log/update_engine' |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 24 | |
| 25 | |
| 26 | class ChromiumOSError(error.InstallError): |
| 27 | """Generic error for ChromiumOS-specific exceptions.""" |
| 28 | pass |
| 29 | |
| 30 | |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 31 | class RootFSUpdateError(ChromiumOSError): |
| 32 | """Raised when the RootFS fails to update.""" |
| 33 | pass |
| 34 | |
| 35 | |
| 36 | class StatefulUpdateError(ChromiumOSError): |
| 37 | """Raised when the stateful partition fails to update.""" |
| 38 | pass |
| 39 | |
| 40 | |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 41 | def url_to_version(update_url): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 42 | """Return the version based on update_url. |
| 43 | |
| 44 | @param update_url: url to the image to update to. |
| 45 | |
| 46 | """ |
Dale Curtis | ddfdb94 | 2011-07-14 13:59:24 -0700 | [diff] [blame] | 47 | # The Chrome OS version is generally the last element in the URL. The only |
| 48 | # exception is delta update URLs, which are rooted under the version; e.g., |
| 49 | # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to |
| 50 | # strip off the au section of the path before reading the version. |
Dan Shi | 5002cfc | 2013-04-29 10:45:05 -0700 | [diff] [blame] | 51 | return re.sub('/au/.*', '', |
| 52 | urlparse.urlparse(update_url).path).split('/')[-1].strip() |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 53 | |
| 54 | |
Scott Zawalski | eadbf70 | 2013-03-14 09:23:06 -0400 | [diff] [blame] | 55 | def url_to_image_name(update_url): |
| 56 | """Return the image name based on update_url. |
| 57 | |
| 58 | From a URL like: |
| 59 | http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0 |
| 60 | return lumpy-release/R27-3837.0.0 |
| 61 | |
| 62 | @param update_url: url to the image to update to. |
| 63 | @returns a string representing the image name in the update_url. |
| 64 | |
| 65 | """ |
| 66 | return '/'.join(urlparse.urlparse(update_url).path.split('/')[-2:]) |
| 67 | |
| 68 | |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 69 | class ChromiumOSUpdater(): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 70 | """Helper class used to update DUT with image of desired version.""" |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 71 | KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3} |
| 72 | KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5} |
| 73 | |
| 74 | |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 75 | def __init__(self, update_url, host=None, local_devserver=False): |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 76 | self.host = host |
| 77 | self.update_url = update_url |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 78 | self._update_error_queue = multiprocessing.Queue(2) |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 79 | self.local_devserver = local_devserver |
| 80 | if not local_devserver: |
| 81 | self.update_version = url_to_version(update_url) |
| 82 | else: |
| 83 | self.update_version = None |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 84 | |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 85 | def check_update_status(self): |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 86 | """Return current status from update-engine.""" |
| 87 | update_status = self._run( |
| 88 | '%s -status 2>&1 | grep CURRENT_OP' % UPDATER_BIN) |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 89 | return update_status.stdout.strip().split('=')[-1] |
| 90 | |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 91 | |
| 92 | def reset_update_engine(self): |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 93 | """Restarts the update-engine service.""" |
Darin Petkov | 7d57299 | 2010-09-23 10:11:05 -0700 | [diff] [blame] | 94 | self._run('rm -f %s' % UPDATED_MARKER) |
Sean O | 267c00b | 2010-08-31 15:54:55 +0200 | [diff] [blame] | 95 | try: |
| 96 | self._run('initctl stop update-engine') |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 97 | except error.AutoservRunError: |
Sean O | 267c00b | 2010-08-31 15:54:55 +0200 | [diff] [blame] | 98 | logging.warn('Stopping update-engine service failed. Already dead?') |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 99 | self._run('initctl start update-engine') |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 100 | |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 101 | if self.check_update_status() != UPDATER_IDLE: |
| 102 | raise ChromiumOSError('%s is not in an installable state' % |
| 103 | self.host.hostname) |
| 104 | |
| 105 | |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 106 | def _run(self, cmd, *args, **kwargs): |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 107 | """Abbreviated form of self.host.run(...)""" |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 108 | return self.host.run(cmd, *args, **kwargs) |
| 109 | |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 110 | |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 111 | def rootdev(self, options=''): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 112 | """Returns the stripped output of rootdev <options>. |
| 113 | |
| 114 | @param options: options to run rootdev. |
| 115 | |
| 116 | """ |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 117 | return self._run('rootdev %s' % options).stdout.strip() |
| 118 | |
| 119 | |
| 120 | def get_kernel_state(self): |
| 121 | """Returns the (<active>, <inactive>) kernel state as a pair.""" |
| 122 | active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0]) |
| 123 | if active_root == self.KERNEL_A['root']: |
| 124 | return self.KERNEL_A, self.KERNEL_B |
| 125 | elif active_root == self.KERNEL_B['root']: |
| 126 | return self.KERNEL_B, self.KERNEL_A |
| 127 | else: |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 128 | raise ChromiumOSError('Encountered unknown root partition: %s' % |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 129 | active_root) |
| 130 | |
| 131 | |
| 132 | def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'): |
| 133 | """Return numeric cgpt value for the specified flag, kernel, device. """ |
| 134 | return int(self._run('cgpt show -n -i %d %s %s' % ( |
| 135 | kernel['kernel'], flag, dev)).stdout.strip()) |
| 136 | |
| 137 | |
| 138 | def get_kernel_priority(self, kernel): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 139 | """Return numeric priority for the specified kernel. |
| 140 | |
| 141 | @param kernel: information of the given kernel, KERNEL_A or KERNEL_B. |
| 142 | |
| 143 | """ |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 144 | return self._cgpt('-P', kernel) |
| 145 | |
| 146 | |
| 147 | def get_kernel_success(self, kernel): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 148 | """Return boolean success flag for the specified kernel. |
| 149 | |
| 150 | @param kernel: information of the given kernel, KERNEL_A or KERNEL_B. |
| 151 | |
| 152 | """ |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 153 | return self._cgpt('-S', kernel) != 0 |
| 154 | |
| 155 | |
| 156 | def get_kernel_tries(self, kernel): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 157 | """Return tries count for the specified kernel. |
| 158 | |
| 159 | @param kernel: information of the given kernel, KERNEL_A or KERNEL_B. |
| 160 | |
| 161 | """ |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 162 | return self._cgpt('-T', kernel) |
Sean O | 267c00b | 2010-08-31 15:54:55 +0200 | [diff] [blame] | 163 | |
| 164 | |
Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 165 | def get_stateful_update_script(self): |
| 166 | """Returns the path to the stateful update script on the target.""" |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 167 | # We attempt to load the local stateful update path in 3 different |
| 168 | # ways. First we use the location specified in the autotest global |
| 169 | # config. If this doesn't exist, we attempt to use the Chromium OS |
| 170 | # Chroot path to the installed script. If all else fails, we use the |
| 171 | # stateful update script on the host. |
Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 172 | stateful_update_path = os.path.join( |
| 173 | global_config.global_config.get_config_value( |
| 174 | 'CROS', 'source_tree', default=''), |
| 175 | LOCAL_STATEFUL_UPDATE_PATH) |
| 176 | |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 177 | if not os.path.exists(stateful_update_path): |
| 178 | logging.warn('Could not find Chrome OS source location for ' |
| 179 | 'stateful_update script at %s, falling back to chroot ' |
| 180 | 'copy.', stateful_update_path) |
| 181 | stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH |
| 182 | |
| 183 | if not os.path.exists(stateful_update_path): |
| 184 | logging.warn('Could not chroot stateful_update script, falling ' |
| 185 | 'back on client copy.') |
| 186 | statefuldev_script = REMOTE_STATEUL_UPDATE_PATH |
| 187 | else: |
Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 188 | self.host.send_file( |
| 189 | stateful_update_path, STATEFUL_UPDATE, delete_dest=True) |
| 190 | statefuldev_script = STATEFUL_UPDATE |
Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 191 | |
| 192 | return statefuldev_script |
| 193 | |
| 194 | |
| 195 | def reset_stateful_partition(self): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 196 | """Clear any pending stateful update request.""" |
Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 197 | statefuldev_cmd = [self.get_stateful_update_script()] |
| 198 | statefuldev_cmd += ['--stateful_change=reset', '2>&1'] |
| 199 | # This shouldn't take any time at all. |
| 200 | self._run(' '.join(statefuldev_cmd), timeout=10) |
| 201 | |
| 202 | |
Sean O | 267c00b | 2010-08-31 15:54:55 +0200 | [diff] [blame] | 203 | def revert_boot_partition(self): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 204 | """Revert the boot partition.""" |
Dale Curtis | d9b26b9 | 2011-10-24 13:34:46 -0700 | [diff] [blame] | 205 | part = self.rootdev('-s') |
Sean O | 267c00b | 2010-08-31 15:54:55 +0200 | [diff] [blame] | 206 | logging.warn('Reverting update; Boot partition will be %s', part) |
| 207 | return self._run('/postinst %s 2>&1' % part) |
| 208 | |
| 209 | |
Gilad Arnold | 0ed760c | 2012-11-05 23:42:53 -0800 | [diff] [blame] | 210 | def trigger_update(self): |
| 211 | """Triggers a background update on a test image. |
| 212 | |
| 213 | @raise RootFSUpdateError if anything went wrong. |
| 214 | |
| 215 | """ |
| 216 | autoupdate_cmd = '%s --check_for_update --omaha_url=%s' % ( |
| 217 | UPDATER_BIN, self.update_url) |
| 218 | logging.info('triggering update via: %s', autoupdate_cmd) |
| 219 | try: |
| 220 | # This should return immediately, hence the short timeout. |
| 221 | self._run(autoupdate_cmd, timeout=10) |
| 222 | except error.AutoservRunError, e: |
| 223 | raise RootFSUpdateError('update triggering failed on %s: %s' % |
| 224 | (self.host.hostname, str(e))) |
| 225 | |
| 226 | |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 227 | def _update_root(self): |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 228 | logging.info('Updating root partition...') |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 229 | |
| 230 | # Run update_engine using the specified URL. |
| 231 | try: |
| 232 | autoupdate_cmd = '%s --update --omaha_url=%s 2>&1' % ( |
| 233 | UPDATER_BIN, self.update_url) |
| 234 | self._run(autoupdate_cmd, timeout=900) |
| 235 | except error.AutoservRunError: |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 236 | update_error = RootFSUpdateError('update-engine failed on %s' % |
| 237 | self.host.hostname) |
| 238 | self._update_error_queue.put(update_error) |
| 239 | raise update_error |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 240 | |
| 241 | # Check that the installer completed as expected. |
| 242 | status = self.check_update_status() |
| 243 | if status != UPDATER_NEED_REBOOT: |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 244 | update_error = RootFSUpdateError('update-engine error on %s: %s' % |
| 245 | (self.host.hostname, status)) |
| 246 | self._update_error_queue.put(update_error) |
| 247 | raise update_error |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 248 | |
| 249 | |
Chris Sosa | 7231260 | 2013-04-16 15:01:56 -0700 | [diff] [blame] | 250 | def update_stateful(self, clobber=True): |
| 251 | """Updates the stateful partition. |
| 252 | |
| 253 | @param clobber: If True, a clean stateful installation. |
| 254 | """ |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 255 | logging.info('Updating stateful partition...') |
joychen | 03eaad9 | 2013-06-26 09:55:21 -0700 | [diff] [blame^] | 256 | statefuldev_url = self.update_url.replace('update', |
| 257 | 'static') |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 258 | |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 259 | # Attempt stateful partition update; this must succeed so that the newly |
| 260 | # installed host is testable after update. |
Chris Sosa | 7231260 | 2013-04-16 15:01:56 -0700 | [diff] [blame] | 261 | statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url] |
| 262 | if clobber: |
| 263 | statefuldev_cmd.append('--stateful_change=clean') |
| 264 | |
| 265 | statefuldev_cmd.append('2>&1') |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 266 | try: |
| 267 | self._run(' '.join(statefuldev_cmd), timeout=600) |
| 268 | except error.AutoservRunError: |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 269 | update_error = StatefulUpdateError('stateful_update failed on %s' % |
| 270 | self.host.hostname) |
| 271 | self._update_error_queue.put(update_error) |
| 272 | raise update_error |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 273 | |
| 274 | |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 275 | def run_update(self, force_update, update_root=True): |
| 276 | """Update the DUT with image of specific version. |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 277 | |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 278 | @param force_update: True to update DUT even if it's running the same |
| 279 | version already. |
| 280 | @param update_root: True to force a kernel update. If it's False and |
| 281 | force_update is True, stateful update will be used to clean up |
| 282 | the DUT. |
| 283 | |
| 284 | """ |
| 285 | booted_version = self.get_build_id() |
| 286 | if (self.check_version() and not force_update): |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 287 | logging.info('System is already up to date. Skipping update.') |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 288 | return False |
| 289 | |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 290 | if self.update_version: |
| 291 | logging.info('Updating from version %s to %s.', |
| 292 | booted_version, self.update_version) |
Dale Curtis | 53d5586 | 2011-05-16 12:17:59 -0700 | [diff] [blame] | 293 | |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 294 | # Check that Dev Server is accepting connections (from autoserv's host). |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 295 | # If we can't talk to it, the machine host probably can't either. |
| 296 | auserver_host = urlparse.urlparse(self.update_url)[1] |
| 297 | try: |
| 298 | httplib.HTTPConnection(auserver_host).connect() |
Dale Curtis | 5c32c72 | 2011-05-04 19:24:23 -0700 | [diff] [blame] | 299 | except IOError: |
| 300 | raise ChromiumOSError( |
| 301 | 'Update server at %s not available' % auserver_host) |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 302 | |
Chris Sosa | a3ac215 | 2012-05-23 22:23:13 -0700 | [diff] [blame] | 303 | logging.info('Installing from %s to %s', self.update_url, |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 304 | self.host.hostname) |
| 305 | |
Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 306 | # Reset update state. |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 307 | self.reset_update_engine() |
Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 308 | self.reset_stateful_partition() |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 309 | |
Dale Curtis | 1e97318 | 2011-07-12 18:21:36 -0700 | [diff] [blame] | 310 | try: |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 311 | updaters = [ |
| 312 | multiprocessing.process.Process(target=self._update_root), |
Chris Sosa | 7231260 | 2013-04-16 15:01:56 -0700 | [diff] [blame] | 313 | multiprocessing.process.Process(target=self.update_stateful) |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 314 | ] |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 315 | if not update_root: |
| 316 | logging.info('Root update is skipped.') |
| 317 | updaters = updaters[1:] |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 318 | |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 319 | # Run the updaters in parallel. |
| 320 | for updater in updaters: updater.start() |
| 321 | for updater in updaters: updater.join() |
| 322 | |
| 323 | # Re-raise the first error that occurred. |
| 324 | if not self._update_error_queue.empty(): |
| 325 | update_error = self._update_error_queue.get() |
| 326 | self.revert_boot_partition() |
Chris Sosa | 5e4246b | 2012-05-22 18:05:22 -0700 | [diff] [blame] | 327 | self.reset_stateful_partition() |
Chris Sosa | 77556d8 | 2012-04-05 15:23:14 -0700 | [diff] [blame] | 328 | raise update_error |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 329 | |
Dale Curtis | 1e97318 | 2011-07-12 18:21:36 -0700 | [diff] [blame] | 330 | logging.info('Update complete.') |
| 331 | return True |
| 332 | except: |
| 333 | # Collect update engine logs in the event of failure. |
| 334 | if self.host.job: |
| 335 | logging.info('Collecting update engine logs...') |
| 336 | self.host.get_file( |
| 337 | UPDATER_LOGS, self.host.job.sysinfo.sysinfodir, |
| 338 | preserve_perm=False) |
| 339 | raise |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 340 | |
| 341 | |
Dale Curtis | a94c19c | 2011-05-02 15:05:17 -0700 | [diff] [blame] | 342 | def check_version(self): |
Dan Shi | 0f466e8 | 2013-02-22 15:44:58 -0800 | [diff] [blame] | 343 | """Check the image running in DUT has the desired version. |
| 344 | |
| 345 | @returns: True if the DUT's image version matches the version that |
| 346 | the autoupdater tries to update to. |
| 347 | |
| 348 | """ |
Dale Curtis | f57a25f | 2011-05-24 14:40:55 -0700 | [diff] [blame] | 349 | booted_version = self.get_build_id() |
Dan Shi | b95bb86 | 2013-03-22 16:29:28 -0700 | [diff] [blame] | 350 | return (self.update_version and |
| 351 | self.update_version.endswith(booted_version)) |
| 352 | |
| 353 | |
| 354 | def check_version_to_confirm_install(self): |
| 355 | """Check image running in DUT has the desired version to be installed. |
| 356 | |
| 357 | The method should not be used to check if DUT needs to have a full |
| 358 | reimage. Only use it to confirm a image is installed. |
| 359 | |
Dan Shi | 190c780 | 2013-04-04 13:05:30 -0700 | [diff] [blame] | 360 | The method is designed to verify version for following 4 scenarios with |
| 361 | samples of version to update to and expected booted version: |
| 362 | 1. trybot paladin build. |
| 363 | update version: trybot-lumpy-paladin/R27-3837.0.0-b123 |
| 364 | booted version: 3837.0.2013_03_21_1340 |
| 365 | |
| 366 | 2. trybot release build. |
| 367 | update version: trybot-lumpy-release/R27-3837.0.0-b456 |
| 368 | booted version: 3837.0.0 |
| 369 | |
| 370 | 3. buildbot official release build. |
| 371 | update version: lumpy-release/R27-3837.0.0 |
| 372 | booted version: 3837.0.0 |
| 373 | |
| 374 | 4. non-official paladin rc build. |
| 375 | update version: lumpy-paladin/R27-3878.0.0-rc7 |
| 376 | booted version: 3837.0.0-rc7 |
Dan Shi | b95bb86 | 2013-03-22 16:29:28 -0700 | [diff] [blame] | 377 | |
Dan Shi | 7f79551 | 2013-04-12 10:08:17 -0700 | [diff] [blame] | 378 | 5. chrome-perf build. |
| 379 | update version: lumpy-chrome-perf/R28-3837.0.0-b2996 |
| 380 | booted version: 3837.0.0 |
| 381 | |
Dan Shi | 73aa290 | 2013-05-03 11:22:11 -0700 | [diff] [blame] | 382 | 6. pgo-generate build. |
| 383 | update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996 |
| 384 | booted version: 3837.0.0-pgo-generate |
| 385 | |
Dan Shi | b95bb86 | 2013-03-22 16:29:28 -0700 | [diff] [blame] | 386 | When we are checking if a DUT needs to do a full install, we should NOT |
| 387 | use this method to check if the DUT is running the same version, since |
Dan Shi | 190c780 | 2013-04-04 13:05:30 -0700 | [diff] [blame] | 388 | it may return false positive for a DUT running trybot paladin build to |
| 389 | be updated to another trybot paladin build. |
Dan Shi | b95bb86 | 2013-03-22 16:29:28 -0700 | [diff] [blame] | 390 | |
Dan Shi | 190c780 | 2013-04-04 13:05:30 -0700 | [diff] [blame] | 391 | TODO: This logic has a bug if a trybot paladin build failed to be |
| 392 | installed in a DUT running an older trybot paladin build with same |
| 393 | platform number, but different build number (-b###). So to conclusively |
| 394 | determine if a tryjob paladin build is imaged successfully, we may need |
| 395 | to find out the date string from update url. |
Dan Shi | b95bb86 | 2013-03-22 16:29:28 -0700 | [diff] [blame] | 396 | |
| 397 | @returns: True if the DUT's image version (without the date string if |
| 398 | the image is a trybot build), matches the version that the |
| 399 | autoupdater is trying to update to. |
| 400 | |
| 401 | """ |
J. Richard Barnette | ec1de42 | 2013-06-26 15:44:07 -0700 | [diff] [blame] | 402 | # In the local_devserver case, we can't know the expected |
| 403 | # build, so just pass. |
| 404 | if not self.update_version: |
| 405 | return True |
| 406 | |
Dan Shi | b95bb86 | 2013-03-22 16:29:28 -0700 | [diff] [blame] | 407 | # Always try the default check_version method first, this prevents |
| 408 | # any backward compatibility issue. |
| 409 | if self.check_version(): |
| 410 | return True |
| 411 | |
Dan Shi | 190c780 | 2013-04-04 13:05:30 -0700 | [diff] [blame] | 412 | # Remove R#- and -b# at the end of build version |
| 413 | stripped_version = re.sub(r'(R\d+-|-b\d+)', '', self.update_version) |
| 414 | |
Dan Shi | b95bb86 | 2013-03-22 16:29:28 -0700 | [diff] [blame] | 415 | booted_version = self.get_build_id() |
Dan Shi | 190c780 | 2013-04-04 13:05:30 -0700 | [diff] [blame] | 416 | |
Dan Shi | 7f79551 | 2013-04-12 10:08:17 -0700 | [diff] [blame] | 417 | is_trybot_paladin_build = re.match(r'.+trybot-.+-paladin', |
| 418 | self.update_url) |
Dan Shi | 190c780 | 2013-04-04 13:05:30 -0700 | [diff] [blame] | 419 | |
Dan Shi | 7f79551 | 2013-04-12 10:08:17 -0700 | [diff] [blame] | 420 | # Replace date string with 0 in booted_version |
| 421 | booted_version_no_date = re.sub(r'\d{4}_\d{2}_\d{2}_\d+', '0', |
| 422 | booted_version) |
| 423 | has_date_string = booted_version != booted_version_no_date |
| 424 | |
Dan Shi | 73aa290 | 2013-05-03 11:22:11 -0700 | [diff] [blame] | 425 | is_pgo_generate_build = re.match(r'.+-pgo-generate', |
| 426 | self.update_url) |
| 427 | |
| 428 | # Remove |-pgo-generate| in booted_version |
| 429 | booted_version_no_pgo = booted_version.replace('-pgo-generate', '') |
| 430 | has_pgo_generate = booted_version != booted_version_no_pgo |
| 431 | |
Dan Shi | 7f79551 | 2013-04-12 10:08:17 -0700 | [diff] [blame] | 432 | if is_trybot_paladin_build: |
| 433 | if not has_date_string: |
| 434 | logging.error('A trybot paladin build is expected. Version ' + |
| 435 | '"%s" is not a paladin build.', booted_version) |
Dan Shi | 190c780 | 2013-04-04 13:05:30 -0700 | [diff] [blame] | 436 | return False |
| 437 | return stripped_version == booted_version_no_date |
Dan Shi | 73aa290 | 2013-05-03 11:22:11 -0700 | [diff] [blame] | 438 | elif is_pgo_generate_build: |
| 439 | if not has_pgo_generate: |
| 440 | logging.error('A pgo-generate build is expected. Version ' + |
| 441 | '"%s" is not a pgo-generate build.', |
| 442 | booted_version) |
| 443 | return False |
| 444 | return stripped_version == booted_version_no_pgo |
Dan Shi | 7f79551 | 2013-04-12 10:08:17 -0700 | [diff] [blame] | 445 | else: |
| 446 | if has_date_string: |
| 447 | logging.error('Unexpected date found in a non trybot paladin' + |
| 448 | ' build.') |
| 449 | return False |
| 450 | # Versioned build, i.e., rc or release build. |
| 451 | return stripped_version == booted_version |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 452 | |
Sean O | c053dfe | 2010-08-23 18:22:26 +0200 | [diff] [blame] | 453 | |
Sean O'Connor | 5346e4e | 2010-08-12 18:49:24 +0200 | [diff] [blame] | 454 | def get_build_id(self): |
Dale Curtis | 793f912 | 2011-02-04 15:00:52 -0800 | [diff] [blame] | 455 | """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release.""" |
| 456 | return self._run('grep CHROMEOS_RELEASE_VERSION' |
| 457 | ' /etc/lsb-release').stdout.split('=')[1].strip() |