blob: 66c739fa6b2fcc9047391c42cc845c4a21750b10 [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
Amin Hassani5cda21d2020-08-10 15:24:44 -07008import os
Sean O'Connor5346e4e2010-08-12 18:49:24 +02009import re
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -070010import six
Congbin Guo63ae0302019-08-12 16:37:49 -070011import sys
Prashanth B32baa9b2014-03-13 13:23:01 -070012import urllib2
Richard Barnette0beb14b2018-05-15 18:07:52 +000013import urlparse
Sean O'Connor5346e4e2010-08-12 18:49:24 +020014
Chris Sosa65425082013-10-16 13:26:22 -070015from autotest_lib.client.bin import utils
Dale Curtis5c32c722011-05-04 19:24:23 -070016from autotest_lib.client.common_lib import error, global_config
Prashanth B32baa9b2014-03-13 13:23:01 -070017from autotest_lib.client.common_lib.cros import dev_server
David Haddock77b75c32020-05-14 01:56:32 -070018from autotest_lib.client.common_lib.cros import kernel_utils
Richard Barnette0beb14b2018-05-15 18:07:52 +000019from autotest_lib.server import autotest
Shelley Chen61d28982016-10-28 09:40:20 -070020from autotest_lib.server import utils as server_utils
Richard Barnette0beb14b2018-05-15 18:07:52 +000021from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
22from autotest_lib.server.cros.dynamic_suite import tools
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -080023from chromite.lib import retry_util
Dan Shif3a35f72016-01-25 11:18:14 -080024
Shelley Chen16b8df32016-10-27 16:24:21 -070025try:
26 from chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080027except ImportError:
28 metrics = utils.metrics_mock
Sean O'Connor5346e4e2010-08-12 18:49:24 +020029
Gwendal Grignou3e96cc22017-06-07 16:22:51 -070030
Richard Barnette621a8e42018-06-25 17:34:11 -070031def _metric_name(base_name):
32 return 'chromeos/autotest/provision/' + base_name
33
34
Dale Curtis5c32c722011-05-04 19:24:23 -070035# Local stateful update path is relative to the CrOS source directory.
Sean O'Connor5346e4e2010-08-12 18:49:24 +020036UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020037UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
beeps5e8c45a2013-12-17 22:05:11 -080038# A list of update engine client states that occur after an update is triggered.
Garry Wangcd769872019-06-07 16:04:17 -070039UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FOR_UPDATE',
beeps5e8c45a2013-12-17 22:05:11 -080040 'UPDATE_STATUS_UPDATE_AVAILABLE',
41 'UPDATE_STATUS_DOWNLOADING',
Garry Wangcd769872019-06-07 16:04:17 -070042 'UPDATE_STATUS_FINALIZING',
43 'UPDATE_STATUS_VERIFYING',
44 'UPDATE_STATUS_REPORTING_ERROR_EVENT',
45 'UPDATE_STATUS_ATTEMPTING_ROLLBACK']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020046
Richard Barnette0beb14b2018-05-15 18:07:52 +000047
Richard Barnette3e8b2282018-05-15 20:42:20 +000048_STATEFUL_UPDATE_SCRIPT = 'stateful_update'
Richard Barnettee86b1ce2018-06-07 10:37:23 -070049_QUICK_PROVISION_SCRIPT = 'quick-provision'
Richard Barnette3e8b2282018-05-15 20:42:20 +000050
51_UPDATER_BIN = '/usr/bin/update_engine_client'
52_UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
53
Richard Barnette0beb14b2018-05-15 18:07:52 +000054# PROVISION_FAILED - A flag file to indicate provision failures. The
55# file is created at the start of any AU procedure (see
Richard Barnette9d43e562018-06-05 17:20:10 +000056# `ChromiumOSUpdater._prepare_host()`). The file's location in
Richard Barnette0beb14b2018-05-15 18:07:52 +000057# stateful means that on successul update it will be removed. Thus, if
58# this file exists, it indicates that we've tried and failed in a
59# previous attempt to update.
60PROVISION_FAILED = '/var/tmp/provision_failed'
61
62
Richard Barnette3e8b2282018-05-15 20:42:20 +000063# A flag file used to enable special handling in lab DUTs. Some
64# parts of the system in Chromium OS test images will behave in ways
65# convenient to the test lab when this file is present. Generally,
66# we create this immediately after any update completes.
67_LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
68
69
Richard Barnette3ef29a82018-06-28 13:52:54 -070070# _TARGET_VERSION - A file containing the new version to which we plan
71# to update. This file is used by the CrOS shutdown code to detect and
72# handle certain version downgrade cases. Specifically: Downgrading
73# may trigger an unwanted powerwash in the target build when the
74# following conditions are met:
75# * Source build is a v4.4 kernel with R69-10756.0.0 or later.
76# * Target build predates the R69-10756.0.0 cutoff.
77# When this file is present and indicates a downgrade, the OS shutdown
78# code on the DUT knows how to prevent the powerwash.
79_TARGET_VERSION = '/run/update_target_version'
80
81
Richard Barnette5adb6d42018-06-28 15:52:32 -070082# _REBOOT_FAILURE_MESSAGE - This is the standard message text returned
83# when the Host.reboot() method fails. The source of this text comes
84# from `wait_for_restart()` in client/common_lib/hosts/base_classes.py.
85
86_REBOOT_FAILURE_MESSAGE = 'Host did not return from reboot'
87
88
Congbin Guoeb7aa2d2019-07-15 16:10:44 -070089DEVSERVER_PORT = '8082'
90GS_CACHE_PORT = '8888'
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 """
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700233 return urlparse.urlparse(update_url).path[len('/update/'):]
Scott Zawalskieadbf702013-03-14 09:23:06 -0400234
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
Richard Barnette3e8b2282018-05-15 20:42:20 +0000333class ChromiumOSUpdater(object):
334 """Chromium OS specific DUT update functionality."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700335
Richard Barnette60e759e2018-07-21 20:56:59 -0700336 def __init__(self, update_url, host=None, interactive=True,
Garry Wang01a1d482020-08-02 20:46:53 -0700337 use_quick_provision=False, is_release_bucket=None,
338 is_servohost=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700339 """Initializes the object.
340
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700341 @param update_url: The URL we want the update to use.
342 @param host: A client.common_lib.hosts.Host implementation.
David Haddock76a4c882017-12-13 18:50:09 -0800343 @param interactive: Bool whether we are doing an interactive update.
Richard Barnette60e759e2018-07-21 20:56:59 -0700344 @param use_quick_provision: Whether we should attempt to perform
345 the update using the quick-provision script.
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700346 @param is_release_bucket: If True, use release bucket
347 gs://chromeos-releases.
Garry Wang01a1d482020-08-02 20:46:53 -0700348 @param is_servohost: Bool whether the update target is a servohost.
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
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700355 self._is_release_bucket = is_release_bucket
Garry Wang01a1d482020-08-02 20:46:53 -0700356 self._is_servohost = is_servohost
357
Richard Barnette3e8b2282018-05-15 20:42:20 +0000358
359 def _run(self, cmd, *args, **kwargs):
360 """Abbreviated form of self.host.run(...)"""
361 return self.host.run(cmd, *args, **kwargs)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700362
363
364 def check_update_status(self):
365 """Returns the current update engine state.
366
367 We use the `update_engine_client -status' command and parse the line
368 indicating the update state, e.g. "CURRENT_OP=UPDATE_STATUS_IDLE".
369 """
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800370 update_status = self.host.run(command='%s -status | grep CURRENT_OP' %
Richard Barnette3e8b2282018-05-15 20:42:20 +0000371 _UPDATER_BIN)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700372 return update_status.stdout.strip().split('=')[-1]
373
374
Richard Barnette55d1af82018-05-22 23:40:14 +0000375 def _rootdev(self, options=''):
376 """Returns the stripped output of rootdev <options>.
377
378 @param options: options to run rootdev.
379
380 """
381 return self._run('rootdev %s' % options).stdout.strip()
382
383
Richard Barnette3e8b2282018-05-15 20:42:20 +0000384 def _get_last_update_error(self):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800385 """Get the last autoupdate error code."""
Richard Barnette3e8b2282018-05-15 20:42:20 +0000386 command_result = self._run(
387 '%s --last_attempt_error' % _UPDATER_BIN)
388 return command_result.stdout.strip().replace('\n', ', ')
Shuqian Zhaod9992722016-02-29 12:26:38 -0800389
390
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800391 def _base_update_handler_no_retry(self, run_args):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800392 """Base function to handle a remote update ssh call.
393
394 @param run_args: Dictionary of args passed to ssh_host.run function.
Shuqian Zhaod9992722016-02-29 12:26:38 -0800395
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800396 @throws: intercepts and re-throws all exceptions
Shuqian Zhaod9992722016-02-29 12:26:38 -0800397 """
Shuqian Zhaod9992722016-02-29 12:26:38 -0800398 try:
399 self.host.run(**run_args)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800400 except Exception as e:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800401 logging.debug('exception in update handler: %s', e)
402 raise e
Shuqian Zhaod9992722016-02-29 12:26:38 -0800403
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800404
405 def _base_update_handler(self, run_args, err_msg_prefix=None):
406 """Handle a remote update ssh call, possibly with retries.
407
408 @param run_args: Dictionary of args passed to ssh_host.run function.
409 @param err_msg_prefix: Prefix of the exception error message.
410 """
411 def exception_handler(e):
412 """Examines exceptions and returns True if the update handler
413 should be retried.
414
415 @param e: the exception intercepted by the retry util.
416 """
417 return (isinstance(e, error.AutoservSSHTimeout) or
418 (isinstance(e, error.GenericHostRunError) and
419 hasattr(e, 'description') and
420 (re.search('ERROR_CODE=37', e.description) or
421 re.search('generic error .255.', e.description))))
422
423 try:
424 # Try the update twice (arg 2 is max_retry, not including the first
425 # call). Some exceptions may be caught by the retry handler.
426 retry_util.GenericRetry(exception_handler, 1,
427 self._base_update_handler_no_retry,
428 run_args)
429 except Exception as e:
430 message = err_msg_prefix + ': ' + str(e)
431 raise RootFSUpdateError(message)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800432
433
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800434 def _wait_for_update_service(self):
435 """Ensure that the update engine daemon is running, possibly
436 by waiting for it a bit in case the DUT just rebooted and the
437 service hasn't started yet.
438 """
439 def handler(e):
440 """Retry exception handler.
441
442 Assumes that the error is due to the update service not having
443 started yet.
444
445 @param e: the exception intercepted by the retry util.
446 """
447 if isinstance(e, error.AutoservRunError):
448 logging.debug('update service check exception: %s\n'
449 'retrying...', e)
450 return True
451 else:
452 return False
453
454 # Retry at most three times, every 5s.
455 status = retry_util.GenericRetry(handler, 3,
456 self.check_update_status,
457 sleep=5)
458
459 # Expect the update engine to be idle.
460 if status != UPDATER_IDLE:
Richard Barnette9d43e562018-06-05 17:20:10 +0000461 raise RootFSUpdateError(
462 'Update engine status is %s (%s was expected).'
463 % (status, UPDATER_IDLE))
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800464
465
Richard Barnette55d1af82018-05-22 23:40:14 +0000466 def _reset_update_engine(self):
467 """Resets the host to prepare for a clean update regardless of state."""
468 self._run('stop ui || true')
469 self._run('stop update-engine || true')
470 self._run('start update-engine')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800471 self._wait_for_update_service()
472
Richard Barnette55d1af82018-05-22 23:40:14 +0000473
474 def _reset_stateful_partition(self):
475 """Clear any pending stateful update request."""
Amin Hassani5cda21d2020-08-10 15:24:44 -0700476 cmd = ['rm', '-rf']
477 for f in ('var_new', 'dev_image_new', '.update_available'):
478 cmd += [os.path.join('/mnt/stateful_partition', f)]
479 cmd += [_TARGET_VERSION, '2>&1']
480 self._run(cmd)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700481
482
483 def _set_target_version(self):
484 """Set the "target version" for the update."""
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700485 # Version strings that come from release buckets do not have RXX- at the
486 # beginning. So remove this prefix only if the version has it.
487 version_number = (self.update_version.split('-')[1]
488 if '-' in self.update_version
489 else self.update_version)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700490 self._run('echo %s > %s' % (version_number, _TARGET_VERSION))
Richard Barnette55d1af82018-05-22 23:40:14 +0000491
492
493 def _revert_boot_partition(self):
494 """Revert the boot partition."""
495 part = self._rootdev('-s')
496 logging.warning('Reverting update; Boot partition will be %s', part)
497 return self._run('/postinst %s 2>&1' % part)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700498
499
500 def _verify_update_completed(self):
501 """Verifies that an update has completed.
502
Richard Barnette9d43e562018-06-05 17:20:10 +0000503 @raise RootFSUpdateError if the DUT doesn't indicate that
504 download is complete and the DUT is ready for reboot.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700505 """
506 status = self.check_update_status()
507 if status != UPDATER_NEED_REBOOT:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800508 error_msg = ''
509 if status == UPDATER_IDLE:
Richard Barnette3e8b2282018-05-15 20:42:20 +0000510 error_msg = 'Update error: %s' % self._get_last_update_error()
Richard Barnette9d43e562018-06-05 17:20:10 +0000511 raise RootFSUpdateError(
512 'Update engine status is %s (%s was expected). %s'
513 % (status, UPDATER_NEED_REBOOT, error_msg))
David Haddock77b75c32020-05-14 01:56:32 -0700514 return kernel_utils.verify_kernel_state_after_update(self.host)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700515
516
Richard Barnette55d1af82018-05-22 23:40:14 +0000517 def trigger_update(self):
Richard Barnette9d43e562018-06-05 17:20:10 +0000518 """Triggers a background update."""
519 # If this function is called immediately after reboot (which it
520 # can be), there is no guarantee that the update engine is up
521 # and running yet, so wait for it.
Richard Barnette55d1af82018-05-22 23:40:14 +0000522 self._wait_for_update_service()
523
524 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
525 (_UPDATER_BIN, self.update_url))
526 run_args = {'command': autoupdate_cmd}
527 err_prefix = 'Failed to trigger an update on %s. ' % self.host.hostname
528 logging.info('Triggering update via: %s', autoupdate_cmd)
529 metric_fields = {'success': False}
530 try:
531 self._base_update_handler(run_args, err_prefix)
532 metric_fields['success'] = True
533 finally:
534 c = metrics.Counter('chromeos/autotest/autoupdater/trigger')
Richard Barnette621a8e42018-06-25 17:34:11 -0700535 metric_fields.update(_get_metric_fields(self.update_url))
Richard Barnette55d1af82018-05-22 23:40:14 +0000536 c.increment(fields=metric_fields)
537
538
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700539 def update_image(self):
Richard Barnette18fd5842018-05-25 18:21:14 +0000540 """Updates the device root FS and kernel and verifies success."""
Shuqian Zhaofe4d62e2016-06-23 14:46:45 -0700541 autoupdate_cmd = ('%s --update --omaha_url=%s' %
Richard Barnette3e8b2282018-05-15 20:42:20 +0000542 (_UPDATER_BIN, self.update_url))
David Haddock76a4c882017-12-13 18:50:09 -0800543 if not self.interactive:
544 autoupdate_cmd = '%s --interactive=false' % autoupdate_cmd
Shuqian Zhaod9992722016-02-29 12:26:38 -0800545 run_args = {'command': autoupdate_cmd, 'timeout': 3600}
546 err_prefix = ('Failed to install device image using payload at %s '
547 'on %s. ' % (self.update_url, self.host.hostname))
548 logging.info('Updating image via: %s', autoupdate_cmd)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700549 metric_fields = {'success': False}
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800550 try:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800551 self._base_update_handler(run_args, err_prefix)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700552 metric_fields['success'] = True
553 finally:
Allen Li1a5cc0a2017-06-20 14:08:59 -0700554 c = metrics.Counter('chromeos/autotest/autoupdater/update')
Richard Barnette621a8e42018-06-25 17:34:11 -0700555 metric_fields.update(_get_metric_fields(self.update_url))
Allen Li1a5cc0a2017-06-20 14:08:59 -0700556 c.increment(fields=metric_fields)
Richard Barnette4d211c92018-05-24 18:56:08 +0000557 return self._verify_update_completed()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700558
559
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700560 def _get_remote_script(self, script_name):
561 """Ensure that `script_name` is present on the DUT.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700562
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700563 The given script (e.g. `stateful_update`) may be present in the
564 stateful partition under /usr/local/bin, or we may have to
565 download it from the devserver.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700566
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700567 Determine whether the script is present or must be downloaded
568 and download if necessary. Then, return a command fragment
569 sufficient to run the script from whereever it now lives on the
570 DUT.
Richard Barnette9d43e562018-06-05 17:20:10 +0000571
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700572 @param script_name The name of the script as expected in
573 /usr/local/bin and on the devserver.
574 @return A string with the command (minus arguments) that will
575 run the target script.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700576 """
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700577 remote_script = '/usr/local/bin/%s' % script_name
578 if self.host.path_exists(remote_script):
579 return remote_script
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800580 self.host.run('mkdir -p -m 1777 /usr/local/tmp')
581 remote_tmp_script = '/usr/local/tmp/%s' % script_name
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700582 server_name = urlparse.urlparse(self.update_url)[1]
583 script_url = 'http://%s/static/%s' % (server_name, script_name)
Dana Goyette353d1d92019-06-27 10:43:59 -0700584 fetch_script = 'curl -Ss -o %s %s && head -1 %s' % (
585 remote_tmp_script, script_url, remote_tmp_script)
Chris Sosa5e4246b2012-05-22 18:05:22 -0700586
Dana Goyette353d1d92019-06-27 10:43:59 -0700587 first_line = self._run(fetch_script).stdout.strip()
588
589 if first_line and first_line.startswith('#!'):
590 script_interpreter = first_line.lstrip('#!')
591 if script_interpreter:
592 return '%s %s' % (script_interpreter, remote_tmp_script)
593 return None
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700594
595 def _get_stateful_update_script(self):
596 """Returns a command to run the stateful update script.
597
598 Find `stateful_update` on the target or install it, as
599 necessary. If installation fails, raise an exception.
600
601 @raise StatefulUpdateError if the script can't be found or
602 installed.
603 @return A string that can be joined with arguments to run the
604 `stateful_update` command on the DUT.
605 """
606 script_command = self._get_remote_script(_STATEFUL_UPDATE_SCRIPT)
607 if not script_command:
608 raise StatefulUpdateError('Could not install %s on DUT'
Richard Barnette9d43e562018-06-05 17:20:10 +0000609 % _STATEFUL_UPDATE_SCRIPT)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700610 return script_command
Chris Sosa5e4246b2012-05-22 18:05:22 -0700611
612
Chris Sosa72312602013-04-16 15:01:56 -0700613 def update_stateful(self, clobber=True):
614 """Updates the stateful partition.
615
616 @param clobber: If True, a clean stateful installation.
Richard Barnette9d43e562018-06-05 17:20:10 +0000617
618 @raise StatefulUpdateError if the update script fails to
619 complete successfully.
Chris Sosa72312602013-04-16 15:01:56 -0700620 """
Chris Sosa77556d82012-04-05 15:23:14 -0700621 logging.info('Updating stateful partition...')
Richard Barnette18fd5842018-05-25 18:21:14 +0000622 statefuldev_url = self.update_url.replace('update', 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700623
Dale Curtis5c32c722011-05-04 19:24:23 -0700624 # Attempt stateful partition update; this must succeed so that the newly
625 # installed host is testable after update.
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700626 statefuldev_cmd = [self._get_stateful_update_script(), statefuldev_url]
Chris Sosa72312602013-04-16 15:01:56 -0700627 if clobber:
628 statefuldev_cmd.append('--stateful_change=clean')
629
630 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700631 try:
Dan Shi205b8732016-01-25 10:56:22 -0800632 self._run(' '.join(statefuldev_cmd), timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700633 except error.AutoservRunError:
Richard Barnette18fd5842018-05-25 18:21:14 +0000634 raise StatefulUpdateError(
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700635 'Failed to perform stateful update on %s' %
636 self.host.hostname)
Dale Curtis5c32c722011-05-04 19:24:23 -0700637
Chris Sosaa3ac2152012-05-23 22:23:13 -0700638
Richard Barnette14ee84c2018-05-18 20:23:42 +0000639 def _prepare_host(self):
640 """Make sure the target DUT is working and ready for update.
641
642 Initially, the target DUT's state is unknown. The DUT is
643 expected to be online, but we strive to be forgiving if Chrome
644 and/or the update engine aren't fully functional.
645 """
646 # Summary of work, and the rationale:
647 # 1. Reboot, because it's a good way to clear out problems.
648 # 2. Touch the PROVISION_FAILED file, to allow repair to detect
649 # failure later.
650 # 3. Run the hook for host class specific preparation.
651 # 4. Stop Chrome, because the system is designed to eventually
652 # reboot if Chrome is stuck in a crash loop.
653 # 5. Force `update-engine` to start, because if Chrome failed
654 # to start properly, the status of the `update-engine` job
655 # will be uncertain.
Richard Barnette5adb6d42018-06-28 15:52:32 -0700656 if not self.host.is_up():
657 raise HostUpdateError(self.host.hostname,
658 HostUpdateError.DUT_DOWN)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000659 self._reset_stateful_partition()
Garry Wang01a1d482020-08-02 20:46:53 -0700660 # Servohost reboot logic is handled by themselves.
661 if not self._is_servohost:
662 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
663 self._run('touch %s' % PROVISION_FAILED)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000664 self.host.prepare_for_update()
Garry Wang01a1d482020-08-02 20:46:53 -0700665 # Servohost will only update via quick provision.
666 if not self._is_servohost:
667 self._reset_update_engine()
Richard Barnette14ee84c2018-05-18 20:23:42 +0000668 logging.info('Updating from version %s to %s.',
669 self.host.get_release_version(),
670 self.update_version)
671
Amin Hassani95f86e02020-07-14 13:06:03 -0700672 def _install_via_update_engine(self, devserver_name, image_name):
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700673 """Install an updating using the production AU flow.
674
Amin Hassani95f86e02020-07-14 13:06:03 -0700675 This uses the standard AU flow and the `stateful_update` script to
676 download and install a root FS, kernel and stateful filesystem content.
677
678 @param devserver_name: The devserver name and port (optional).
679 @param image_name: The image to be installed.
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700680
681 @return The kernel expected to be booted next.
682 """
683 logging.info('Installing image using update_engine.')
Amin Hassani95f86e02020-07-14 13:06:03 -0700684 ds = dev_server.ImageServer('http://%s' % devserver_name)
685 try:
686 ds.stage_artifacts(image_name, ['full_payload', 'stateful',
687 'autotest_packages'])
688 except dev_server.DevServerException as e:
689 raise error.TestFail, str(e), sys.exc_info()[2]
690
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700691 expected_kernel = self.update_image()
692 self.update_stateful()
Richard Barnette3ef29a82018-06-28 13:52:54 -0700693 self._set_target_version()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700694 return expected_kernel
695
696
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700697 def _quick_provision_with_gs_cache(self, provision_command, devserver_name,
698 image_name):
699 """Run quick_provision using GsCache server.
700
701 @param provision_command: The path of quick_provision command.
702 @param devserver_name: The devserver name and port (optional).
703 @param image_name: The image to be installed.
704 """
705 logging.info('Try quick provision with gs_cache.')
706 # If enabled, GsCache server listion on different port on the
707 # devserver.
708 gs_cache_server = devserver_name.replace(DEVSERVER_PORT, GS_CACHE_PORT)
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700709 gs_cache_url = ('http://%s/download/%s'
710 % (gs_cache_server,
711 'chromeos-releases' if self._is_release_bucket
712 else 'chromeos-image-archive'))
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700713
714 # Check if GS_Cache server is enabled on the server.
Congbin Guo4a2a6642019-08-12 15:03:01 -0700715 self._run('curl -s -o /dev/null %s' % gs_cache_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700716
717 command = '%s --noreboot %s %s' % (provision_command, image_name,
718 gs_cache_url)
719 self._run(command)
720 metrics.Counter(_metric_name('quick_provision')).increment(
721 fields={'devserver': devserver_name, 'gs_cache': True})
722
723
724 def _quick_provision_with_devserver(self, provision_command,
725 devserver_name, image_name):
726 """Run quick_provision using legacy devserver.
727
728 @param provision_command: The path of quick_provision command.
729 @param devserver_name: The devserver name and port (optional).
730 @param image_name: The image to be installed.
731 """
Congbin Guo63ae0302019-08-12 16:37:49 -0700732 logging.info('Try quick provision with devserver.')
733 ds = dev_server.ImageServer('http://%s' % devserver_name)
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700734 archive_url = ('gs://chromeos-releases/%s' % image_name
735 if self._is_release_bucket else None)
Congbin Guo63ae0302019-08-12 16:37:49 -0700736 try:
Amin Hassani95f86e02020-07-14 13:06:03 -0700737 ds.stage_artifacts(image_name, ['quick_provision', 'stateful',
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700738 'autotest_packages'],
739 archive_url=archive_url)
Congbin Guo63ae0302019-08-12 16:37:49 -0700740 except dev_server.DevServerException as e:
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -0700741 six.reraise(error.TestFail, str(e), sys.exc_info()[2])
Congbin Guo63ae0302019-08-12 16:37:49 -0700742
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700743 static_url = 'http://%s/static' % devserver_name
744 command = '%s --noreboot %s %s' % (provision_command, image_name,
745 static_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700746 self._run(command)
747 metrics.Counter(_metric_name('quick_provision')).increment(
748 fields={'devserver': devserver_name, 'gs_cache': False})
749
750
Amin Hassani95f86e02020-07-14 13:06:03 -0700751 def _install_via_quick_provision(self, server_name, image_name):
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700752 """Install an updating using the `quick-provision` script.
753
754 This uses the `quick-provision` script to download and install
755 a root FS, kernel and stateful filesystem content.
756
Amin Hassani95f86e02020-07-14 13:06:03 -0700757 @param server_name: The devserver name and port (optional).
758 @param image_name: The image to be installed.
759
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700760 @return The kernel expected to be booted next.
761 """
Amin Hassanib04420b2020-07-08 18:46:11 +0000762 logging.info('Installing image using quick-provision.')
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700763 provision_command = self._get_remote_script(_QUICK_PROVISION_SCRIPT)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700764 try:
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700765 try:
766 self._quick_provision_with_gs_cache(provision_command,
767 server_name, image_name)
Amin Hassani95f86e02020-07-14 13:06:03 -0700768 except Exception as e:
769 logging.error('Failed to quick-provision with gscache with '
770 'error %s', e)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700771 self._quick_provision_with_devserver(provision_command,
772 server_name, image_name)
773
Richard Barnette3ef29a82018-06-28 13:52:54 -0700774 self._set_target_version()
David Haddock77b75c32020-05-14 01:56:32 -0700775 return kernel_utils.verify_kernel_state_after_update(self.host)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700776 except Exception:
777 # N.B. We handle only `Exception` here. Non-Exception
778 # classes (such as KeyboardInterrupt) are handled by our
779 # caller.
780 logging.exception('quick-provision script failed; '
781 'will fall back to update_engine.')
782 self._revert_boot_partition()
783 self._reset_stateful_partition()
784 self._reset_update_engine()
785 return None
786
787
Richard Barnette54d14f52018-05-18 16:39:49 +0000788 def _install_update(self):
Richard Barnette0beb14b2018-05-15 18:07:52 +0000789 """Install the requested image on the DUT, but don't start it.
790
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700791 This downloads and installs a root FS, kernel and stateful
792 filesystem content. This does not reboot the DUT, so the update
793 is merely pending when the method returns.
794
795 @return The kernel expected to be booted next.
Dan Shi0f466e82013-02-22 15:44:58 -0800796 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000797 logging.info('Installing image at %s onto %s',
798 self.update_url, self.host.hostname)
Amin Hassani95f86e02020-07-14 13:06:03 -0700799 server_name = urlparse.urlparse(self.update_url)[1]
800 image_name = url_to_image_name(self.update_url)
801
Amin Hassani78377a82020-07-24 14:04:13 -0700802 if self._use_quick_provision:
803 return self._install_via_quick_provision(server_name, image_name)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200804 try:
Amin Hassani78377a82020-07-24 14:04:13 -0700805 return self._install_via_update_engine(server_name, image_name)
Dale Curtis1e973182011-07-12 18:21:36 -0700806 except:
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700807 # N.B. This handling code includes non-Exception classes such
808 # as KeyboardInterrupt. We need to clean up, but we also must
809 # re-raise.
Richard Barnette14ee84c2018-05-18 20:23:42 +0000810 self._revert_boot_partition()
811 self._reset_stateful_partition()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700812 self._reset_update_engine()
Dale Curtis1e973182011-07-12 18:21:36 -0700813 # Collect update engine logs in the event of failure.
814 if self.host.job:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700815 logging.info('Collecting update engine logs due to failure...')
Dale Curtis1e973182011-07-12 18:21:36 -0700816 self.host.get_file(
Richard Barnette3e8b2282018-05-15 20:42:20 +0000817 _UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
Gilad Arnold0c0df732015-09-21 06:37:59 -0700818 preserve_perm=False)
Richard Barnette3e8b2282018-05-15 20:42:20 +0000819 _list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700820 raise
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200821
822
Richard Barnette14ee84c2018-05-18 20:23:42 +0000823 def _complete_update(self, expected_kernel):
824 """Finish the update, and confirm that it succeeded.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000825
Richard Barnette14ee84c2018-05-18 20:23:42 +0000826 Initial condition is that the target build has been downloaded
827 and installed on the DUT, but has not yet been booted. This
828 function is responsible for rebooting the DUT, and checking that
829 the new build is running successfully.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000830
Richard Barnette14ee84c2018-05-18 20:23:42 +0000831 @param expected_kernel: kernel expected to be active after reboot.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000832 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000833 # Regarding the 'crossystem' command below: In some cases,
834 # the update flow puts the TPM into a state such that it
835 # fails verification. We don't know why. However, this
836 # call papers over the problem by clearing the TPM during
837 # the reboot.
838 #
839 # We ignore failures from 'crossystem'. Although failure
840 # here is unexpected, and could signal a bug, the point of
841 # the exercise is to paper over problems; allowing this to
842 # fail would defeat the purpose.
843 self._run('crossystem clear_tpm_owner_request=1',
844 ignore_status=True)
845 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
846
Richard Barnette0beb14b2018-05-15 18:07:52 +0000847 # Touch the lab machine file to leave a marker that
848 # distinguishes this image from other test images.
849 # Afterwards, we must re-run the autoreboot script because
850 # it depends on the _LAB_MACHINE_FILE.
851 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
852 '( touch "$FILE" ; start autoreboot )')
Richard Barnette3e8b2282018-05-15 20:42:20 +0000853 self._run(autoreboot_cmd % _LAB_MACHINE_FILE)
David Haddock3446a642020-05-26 03:26:49 -0700854 kernel_utils.verify_boot_expectations(
855 expected_kernel, NewBuildUpdateError.ROLLBACK_FAILURE, self.host)
Richard Barnette0beb14b2018-05-15 18:07:52 +0000856
857 logging.debug('Cleaning up old autotest directories.')
858 try:
859 installed_autodir = autotest.Autotest.get_installed_autodir(
860 self.host)
861 self._run('rm -rf ' + installed_autodir)
862 except autotest.AutodirNotFoundError:
863 logging.debug('No autotest installed directory found.')
864
865
Richard Barnette4c81b972018-07-18 12:35:16 -0700866 def run_update(self):
867 """Perform a full update of a DUT in the test lab.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000868
Richard Barnette4c81b972018-07-18 12:35:16 -0700869 This downloads and installs the root FS and stateful partition
870 content needed for the update specified in `self.host` and
871 `self.update_url`. The update is performed according to the
872 requirements for provisioning a DUT for testing the requested
873 build.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000874
Richard Barnette4c81b972018-07-18 12:35:16 -0700875 At the end of the procedure, metrics are reported describing the
876 outcome of the operation.
877
878 @returns A tuple of the form `(image_name, attributes)`, where
879 `image_name` is the name of the image installed, and
880 `attributes` is new attributes to be applied to the DUT.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000881 """
Richard Barnette4c81b972018-07-18 12:35:16 -0700882 server_name = dev_server.get_resolved_hostname(self.update_url)
883 metrics.Counter(_metric_name('install')).increment(
884 fields={'devserver': server_name})
885
Richard Barnette9d43e562018-06-05 17:20:10 +0000886 try:
887 self._prepare_host()
888 except _AttributedUpdateError:
889 raise
890 except Exception as e:
891 logging.exception('Failure preparing host prior to update.')
892 raise HostUpdateError(self.host.hostname, str(e))
893
894 try:
895 expected_kernel = self._install_update()
896 except _AttributedUpdateError:
897 raise
898 except Exception as e:
899 logging.exception('Failure during download and install.')
900 raise ImageInstallError(self.host.hostname, server_name, str(e))
901
Garry Wang01a1d482020-08-02 20:46:53 -0700902 # Servohost will handle post update process themselves.
903 if not self._is_servohost:
904 try:
905 self._complete_update(expected_kernel)
906 except _AttributedUpdateError:
907 raise
908 except Exception as e:
909 logging.exception('Failure from build after update.')
910 raise NewBuildUpdateError(self.update_version, str(e))
Richard Barnette0beb14b2018-05-15 18:07:52 +0000911
Richard Barnette0beb14b2018-05-15 18:07:52 +0000912 image_name = url_to_image_name(self.update_url)
913 # update_url is different from devserver url needed to stage autotest
914 # packages, therefore, resolve a new devserver url here.
915 devserver_url = dev_server.ImageServer.resolve(
916 image_name, self.host.hostname).url()
917 repo_url = tools.get_package_url(devserver_url, image_name)
918 return image_name, {ds_constants.JOB_REPO_URL: repo_url}