blob: 2bc8ce2179fd5dfbc709c0493c956ce7df4d2b10 [file] [log] [blame]
Chris Sosa5e4246b2012-05-22 18:05:22 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Sean O'Connor5346e4e2010-08-12 18:49:24 +02002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Don Garrett56b1cc82013-12-06 17:49:20 -08005import glob
Sean O'Connor5346e4e2010-08-12 18:49:24 +02006import httplib
7import logging
Chris Sosa77556d82012-04-05 15:23:14 -07008import multiprocessing
Dale Curtis5c32c722011-05-04 19:24:23 -07009import os
Sean O'Connor5346e4e2010-08-12 18:49:24 +020010import re
Sean O'Connor5346e4e2010-08-12 18:49:24 +020011import urlparse
Prashanth B32baa9b2014-03-13 13:23:01 -070012import urllib2
Sean O'Connor5346e4e2010-08-12 18:49:24 +020013
Chris Sosa65425082013-10-16 13:26:22 -070014from autotest_lib.client.bin import utils
Dale Curtis5c32c722011-05-04 19:24:23 -070015from autotest_lib.client.common_lib import error, global_config
Prashanth B32baa9b2014-03-13 13:23:01 -070016from autotest_lib.client.common_lib.cros import dev_server
Sean O'Connor5346e4e2010-08-12 18:49:24 +020017
Dale Curtis5c32c722011-05-04 19:24:23 -070018# Local stateful update path is relative to the CrOS source directory.
19LOCAL_STATEFUL_UPDATE_PATH = 'src/platform/dev/stateful_update'
Chris Sosaa3ac2152012-05-23 22:23:13 -070020LOCAL_CHROOT_STATEFUL_UPDATE_PATH = '/usr/bin/stateful_update'
Dale Curtis5c32c722011-05-04 19:24:23 -070021REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update'
22STATEFUL_UPDATE = '/tmp/stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020023UPDATER_BIN = '/usr/bin/update_engine_client'
24UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020025UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
Darin Petkov7d572992010-09-23 10:11:05 -070026UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
Gwendal Grignou81939562014-02-14 12:25:00 -080027UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
beeps5e8c45a2013-12-17 22:05:11 -080028# A list of update engine client states that occur after an update is triggered.
29UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FORUPDATE',
30 'UPDATE_STATUS_UPDATE_AVAILABLE',
31 'UPDATE_STATUS_DOWNLOADING',
32 'UPDATE_STATUS_FINALIZING']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020033
34class ChromiumOSError(error.InstallError):
35 """Generic error for ChromiumOS-specific exceptions."""
36 pass
37
38
Chris Sosa77556d82012-04-05 15:23:14 -070039class RootFSUpdateError(ChromiumOSError):
40 """Raised when the RootFS fails to update."""
41 pass
42
43
44class StatefulUpdateError(ChromiumOSError):
45 """Raised when the stateful partition fails to update."""
46 pass
47
48
Sean O'Connor5346e4e2010-08-12 18:49:24 +020049def url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -080050 """Return the version based on update_url.
51
52 @param update_url: url to the image to update to.
53
54 """
Dale Curtisddfdb942011-07-14 13:59:24 -070055 # The Chrome OS version is generally the last element in the URL. The only
56 # exception is delta update URLs, which are rooted under the version; e.g.,
57 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
58 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -070059 return re.sub('/au/.*', '',
60 urlparse.urlparse(update_url).path).split('/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +020061
62
Scott Zawalskieadbf702013-03-14 09:23:06 -040063def url_to_image_name(update_url):
64 """Return the image name based on update_url.
65
66 From a URL like:
67 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
68 return lumpy-release/R27-3837.0.0
69
70 @param update_url: url to the image to update to.
71 @returns a string representing the image name in the update_url.
72
73 """
74 return '/'.join(urlparse.urlparse(update_url).path.split('/')[-2:])
75
76
Prashanth B32baa9b2014-03-13 13:23:01 -070077def _get_devserver_build_from_update_url(update_url):
78 """Get the devserver and build from the update url.
79
80 @param update_url: The url for update.
81 Eg: http://devserver:port/update/build.
82
83 @return: A tuple of (devserver url, build) or None if the update_url
84 doesn't match the expected pattern.
85
86 @raises ValueError: If the update_url doesn't match the expected pattern.
87 @raises ValueError: If no global_config was found, or it doesn't contain an
88 image_url_pattern.
89 """
90 pattern = global_config.global_config.get_config_value(
91 'CROS', 'image_url_pattern', type=str, default='')
92 if not pattern:
93 raise ValueError('Cannot parse update_url, the global config needs '
94 'an image_url_pattern.')
95 re_pattern = pattern.replace('%s', '(\S+)')
96 parts = re.search(re_pattern, update_url)
97 if not parts or len(parts.groups()) < 2:
98 raise ValueError('%s is not an update url' % update_url)
99 return parts.groups()
100
101
102def list_image_dir_contents(update_url):
103 """Lists the contents of the devserver for a given build/update_url.
104
105 @param update_url: An update url. Eg: http://devserver:port/update/build.
106 """
107 if not update_url:
108 logging.warning('Need update_url to list contents of the devserver.')
109 return
110 error_msg = 'Cannot check contents of devserver, update url %s' % update_url
111 try:
112 devserver_url, build = _get_devserver_build_from_update_url(update_url)
113 except ValueError as e:
114 logging.warning('%s: %s', error_msg, e)
115 return
116 devserver = dev_server.ImageServer(devserver_url)
117 try:
118 devserver.list_image_dir(build)
119 # The devserver will retry on URLError to avoid flaky connections, but will
120 # eventually raise the URLError if it persists. All HTTPErrors get
121 # converted to DevServerExceptions.
122 except (dev_server.DevServerException, urllib2.URLError) as e:
123 logging.warning('%s: %s', error_msg, e)
124
125
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200126class ChromiumOSUpdater():
Dan Shi0f466e82013-02-22 15:44:58 -0800127 """Helper class used to update DUT with image of desired version."""
Dale Curtisa94c19c2011-05-02 15:05:17 -0700128 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
129 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
Chris Sosa65425082013-10-16 13:26:22 -0700130 # Time to wait for new kernel to be marked successful after
131 # auto update.
132 KERNEL_UPDATE_TIMEOUT = 120
Dale Curtisa94c19c2011-05-02 15:05:17 -0700133
134
Chris Sosaa3ac2152012-05-23 22:23:13 -0700135 def __init__(self, update_url, host=None, local_devserver=False):
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200136 self.host = host
137 self.update_url = update_url
Chris Sosa77556d82012-04-05 15:23:14 -0700138 self._update_error_queue = multiprocessing.Queue(2)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700139 self.local_devserver = local_devserver
140 if not local_devserver:
141 self.update_version = url_to_version(update_url)
142 else:
143 self.update_version = None
Sean Oc053dfe2010-08-23 18:22:26 +0200144
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200145 def check_update_status(self):
Dale Curtis5c32c722011-05-04 19:24:23 -0700146 """Return current status from update-engine."""
147 update_status = self._run(
148 '%s -status 2>&1 | grep CURRENT_OP' % UPDATER_BIN)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200149 return update_status.stdout.strip().split('=')[-1]
150
Sean Oc053dfe2010-08-23 18:22:26 +0200151
152 def reset_update_engine(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700153 """Resets the host to prepare for a clean update regardless of state."""
Darin Petkov7d572992010-09-23 10:11:05 -0700154 self._run('rm -f %s' % UPDATED_MARKER)
Chris Sosae92399e2015-04-24 11:32:59 -0700155 self._run('stop ui || true')
156 self._run('stop update-engine || true')
157 self._run('start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700158
Sean Oc053dfe2010-08-23 18:22:26 +0200159 if self.check_update_status() != UPDATER_IDLE:
160 raise ChromiumOSError('%s is not in an installable state' %
161 self.host.hostname)
162
163
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200164 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700165 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200166 return self.host.run(cmd, *args, **kwargs)
167
Sean Oc053dfe2010-08-23 18:22:26 +0200168
Dale Curtisa94c19c2011-05-02 15:05:17 -0700169 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800170 """Returns the stripped output of rootdev <options>.
171
172 @param options: options to run rootdev.
173
174 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700175 return self._run('rootdev %s' % options).stdout.strip()
176
177
178 def get_kernel_state(self):
179 """Returns the (<active>, <inactive>) kernel state as a pair."""
180 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
181 if active_root == self.KERNEL_A['root']:
182 return self.KERNEL_A, self.KERNEL_B
183 elif active_root == self.KERNEL_B['root']:
184 return self.KERNEL_B, self.KERNEL_A
185 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700186 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700187 active_root)
188
189
190 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
191 """Return numeric cgpt value for the specified flag, kernel, device. """
192 return int(self._run('cgpt show -n -i %d %s %s' % (
193 kernel['kernel'], flag, dev)).stdout.strip())
194
195
196 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800197 """Return numeric priority for the specified kernel.
198
199 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
200
201 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700202 return self._cgpt('-P', kernel)
203
204
205 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800206 """Return boolean success flag for the specified kernel.
207
208 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
209
210 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700211 return self._cgpt('-S', kernel) != 0
212
213
214 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800215 """Return tries count for the specified kernel.
216
217 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
218
219 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700220 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200221
222
Chris Sosa5e4246b2012-05-22 18:05:22 -0700223 def get_stateful_update_script(self):
224 """Returns the path to the stateful update script on the target."""
Chris Sosaa3ac2152012-05-23 22:23:13 -0700225 # We attempt to load the local stateful update path in 3 different
226 # ways. First we use the location specified in the autotest global
227 # config. If this doesn't exist, we attempt to use the Chromium OS
228 # Chroot path to the installed script. If all else fails, we use the
229 # stateful update script on the host.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700230 stateful_update_path = os.path.join(
231 global_config.global_config.get_config_value(
232 'CROS', 'source_tree', default=''),
233 LOCAL_STATEFUL_UPDATE_PATH)
234
Chris Sosaa3ac2152012-05-23 22:23:13 -0700235 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700236 logging.warning('Could not find Chrome OS source location for '
Chris Sosaa3ac2152012-05-23 22:23:13 -0700237 'stateful_update script at %s, falling back to chroot '
238 'copy.', stateful_update_path)
239 stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
240
241 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700242 logging.warning('Could not chroot stateful_update script, falling '
Chris Sosaa3ac2152012-05-23 22:23:13 -0700243 'back on client copy.')
244 statefuldev_script = REMOTE_STATEUL_UPDATE_PATH
245 else:
Chris Sosa5e4246b2012-05-22 18:05:22 -0700246 self.host.send_file(
247 stateful_update_path, STATEFUL_UPDATE, delete_dest=True)
248 statefuldev_script = STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700249
250 return statefuldev_script
251
252
253 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800254 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700255 statefuldev_cmd = [self.get_stateful_update_script()]
256 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700257 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700258
259
Sean O267c00b2010-08-31 15:54:55 +0200260 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800261 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700262 part = self.rootdev('-s')
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700263 logging.warning('Reverting update; Boot partition will be %s', part)
Sean O267c00b2010-08-31 15:54:55 +0200264 return self._run('/postinst %s 2>&1' % part)
265
266
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800267 def trigger_update(self):
268 """Triggers a background update on a test image.
269
270 @raise RootFSUpdateError if anything went wrong.
271
272 """
273 autoupdate_cmd = '%s --check_for_update --omaha_url=%s' % (
274 UPDATER_BIN, self.update_url)
Gilad Arnold0338ff32013-10-02 12:16:26 -0700275 logging.info('Triggering update via: %s', autoupdate_cmd)
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800276 try:
Chris Sosa66d74072013-09-19 11:21:29 -0700277 self._run(autoupdate_cmd)
Don Garrettdf8aef72013-12-16 11:12:41 -0800278 except (error.AutoservSshPermissionDeniedError,
279 error.AutoservSSHTimeout) as e:
280 raise RootFSUpdateError('SSH on %s is seeing %s' %
281 (self.host.hostname, type(e).__name__))
282 except error.AutoservRunError as e:
283
284 # Check if the exit code is 255, if so it's probably a generic
285 # SSH error.
286 result = e.args[1]
287 if result.exit_status == 255:
288 raise RootFSUpdateError('SSH on %s is seeing a generic error.' %
289 self.host.hostname)
290
291 # We have ruled out all SSH cases, the error code is from
292 # update_engine_client, though we still don't know why.
Prashanth B32baa9b2014-03-13 13:23:01 -0700293 list_image_dir_contents(self.update_url)
Don Garrettdf8aef72013-12-16 11:12:41 -0800294 raise RootFSUpdateError(
295 'devserver unreachable, payload unavailable, '
296 'or AU bug (unlikely) on %s: %s' %
297 (self.host.hostname, type(e).__name__))
298
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800299
Chris Sosac1932172013-10-16 13:28:53 -0700300 def _verify_update_completed(self):
301 """Verifies that an update has completed.
302
303 @raise RootFSUpdateError: if verification fails.
304 """
305 status = self.check_update_status()
306 if status != UPDATER_NEED_REBOOT:
307 raise RootFSUpdateError('Update did not complete with correct '
308 'status. Expecting %s, actual %s' %
309 (UPDATER_NEED_REBOOT, status))
310
311
312 def rollback_rootfs(self, powerwash):
313 """Triggers rollback and waits for it to complete.
314
315 @param powerwash: If true, powerwash as part of rollback.
316
317 @raise RootFSUpdateError if anything went wrong.
318
319 """
Dan Shi549fb822015-03-24 18:01:11 -0700320 version = self.host.get_release_version()
Chris Sosac8617522014-06-09 23:22:26 +0000321 # Introduced can_rollback in M36 (build 5772). # etc/lsb-release matches
322 # X.Y.Z. This version split just pulls the first part out.
323 try:
324 build_number = int(version.split('.')[0])
325 except ValueError:
326 logging.error('Could not parse build number.')
327 build_number = 0
328
329 if build_number >= 5772:
330 can_rollback_cmd = '%s --can_rollback' % (UPDATER_BIN)
331 logging.info('Checking for rollback.')
332 try:
333 self._run(can_rollback_cmd)
334 except error.AutoservRunError as e:
335 raise RootFSUpdateError("Rollback isn't possible on %s: %s" %
336 (self.host.hostname, str(e)))
337
338 rollback_cmd = '%s --rollback --follow' % (UPDATER_BIN)
Chris Sosac1932172013-10-16 13:28:53 -0700339 if not powerwash:
340 rollback_cmd += ' --nopowerwash'
341
Chris Sosac8617522014-06-09 23:22:26 +0000342 logging.info('Performing rollback.')
Chris Sosac1932172013-10-16 13:28:53 -0700343 try:
344 self._run(rollback_cmd)
Chris Sosac1932172013-10-16 13:28:53 -0700345 except error.AutoservRunError as e:
346 raise RootFSUpdateError('Rollback failed on %s: %s' %
347 (self.host.hostname, str(e)))
348
349 self._verify_update_completed()
350
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800351
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700352 def update_rootfs(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700353 """Run the standard command to force an update."""
Dale Curtis5c32c722011-05-04 19:24:23 -0700354 try:
355 autoupdate_cmd = '%s --update --omaha_url=%s 2>&1' % (
356 UPDATER_BIN, self.update_url)
Chris Sosae92399e2015-04-24 11:32:59 -0700357 self._run(autoupdate_cmd, timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700358 except error.AutoservRunError:
Prashanth B32baa9b2014-03-13 13:23:01 -0700359 list_image_dir_contents(self.update_url)
Chris Sosa77556d82012-04-05 15:23:14 -0700360 update_error = RootFSUpdateError('update-engine failed on %s' %
361 self.host.hostname)
362 self._update_error_queue.put(update_error)
363 raise update_error
Simran Basi3b858a22015-03-17 16:23:24 -0700364 except Exception as e:
365 # Don't allow other exceptions to not be caught.
366 self._update_error_queue.put(e)
367 raise e
Dale Curtis5c32c722011-05-04 19:24:23 -0700368
Chris Sosac1932172013-10-16 13:28:53 -0700369 try:
370 self._verify_update_completed()
371 except RootFSUpdateError as e:
372 self._update_error_queue.put(e)
373 raise
Dale Curtis5c32c722011-05-04 19:24:23 -0700374
375
Chris Sosa72312602013-04-16 15:01:56 -0700376 def update_stateful(self, clobber=True):
377 """Updates the stateful partition.
378
379 @param clobber: If True, a clean stateful installation.
380 """
Chris Sosa77556d82012-04-05 15:23:14 -0700381 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700382 statefuldev_url = self.update_url.replace('update',
383 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700384
Dale Curtis5c32c722011-05-04 19:24:23 -0700385 # Attempt stateful partition update; this must succeed so that the newly
386 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700387 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
388 if clobber:
389 statefuldev_cmd.append('--stateful_change=clean')
390
391 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700392 try:
393 self._run(' '.join(statefuldev_cmd), timeout=600)
394 except error.AutoservRunError:
Chris Sosa77556d82012-04-05 15:23:14 -0700395 update_error = StatefulUpdateError('stateful_update failed on %s' %
396 self.host.hostname)
397 self._update_error_queue.put(update_error)
398 raise update_error
Simran Basi3b858a22015-03-17 16:23:24 -0700399 except Exception as e:
400 # Don't allow other exceptions to not be caught.
401 self._update_error_queue.put(e)
402 raise e
Dale Curtis5c32c722011-05-04 19:24:23 -0700403
404
Chris Sosae92399e2015-04-24 11:32:59 -0700405 def run_update(self, update_root=True):
Dan Shi0f466e82013-02-22 15:44:58 -0800406 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700407
Chris Sosae92399e2015-04-24 11:32:59 -0700408 @param update_root: True to force a rootfs update.
Dan Shi0f466e82013-02-22 15:44:58 -0800409 """
Dan Shi549fb822015-03-24 18:01:11 -0700410 booted_version = self.host.get_release_version()
Chris Sosaa3ac2152012-05-23 22:23:13 -0700411 if self.update_version:
412 logging.info('Updating from version %s to %s.',
413 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700414
Dale Curtis5c32c722011-05-04 19:24:23 -0700415 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200416 # If we can't talk to it, the machine host probably can't either.
417 auserver_host = urlparse.urlparse(self.update_url)[1]
418 try:
419 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700420 except IOError:
421 raise ChromiumOSError(
422 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200423
Chris Sosaa3ac2152012-05-23 22:23:13 -0700424 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700425 self.host.hostname)
426
Chris Sosa5e4246b2012-05-22 18:05:22 -0700427 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700428 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700429 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200430
Dale Curtis1e973182011-07-12 18:21:36 -0700431 try:
Chris Sosa77556d82012-04-05 15:23:14 -0700432 updaters = [
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700433 multiprocessing.process.Process(target=self.update_rootfs),
Chris Sosa72312602013-04-16 15:01:56 -0700434 multiprocessing.process.Process(target=self.update_stateful)
Chris Sosa77556d82012-04-05 15:23:14 -0700435 ]
Dan Shi0f466e82013-02-22 15:44:58 -0800436 if not update_root:
437 logging.info('Root update is skipped.')
438 updaters = updaters[1:]
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200439
Chris Sosa77556d82012-04-05 15:23:14 -0700440 # Run the updaters in parallel.
441 for updater in updaters: updater.start()
442 for updater in updaters: updater.join()
443
444 # Re-raise the first error that occurred.
445 if not self._update_error_queue.empty():
446 update_error = self._update_error_queue.get()
447 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700448 self.reset_stateful_partition()
Chris Sosa77556d82012-04-05 15:23:14 -0700449 raise update_error
Sean Oc053dfe2010-08-23 18:22:26 +0200450
Dale Curtis1e973182011-07-12 18:21:36 -0700451 logging.info('Update complete.')
Dale Curtis1e973182011-07-12 18:21:36 -0700452 except:
453 # Collect update engine logs in the event of failure.
454 if self.host.job:
455 logging.info('Collecting update engine logs...')
456 self.host.get_file(
457 UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
458 preserve_perm=False)
Prashanth B32baa9b2014-03-13 13:23:01 -0700459 list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700460 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700461 finally:
462 self.host.show_update_engine_log()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200463
464
Dale Curtisa94c19c2011-05-02 15:05:17 -0700465 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800466 """Check the image running in DUT has the desired version.
467
468 @returns: True if the DUT's image version matches the version that
469 the autoupdater tries to update to.
470
471 """
Dan Shi549fb822015-03-24 18:01:11 -0700472 booted_version = self.host.get_release_version()
Dan Shib95bb862013-03-22 16:29:28 -0700473 return (self.update_version and
474 self.update_version.endswith(booted_version))
475
476
477 def check_version_to_confirm_install(self):
478 """Check image running in DUT has the desired version to be installed.
479
480 The method should not be used to check if DUT needs to have a full
481 reimage. Only use it to confirm a image is installed.
482
Dan Shi549fb822015-03-24 18:01:11 -0700483 The method is designed to verify version for following 6 scenarios with
Dan Shi190c7802013-04-04 13:05:30 -0700484 samples of version to update to and expected booted version:
485 1. trybot paladin build.
486 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
487 booted version: 3837.0.2013_03_21_1340
488
489 2. trybot release build.
490 update version: trybot-lumpy-release/R27-3837.0.0-b456
491 booted version: 3837.0.0
492
493 3. buildbot official release build.
494 update version: lumpy-release/R27-3837.0.0
495 booted version: 3837.0.0
496
497 4. non-official paladin rc build.
498 update version: lumpy-paladin/R27-3878.0.0-rc7
499 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700500
Dan Shi7f795512013-04-12 10:08:17 -0700501 5. chrome-perf build.
502 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
503 booted version: 3837.0.0
504
Dan Shi73aa2902013-05-03 11:22:11 -0700505 6. pgo-generate build.
506 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
507 booted version: 3837.0.0-pgo-generate
508
Dan Shib95bb862013-03-22 16:29:28 -0700509 When we are checking if a DUT needs to do a full install, we should NOT
510 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700511 it may return false positive for a DUT running trybot paladin build to
512 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700513
Dan Shi190c7802013-04-04 13:05:30 -0700514 TODO: This logic has a bug if a trybot paladin build failed to be
515 installed in a DUT running an older trybot paladin build with same
516 platform number, but different build number (-b###). So to conclusively
517 determine if a tryjob paladin build is imaged successfully, we may need
518 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700519
520 @returns: True if the DUT's image version (without the date string if
521 the image is a trybot build), matches the version that the
522 autoupdater is trying to update to.
523
524 """
J. Richard Barnetteec1de422013-06-26 15:44:07 -0700525 # In the local_devserver case, we can't know the expected
526 # build, so just pass.
527 if not self.update_version:
528 return True
529
Dan Shib95bb862013-03-22 16:29:28 -0700530 # Always try the default check_version method first, this prevents
531 # any backward compatibility issue.
532 if self.check_version():
533 return True
534
Dan Shi549fb822015-03-24 18:01:11 -0700535 return utils.version_match(self.update_version,
536 self.host.get_release_version(),
537 self.update_url)
Chris Sosa65425082013-10-16 13:26:22 -0700538
539
540 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
541 """Verifies that we fully booted given expected kernel state.
542
543 This method both verifies that we booted using the correct kernel
544 state and that the OS has marked the kernel as good.
545
546 @param expected_kernel_state: kernel state that we are verifying with
547 i.e. I expect to be booted onto partition 4 etc. See output of
548 get_kernel_state.
549 @param rollback_message: string to raise as a ChromiumOSError
550 if we booted with the wrong partition.
551
552 @raises ChromiumOSError: If we didn't.
553 """
554 # Figure out the newly active kernel.
555 active_kernel_state = self.get_kernel_state()[0]
556
557 # Check for rollback due to a bad build.
558 if (expected_kernel_state and
559 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800560
561 # Kernel crash reports should be wiped between test runs, but
562 # may persist from earlier parts of the test, or from problems
563 # with provisioning.
564 #
565 # Kernel crash reports will NOT be present if the crash happened
566 # before encrypted stateful is mounted.
567 #
568 # TODO(dgarrett): Integrate with server/crashcollect.py at some
569 # point.
570 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
571 if kernel_crashes:
572 rollback_message += ': kernel_crash'
573 logging.debug('Found %d kernel crash reports:',
574 len(kernel_crashes))
575 # The crash names contain timestamps that may be useful:
576 # kernel.20131207.005945.0.kcrash
577 for crash in kernel_crashes:
Dan Shi0942b1d2015-03-31 11:07:00 -0700578 logging.debug(' %s', os.path.basename(crash))
Don Garrett56b1cc82013-12-06 17:49:20 -0800579
Chris Sosa65425082013-10-16 13:26:22 -0700580 # Print out some information to make it easier to debug
581 # the rollback.
582 logging.debug('Dumping partition table.')
583 self._run('cgpt show $(rootdev -s -d)')
584 logging.debug('Dumping crossystem for firmware debugging.')
585 self._run('crossystem --all')
586 raise ChromiumOSError(rollback_message)
587
588 # Make sure chromeos-setgoodkernel runs.
589 try:
590 utils.poll_for_condition(
591 lambda: (self.get_kernel_tries(active_kernel_state) == 0
592 and self.get_kernel_success(active_kernel_state)),
593 exception=ChromiumOSError(),
594 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
595 except ChromiumOSError:
596 services_status = self._run('status system-services').stdout
597 if services_status != 'system-services start/running\n':
598 event = ('Chrome failed to reach login screen')
599 else:
600 event = ('update-engine failed to call '
601 'chromeos-setgoodkernel')
602 raise ChromiumOSError(
603 'After update and reboot, %s '
604 'within %d seconds' % (event,
605 self.KERNEL_UPDATE_TIMEOUT))