blob: dd074e2f4741a7a4d501c3e2fa60786729a8dcd4 [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
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -07005from __future__ import print_function
6
Sean O'Connor5346e4e2010-08-12 18:49:24 +02007import logging
8import re
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -07009import six
Congbin Guo63ae0302019-08-12 16:37:49 -070010import sys
Prashanth B32baa9b2014-03-13 13:23:01 -070011import urllib2
Richard Barnette0beb14b2018-05-15 18:07:52 +000012import urlparse
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
David Haddock77b75c32020-05-14 01:56:32 -070017from autotest_lib.client.common_lib.cros import kernel_utils
Richard Barnette0beb14b2018-05-15 18:07:52 +000018from autotest_lib.server import autotest
Shelley Chen61d28982016-10-28 09:40:20 -070019from autotest_lib.server import utils as server_utils
Richard Barnette0beb14b2018-05-15 18:07:52 +000020from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
21from autotest_lib.server.cros.dynamic_suite import tools
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -080022from chromite.lib import retry_util
Dan Shif3a35f72016-01-25 11:18:14 -080023
Shelley Chen16b8df32016-10-27 16:24:21 -070024try:
25 from chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080026except ImportError:
27 metrics = utils.metrics_mock
Sean O'Connor5346e4e2010-08-12 18:49:24 +020028
Gwendal Grignou3e96cc22017-06-07 16:22:51 -070029
Richard Barnette621a8e42018-06-25 17:34:11 -070030def _metric_name(base_name):
31 return 'chromeos/autotest/provision/' + base_name
32
33
Dale Curtis5c32c722011-05-04 19:24:23 -070034# Local stateful update path is relative to the CrOS source directory.
Sean O'Connor5346e4e2010-08-12 18:49:24 +020035UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020036UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
beeps5e8c45a2013-12-17 22:05:11 -080037# A list of update engine client states that occur after an update is triggered.
Garry Wangcd769872019-06-07 16:04:17 -070038UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FOR_UPDATE',
beeps5e8c45a2013-12-17 22:05:11 -080039 'UPDATE_STATUS_UPDATE_AVAILABLE',
40 'UPDATE_STATUS_DOWNLOADING',
Garry Wangcd769872019-06-07 16:04:17 -070041 'UPDATE_STATUS_FINALIZING',
42 'UPDATE_STATUS_VERIFYING',
43 'UPDATE_STATUS_REPORTING_ERROR_EVENT',
44 'UPDATE_STATUS_ATTEMPTING_ROLLBACK']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020045
Richard Barnette0beb14b2018-05-15 18:07:52 +000046
Richard Barnette3e8b2282018-05-15 20:42:20 +000047_STATEFUL_UPDATE_SCRIPT = 'stateful_update'
Richard Barnettee86b1ce2018-06-07 10:37:23 -070048_QUICK_PROVISION_SCRIPT = 'quick-provision'
Richard Barnette3e8b2282018-05-15 20:42:20 +000049
50_UPDATER_BIN = '/usr/bin/update_engine_client'
51_UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
52
Richard Barnette0beb14b2018-05-15 18:07:52 +000053# PROVISION_FAILED - A flag file to indicate provision failures. The
54# file is created at the start of any AU procedure (see
Richard Barnette9d43e562018-06-05 17:20:10 +000055# `ChromiumOSUpdater._prepare_host()`). The file's location in
Richard Barnette0beb14b2018-05-15 18:07:52 +000056# stateful means that on successul update it will be removed. Thus, if
57# this file exists, it indicates that we've tried and failed in a
58# previous attempt to update.
59PROVISION_FAILED = '/var/tmp/provision_failed'
60
61
Richard Barnette3e8b2282018-05-15 20:42:20 +000062# A flag file used to enable special handling in lab DUTs. Some
63# parts of the system in Chromium OS test images will behave in ways
64# convenient to the test lab when this file is present. Generally,
65# we create this immediately after any update completes.
66_LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
67
68
Richard Barnette3ef29a82018-06-28 13:52:54 -070069# _TARGET_VERSION - A file containing the new version to which we plan
70# to update. This file is used by the CrOS shutdown code to detect and
71# handle certain version downgrade cases. Specifically: Downgrading
72# may trigger an unwanted powerwash in the target build when the
73# following conditions are met:
74# * Source build is a v4.4 kernel with R69-10756.0.0 or later.
75# * Target build predates the R69-10756.0.0 cutoff.
76# When this file is present and indicates a downgrade, the OS shutdown
77# code on the DUT knows how to prevent the powerwash.
78_TARGET_VERSION = '/run/update_target_version'
79
80
Richard Barnette5adb6d42018-06-28 15:52:32 -070081# _REBOOT_FAILURE_MESSAGE - This is the standard message text returned
82# when the Host.reboot() method fails. The source of this text comes
83# from `wait_for_restart()` in client/common_lib/hosts/base_classes.py.
84
85_REBOOT_FAILURE_MESSAGE = 'Host did not return from reboot'
86
87
Congbin Guoeb7aa2d2019-07-15 16:10:44 -070088DEVSERVER_PORT = '8082'
89GS_CACHE_PORT = '8888'
90
91
Richard Barnette9d43e562018-06-05 17:20:10 +000092class RootFSUpdateError(error.TestFail):
Chris Sosa77556d82012-04-05 15:23:14 -070093 """Raised when the RootFS fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070094
95
Richard Barnette9d43e562018-06-05 17:20:10 +000096class StatefulUpdateError(error.TestFail):
Chris Sosa77556d82012-04-05 15:23:14 -070097 """Raised when the stateful partition fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070098
99
Richard Barnette9d43e562018-06-05 17:20:10 +0000100class _AttributedUpdateError(error.TestFail):
101 """Update failure with an attributed cause."""
102
103 def __init__(self, attribution, msg):
104 super(_AttributedUpdateError, self).__init__(
105 '%s: %s' % (attribution, msg))
Richard Barnette5adb6d42018-06-28 15:52:32 -0700106 self._message = msg
107
108 def _classify(self):
109 for err_pattern, classification in self._CLASSIFIERS:
110 if re.match(err_pattern, self._message):
111 return classification
112 return None
113
114 @property
115 def failure_summary(self):
116 """Summarize this error for metrics reporting."""
117 classification = self._classify()
118 if classification:
119 return '%s: %s' % (self._SUMMARY, classification)
120 else:
121 return self._SUMMARY
Richard Barnette9d43e562018-06-05 17:20:10 +0000122
123
124class HostUpdateError(_AttributedUpdateError):
125 """Failure updating a DUT attributable to the DUT.
126
127 This class of exception should be raised when the most likely cause
128 of failure was a condition existing on the DUT prior to the update,
129 such as a hardware problem, or a bug in the software on the DUT.
130 """
131
Richard Barnette5adb6d42018-06-28 15:52:32 -0700132 DUT_DOWN = 'No answer to ssh'
133
134 _SUMMARY = 'DUT failed prior to update'
135 _CLASSIFIERS = [
136 (DUT_DOWN, DUT_DOWN),
137 (_REBOOT_FAILURE_MESSAGE, 'Reboot failed'),
138 ]
139
Richard Barnette9d43e562018-06-05 17:20:10 +0000140 def __init__(self, hostname, msg):
141 super(HostUpdateError, self).__init__(
142 'Error on %s prior to update' % hostname, msg)
143
144
145class DevServerError(_AttributedUpdateError):
146 """Failure updating a DUT attributable to the devserver.
147
148 This class of exception should be raised when the most likely cause
149 of failure was the devserver serving the target image for update.
150 """
151
Richard Barnette5adb6d42018-06-28 15:52:32 -0700152 _SUMMARY = 'Devserver failed prior to update'
153 _CLASSIFIERS = []
154
Richard Barnette9d43e562018-06-05 17:20:10 +0000155 def __init__(self, devserver, msg):
156 super(DevServerError, self).__init__(
157 'Devserver error on %s' % devserver, msg)
158
159
160class ImageInstallError(_AttributedUpdateError):
161 """Failure updating a DUT when installing from the devserver.
162
163 This class of exception should be raised when the target DUT fails
164 to download and install the target image from the devserver, and
165 either the devserver or the DUT might be at fault.
166 """
167
Richard Barnette5adb6d42018-06-28 15:52:32 -0700168 _SUMMARY = 'Image failed to download and install'
169 _CLASSIFIERS = []
170
Richard Barnette9d43e562018-06-05 17:20:10 +0000171 def __init__(self, hostname, devserver, msg):
172 super(ImageInstallError, self).__init__(
173 'Download and install failed from %s onto %s'
174 % (devserver, hostname), msg)
175
176
177class NewBuildUpdateError(_AttributedUpdateError):
178 """Failure updating a DUT attributable to the target build.
179
180 This class of exception should be raised when updating to a new
181 build fails, and the most likely cause of the failure is a bug in
182 the newly installed target build.
183 """
184
Richard Barnette5adb6d42018-06-28 15:52:32 -0700185 CHROME_FAILURE = 'Chrome failed to reach login screen'
186 UPDATE_ENGINE_FAILURE = ('update-engine failed to call '
187 'chromeos-setgoodkernel')
188 ROLLBACK_FAILURE = 'System rolled back to previous build'
189
190 _SUMMARY = 'New build failed'
191 _CLASSIFIERS = [
192 (CHROME_FAILURE, 'Chrome did not start'),
193 (UPDATE_ENGINE_FAILURE, 'update-engine did not start'),
194 (ROLLBACK_FAILURE, ROLLBACK_FAILURE),
195 ]
196
Richard Barnette9d43e562018-06-05 17:20:10 +0000197 def __init__(self, update_version, msg):
198 super(NewBuildUpdateError, self).__init__(
199 'Failure in build %s' % update_version, msg)
200
Richard Barnette621a8e42018-06-25 17:34:11 -0700201 @property
202 def failure_summary(self):
203 #pylint: disable=missing-docstring
204 return 'Build failed to work after installing'
205
Richard Barnette9d43e562018-06-05 17:20:10 +0000206
Richard Barnette3e8b2282018-05-15 20:42:20 +0000207def _url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -0800208 """Return the version based on update_url.
209
210 @param update_url: url to the image to update to.
211
212 """
Dale Curtisddfdb942011-07-14 13:59:24 -0700213 # The Chrome OS version is generally the last element in the URL. The only
214 # exception is delta update URLs, which are rooted under the version; e.g.,
215 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
216 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -0700217 return re.sub('/au/.*', '',
218 urlparse.urlparse(update_url).path).split('/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200219
220
Scott Zawalskieadbf702013-03-14 09:23:06 -0400221def url_to_image_name(update_url):
222 """Return the image name based on update_url.
223
224 From a URL like:
225 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
226 return lumpy-release/R27-3837.0.0
227
228 @param update_url: url to the image to update to.
229 @returns a string representing the image name in the update_url.
230
231 """
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700232 return urlparse.urlparse(update_url).path[len('/update/'):]
Scott Zawalskieadbf702013-03-14 09:23:06 -0400233
234
Richard Barnette4c81b972018-07-18 12:35:16 -0700235def get_update_failure_reason(exception):
236 """Convert an exception into a failure reason for metrics.
237
238 The passed in `exception` should be one raised by failure of
239 `ChromiumOSUpdater.run_update`. The returned string will describe
240 the failure. If the input exception value is not a truish value
241 the return value will be `None`.
242
243 The number of possible return strings is restricted to a limited
244 enumeration of values so that the string may be safely used in
245 Monarch metrics without worrying about cardinality of the range of
246 string values.
247
248 @param exception Exception to be converted to a failure reason.
249
250 @return A string suitable for use in Monarch metrics, or `None`.
251 """
252 if exception:
253 if isinstance(exception, _AttributedUpdateError):
254 return exception.failure_summary
255 else:
256 return 'Unknown Error: %s' % type(exception).__name__
257 return None
258
259
Prashanth B32baa9b2014-03-13 13:23:01 -0700260def _get_devserver_build_from_update_url(update_url):
261 """Get the devserver and build from the update url.
262
263 @param update_url: The url for update.
264 Eg: http://devserver:port/update/build.
265
266 @return: A tuple of (devserver url, build) or None if the update_url
267 doesn't match the expected pattern.
268
269 @raises ValueError: If the update_url doesn't match the expected pattern.
270 @raises ValueError: If no global_config was found, or it doesn't contain an
271 image_url_pattern.
272 """
273 pattern = global_config.global_config.get_config_value(
274 'CROS', 'image_url_pattern', type=str, default='')
275 if not pattern:
276 raise ValueError('Cannot parse update_url, the global config needs '
277 'an image_url_pattern.')
278 re_pattern = pattern.replace('%s', '(\S+)')
279 parts = re.search(re_pattern, update_url)
280 if not parts or len(parts.groups()) < 2:
281 raise ValueError('%s is not an update url' % update_url)
282 return parts.groups()
283
284
Richard Barnette3e8b2282018-05-15 20:42:20 +0000285def _list_image_dir_contents(update_url):
Prashanth B32baa9b2014-03-13 13:23:01 -0700286 """Lists the contents of the devserver for a given build/update_url.
287
288 @param update_url: An update url. Eg: http://devserver:port/update/build.
289 """
290 if not update_url:
291 logging.warning('Need update_url to list contents of the devserver.')
292 return
293 error_msg = 'Cannot check contents of devserver, update url %s' % update_url
294 try:
295 devserver_url, build = _get_devserver_build_from_update_url(update_url)
296 except ValueError as e:
297 logging.warning('%s: %s', error_msg, e)
298 return
299 devserver = dev_server.ImageServer(devserver_url)
300 try:
301 devserver.list_image_dir(build)
302 # The devserver will retry on URLError to avoid flaky connections, but will
303 # eventually raise the URLError if it persists. All HTTPErrors get
304 # converted to DevServerExceptions.
305 except (dev_server.DevServerException, urllib2.URLError) as e:
306 logging.warning('%s: %s', error_msg, e)
307
308
Richard Barnette621a8e42018-06-25 17:34:11 -0700309def _get_metric_fields(update_url):
310 """Return a dict of metric fields.
311
312 This is used for sending autoupdate metrics for the given update URL.
313
314 @param update_url Metrics fields will be calculated from this URL.
315 """
316 build_name = url_to_image_name(update_url)
317 try:
318 board, build_type, milestone, _ = server_utils.ParseBuildName(
319 build_name)
320 except server_utils.ParseBuildNameException:
321 logging.warning('Unable to parse build name %s for metrics. '
322 'Continuing anyway.', build_name)
323 board, build_type, milestone = ('', '', '')
324 return {
325 'dev_server': dev_server.get_resolved_hostname(update_url),
326 'board': board,
327 'build_type': build_type,
328 'milestone': milestone,
329 }
330
331
Richard Barnette3e8b2282018-05-15 20:42:20 +0000332class ChromiumOSUpdater(object):
333 """Chromium OS specific DUT update functionality."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700334
Richard Barnette60e759e2018-07-21 20:56:59 -0700335 def __init__(self, update_url, host=None, interactive=True,
Garry Wang01a1d482020-08-02 20:46:53 -0700336 use_quick_provision=False, is_release_bucket=None,
337 is_servohost=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700338 """Initializes the object.
339
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700340 @param update_url: The URL we want the update to use.
341 @param host: A client.common_lib.hosts.Host implementation.
David Haddock76a4c882017-12-13 18:50:09 -0800342 @param interactive: Bool whether we are doing an interactive update.
Richard Barnette60e759e2018-07-21 20:56:59 -0700343 @param use_quick_provision: Whether we should attempt to perform
344 the update using the quick-provision script.
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700345 @param is_release_bucket: If True, use release bucket
346 gs://chromeos-releases.
Garry Wang01a1d482020-08-02 20:46:53 -0700347 @param is_servohost: Bool whether the update target is a servohost.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700348 """
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700349 self.update_url = update_url
350 self.host = host
David Haddock76a4c882017-12-13 18:50:09 -0800351 self.interactive = interactive
Richard Barnette3e8b2282018-05-15 20:42:20 +0000352 self.update_version = _url_to_version(update_url)
Richard Barnette60e759e2018-07-21 20:56:59 -0700353 self._use_quick_provision = use_quick_provision
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700354 self._is_release_bucket = is_release_bucket
Garry Wang01a1d482020-08-02 20:46:53 -0700355 self._is_servohost = is_servohost
356
Richard Barnette3e8b2282018-05-15 20:42:20 +0000357
358 def _run(self, cmd, *args, **kwargs):
359 """Abbreviated form of self.host.run(...)"""
360 return self.host.run(cmd, *args, **kwargs)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700361
362
363 def check_update_status(self):
364 """Returns the current update engine state.
365
366 We use the `update_engine_client -status' command and parse the line
367 indicating the update state, e.g. "CURRENT_OP=UPDATE_STATUS_IDLE".
368 """
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800369 update_status = self.host.run(command='%s -status | grep CURRENT_OP' %
Richard Barnette3e8b2282018-05-15 20:42:20 +0000370 _UPDATER_BIN)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700371 return update_status.stdout.strip().split('=')[-1]
372
373
Richard Barnette55d1af82018-05-22 23:40:14 +0000374 def _rootdev(self, options=''):
375 """Returns the stripped output of rootdev <options>.
376
377 @param options: options to run rootdev.
378
379 """
380 return self._run('rootdev %s' % options).stdout.strip()
381
382
Richard Barnette3e8b2282018-05-15 20:42:20 +0000383 def _get_last_update_error(self):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800384 """Get the last autoupdate error code."""
Richard Barnette3e8b2282018-05-15 20:42:20 +0000385 command_result = self._run(
386 '%s --last_attempt_error' % _UPDATER_BIN)
387 return command_result.stdout.strip().replace('\n', ', ')
Shuqian Zhaod9992722016-02-29 12:26:38 -0800388
389
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800390 def _base_update_handler_no_retry(self, run_args):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800391 """Base function to handle a remote update ssh call.
392
393 @param run_args: Dictionary of args passed to ssh_host.run function.
Shuqian Zhaod9992722016-02-29 12:26:38 -0800394
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800395 @throws: intercepts and re-throws all exceptions
Shuqian Zhaod9992722016-02-29 12:26:38 -0800396 """
Shuqian Zhaod9992722016-02-29 12:26:38 -0800397 try:
398 self.host.run(**run_args)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800399 except Exception as e:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800400 logging.debug('exception in update handler: %s', e)
401 raise e
Shuqian Zhaod9992722016-02-29 12:26:38 -0800402
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800403
404 def _base_update_handler(self, run_args, err_msg_prefix=None):
405 """Handle a remote update ssh call, possibly with retries.
406
407 @param run_args: Dictionary of args passed to ssh_host.run function.
408 @param err_msg_prefix: Prefix of the exception error message.
409 """
410 def exception_handler(e):
411 """Examines exceptions and returns True if the update handler
412 should be retried.
413
414 @param e: the exception intercepted by the retry util.
415 """
416 return (isinstance(e, error.AutoservSSHTimeout) or
417 (isinstance(e, error.GenericHostRunError) and
418 hasattr(e, 'description') and
419 (re.search('ERROR_CODE=37', e.description) or
420 re.search('generic error .255.', e.description))))
421
422 try:
423 # Try the update twice (arg 2 is max_retry, not including the first
424 # call). Some exceptions may be caught by the retry handler.
425 retry_util.GenericRetry(exception_handler, 1,
426 self._base_update_handler_no_retry,
427 run_args)
428 except Exception as e:
429 message = err_msg_prefix + ': ' + str(e)
430 raise RootFSUpdateError(message)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800431
432
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800433 def _wait_for_update_service(self):
434 """Ensure that the update engine daemon is running, possibly
435 by waiting for it a bit in case the DUT just rebooted and the
436 service hasn't started yet.
437 """
438 def handler(e):
439 """Retry exception handler.
440
441 Assumes that the error is due to the update service not having
442 started yet.
443
444 @param e: the exception intercepted by the retry util.
445 """
446 if isinstance(e, error.AutoservRunError):
447 logging.debug('update service check exception: %s\n'
448 'retrying...', e)
449 return True
450 else:
451 return False
452
453 # Retry at most three times, every 5s.
454 status = retry_util.GenericRetry(handler, 3,
455 self.check_update_status,
456 sleep=5)
457
458 # Expect the update engine to be idle.
459 if status != UPDATER_IDLE:
Richard Barnette9d43e562018-06-05 17:20:10 +0000460 raise RootFSUpdateError(
461 'Update engine status is %s (%s was expected).'
462 % (status, UPDATER_IDLE))
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800463
464
Richard Barnette55d1af82018-05-22 23:40:14 +0000465 def _reset_update_engine(self):
466 """Resets the host to prepare for a clean update regardless of state."""
467 self._run('stop ui || true')
468 self._run('stop update-engine || true')
469 self._run('start update-engine')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800470 self._wait_for_update_service()
471
Richard Barnette55d1af82018-05-22 23:40:14 +0000472
473 def _reset_stateful_partition(self):
474 """Clear any pending stateful update request."""
Richard Barnette18fd5842018-05-25 18:21:14 +0000475 self._run('%s --stateful_change=reset 2>&1'
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700476 % self._get_stateful_update_script())
Richard Barnette3ef29a82018-06-28 13:52:54 -0700477 self._run('rm -f %s' % _TARGET_VERSION)
478
479
480 def _set_target_version(self):
481 """Set the "target version" for the update."""
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700482 # Version strings that come from release buckets do not have RXX- at the
483 # beginning. So remove this prefix only if the version has it.
484 version_number = (self.update_version.split('-')[1]
485 if '-' in self.update_version
486 else self.update_version)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700487 self._run('echo %s > %s' % (version_number, _TARGET_VERSION))
Richard Barnette55d1af82018-05-22 23:40:14 +0000488
489
490 def _revert_boot_partition(self):
491 """Revert the boot partition."""
492 part = self._rootdev('-s')
493 logging.warning('Reverting update; Boot partition will be %s', part)
494 return self._run('/postinst %s 2>&1' % part)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700495
496
497 def _verify_update_completed(self):
498 """Verifies that an update has completed.
499
Richard Barnette9d43e562018-06-05 17:20:10 +0000500 @raise RootFSUpdateError if the DUT doesn't indicate that
501 download is complete and the DUT is ready for reboot.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700502 """
503 status = self.check_update_status()
504 if status != UPDATER_NEED_REBOOT:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800505 error_msg = ''
506 if status == UPDATER_IDLE:
Richard Barnette3e8b2282018-05-15 20:42:20 +0000507 error_msg = 'Update error: %s' % self._get_last_update_error()
Richard Barnette9d43e562018-06-05 17:20:10 +0000508 raise RootFSUpdateError(
509 'Update engine status is %s (%s was expected). %s'
510 % (status, UPDATER_NEED_REBOOT, error_msg))
David Haddock77b75c32020-05-14 01:56:32 -0700511 return kernel_utils.verify_kernel_state_after_update(self.host)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700512
513
Richard Barnette55d1af82018-05-22 23:40:14 +0000514 def trigger_update(self):
Richard Barnette9d43e562018-06-05 17:20:10 +0000515 """Triggers a background update."""
516 # If this function is called immediately after reboot (which it
517 # can be), there is no guarantee that the update engine is up
518 # and running yet, so wait for it.
Richard Barnette55d1af82018-05-22 23:40:14 +0000519 self._wait_for_update_service()
520
521 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
522 (_UPDATER_BIN, self.update_url))
523 run_args = {'command': autoupdate_cmd}
524 err_prefix = 'Failed to trigger an update on %s. ' % self.host.hostname
525 logging.info('Triggering update via: %s', autoupdate_cmd)
526 metric_fields = {'success': False}
527 try:
528 self._base_update_handler(run_args, err_prefix)
529 metric_fields['success'] = True
530 finally:
531 c = metrics.Counter('chromeos/autotest/autoupdater/trigger')
Richard Barnette621a8e42018-06-25 17:34:11 -0700532 metric_fields.update(_get_metric_fields(self.update_url))
Richard Barnette55d1af82018-05-22 23:40:14 +0000533 c.increment(fields=metric_fields)
534
535
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700536 def update_image(self):
Richard Barnette18fd5842018-05-25 18:21:14 +0000537 """Updates the device root FS and kernel and verifies success."""
Shuqian Zhaofe4d62e2016-06-23 14:46:45 -0700538 autoupdate_cmd = ('%s --update --omaha_url=%s' %
Richard Barnette3e8b2282018-05-15 20:42:20 +0000539 (_UPDATER_BIN, self.update_url))
David Haddock76a4c882017-12-13 18:50:09 -0800540 if not self.interactive:
541 autoupdate_cmd = '%s --interactive=false' % autoupdate_cmd
Shuqian Zhaod9992722016-02-29 12:26:38 -0800542 run_args = {'command': autoupdate_cmd, 'timeout': 3600}
543 err_prefix = ('Failed to install device image using payload at %s '
544 'on %s. ' % (self.update_url, self.host.hostname))
545 logging.info('Updating image via: %s', autoupdate_cmd)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700546 metric_fields = {'success': False}
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800547 try:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800548 self._base_update_handler(run_args, err_prefix)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700549 metric_fields['success'] = True
550 finally:
Allen Li1a5cc0a2017-06-20 14:08:59 -0700551 c = metrics.Counter('chromeos/autotest/autoupdater/update')
Richard Barnette621a8e42018-06-25 17:34:11 -0700552 metric_fields.update(_get_metric_fields(self.update_url))
Allen Li1a5cc0a2017-06-20 14:08:59 -0700553 c.increment(fields=metric_fields)
Richard Barnette4d211c92018-05-24 18:56:08 +0000554 return self._verify_update_completed()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700555
556
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700557 def _get_remote_script(self, script_name):
558 """Ensure that `script_name` is present on the DUT.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700559
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700560 The given script (e.g. `stateful_update`) may be present in the
561 stateful partition under /usr/local/bin, or we may have to
562 download it from the devserver.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700563
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700564 Determine whether the script is present or must be downloaded
565 and download if necessary. Then, return a command fragment
566 sufficient to run the script from whereever it now lives on the
567 DUT.
Richard Barnette9d43e562018-06-05 17:20:10 +0000568
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700569 @param script_name The name of the script as expected in
570 /usr/local/bin and on the devserver.
571 @return A string with the command (minus arguments) that will
572 run the target script.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700573 """
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700574 remote_script = '/usr/local/bin/%s' % script_name
575 if self.host.path_exists(remote_script):
576 return remote_script
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800577 self.host.run('mkdir -p -m 1777 /usr/local/tmp')
578 remote_tmp_script = '/usr/local/tmp/%s' % script_name
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700579 server_name = urlparse.urlparse(self.update_url)[1]
580 script_url = 'http://%s/static/%s' % (server_name, script_name)
Dana Goyette353d1d92019-06-27 10:43:59 -0700581 fetch_script = 'curl -Ss -o %s %s && head -1 %s' % (
582 remote_tmp_script, script_url, remote_tmp_script)
Chris Sosa5e4246b2012-05-22 18:05:22 -0700583
Dana Goyette353d1d92019-06-27 10:43:59 -0700584 first_line = self._run(fetch_script).stdout.strip()
585
586 if first_line and first_line.startswith('#!'):
587 script_interpreter = first_line.lstrip('#!')
588 if script_interpreter:
589 return '%s %s' % (script_interpreter, remote_tmp_script)
590 return None
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700591
592 def _get_stateful_update_script(self):
593 """Returns a command to run the stateful update script.
594
595 Find `stateful_update` on the target or install it, as
596 necessary. If installation fails, raise an exception.
597
598 @raise StatefulUpdateError if the script can't be found or
599 installed.
600 @return A string that can be joined with arguments to run the
601 `stateful_update` command on the DUT.
602 """
603 script_command = self._get_remote_script(_STATEFUL_UPDATE_SCRIPT)
604 if not script_command:
605 raise StatefulUpdateError('Could not install %s on DUT'
Richard Barnette9d43e562018-06-05 17:20:10 +0000606 % _STATEFUL_UPDATE_SCRIPT)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700607 return script_command
Chris Sosa5e4246b2012-05-22 18:05:22 -0700608
609
Chris Sosa72312602013-04-16 15:01:56 -0700610 def update_stateful(self, clobber=True):
611 """Updates the stateful partition.
612
613 @param clobber: If True, a clean stateful installation.
Richard Barnette9d43e562018-06-05 17:20:10 +0000614
615 @raise StatefulUpdateError if the update script fails to
616 complete successfully.
Chris Sosa72312602013-04-16 15:01:56 -0700617 """
Chris Sosa77556d82012-04-05 15:23:14 -0700618 logging.info('Updating stateful partition...')
Richard Barnette18fd5842018-05-25 18:21:14 +0000619 statefuldev_url = self.update_url.replace('update', 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700620
Dale Curtis5c32c722011-05-04 19:24:23 -0700621 # Attempt stateful partition update; this must succeed so that the newly
622 # installed host is testable after update.
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700623 statefuldev_cmd = [self._get_stateful_update_script(), statefuldev_url]
Chris Sosa72312602013-04-16 15:01:56 -0700624 if clobber:
625 statefuldev_cmd.append('--stateful_change=clean')
626
627 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700628 try:
Dan Shi205b8732016-01-25 10:56:22 -0800629 self._run(' '.join(statefuldev_cmd), timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700630 except error.AutoservRunError:
Richard Barnette18fd5842018-05-25 18:21:14 +0000631 raise StatefulUpdateError(
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700632 'Failed to perform stateful update on %s' %
633 self.host.hostname)
Dale Curtis5c32c722011-05-04 19:24:23 -0700634
Chris Sosaa3ac2152012-05-23 22:23:13 -0700635
Richard Barnette14ee84c2018-05-18 20:23:42 +0000636 def _prepare_host(self):
637 """Make sure the target DUT is working and ready for update.
638
639 Initially, the target DUT's state is unknown. The DUT is
640 expected to be online, but we strive to be forgiving if Chrome
641 and/or the update engine aren't fully functional.
642 """
643 # Summary of work, and the rationale:
644 # 1. Reboot, because it's a good way to clear out problems.
645 # 2. Touch the PROVISION_FAILED file, to allow repair to detect
646 # failure later.
647 # 3. Run the hook for host class specific preparation.
648 # 4. Stop Chrome, because the system is designed to eventually
649 # reboot if Chrome is stuck in a crash loop.
650 # 5. Force `update-engine` to start, because if Chrome failed
651 # to start properly, the status of the `update-engine` job
652 # will be uncertain.
Richard Barnette5adb6d42018-06-28 15:52:32 -0700653 if not self.host.is_up():
654 raise HostUpdateError(self.host.hostname,
655 HostUpdateError.DUT_DOWN)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000656 self._reset_stateful_partition()
Garry Wang01a1d482020-08-02 20:46:53 -0700657 # Servohost reboot logic is handled by themselves.
658 if not self._is_servohost:
659 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
660 self._run('touch %s' % PROVISION_FAILED)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000661 self.host.prepare_for_update()
Garry Wang01a1d482020-08-02 20:46:53 -0700662 # Servohost will only update via quick provision.
663 if not self._is_servohost:
664 self._reset_update_engine()
Richard Barnette14ee84c2018-05-18 20:23:42 +0000665 logging.info('Updating from version %s to %s.',
666 self.host.get_release_version(),
667 self.update_version)
668
Amin Hassani95f86e02020-07-14 13:06:03 -0700669 def _install_via_update_engine(self, devserver_name, image_name):
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700670 """Install an updating using the production AU flow.
671
Amin Hassani95f86e02020-07-14 13:06:03 -0700672 This uses the standard AU flow and the `stateful_update` script to
673 download and install a root FS, kernel and stateful filesystem content.
674
675 @param devserver_name: The devserver name and port (optional).
676 @param image_name: The image to be installed.
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700677
678 @return The kernel expected to be booted next.
679 """
680 logging.info('Installing image using update_engine.')
Amin Hassani95f86e02020-07-14 13:06:03 -0700681 ds = dev_server.ImageServer('http://%s' % devserver_name)
682 try:
683 ds.stage_artifacts(image_name, ['full_payload', 'stateful',
684 'autotest_packages'])
685 except dev_server.DevServerException as e:
686 raise error.TestFail, str(e), sys.exc_info()[2]
687
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700688 expected_kernel = self.update_image()
689 self.update_stateful()
Richard Barnette3ef29a82018-06-28 13:52:54 -0700690 self._set_target_version()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700691 return expected_kernel
692
693
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700694 def _quick_provision_with_gs_cache(self, provision_command, devserver_name,
695 image_name):
696 """Run quick_provision using GsCache server.
697
698 @param provision_command: The path of quick_provision command.
699 @param devserver_name: The devserver name and port (optional).
700 @param image_name: The image to be installed.
701 """
702 logging.info('Try quick provision with gs_cache.')
703 # If enabled, GsCache server listion on different port on the
704 # devserver.
705 gs_cache_server = devserver_name.replace(DEVSERVER_PORT, GS_CACHE_PORT)
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700706 gs_cache_url = ('http://%s/download/%s'
707 % (gs_cache_server,
708 'chromeos-releases' if self._is_release_bucket
709 else 'chromeos-image-archive'))
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700710
711 # Check if GS_Cache server is enabled on the server.
Congbin Guo4a2a6642019-08-12 15:03:01 -0700712 self._run('curl -s -o /dev/null %s' % gs_cache_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700713
714 command = '%s --noreboot %s %s' % (provision_command, image_name,
715 gs_cache_url)
716 self._run(command)
717 metrics.Counter(_metric_name('quick_provision')).increment(
718 fields={'devserver': devserver_name, 'gs_cache': True})
719
720
721 def _quick_provision_with_devserver(self, provision_command,
722 devserver_name, image_name):
723 """Run quick_provision using legacy devserver.
724
725 @param provision_command: The path of quick_provision command.
726 @param devserver_name: The devserver name and port (optional).
727 @param image_name: The image to be installed.
728 """
Congbin Guo63ae0302019-08-12 16:37:49 -0700729 logging.info('Try quick provision with devserver.')
730 ds = dev_server.ImageServer('http://%s' % devserver_name)
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700731 archive_url = ('gs://chromeos-releases/%s' % image_name
732 if self._is_release_bucket else None)
Congbin Guo63ae0302019-08-12 16:37:49 -0700733 try:
Amin Hassani95f86e02020-07-14 13:06:03 -0700734 ds.stage_artifacts(image_name, ['quick_provision', 'stateful',
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700735 'autotest_packages'],
736 archive_url=archive_url)
Congbin Guo63ae0302019-08-12 16:37:49 -0700737 except dev_server.DevServerException as e:
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -0700738 six.reraise(error.TestFail, str(e), sys.exc_info()[2])
Congbin Guo63ae0302019-08-12 16:37:49 -0700739
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700740 static_url = 'http://%s/static' % devserver_name
741 command = '%s --noreboot %s %s' % (provision_command, image_name,
742 static_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700743 self._run(command)
744 metrics.Counter(_metric_name('quick_provision')).increment(
745 fields={'devserver': devserver_name, 'gs_cache': False})
746
747
Amin Hassani95f86e02020-07-14 13:06:03 -0700748 def _install_via_quick_provision(self, server_name, image_name):
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700749 """Install an updating using the `quick-provision` script.
750
751 This uses the `quick-provision` script to download and install
752 a root FS, kernel and stateful filesystem content.
753
Amin Hassani95f86e02020-07-14 13:06:03 -0700754 @param server_name: The devserver name and port (optional).
755 @param image_name: The image to be installed.
756
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700757 @return The kernel expected to be booted next.
758 """
Amin Hassanib04420b2020-07-08 18:46:11 +0000759 logging.info('Installing image using quick-provision.')
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700760 provision_command = self._get_remote_script(_QUICK_PROVISION_SCRIPT)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700761 try:
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700762 try:
763 self._quick_provision_with_gs_cache(provision_command,
764 server_name, image_name)
Amin Hassani95f86e02020-07-14 13:06:03 -0700765 except Exception as e:
766 logging.error('Failed to quick-provision with gscache with '
767 'error %s', e)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700768 self._quick_provision_with_devserver(provision_command,
769 server_name, image_name)
770
Richard Barnette3ef29a82018-06-28 13:52:54 -0700771 self._set_target_version()
David Haddock77b75c32020-05-14 01:56:32 -0700772 return kernel_utils.verify_kernel_state_after_update(self.host)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700773 except Exception:
774 # N.B. We handle only `Exception` here. Non-Exception
775 # classes (such as KeyboardInterrupt) are handled by our
776 # caller.
777 logging.exception('quick-provision script failed; '
778 'will fall back to update_engine.')
779 self._revert_boot_partition()
780 self._reset_stateful_partition()
781 self._reset_update_engine()
782 return None
783
784
Richard Barnette54d14f52018-05-18 16:39:49 +0000785 def _install_update(self):
Richard Barnette0beb14b2018-05-15 18:07:52 +0000786 """Install the requested image on the DUT, but don't start it.
787
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700788 This downloads and installs a root FS, kernel and stateful
789 filesystem content. This does not reboot the DUT, so the update
790 is merely pending when the method returns.
791
792 @return The kernel expected to be booted next.
Dan Shi0f466e82013-02-22 15:44:58 -0800793 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000794 logging.info('Installing image at %s onto %s',
795 self.update_url, self.host.hostname)
Amin Hassani95f86e02020-07-14 13:06:03 -0700796 server_name = urlparse.urlparse(self.update_url)[1]
797 image_name = url_to_image_name(self.update_url)
798
Amin Hassani78377a82020-07-24 14:04:13 -0700799 if self._use_quick_provision:
800 return self._install_via_quick_provision(server_name, image_name)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200801 try:
Amin Hassani78377a82020-07-24 14:04:13 -0700802 return self._install_via_update_engine(server_name, image_name)
Dale Curtis1e973182011-07-12 18:21:36 -0700803 except:
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700804 # N.B. This handling code includes non-Exception classes such
805 # as KeyboardInterrupt. We need to clean up, but we also must
806 # re-raise.
Richard Barnette14ee84c2018-05-18 20:23:42 +0000807 self._revert_boot_partition()
808 self._reset_stateful_partition()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700809 self._reset_update_engine()
Dale Curtis1e973182011-07-12 18:21:36 -0700810 # Collect update engine logs in the event of failure.
811 if self.host.job:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700812 logging.info('Collecting update engine logs due to failure...')
Dale Curtis1e973182011-07-12 18:21:36 -0700813 self.host.get_file(
Richard Barnette3e8b2282018-05-15 20:42:20 +0000814 _UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
Gilad Arnold0c0df732015-09-21 06:37:59 -0700815 preserve_perm=False)
Richard Barnette3e8b2282018-05-15 20:42:20 +0000816 _list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700817 raise
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200818
819
Richard Barnette14ee84c2018-05-18 20:23:42 +0000820 def _complete_update(self, expected_kernel):
821 """Finish the update, and confirm that it succeeded.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000822
Richard Barnette14ee84c2018-05-18 20:23:42 +0000823 Initial condition is that the target build has been downloaded
824 and installed on the DUT, but has not yet been booted. This
825 function is responsible for rebooting the DUT, and checking that
826 the new build is running successfully.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000827
Richard Barnette14ee84c2018-05-18 20:23:42 +0000828 @param expected_kernel: kernel expected to be active after reboot.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000829 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000830 # Regarding the 'crossystem' command below: In some cases,
831 # the update flow puts the TPM into a state such that it
832 # fails verification. We don't know why. However, this
833 # call papers over the problem by clearing the TPM during
834 # the reboot.
835 #
836 # We ignore failures from 'crossystem'. Although failure
837 # here is unexpected, and could signal a bug, the point of
838 # the exercise is to paper over problems; allowing this to
839 # fail would defeat the purpose.
840 self._run('crossystem clear_tpm_owner_request=1',
841 ignore_status=True)
842 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
843
Richard Barnette0beb14b2018-05-15 18:07:52 +0000844 # Touch the lab machine file to leave a marker that
845 # distinguishes this image from other test images.
846 # Afterwards, we must re-run the autoreboot script because
847 # it depends on the _LAB_MACHINE_FILE.
848 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
849 '( touch "$FILE" ; start autoreboot )')
Richard Barnette3e8b2282018-05-15 20:42:20 +0000850 self._run(autoreboot_cmd % _LAB_MACHINE_FILE)
David Haddock3446a642020-05-26 03:26:49 -0700851 kernel_utils.verify_boot_expectations(
852 expected_kernel, NewBuildUpdateError.ROLLBACK_FAILURE, self.host)
Richard Barnette0beb14b2018-05-15 18:07:52 +0000853
854 logging.debug('Cleaning up old autotest directories.')
855 try:
856 installed_autodir = autotest.Autotest.get_installed_autodir(
857 self.host)
858 self._run('rm -rf ' + installed_autodir)
859 except autotest.AutodirNotFoundError:
860 logging.debug('No autotest installed directory found.')
861
862
Richard Barnette4c81b972018-07-18 12:35:16 -0700863 def run_update(self):
864 """Perform a full update of a DUT in the test lab.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000865
Richard Barnette4c81b972018-07-18 12:35:16 -0700866 This downloads and installs the root FS and stateful partition
867 content needed for the update specified in `self.host` and
868 `self.update_url`. The update is performed according to the
869 requirements for provisioning a DUT for testing the requested
870 build.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000871
Richard Barnette4c81b972018-07-18 12:35:16 -0700872 At the end of the procedure, metrics are reported describing the
873 outcome of the operation.
874
875 @returns A tuple of the form `(image_name, attributes)`, where
876 `image_name` is the name of the image installed, and
877 `attributes` is new attributes to be applied to the DUT.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000878 """
Richard Barnette4c81b972018-07-18 12:35:16 -0700879 server_name = dev_server.get_resolved_hostname(self.update_url)
880 metrics.Counter(_metric_name('install')).increment(
881 fields={'devserver': server_name})
882
Richard Barnette9d43e562018-06-05 17:20:10 +0000883 try:
884 self._prepare_host()
885 except _AttributedUpdateError:
886 raise
887 except Exception as e:
888 logging.exception('Failure preparing host prior to update.')
889 raise HostUpdateError(self.host.hostname, str(e))
890
891 try:
892 expected_kernel = self._install_update()
893 except _AttributedUpdateError:
894 raise
895 except Exception as e:
896 logging.exception('Failure during download and install.')
897 raise ImageInstallError(self.host.hostname, server_name, str(e))
898
Garry Wang01a1d482020-08-02 20:46:53 -0700899 # Servohost will handle post update process themselves.
900 if not self._is_servohost:
901 try:
902 self._complete_update(expected_kernel)
903 except _AttributedUpdateError:
904 raise
905 except Exception as e:
906 logging.exception('Failure from build after update.')
907 raise NewBuildUpdateError(self.update_version, str(e))
Richard Barnette0beb14b2018-05-15 18:07:52 +0000908
Richard Barnette0beb14b2018-05-15 18:07:52 +0000909 image_name = url_to_image_name(self.update_url)
910 # update_url is different from devserver url needed to stage autotest
911 # packages, therefore, resolve a new devserver url here.
912 devserver_url = dev_server.ImageServer.resolve(
913 image_name, self.host.hostname).url()
914 repo_url = tools.get_package_url(devserver_url, image_name)
915 return image_name, {ds_constants.JOB_REPO_URL: repo_url}