blob: 60f27eabc6126728a2bea6116431ad6896b8ff43 [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 logging
Dale Curtis5c32c722011-05-04 19:24:23 -07007import os
Sean O'Connor5346e4e2010-08-12 18:49:24 +02008import re
Prashanth B32baa9b2014-03-13 13:23:01 -07009import urllib2
Richard Barnette0beb14b2018-05-15 18:07:52 +000010import urlparse
Sean O'Connor5346e4e2010-08-12 18:49:24 +020011
Chris Sosa65425082013-10-16 13:26:22 -070012from autotest_lib.client.bin import utils
Dale Curtis5c32c722011-05-04 19:24:23 -070013from autotest_lib.client.common_lib import error, global_config
Prashanth B32baa9b2014-03-13 13:23:01 -070014from autotest_lib.client.common_lib.cros import dev_server
Richard Barnette0beb14b2018-05-15 18:07:52 +000015from autotest_lib.server import autotest
Shelley Chen61d28982016-10-28 09:40:20 -070016from autotest_lib.server import utils as server_utils
Richard Barnette0beb14b2018-05-15 18:07:52 +000017from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
18from autotest_lib.server.cros.dynamic_suite import tools
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -080019from chromite.lib import retry_util
Dan Shif3a35f72016-01-25 11:18:14 -080020
Shelley Chen16b8df32016-10-27 16:24:21 -070021try:
22 from chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080023except ImportError:
24 metrics = utils.metrics_mock
Sean O'Connor5346e4e2010-08-12 18:49:24 +020025
Gwendal Grignou3e96cc22017-06-07 16:22:51 -070026
Richard Barnette621a8e42018-06-25 17:34:11 -070027def _metric_name(base_name):
28 return 'chromeos/autotest/provision/' + base_name
29
30
Dale Curtis5c32c722011-05-04 19:24:23 -070031# Local stateful update path is relative to the CrOS source directory.
Sean O'Connor5346e4e2010-08-12 18:49:24 +020032UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020033UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
beeps5e8c45a2013-12-17 22:05:11 -080034# A list of update engine client states that occur after an update is triggered.
Garry Wangcd769872019-06-07 16:04:17 -070035UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FOR_UPDATE',
beeps5e8c45a2013-12-17 22:05:11 -080036 'UPDATE_STATUS_UPDATE_AVAILABLE',
37 'UPDATE_STATUS_DOWNLOADING',
Garry Wangcd769872019-06-07 16:04:17 -070038 'UPDATE_STATUS_FINALIZING',
39 'UPDATE_STATUS_VERIFYING',
40 'UPDATE_STATUS_REPORTING_ERROR_EVENT',
41 'UPDATE_STATUS_ATTEMPTING_ROLLBACK']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020042
Richard Barnette0beb14b2018-05-15 18:07:52 +000043
Richard Barnette3e8b2282018-05-15 20:42:20 +000044_STATEFUL_UPDATE_SCRIPT = 'stateful_update'
Richard Barnettee86b1ce2018-06-07 10:37:23 -070045_QUICK_PROVISION_SCRIPT = 'quick-provision'
Richard Barnette3e8b2282018-05-15 20:42:20 +000046
47_UPDATER_BIN = '/usr/bin/update_engine_client'
48_UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
49
50_KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
51_KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
52
53# Time to wait for new kernel to be marked successful after
54# auto update.
55_KERNEL_UPDATE_TIMEOUT = 120
56
57
Richard Barnette0beb14b2018-05-15 18:07:52 +000058# PROVISION_FAILED - A flag file to indicate provision failures. The
59# file is created at the start of any AU procedure (see
Richard Barnette9d43e562018-06-05 17:20:10 +000060# `ChromiumOSUpdater._prepare_host()`). The file's location in
Richard Barnette0beb14b2018-05-15 18:07:52 +000061# stateful means that on successul update it will be removed. Thus, if
62# this file exists, it indicates that we've tried and failed in a
63# previous attempt to update.
64PROVISION_FAILED = '/var/tmp/provision_failed'
65
66
Richard Barnette3e8b2282018-05-15 20:42:20 +000067# A flag file used to enable special handling in lab DUTs. Some
68# parts of the system in Chromium OS test images will behave in ways
69# convenient to the test lab when this file is present. Generally,
70# we create this immediately after any update completes.
71_LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
72
73
Richard Barnette3ef29a82018-06-28 13:52:54 -070074# _TARGET_VERSION - A file containing the new version to which we plan
75# to update. This file is used by the CrOS shutdown code to detect and
76# handle certain version downgrade cases. Specifically: Downgrading
77# may trigger an unwanted powerwash in the target build when the
78# following conditions are met:
79# * Source build is a v4.4 kernel with R69-10756.0.0 or later.
80# * Target build predates the R69-10756.0.0 cutoff.
81# When this file is present and indicates a downgrade, the OS shutdown
82# code on the DUT knows how to prevent the powerwash.
83_TARGET_VERSION = '/run/update_target_version'
84
85
Richard Barnette5adb6d42018-06-28 15:52:32 -070086# _REBOOT_FAILURE_MESSAGE - This is the standard message text returned
87# when the Host.reboot() method fails. The source of this text comes
88# from `wait_for_restart()` in client/common_lib/hosts/base_classes.py.
89
90_REBOOT_FAILURE_MESSAGE = 'Host did not return from reboot'
91
92
Richard Barnette9d43e562018-06-05 17:20:10 +000093class RootFSUpdateError(error.TestFail):
Chris Sosa77556d82012-04-05 15:23:14 -070094 """Raised when the RootFS fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070095
96
Richard Barnette9d43e562018-06-05 17:20:10 +000097class StatefulUpdateError(error.TestFail):
Chris Sosa77556d82012-04-05 15:23:14 -070098 """Raised when the stateful partition fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070099
100
Richard Barnette9d43e562018-06-05 17:20:10 +0000101class _AttributedUpdateError(error.TestFail):
102 """Update failure with an attributed cause."""
103
104 def __init__(self, attribution, msg):
105 super(_AttributedUpdateError, self).__init__(
106 '%s: %s' % (attribution, msg))
Richard Barnette5adb6d42018-06-28 15:52:32 -0700107 self._message = msg
108
109 def _classify(self):
110 for err_pattern, classification in self._CLASSIFIERS:
111 if re.match(err_pattern, self._message):
112 return classification
113 return None
114
115 @property
116 def failure_summary(self):
117 """Summarize this error for metrics reporting."""
118 classification = self._classify()
119 if classification:
120 return '%s: %s' % (self._SUMMARY, classification)
121 else:
122 return self._SUMMARY
Richard Barnette9d43e562018-06-05 17:20:10 +0000123
124
125class HostUpdateError(_AttributedUpdateError):
126 """Failure updating a DUT attributable to the DUT.
127
128 This class of exception should be raised when the most likely cause
129 of failure was a condition existing on the DUT prior to the update,
130 such as a hardware problem, or a bug in the software on the DUT.
131 """
132
Richard Barnette5adb6d42018-06-28 15:52:32 -0700133 DUT_DOWN = 'No answer to ssh'
134
135 _SUMMARY = 'DUT failed prior to update'
136 _CLASSIFIERS = [
137 (DUT_DOWN, DUT_DOWN),
138 (_REBOOT_FAILURE_MESSAGE, 'Reboot failed'),
139 ]
140
Richard Barnette9d43e562018-06-05 17:20:10 +0000141 def __init__(self, hostname, msg):
142 super(HostUpdateError, self).__init__(
143 'Error on %s prior to update' % hostname, msg)
144
145
146class DevServerError(_AttributedUpdateError):
147 """Failure updating a DUT attributable to the devserver.
148
149 This class of exception should be raised when the most likely cause
150 of failure was the devserver serving the target image for update.
151 """
152
Richard Barnette5adb6d42018-06-28 15:52:32 -0700153 _SUMMARY = 'Devserver failed prior to update'
154 _CLASSIFIERS = []
155
Richard Barnette9d43e562018-06-05 17:20:10 +0000156 def __init__(self, devserver, msg):
157 super(DevServerError, self).__init__(
158 'Devserver error on %s' % devserver, msg)
159
160
161class ImageInstallError(_AttributedUpdateError):
162 """Failure updating a DUT when installing from the devserver.
163
164 This class of exception should be raised when the target DUT fails
165 to download and install the target image from the devserver, and
166 either the devserver or the DUT might be at fault.
167 """
168
Richard Barnette5adb6d42018-06-28 15:52:32 -0700169 _SUMMARY = 'Image failed to download and install'
170 _CLASSIFIERS = []
171
Richard Barnette9d43e562018-06-05 17:20:10 +0000172 def __init__(self, hostname, devserver, msg):
173 super(ImageInstallError, self).__init__(
174 'Download and install failed from %s onto %s'
175 % (devserver, hostname), msg)
176
177
178class NewBuildUpdateError(_AttributedUpdateError):
179 """Failure updating a DUT attributable to the target build.
180
181 This class of exception should be raised when updating to a new
182 build fails, and the most likely cause of the failure is a bug in
183 the newly installed target build.
184 """
185
Richard Barnette5adb6d42018-06-28 15:52:32 -0700186 CHROME_FAILURE = 'Chrome failed to reach login screen'
187 UPDATE_ENGINE_FAILURE = ('update-engine failed to call '
188 'chromeos-setgoodkernel')
189 ROLLBACK_FAILURE = 'System rolled back to previous build'
190
191 _SUMMARY = 'New build failed'
192 _CLASSIFIERS = [
193 (CHROME_FAILURE, 'Chrome did not start'),
194 (UPDATE_ENGINE_FAILURE, 'update-engine did not start'),
195 (ROLLBACK_FAILURE, ROLLBACK_FAILURE),
196 ]
197
Richard Barnette9d43e562018-06-05 17:20:10 +0000198 def __init__(self, update_version, msg):
199 super(NewBuildUpdateError, self).__init__(
200 'Failure in build %s' % update_version, msg)
201
Richard Barnette621a8e42018-06-25 17:34:11 -0700202 @property
203 def failure_summary(self):
204 #pylint: disable=missing-docstring
205 return 'Build failed to work after installing'
206
Richard Barnette9d43e562018-06-05 17:20:10 +0000207
Richard Barnette3e8b2282018-05-15 20:42:20 +0000208def _url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -0800209 """Return the version based on update_url.
210
211 @param update_url: url to the image to update to.
212
213 """
Dale Curtisddfdb942011-07-14 13:59:24 -0700214 # The Chrome OS version is generally the last element in the URL. The only
215 # exception is delta update URLs, which are rooted under the version; e.g.,
216 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
217 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -0700218 return re.sub('/au/.*', '',
219 urlparse.urlparse(update_url).path).split('/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200220
221
Scott Zawalskieadbf702013-03-14 09:23:06 -0400222def url_to_image_name(update_url):
223 """Return the image name based on update_url.
224
225 From a URL like:
226 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
227 return lumpy-release/R27-3837.0.0
228
229 @param update_url: url to the image to update to.
230 @returns a string representing the image name in the update_url.
231
232 """
233 return '/'.join(urlparse.urlparse(update_url).path.split('/')[-2:])
234
235
Richard Barnette4c81b972018-07-18 12:35:16 -0700236def get_update_failure_reason(exception):
237 """Convert an exception into a failure reason for metrics.
238
239 The passed in `exception` should be one raised by failure of
240 `ChromiumOSUpdater.run_update`. The returned string will describe
241 the failure. If the input exception value is not a truish value
242 the return value will be `None`.
243
244 The number of possible return strings is restricted to a limited
245 enumeration of values so that the string may be safely used in
246 Monarch metrics without worrying about cardinality of the range of
247 string values.
248
249 @param exception Exception to be converted to a failure reason.
250
251 @return A string suitable for use in Monarch metrics, or `None`.
252 """
253 if exception:
254 if isinstance(exception, _AttributedUpdateError):
255 return exception.failure_summary
256 else:
257 return 'Unknown Error: %s' % type(exception).__name__
258 return None
259
260
Prashanth B32baa9b2014-03-13 13:23:01 -0700261def _get_devserver_build_from_update_url(update_url):
262 """Get the devserver and build from the update url.
263
264 @param update_url: The url for update.
265 Eg: http://devserver:port/update/build.
266
267 @return: A tuple of (devserver url, build) or None if the update_url
268 doesn't match the expected pattern.
269
270 @raises ValueError: If the update_url doesn't match the expected pattern.
271 @raises ValueError: If no global_config was found, or it doesn't contain an
272 image_url_pattern.
273 """
274 pattern = global_config.global_config.get_config_value(
275 'CROS', 'image_url_pattern', type=str, default='')
276 if not pattern:
277 raise ValueError('Cannot parse update_url, the global config needs '
278 'an image_url_pattern.')
279 re_pattern = pattern.replace('%s', '(\S+)')
280 parts = re.search(re_pattern, update_url)
281 if not parts or len(parts.groups()) < 2:
282 raise ValueError('%s is not an update url' % update_url)
283 return parts.groups()
284
285
Richard Barnette3e8b2282018-05-15 20:42:20 +0000286def _list_image_dir_contents(update_url):
Prashanth B32baa9b2014-03-13 13:23:01 -0700287 """Lists the contents of the devserver for a given build/update_url.
288
289 @param update_url: An update url. Eg: http://devserver:port/update/build.
290 """
291 if not update_url:
292 logging.warning('Need update_url to list contents of the devserver.')
293 return
294 error_msg = 'Cannot check contents of devserver, update url %s' % update_url
295 try:
296 devserver_url, build = _get_devserver_build_from_update_url(update_url)
297 except ValueError as e:
298 logging.warning('%s: %s', error_msg, e)
299 return
300 devserver = dev_server.ImageServer(devserver_url)
301 try:
302 devserver.list_image_dir(build)
303 # The devserver will retry on URLError to avoid flaky connections, but will
304 # eventually raise the URLError if it persists. All HTTPErrors get
305 # converted to DevServerExceptions.
306 except (dev_server.DevServerException, urllib2.URLError) as e:
307 logging.warning('%s: %s', error_msg, e)
308
309
Richard Barnette621a8e42018-06-25 17:34:11 -0700310def _get_metric_fields(update_url):
311 """Return a dict of metric fields.
312
313 This is used for sending autoupdate metrics for the given update URL.
314
315 @param update_url Metrics fields will be calculated from this URL.
316 """
317 build_name = url_to_image_name(update_url)
318 try:
319 board, build_type, milestone, _ = server_utils.ParseBuildName(
320 build_name)
321 except server_utils.ParseBuildNameException:
322 logging.warning('Unable to parse build name %s for metrics. '
323 'Continuing anyway.', build_name)
324 board, build_type, milestone = ('', '', '')
325 return {
326 'dev_server': dev_server.get_resolved_hostname(update_url),
327 'board': board,
328 'build_type': build_type,
329 'milestone': milestone,
330 }
331
332
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700333# TODO(garnold) This implements shared updater functionality needed for
334# supporting the autoupdate_EndToEnd server-side test. We should probably
335# migrate more of the existing ChromiumOSUpdater functionality to it as we
336# expand non-CrOS support in other tests.
Richard Barnette3e8b2282018-05-15 20:42:20 +0000337class ChromiumOSUpdater(object):
338 """Chromium OS specific DUT update functionality."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700339
Richard Barnette60e759e2018-07-21 20:56:59 -0700340 def __init__(self, update_url, host=None, interactive=True,
341 use_quick_provision=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700342 """Initializes the object.
343
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700344 @param update_url: The URL we want the update to use.
345 @param host: A client.common_lib.hosts.Host implementation.
David Haddock76a4c882017-12-13 18:50:09 -0800346 @param interactive: Bool whether we are doing an interactive update.
Richard Barnette60e759e2018-07-21 20:56:59 -0700347 @param use_quick_provision: Whether we should attempt to perform
348 the update using the quick-provision script.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700349 """
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700350 self.update_url = update_url
351 self.host = host
David Haddock76a4c882017-12-13 18:50:09 -0800352 self.interactive = interactive
Richard Barnette3e8b2282018-05-15 20:42:20 +0000353 self.update_version = _url_to_version(update_url)
Richard Barnette60e759e2018-07-21 20:56:59 -0700354 self._use_quick_provision = use_quick_provision
Richard Barnette3e8b2282018-05-15 20:42:20 +0000355
356
357 def _run(self, cmd, *args, **kwargs):
358 """Abbreviated form of self.host.run(...)"""
359 return self.host.run(cmd, *args, **kwargs)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700360
361
362 def check_update_status(self):
363 """Returns the current update engine state.
364
365 We use the `update_engine_client -status' command and parse the line
366 indicating the update state, e.g. "CURRENT_OP=UPDATE_STATUS_IDLE".
367 """
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800368 update_status = self.host.run(command='%s -status | grep CURRENT_OP' %
Richard Barnette3e8b2282018-05-15 20:42:20 +0000369 _UPDATER_BIN)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700370 return update_status.stdout.strip().split('=')[-1]
371
372
Richard Barnette55d1af82018-05-22 23:40:14 +0000373 def _rootdev(self, options=''):
374 """Returns the stripped output of rootdev <options>.
375
376 @param options: options to run rootdev.
377
378 """
379 return self._run('rootdev %s' % options).stdout.strip()
380
381
382 def get_kernel_state(self):
Richard Barnette9d43e562018-06-05 17:20:10 +0000383 """Returns the (<active>, <inactive>) kernel state as a pair.
384
385 @raise RootFSUpdateError if the DUT reports a root partition
386 number that isn't one of the known valid values.
387 """
Richard Barnette55d1af82018-05-22 23:40:14 +0000388 active_root = int(re.findall('\d+\Z', self._rootdev('-s'))[0])
389 if active_root == _KERNEL_A['root']:
390 return _KERNEL_A, _KERNEL_B
391 elif active_root == _KERNEL_B['root']:
392 return _KERNEL_B, _KERNEL_A
393 else:
Richard Barnette9d43e562018-06-05 17:20:10 +0000394 raise RootFSUpdateError(
395 'Encountered unknown root partition: %s' % active_root)
Richard Barnette55d1af82018-05-22 23:40:14 +0000396
397
Richard Barnette18fd5842018-05-25 18:21:14 +0000398 def _cgpt(self, flag, kernel):
399 """Return numeric cgpt value for the specified flag, kernel, device."""
400 return int(self._run('cgpt show -n -i %d %s $(rootdev -s -d)' % (
401 kernel['kernel'], flag)).stdout.strip())
Richard Barnette55d1af82018-05-22 23:40:14 +0000402
403
404 def _get_next_kernel(self):
405 """Return the kernel that has priority for the next boot."""
406 priority_a = self._cgpt('-P', _KERNEL_A)
407 priority_b = self._cgpt('-P', _KERNEL_B)
408 if priority_a > priority_b:
409 return _KERNEL_A
410 else:
411 return _KERNEL_B
412
413
414 def _get_kernel_success(self, kernel):
415 """Return boolean success flag for the specified kernel.
416
417 @param kernel: information of the given kernel, either _KERNEL_A
418 or _KERNEL_B.
419 """
420 return self._cgpt('-S', kernel) != 0
421
422
423 def _get_kernel_tries(self, kernel):
424 """Return tries count for the specified kernel.
425
426 @param kernel: information of the given kernel, either _KERNEL_A
427 or _KERNEL_B.
428 """
429 return self._cgpt('-T', kernel)
430
431
Richard Barnette3e8b2282018-05-15 20:42:20 +0000432 def _get_last_update_error(self):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800433 """Get the last autoupdate error code."""
Richard Barnette3e8b2282018-05-15 20:42:20 +0000434 command_result = self._run(
435 '%s --last_attempt_error' % _UPDATER_BIN)
436 return command_result.stdout.strip().replace('\n', ', ')
Shuqian Zhaod9992722016-02-29 12:26:38 -0800437
438
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800439 def _base_update_handler_no_retry(self, run_args):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800440 """Base function to handle a remote update ssh call.
441
442 @param run_args: Dictionary of args passed to ssh_host.run function.
Shuqian Zhaod9992722016-02-29 12:26:38 -0800443
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800444 @throws: intercepts and re-throws all exceptions
Shuqian Zhaod9992722016-02-29 12:26:38 -0800445 """
Shuqian Zhaod9992722016-02-29 12:26:38 -0800446 try:
447 self.host.run(**run_args)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800448 except Exception as e:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800449 logging.debug('exception in update handler: %s', e)
450 raise e
Shuqian Zhaod9992722016-02-29 12:26:38 -0800451
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800452
453 def _base_update_handler(self, run_args, err_msg_prefix=None):
454 """Handle a remote update ssh call, possibly with retries.
455
456 @param run_args: Dictionary of args passed to ssh_host.run function.
457 @param err_msg_prefix: Prefix of the exception error message.
458 """
459 def exception_handler(e):
460 """Examines exceptions and returns True if the update handler
461 should be retried.
462
463 @param e: the exception intercepted by the retry util.
464 """
465 return (isinstance(e, error.AutoservSSHTimeout) or
466 (isinstance(e, error.GenericHostRunError) and
467 hasattr(e, 'description') and
468 (re.search('ERROR_CODE=37', e.description) or
469 re.search('generic error .255.', e.description))))
470
471 try:
472 # Try the update twice (arg 2 is max_retry, not including the first
473 # call). Some exceptions may be caught by the retry handler.
474 retry_util.GenericRetry(exception_handler, 1,
475 self._base_update_handler_no_retry,
476 run_args)
477 except Exception as e:
478 message = err_msg_prefix + ': ' + str(e)
479 raise RootFSUpdateError(message)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800480
481
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800482 def _wait_for_update_service(self):
483 """Ensure that the update engine daemon is running, possibly
484 by waiting for it a bit in case the DUT just rebooted and the
485 service hasn't started yet.
486 """
487 def handler(e):
488 """Retry exception handler.
489
490 Assumes that the error is due to the update service not having
491 started yet.
492
493 @param e: the exception intercepted by the retry util.
494 """
495 if isinstance(e, error.AutoservRunError):
496 logging.debug('update service check exception: %s\n'
497 'retrying...', e)
498 return True
499 else:
500 return False
501
502 # Retry at most three times, every 5s.
503 status = retry_util.GenericRetry(handler, 3,
504 self.check_update_status,
505 sleep=5)
506
507 # Expect the update engine to be idle.
508 if status != UPDATER_IDLE:
Richard Barnette9d43e562018-06-05 17:20:10 +0000509 raise RootFSUpdateError(
510 'Update engine status is %s (%s was expected).'
511 % (status, UPDATER_IDLE))
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800512
513
Richard Barnette55d1af82018-05-22 23:40:14 +0000514 def _reset_update_engine(self):
515 """Resets the host to prepare for a clean update regardless of state."""
516 self._run('stop ui || true')
517 self._run('stop update-engine || true')
518 self._run('start update-engine')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800519 self._wait_for_update_service()
520
Richard Barnette55d1af82018-05-22 23:40:14 +0000521
522 def _reset_stateful_partition(self):
523 """Clear any pending stateful update request."""
Richard Barnette18fd5842018-05-25 18:21:14 +0000524 self._run('%s --stateful_change=reset 2>&1'
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700525 % self._get_stateful_update_script())
Richard Barnette3ef29a82018-06-28 13:52:54 -0700526 self._run('rm -f %s' % _TARGET_VERSION)
527
528
529 def _set_target_version(self):
530 """Set the "target version" for the update."""
531 version_number = self.update_version.split('-')[1]
532 self._run('echo %s > %s' % (version_number, _TARGET_VERSION))
Richard Barnette55d1af82018-05-22 23:40:14 +0000533
534
535 def _revert_boot_partition(self):
536 """Revert the boot partition."""
537 part = self._rootdev('-s')
538 logging.warning('Reverting update; Boot partition will be %s', part)
539 return self._run('/postinst %s 2>&1' % part)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700540
541
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700542 def _verify_kernel_state(self):
543 """Verify that the next kernel to boot is correct for update.
544
545 This tests that the kernel state is correct for a successfully
546 downloaded and installed update. That is, the next kernel to
547 boot must be the currently inactive kernel.
548
549 @raise RootFSUpdateError if the DUT next kernel isn't the
550 expected next kernel.
551 """
552 inactive_kernel = self.get_kernel_state()[1]
553 next_kernel = self._get_next_kernel()
554 if next_kernel != inactive_kernel:
555 raise RootFSUpdateError(
556 'Update failed. The kernel for next boot is %s, '
557 'but %s was expected.'
558 % (next_kernel['name'], inactive_kernel['name']))
559 return inactive_kernel
560
561
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700562 def _verify_update_completed(self):
563 """Verifies that an update has completed.
564
Richard Barnette9d43e562018-06-05 17:20:10 +0000565 @raise RootFSUpdateError if the DUT doesn't indicate that
566 download is complete and the DUT is ready for reboot.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700567 """
568 status = self.check_update_status()
569 if status != UPDATER_NEED_REBOOT:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800570 error_msg = ''
571 if status == UPDATER_IDLE:
Richard Barnette3e8b2282018-05-15 20:42:20 +0000572 error_msg = 'Update error: %s' % self._get_last_update_error()
Richard Barnette9d43e562018-06-05 17:20:10 +0000573 raise RootFSUpdateError(
574 'Update engine status is %s (%s was expected). %s'
575 % (status, UPDATER_NEED_REBOOT, error_msg))
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700576 return self._verify_kernel_state()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700577
578
Richard Barnette55d1af82018-05-22 23:40:14 +0000579 def trigger_update(self):
Richard Barnette9d43e562018-06-05 17:20:10 +0000580 """Triggers a background update."""
581 # If this function is called immediately after reboot (which it
582 # can be), there is no guarantee that the update engine is up
583 # and running yet, so wait for it.
Richard Barnette55d1af82018-05-22 23:40:14 +0000584 self._wait_for_update_service()
585
586 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
587 (_UPDATER_BIN, self.update_url))
588 run_args = {'command': autoupdate_cmd}
589 err_prefix = 'Failed to trigger an update on %s. ' % self.host.hostname
590 logging.info('Triggering update via: %s', autoupdate_cmd)
591 metric_fields = {'success': False}
592 try:
593 self._base_update_handler(run_args, err_prefix)
594 metric_fields['success'] = True
595 finally:
596 c = metrics.Counter('chromeos/autotest/autoupdater/trigger')
Richard Barnette621a8e42018-06-25 17:34:11 -0700597 metric_fields.update(_get_metric_fields(self.update_url))
Richard Barnette55d1af82018-05-22 23:40:14 +0000598 c.increment(fields=metric_fields)
599
600
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700601 def update_image(self):
Richard Barnette18fd5842018-05-25 18:21:14 +0000602 """Updates the device root FS and kernel and verifies success."""
Shuqian Zhaofe4d62e2016-06-23 14:46:45 -0700603 autoupdate_cmd = ('%s --update --omaha_url=%s' %
Richard Barnette3e8b2282018-05-15 20:42:20 +0000604 (_UPDATER_BIN, self.update_url))
David Haddock76a4c882017-12-13 18:50:09 -0800605 if not self.interactive:
606 autoupdate_cmd = '%s --interactive=false' % autoupdate_cmd
Shuqian Zhaod9992722016-02-29 12:26:38 -0800607 run_args = {'command': autoupdate_cmd, 'timeout': 3600}
608 err_prefix = ('Failed to install device image using payload at %s '
609 'on %s. ' % (self.update_url, self.host.hostname))
610 logging.info('Updating image via: %s', autoupdate_cmd)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700611 metric_fields = {'success': False}
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800612 try:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800613 self._base_update_handler(run_args, err_prefix)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700614 metric_fields['success'] = True
615 finally:
Allen Li1a5cc0a2017-06-20 14:08:59 -0700616 c = metrics.Counter('chromeos/autotest/autoupdater/update')
Richard Barnette621a8e42018-06-25 17:34:11 -0700617 metric_fields.update(_get_metric_fields(self.update_url))
Allen Li1a5cc0a2017-06-20 14:08:59 -0700618 c.increment(fields=metric_fields)
Richard Barnette4d211c92018-05-24 18:56:08 +0000619 return self._verify_update_completed()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700620
621
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700622 def _get_remote_script(self, script_name):
623 """Ensure that `script_name` is present on the DUT.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700624
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700625 The given script (e.g. `stateful_update`) may be present in the
626 stateful partition under /usr/local/bin, or we may have to
627 download it from the devserver.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700628
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700629 Determine whether the script is present or must be downloaded
630 and download if necessary. Then, return a command fragment
631 sufficient to run the script from whereever it now lives on the
632 DUT.
Richard Barnette9d43e562018-06-05 17:20:10 +0000633
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700634 @param script_name The name of the script as expected in
635 /usr/local/bin and on the devserver.
636 @return A string with the command (minus arguments) that will
637 run the target script.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700638 """
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700639 remote_script = '/usr/local/bin/%s' % script_name
640 if self.host.path_exists(remote_script):
641 return remote_script
642 remote_tmp_script = '/tmp/%s' % script_name
643 server_name = urlparse.urlparse(self.update_url)[1]
644 script_url = 'http://%s/static/%s' % (server_name, script_name)
645 fetch_script = (
646 'curl -o %s %s && head -1 %s | grep "^#!" | sed "s/#!//"') % (
647 remote_tmp_script, script_url, remote_tmp_script)
648 script_interpreter = self._run(fetch_script,
649 ignore_status=True).stdout.strip()
650 if not script_interpreter:
651 return None
652 return '%s %s' % (script_interpreter, remote_tmp_script)
Chris Sosa5e4246b2012-05-22 18:05:22 -0700653
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700654
655 def _get_stateful_update_script(self):
656 """Returns a command to run the stateful update script.
657
658 Find `stateful_update` on the target or install it, as
659 necessary. If installation fails, raise an exception.
660
661 @raise StatefulUpdateError if the script can't be found or
662 installed.
663 @return A string that can be joined with arguments to run the
664 `stateful_update` command on the DUT.
665 """
666 script_command = self._get_remote_script(_STATEFUL_UPDATE_SCRIPT)
667 if not script_command:
668 raise StatefulUpdateError('Could not install %s on DUT'
Richard Barnette9d43e562018-06-05 17:20:10 +0000669 % _STATEFUL_UPDATE_SCRIPT)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700670 return script_command
Chris Sosa5e4246b2012-05-22 18:05:22 -0700671
672
Chris Sosac1932172013-10-16 13:28:53 -0700673 def rollback_rootfs(self, powerwash):
674 """Triggers rollback and waits for it to complete.
675
676 @param powerwash: If true, powerwash as part of rollback.
677
678 @raise RootFSUpdateError if anything went wrong.
Chris Sosac1932172013-10-16 13:28:53 -0700679 """
Dan Shi549fb822015-03-24 18:01:11 -0700680 version = self.host.get_release_version()
Chris Sosac8617522014-06-09 23:22:26 +0000681 # Introduced can_rollback in M36 (build 5772). # etc/lsb-release matches
682 # X.Y.Z. This version split just pulls the first part out.
683 try:
684 build_number = int(version.split('.')[0])
685 except ValueError:
686 logging.error('Could not parse build number.')
687 build_number = 0
688
689 if build_number >= 5772:
Richard Barnette3e8b2282018-05-15 20:42:20 +0000690 can_rollback_cmd = '%s --can_rollback' % _UPDATER_BIN
Chris Sosac8617522014-06-09 23:22:26 +0000691 logging.info('Checking for rollback.')
692 try:
693 self._run(can_rollback_cmd)
694 except error.AutoservRunError as e:
695 raise RootFSUpdateError("Rollback isn't possible on %s: %s" %
696 (self.host.hostname, str(e)))
697
Richard Barnette3e8b2282018-05-15 20:42:20 +0000698 rollback_cmd = '%s --rollback --follow' % _UPDATER_BIN
Chris Sosac1932172013-10-16 13:28:53 -0700699 if not powerwash:
Dan Shif3a35f72016-01-25 11:18:14 -0800700 rollback_cmd += ' --nopowerwash'
Chris Sosac1932172013-10-16 13:28:53 -0700701
Chris Sosac8617522014-06-09 23:22:26 +0000702 logging.info('Performing rollback.')
Chris Sosac1932172013-10-16 13:28:53 -0700703 try:
704 self._run(rollback_cmd)
Chris Sosac1932172013-10-16 13:28:53 -0700705 except error.AutoservRunError as e:
706 raise RootFSUpdateError('Rollback failed on %s: %s' %
707 (self.host.hostname, str(e)))
708
709 self._verify_update_completed()
710
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800711
Chris Sosa72312602013-04-16 15:01:56 -0700712 def update_stateful(self, clobber=True):
713 """Updates the stateful partition.
714
715 @param clobber: If True, a clean stateful installation.
Richard Barnette9d43e562018-06-05 17:20:10 +0000716
717 @raise StatefulUpdateError if the update script fails to
718 complete successfully.
Chris Sosa72312602013-04-16 15:01:56 -0700719 """
Chris Sosa77556d82012-04-05 15:23:14 -0700720 logging.info('Updating stateful partition...')
Richard Barnette18fd5842018-05-25 18:21:14 +0000721 statefuldev_url = self.update_url.replace('update', 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700722
Dale Curtis5c32c722011-05-04 19:24:23 -0700723 # Attempt stateful partition update; this must succeed so that the newly
724 # installed host is testable after update.
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700725 statefuldev_cmd = [self._get_stateful_update_script(), statefuldev_url]
Chris Sosa72312602013-04-16 15:01:56 -0700726 if clobber:
727 statefuldev_cmd.append('--stateful_change=clean')
728
729 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700730 try:
Dan Shi205b8732016-01-25 10:56:22 -0800731 self._run(' '.join(statefuldev_cmd), timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700732 except error.AutoservRunError:
Richard Barnette18fd5842018-05-25 18:21:14 +0000733 raise StatefulUpdateError(
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700734 'Failed to perform stateful update on %s' %
735 self.host.hostname)
Dale Curtis5c32c722011-05-04 19:24:23 -0700736
Chris Sosaa3ac2152012-05-23 22:23:13 -0700737
Richard Barnette54d14f52018-05-18 16:39:49 +0000738 def verify_boot_expectations(self, expected_kernel, rollback_message):
Richard Barnette55d1af82018-05-22 23:40:14 +0000739 """Verifies that we fully booted given expected kernel state.
740
741 This method both verifies that we booted using the correct kernel
742 state and that the OS has marked the kernel as good.
743
Richard Barnette54d14f52018-05-18 16:39:49 +0000744 @param expected_kernel: kernel that we are verifying with,
Richard Barnette55d1af82018-05-22 23:40:14 +0000745 i.e. I expect to be booted onto partition 4 etc. See output of
746 get_kernel_state.
Richard Barnette9d43e562018-06-05 17:20:10 +0000747 @param rollback_message: string include in except message text
Richard Barnette55d1af82018-05-22 23:40:14 +0000748 if we booted with the wrong partition.
749
Richard Barnette9d43e562018-06-05 17:20:10 +0000750 @raise NewBuildUpdateError if any of the various checks fail.
Richard Barnette55d1af82018-05-22 23:40:14 +0000751 """
752 # Figure out the newly active kernel.
Richard Barnette54d14f52018-05-18 16:39:49 +0000753 active_kernel = self.get_kernel_state()[0]
Richard Barnette55d1af82018-05-22 23:40:14 +0000754
755 # Check for rollback due to a bad build.
Richard Barnette54d14f52018-05-18 16:39:49 +0000756 if active_kernel != expected_kernel:
Richard Barnette55d1af82018-05-22 23:40:14 +0000757
758 # Kernel crash reports should be wiped between test runs, but
759 # may persist from earlier parts of the test, or from problems
760 # with provisioning.
761 #
762 # Kernel crash reports will NOT be present if the crash happened
763 # before encrypted stateful is mounted.
764 #
765 # TODO(dgarrett): Integrate with server/crashcollect.py at some
766 # point.
767 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
768 if kernel_crashes:
769 rollback_message += ': kernel_crash'
770 logging.debug('Found %d kernel crash reports:',
771 len(kernel_crashes))
772 # The crash names contain timestamps that may be useful:
773 # kernel.20131207.005945.0.kcrash
774 for crash in kernel_crashes:
775 logging.debug(' %s', os.path.basename(crash))
776
777 # Print out some information to make it easier to debug
778 # the rollback.
779 logging.debug('Dumping partition table.')
780 self._run('cgpt show $(rootdev -s -d)')
781 logging.debug('Dumping crossystem for firmware debugging.')
782 self._run('crossystem --all')
Richard Barnette9d43e562018-06-05 17:20:10 +0000783 raise NewBuildUpdateError(self.update_version, rollback_message)
Richard Barnette55d1af82018-05-22 23:40:14 +0000784
785 # Make sure chromeos-setgoodkernel runs.
786 try:
787 utils.poll_for_condition(
Richard Barnette54d14f52018-05-18 16:39:49 +0000788 lambda: (self._get_kernel_tries(active_kernel) == 0
789 and self._get_kernel_success(active_kernel)),
Richard Barnette9d43e562018-06-05 17:20:10 +0000790 exception=RootFSUpdateError(),
Richard Barnette55d1af82018-05-22 23:40:14 +0000791 timeout=_KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
Richard Barnette9d43e562018-06-05 17:20:10 +0000792 except RootFSUpdateError:
Richard Barnette55d1af82018-05-22 23:40:14 +0000793 services_status = self._run('status system-services').stdout
794 if services_status != 'system-services start/running\n':
Richard Barnette5adb6d42018-06-28 15:52:32 -0700795 event = NewBuildUpdateError.CHROME_FAILURE
Richard Barnette55d1af82018-05-22 23:40:14 +0000796 else:
Richard Barnette5adb6d42018-06-28 15:52:32 -0700797 event = NewBuildUpdateError.UPDATE_ENGINE_FAILURE
Richard Barnette9d43e562018-06-05 17:20:10 +0000798 raise NewBuildUpdateError(self.update_version, event)
Richard Barnette55d1af82018-05-22 23:40:14 +0000799
800
Richard Barnette14ee84c2018-05-18 20:23:42 +0000801 def _prepare_host(self):
802 """Make sure the target DUT is working and ready for update.
803
804 Initially, the target DUT's state is unknown. The DUT is
805 expected to be online, but we strive to be forgiving if Chrome
806 and/or the update engine aren't fully functional.
807 """
808 # Summary of work, and the rationale:
809 # 1. Reboot, because it's a good way to clear out problems.
810 # 2. Touch the PROVISION_FAILED file, to allow repair to detect
811 # failure later.
812 # 3. Run the hook for host class specific preparation.
813 # 4. Stop Chrome, because the system is designed to eventually
814 # reboot if Chrome is stuck in a crash loop.
815 # 5. Force `update-engine` to start, because if Chrome failed
816 # to start properly, the status of the `update-engine` job
817 # will be uncertain.
Richard Barnette5adb6d42018-06-28 15:52:32 -0700818 if not self.host.is_up():
819 raise HostUpdateError(self.host.hostname,
820 HostUpdateError.DUT_DOWN)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000821 self._reset_stateful_partition()
822 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
823 self._run('touch %s' % PROVISION_FAILED)
824 self.host.prepare_for_update()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700825 self._reset_update_engine()
Richard Barnette14ee84c2018-05-18 20:23:42 +0000826 logging.info('Updating from version %s to %s.',
827 self.host.get_release_version(),
828 self.update_version)
829
830
831 def _verify_devserver(self):
Richard Barnette9d43e562018-06-05 17:20:10 +0000832 """Check that our chosen devserver is still working.
833
834 @raise DevServerError if the devserver fails any sanity check.
835 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000836 server = 'http://%s' % urlparse.urlparse(self.update_url)[1]
837 try:
838 if not dev_server.ImageServer.devserver_healthy(server):
Richard Barnette9d43e562018-06-05 17:20:10 +0000839 raise DevServerError(
840 server, 'Devserver is not healthy')
Richard Barnette14ee84c2018-05-18 20:23:42 +0000841 except Exception as e:
Richard Barnette9d43e562018-06-05 17:20:10 +0000842 raise DevServerError(
843 server, 'Devserver is not up and available')
Richard Barnette14ee84c2018-05-18 20:23:42 +0000844
845
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700846 def _install_via_update_engine(self):
847 """Install an updating using the production AU flow.
848
849 This uses the standard AU flow and the `stateful_update` script
850 to download and install a root FS, kernel and stateful
851 filesystem content.
852
853 @return The kernel expected to be booted next.
854 """
855 logging.info('Installing image using update_engine.')
856 expected_kernel = self.update_image()
857 self.update_stateful()
Richard Barnette3ef29a82018-06-28 13:52:54 -0700858 self._set_target_version()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700859 return expected_kernel
860
861
862 def _install_via_quick_provision(self):
863 """Install an updating using the `quick-provision` script.
864
865 This uses the `quick-provision` script to download and install
866 a root FS, kernel and stateful filesystem content.
867
868 @return The kernel expected to be booted next.
869 """
Richard Barnette60e759e2018-07-21 20:56:59 -0700870 if not self._use_quick_provision:
871 return None
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700872 image_name = url_to_image_name(self.update_url)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700873 logging.info('Installing image using quick-provision.')
874 provision_command = self._get_remote_script(_QUICK_PROVISION_SCRIPT)
875 server_name = urlparse.urlparse(self.update_url)[1]
876 static_url = 'http://%s/static' % server_name
877 command = '%s --noreboot %s %s' % (
878 provision_command, image_name, static_url)
879 try:
880 self._run(command)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700881 self._set_target_version()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700882 return self._verify_kernel_state()
883 except Exception:
884 # N.B. We handle only `Exception` here. Non-Exception
885 # classes (such as KeyboardInterrupt) are handled by our
886 # caller.
887 logging.exception('quick-provision script failed; '
888 'will fall back to update_engine.')
889 self._revert_boot_partition()
890 self._reset_stateful_partition()
891 self._reset_update_engine()
892 return None
893
894
Richard Barnette54d14f52018-05-18 16:39:49 +0000895 def _install_update(self):
Richard Barnette0beb14b2018-05-15 18:07:52 +0000896 """Install the requested image on the DUT, but don't start it.
897
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700898 This downloads and installs a root FS, kernel and stateful
899 filesystem content. This does not reboot the DUT, so the update
900 is merely pending when the method returns.
901
902 @return The kernel expected to be booted next.
Dan Shi0f466e82013-02-22 15:44:58 -0800903 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000904 logging.info('Installing image at %s onto %s',
905 self.update_url, self.host.hostname)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200906 try:
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700907 return (self._install_via_quick_provision()
908 or self._install_via_update_engine())
Dale Curtis1e973182011-07-12 18:21:36 -0700909 except:
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700910 # N.B. This handling code includes non-Exception classes such
911 # as KeyboardInterrupt. We need to clean up, but we also must
912 # re-raise.
Richard Barnette14ee84c2018-05-18 20:23:42 +0000913 self._revert_boot_partition()
914 self._reset_stateful_partition()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700915 self._reset_update_engine()
Dale Curtis1e973182011-07-12 18:21:36 -0700916 # Collect update engine logs in the event of failure.
917 if self.host.job:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700918 logging.info('Collecting update engine logs due to failure...')
Dale Curtis1e973182011-07-12 18:21:36 -0700919 self.host.get_file(
Richard Barnette3e8b2282018-05-15 20:42:20 +0000920 _UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
Gilad Arnold0c0df732015-09-21 06:37:59 -0700921 preserve_perm=False)
Richard Barnette3e8b2282018-05-15 20:42:20 +0000922 _list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700923 raise
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200924
925
Richard Barnette14ee84c2018-05-18 20:23:42 +0000926 def _complete_update(self, expected_kernel):
927 """Finish the update, and confirm that it succeeded.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000928
Richard Barnette14ee84c2018-05-18 20:23:42 +0000929 Initial condition is that the target build has been downloaded
930 and installed on the DUT, but has not yet been booted. This
931 function is responsible for rebooting the DUT, and checking that
932 the new build is running successfully.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000933
Richard Barnette14ee84c2018-05-18 20:23:42 +0000934 @param expected_kernel: kernel expected to be active after reboot.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000935 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000936 # Regarding the 'crossystem' command below: In some cases,
937 # the update flow puts the TPM into a state such that it
938 # fails verification. We don't know why. However, this
939 # call papers over the problem by clearing the TPM during
940 # the reboot.
941 #
942 # We ignore failures from 'crossystem'. Although failure
943 # here is unexpected, and could signal a bug, the point of
944 # the exercise is to paper over problems; allowing this to
945 # fail would defeat the purpose.
946 self._run('crossystem clear_tpm_owner_request=1',
947 ignore_status=True)
948 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
949
Richard Barnette0beb14b2018-05-15 18:07:52 +0000950 # Touch the lab machine file to leave a marker that
951 # distinguishes this image from other test images.
952 # Afterwards, we must re-run the autoreboot script because
953 # it depends on the _LAB_MACHINE_FILE.
954 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
955 '( touch "$FILE" ; start autoreboot )')
Richard Barnette3e8b2282018-05-15 20:42:20 +0000956 self._run(autoreboot_cmd % _LAB_MACHINE_FILE)
Richard Barnette0beb14b2018-05-15 18:07:52 +0000957 self.verify_boot_expectations(
Richard Barnette5adb6d42018-06-28 15:52:32 -0700958 expected_kernel, NewBuildUpdateError.ROLLBACK_FAILURE)
Richard Barnette0beb14b2018-05-15 18:07:52 +0000959
960 logging.debug('Cleaning up old autotest directories.')
961 try:
962 installed_autodir = autotest.Autotest.get_installed_autodir(
963 self.host)
964 self._run('rm -rf ' + installed_autodir)
965 except autotest.AutodirNotFoundError:
966 logging.debug('No autotest installed directory found.')
967
968
Richard Barnette4c81b972018-07-18 12:35:16 -0700969 def run_update(self):
970 """Perform a full update of a DUT in the test lab.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000971
Richard Barnette4c81b972018-07-18 12:35:16 -0700972 This downloads and installs the root FS and stateful partition
973 content needed for the update specified in `self.host` and
974 `self.update_url`. The update is performed according to the
975 requirements for provisioning a DUT for testing the requested
976 build.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000977
Richard Barnette4c81b972018-07-18 12:35:16 -0700978 At the end of the procedure, metrics are reported describing the
979 outcome of the operation.
980
981 @returns A tuple of the form `(image_name, attributes)`, where
982 `image_name` is the name of the image installed, and
983 `attributes` is new attributes to be applied to the DUT.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000984 """
Richard Barnette4c81b972018-07-18 12:35:16 -0700985 server_name = dev_server.get_resolved_hostname(self.update_url)
986 metrics.Counter(_metric_name('install')).increment(
987 fields={'devserver': server_name})
988
Richard Barnette14ee84c2018-05-18 20:23:42 +0000989 self._verify_devserver()
Richard Barnette9d43e562018-06-05 17:20:10 +0000990
991 try:
992 self._prepare_host()
993 except _AttributedUpdateError:
994 raise
995 except Exception as e:
996 logging.exception('Failure preparing host prior to update.')
997 raise HostUpdateError(self.host.hostname, str(e))
998
999 try:
1000 expected_kernel = self._install_update()
1001 except _AttributedUpdateError:
1002 raise
1003 except Exception as e:
1004 logging.exception('Failure during download and install.')
Richard Barnette621a8e42018-06-25 17:34:11 -07001005 server_name = dev_server.get_resolved_hostname(self.update_url)
Richard Barnette9d43e562018-06-05 17:20:10 +00001006 raise ImageInstallError(self.host.hostname, server_name, str(e))
1007
1008 try:
1009 self._complete_update(expected_kernel)
1010 except _AttributedUpdateError:
1011 raise
1012 except Exception as e:
1013 logging.exception('Failure from build after update.')
1014 raise NewBuildUpdateError(self.update_version, str(e))
Richard Barnette0beb14b2018-05-15 18:07:52 +00001015
Richard Barnette0beb14b2018-05-15 18:07:52 +00001016 image_name = url_to_image_name(self.update_url)
1017 # update_url is different from devserver url needed to stage autotest
1018 # packages, therefore, resolve a new devserver url here.
1019 devserver_url = dev_server.ImageServer.resolve(
1020 image_name, self.host.hostname).url()
1021 repo_url = tools.get_package_url(devserver_url, image_name)
1022 return image_name, {ds_constants.JOB_REPO_URL: repo_url}