blob: 17fb49007badd35dd6bd67681d03d2facc06ebbd [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):
Dale Curtis5c32c722011-05-04 19:24:23 -0700153 """Restarts the update-engine service."""
Darin Petkov7d572992010-09-23 10:11:05 -0700154 self._run('rm -f %s' % UPDATED_MARKER)
Sean O267c00b2010-08-31 15:54:55 +0200155 try:
156 self._run('initctl stop update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700157 except error.AutoservRunError:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700158 logging.warning('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +0200159 self._run('initctl start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700160
Sean Oc053dfe2010-08-23 18:22:26 +0200161 if self.check_update_status() != UPDATER_IDLE:
162 raise ChromiumOSError('%s is not in an installable state' %
163 self.host.hostname)
164
165
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200166 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700167 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200168 return self.host.run(cmd, *args, **kwargs)
169
Sean Oc053dfe2010-08-23 18:22:26 +0200170
Dale Curtisa94c19c2011-05-02 15:05:17 -0700171 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800172 """Returns the stripped output of rootdev <options>.
173
174 @param options: options to run rootdev.
175
176 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700177 return self._run('rootdev %s' % options).stdout.strip()
178
179
180 def get_kernel_state(self):
181 """Returns the (<active>, <inactive>) kernel state as a pair."""
182 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
183 if active_root == self.KERNEL_A['root']:
184 return self.KERNEL_A, self.KERNEL_B
185 elif active_root == self.KERNEL_B['root']:
186 return self.KERNEL_B, self.KERNEL_A
187 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700188 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700189 active_root)
190
191
192 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
193 """Return numeric cgpt value for the specified flag, kernel, device. """
194 return int(self._run('cgpt show -n -i %d %s %s' % (
195 kernel['kernel'], flag, dev)).stdout.strip())
196
197
198 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800199 """Return numeric priority for the specified kernel.
200
201 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
202
203 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700204 return self._cgpt('-P', kernel)
205
206
207 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800208 """Return boolean success flag for the specified kernel.
209
210 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
211
212 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700213 return self._cgpt('-S', kernel) != 0
214
215
216 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800217 """Return tries count for the specified kernel.
218
219 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
220
221 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700222 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200223
224
Chris Sosa5e4246b2012-05-22 18:05:22 -0700225 def get_stateful_update_script(self):
226 """Returns the path to the stateful update script on the target."""
Chris Sosaa3ac2152012-05-23 22:23:13 -0700227 # We attempt to load the local stateful update path in 3 different
228 # ways. First we use the location specified in the autotest global
229 # config. If this doesn't exist, we attempt to use the Chromium OS
230 # Chroot path to the installed script. If all else fails, we use the
231 # stateful update script on the host.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700232 stateful_update_path = os.path.join(
233 global_config.global_config.get_config_value(
234 'CROS', 'source_tree', default=''),
235 LOCAL_STATEFUL_UPDATE_PATH)
236
Chris Sosaa3ac2152012-05-23 22:23:13 -0700237 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700238 logging.warning('Could not find Chrome OS source location for '
Chris Sosaa3ac2152012-05-23 22:23:13 -0700239 'stateful_update script at %s, falling back to chroot '
240 'copy.', stateful_update_path)
241 stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
242
243 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700244 logging.warning('Could not chroot stateful_update script, falling '
Chris Sosaa3ac2152012-05-23 22:23:13 -0700245 'back on client copy.')
246 statefuldev_script = REMOTE_STATEUL_UPDATE_PATH
247 else:
Chris Sosa5e4246b2012-05-22 18:05:22 -0700248 self.host.send_file(
249 stateful_update_path, STATEFUL_UPDATE, delete_dest=True)
250 statefuldev_script = STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700251
252 return statefuldev_script
253
254
255 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800256 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700257 statefuldev_cmd = [self.get_stateful_update_script()]
258 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700259 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700260
261
Sean O267c00b2010-08-31 15:54:55 +0200262 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800263 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700264 part = self.rootdev('-s')
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700265 logging.warning('Reverting update; Boot partition will be %s', part)
Sean O267c00b2010-08-31 15:54:55 +0200266 return self._run('/postinst %s 2>&1' % part)
267
268
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800269 def trigger_update(self):
270 """Triggers a background update on a test image.
271
272 @raise RootFSUpdateError if anything went wrong.
273
274 """
275 autoupdate_cmd = '%s --check_for_update --omaha_url=%s' % (
276 UPDATER_BIN, self.update_url)
Gilad Arnold0338ff32013-10-02 12:16:26 -0700277 logging.info('Triggering update via: %s', autoupdate_cmd)
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800278 try:
Chris Sosa66d74072013-09-19 11:21:29 -0700279 self._run(autoupdate_cmd)
Don Garrettdf8aef72013-12-16 11:12:41 -0800280 except (error.AutoservSshPermissionDeniedError,
281 error.AutoservSSHTimeout) as e:
282 raise RootFSUpdateError('SSH on %s is seeing %s' %
283 (self.host.hostname, type(e).__name__))
284 except error.AutoservRunError as e:
285
286 # Check if the exit code is 255, if so it's probably a generic
287 # SSH error.
288 result = e.args[1]
289 if result.exit_status == 255:
290 raise RootFSUpdateError('SSH on %s is seeing a generic error.' %
291 self.host.hostname)
292
293 # We have ruled out all SSH cases, the error code is from
294 # update_engine_client, though we still don't know why.
Prashanth B32baa9b2014-03-13 13:23:01 -0700295 list_image_dir_contents(self.update_url)
Don Garrettdf8aef72013-12-16 11:12:41 -0800296 raise RootFSUpdateError(
297 'devserver unreachable, payload unavailable, '
298 'or AU bug (unlikely) on %s: %s' %
299 (self.host.hostname, type(e).__name__))
300
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800301
Chris Sosac1932172013-10-16 13:28:53 -0700302 def _verify_update_completed(self):
303 """Verifies that an update has completed.
304
305 @raise RootFSUpdateError: if verification fails.
306 """
307 status = self.check_update_status()
308 if status != UPDATER_NEED_REBOOT:
309 raise RootFSUpdateError('Update did not complete with correct '
310 'status. Expecting %s, actual %s' %
311 (UPDATER_NEED_REBOOT, status))
312
313
314 def rollback_rootfs(self, powerwash):
315 """Triggers rollback and waits for it to complete.
316
317 @param powerwash: If true, powerwash as part of rollback.
318
319 @raise RootFSUpdateError if anything went wrong.
320
321 """
Chris Sosac8617522014-06-09 23:22:26 +0000322 version = self.get_build_id()
323 # Introduced can_rollback in M36 (build 5772). # etc/lsb-release matches
324 # X.Y.Z. This version split just pulls the first part out.
325 try:
326 build_number = int(version.split('.')[0])
327 except ValueError:
328 logging.error('Could not parse build number.')
329 build_number = 0
330
331 if build_number >= 5772:
332 can_rollback_cmd = '%s --can_rollback' % (UPDATER_BIN)
333 logging.info('Checking for rollback.')
334 try:
335 self._run(can_rollback_cmd)
336 except error.AutoservRunError as e:
337 raise RootFSUpdateError("Rollback isn't possible on %s: %s" %
338 (self.host.hostname, str(e)))
339
340 rollback_cmd = '%s --rollback --follow' % (UPDATER_BIN)
Chris Sosac1932172013-10-16 13:28:53 -0700341 if not powerwash:
342 rollback_cmd += ' --nopowerwash'
343
Chris Sosac8617522014-06-09 23:22:26 +0000344 logging.info('Performing rollback.')
Chris Sosac1932172013-10-16 13:28:53 -0700345 try:
346 self._run(rollback_cmd)
Chris Sosac1932172013-10-16 13:28:53 -0700347 except error.AutoservRunError as e:
348 raise RootFSUpdateError('Rollback failed on %s: %s' %
349 (self.host.hostname, str(e)))
350
351 self._verify_update_completed()
352
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800353
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700354 def update_rootfs(self):
355 """Updates the rootfs partition only."""
Chris Sosa77556d82012-04-05 15:23:14 -0700356 logging.info('Updating root partition...')
Dale Curtis5c32c722011-05-04 19:24:23 -0700357
358 # Run update_engine using the specified URL.
359 try:
360 autoupdate_cmd = '%s --update --omaha_url=%s 2>&1' % (
361 UPDATER_BIN, self.update_url)
362 self._run(autoupdate_cmd, timeout=900)
363 except error.AutoservRunError:
Prashanth B32baa9b2014-03-13 13:23:01 -0700364 list_image_dir_contents(self.update_url)
Chris Sosa77556d82012-04-05 15:23:14 -0700365 update_error = RootFSUpdateError('update-engine failed on %s' %
366 self.host.hostname)
367 self._update_error_queue.put(update_error)
368 raise update_error
Dale Curtis5c32c722011-05-04 19:24:23 -0700369
Chris Sosac1932172013-10-16 13:28:53 -0700370 try:
371 self._verify_update_completed()
372 except RootFSUpdateError as e:
373 self._update_error_queue.put(e)
374 raise
Dale Curtis5c32c722011-05-04 19:24:23 -0700375
376
Chris Sosa72312602013-04-16 15:01:56 -0700377 def update_stateful(self, clobber=True):
378 """Updates the stateful partition.
379
380 @param clobber: If True, a clean stateful installation.
381 """
Chris Sosa77556d82012-04-05 15:23:14 -0700382 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700383 statefuldev_url = self.update_url.replace('update',
384 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700385
Dale Curtis5c32c722011-05-04 19:24:23 -0700386 # Attempt stateful partition update; this must succeed so that the newly
387 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700388 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
389 if clobber:
390 statefuldev_cmd.append('--stateful_change=clean')
391
392 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700393 try:
394 self._run(' '.join(statefuldev_cmd), timeout=600)
395 except error.AutoservRunError:
Chris Sosa77556d82012-04-05 15:23:14 -0700396 update_error = StatefulUpdateError('stateful_update failed on %s' %
397 self.host.hostname)
398 self._update_error_queue.put(update_error)
399 raise update_error
Dale Curtis5c32c722011-05-04 19:24:23 -0700400
401
Dan Shi0f466e82013-02-22 15:44:58 -0800402 def run_update(self, force_update, update_root=True):
403 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700404
Dan Shi0f466e82013-02-22 15:44:58 -0800405 @param force_update: True to update DUT even if it's running the same
406 version already.
407 @param update_root: True to force a kernel update. If it's False and
408 force_update is True, stateful update will be used to clean up
409 the DUT.
410
411 """
412 booted_version = self.get_build_id()
413 if (self.check_version() and not force_update):
Dale Curtisa94c19c2011-05-02 15:05:17 -0700414 logging.info('System is already up to date. Skipping update.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200415 return False
416
Chris Sosaa3ac2152012-05-23 22:23:13 -0700417 if self.update_version:
418 logging.info('Updating from version %s to %s.',
419 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700420
Dale Curtis5c32c722011-05-04 19:24:23 -0700421 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200422 # If we can't talk to it, the machine host probably can't either.
423 auserver_host = urlparse.urlparse(self.update_url)[1]
424 try:
425 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700426 except IOError:
427 raise ChromiumOSError(
428 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200429
Chris Sosaa3ac2152012-05-23 22:23:13 -0700430 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700431 self.host.hostname)
432
Chris Sosa5e4246b2012-05-22 18:05:22 -0700433 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700434 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700435 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200436
Dale Curtis1e973182011-07-12 18:21:36 -0700437 try:
Chris Sosa77556d82012-04-05 15:23:14 -0700438 updaters = [
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700439 multiprocessing.process.Process(target=self.update_rootfs),
Chris Sosa72312602013-04-16 15:01:56 -0700440 multiprocessing.process.Process(target=self.update_stateful)
Chris Sosa77556d82012-04-05 15:23:14 -0700441 ]
Dan Shi0f466e82013-02-22 15:44:58 -0800442 if not update_root:
443 logging.info('Root update is skipped.')
444 updaters = updaters[1:]
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200445
Chris Sosa77556d82012-04-05 15:23:14 -0700446 # Run the updaters in parallel.
447 for updater in updaters: updater.start()
448 for updater in updaters: updater.join()
449
450 # Re-raise the first error that occurred.
451 if not self._update_error_queue.empty():
452 update_error = self._update_error_queue.get()
453 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700454 self.reset_stateful_partition()
Chris Sosa77556d82012-04-05 15:23:14 -0700455 raise update_error
Sean Oc053dfe2010-08-23 18:22:26 +0200456
Dale Curtis1e973182011-07-12 18:21:36 -0700457 logging.info('Update complete.')
458 return True
459 except:
460 # Collect update engine logs in the event of failure.
461 if self.host.job:
462 logging.info('Collecting update engine logs...')
463 self.host.get_file(
464 UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
465 preserve_perm=False)
Prashanth B32baa9b2014-03-13 13:23:01 -0700466 list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700467 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700468 finally:
469 self.host.show_update_engine_log()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200470
471
Dale Curtisa94c19c2011-05-02 15:05:17 -0700472 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800473 """Check the image running in DUT has the desired version.
474
475 @returns: True if the DUT's image version matches the version that
476 the autoupdater tries to update to.
477
478 """
Dale Curtisf57a25f2011-05-24 14:40:55 -0700479 booted_version = self.get_build_id()
Dan Shib95bb862013-03-22 16:29:28 -0700480 return (self.update_version and
481 self.update_version.endswith(booted_version))
482
483
484 def check_version_to_confirm_install(self):
485 """Check image running in DUT has the desired version to be installed.
486
487 The method should not be used to check if DUT needs to have a full
488 reimage. Only use it to confirm a image is installed.
489
Dan Shi190c7802013-04-04 13:05:30 -0700490 The method is designed to verify version for following 4 scenarios with
491 samples of version to update to and expected booted version:
492 1. trybot paladin build.
493 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
494 booted version: 3837.0.2013_03_21_1340
495
496 2. trybot release build.
497 update version: trybot-lumpy-release/R27-3837.0.0-b456
498 booted version: 3837.0.0
499
500 3. buildbot official release build.
501 update version: lumpy-release/R27-3837.0.0
502 booted version: 3837.0.0
503
504 4. non-official paladin rc build.
505 update version: lumpy-paladin/R27-3878.0.0-rc7
506 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700507
Dan Shi7f795512013-04-12 10:08:17 -0700508 5. chrome-perf build.
509 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
510 booted version: 3837.0.0
511
Dan Shi73aa2902013-05-03 11:22:11 -0700512 6. pgo-generate build.
513 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
514 booted version: 3837.0.0-pgo-generate
515
Dan Shib95bb862013-03-22 16:29:28 -0700516 When we are checking if a DUT needs to do a full install, we should NOT
517 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700518 it may return false positive for a DUT running trybot paladin build to
519 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700520
Dan Shi190c7802013-04-04 13:05:30 -0700521 TODO: This logic has a bug if a trybot paladin build failed to be
522 installed in a DUT running an older trybot paladin build with same
523 platform number, but different build number (-b###). So to conclusively
524 determine if a tryjob paladin build is imaged successfully, we may need
525 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700526
527 @returns: True if the DUT's image version (without the date string if
528 the image is a trybot build), matches the version that the
529 autoupdater is trying to update to.
530
531 """
J. Richard Barnetteec1de422013-06-26 15:44:07 -0700532 # In the local_devserver case, we can't know the expected
533 # build, so just pass.
534 if not self.update_version:
535 return True
536
Dan Shib95bb862013-03-22 16:29:28 -0700537 # Always try the default check_version method first, this prevents
538 # any backward compatibility issue.
539 if self.check_version():
540 return True
541
Dan Shi190c7802013-04-04 13:05:30 -0700542 # Remove R#- and -b# at the end of build version
543 stripped_version = re.sub(r'(R\d+-|-b\d+)', '', self.update_version)
544
Dan Shib95bb862013-03-22 16:29:28 -0700545 booted_version = self.get_build_id()
Dan Shi190c7802013-04-04 13:05:30 -0700546
Dan Shi7f795512013-04-12 10:08:17 -0700547 is_trybot_paladin_build = re.match(r'.+trybot-.+-paladin',
548 self.update_url)
Dan Shi190c7802013-04-04 13:05:30 -0700549
Dan Shi7f795512013-04-12 10:08:17 -0700550 # Replace date string with 0 in booted_version
551 booted_version_no_date = re.sub(r'\d{4}_\d{2}_\d{2}_\d+', '0',
552 booted_version)
553 has_date_string = booted_version != booted_version_no_date
554
Dan Shi73aa2902013-05-03 11:22:11 -0700555 is_pgo_generate_build = re.match(r'.+-pgo-generate',
556 self.update_url)
557
558 # Remove |-pgo-generate| in booted_version
559 booted_version_no_pgo = booted_version.replace('-pgo-generate', '')
560 has_pgo_generate = booted_version != booted_version_no_pgo
561
Dan Shi7f795512013-04-12 10:08:17 -0700562 if is_trybot_paladin_build:
563 if not has_date_string:
564 logging.error('A trybot paladin build is expected. Version ' +
565 '"%s" is not a paladin build.', booted_version)
Dan Shi190c7802013-04-04 13:05:30 -0700566 return False
567 return stripped_version == booted_version_no_date
Dan Shi73aa2902013-05-03 11:22:11 -0700568 elif is_pgo_generate_build:
569 if not has_pgo_generate:
570 logging.error('A pgo-generate build is expected. Version ' +
571 '"%s" is not a pgo-generate build.',
572 booted_version)
573 return False
574 return stripped_version == booted_version_no_pgo
Dan Shi7f795512013-04-12 10:08:17 -0700575 else:
576 if has_date_string:
577 logging.error('Unexpected date found in a non trybot paladin' +
578 ' build.')
579 return False
580 # Versioned build, i.e., rc or release build.
581 return stripped_version == booted_version
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200582
Sean Oc053dfe2010-08-23 18:22:26 +0200583
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200584 def get_build_id(self):
Dale Curtis793f9122011-02-04 15:00:52 -0800585 """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release."""
586 return self._run('grep CHROMEOS_RELEASE_VERSION'
587 ' /etc/lsb-release').stdout.split('=')[1].strip()
Chris Sosa65425082013-10-16 13:26:22 -0700588
589
590 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
591 """Verifies that we fully booted given expected kernel state.
592
593 This method both verifies that we booted using the correct kernel
594 state and that the OS has marked the kernel as good.
595
596 @param expected_kernel_state: kernel state that we are verifying with
597 i.e. I expect to be booted onto partition 4 etc. See output of
598 get_kernel_state.
599 @param rollback_message: string to raise as a ChromiumOSError
600 if we booted with the wrong partition.
601
602 @raises ChromiumOSError: If we didn't.
603 """
604 # Figure out the newly active kernel.
605 active_kernel_state = self.get_kernel_state()[0]
606
607 # Check for rollback due to a bad build.
608 if (expected_kernel_state and
609 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800610
611 # Kernel crash reports should be wiped between test runs, but
612 # may persist from earlier parts of the test, or from problems
613 # with provisioning.
614 #
615 # Kernel crash reports will NOT be present if the crash happened
616 # before encrypted stateful is mounted.
617 #
618 # TODO(dgarrett): Integrate with server/crashcollect.py at some
619 # point.
620 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
621 if kernel_crashes:
622 rollback_message += ': kernel_crash'
623 logging.debug('Found %d kernel crash reports:',
624 len(kernel_crashes))
625 # The crash names contain timestamps that may be useful:
626 # kernel.20131207.005945.0.kcrash
627 for crash in kernel_crashes:
628 logging.debug(' %s', os.path.basename(crash))
629
Chris Sosa65425082013-10-16 13:26:22 -0700630 # Print out some information to make it easier to debug
631 # the rollback.
632 logging.debug('Dumping partition table.')
633 self._run('cgpt show $(rootdev -s -d)')
634 logging.debug('Dumping crossystem for firmware debugging.')
635 self._run('crossystem --all')
636 raise ChromiumOSError(rollback_message)
637
638 # Make sure chromeos-setgoodkernel runs.
639 try:
640 utils.poll_for_condition(
641 lambda: (self.get_kernel_tries(active_kernel_state) == 0
642 and self.get_kernel_success(active_kernel_state)),
643 exception=ChromiumOSError(),
644 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
645 except ChromiumOSError:
646 services_status = self._run('status system-services').stdout
647 if services_status != 'system-services start/running\n':
648 event = ('Chrome failed to reach login screen')
649 else:
650 event = ('update-engine failed to call '
651 'chromeos-setgoodkernel')
652 raise ChromiumOSError(
653 'After update and reboot, %s '
654 'within %d seconds' % (event,
655 self.KERNEL_UPDATE_TIMEOUT))