blob: a41e7f485d5839dcf2ddef1c96ef8b9ffcd21235 [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,
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700336 use_quick_provision=False, is_release_bucket=None):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700337 """Initializes the object.
338
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700339 @param update_url: The URL we want the update to use.
340 @param host: A client.common_lib.hosts.Host implementation.
David Haddock76a4c882017-12-13 18:50:09 -0800341 @param interactive: Bool whether we are doing an interactive update.
Richard Barnette60e759e2018-07-21 20:56:59 -0700342 @param use_quick_provision: Whether we should attempt to perform
343 the update using the quick-provision script.
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700344 @param is_release_bucket: If True, use release bucket
345 gs://chromeos-releases.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700346 """
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700347 self.update_url = update_url
348 self.host = host
David Haddock76a4c882017-12-13 18:50:09 -0800349 self.interactive = interactive
Richard Barnette3e8b2282018-05-15 20:42:20 +0000350 self.update_version = _url_to_version(update_url)
Richard Barnette60e759e2018-07-21 20:56:59 -0700351 self._use_quick_provision = use_quick_provision
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700352 self._is_release_bucket = is_release_bucket
Richard Barnette3e8b2282018-05-15 20:42:20 +0000353
354 def _run(self, cmd, *args, **kwargs):
355 """Abbreviated form of self.host.run(...)"""
356 return self.host.run(cmd, *args, **kwargs)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700357
358
359 def check_update_status(self):
360 """Returns the current update engine state.
361
362 We use the `update_engine_client -status' command and parse the line
363 indicating the update state, e.g. "CURRENT_OP=UPDATE_STATUS_IDLE".
364 """
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800365 update_status = self.host.run(command='%s -status | grep CURRENT_OP' %
Richard Barnette3e8b2282018-05-15 20:42:20 +0000366 _UPDATER_BIN)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700367 return update_status.stdout.strip().split('=')[-1]
368
369
Richard Barnette55d1af82018-05-22 23:40:14 +0000370 def _rootdev(self, options=''):
371 """Returns the stripped output of rootdev <options>.
372
373 @param options: options to run rootdev.
374
375 """
376 return self._run('rootdev %s' % options).stdout.strip()
377
378
Richard Barnette3e8b2282018-05-15 20:42:20 +0000379 def _get_last_update_error(self):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800380 """Get the last autoupdate error code."""
Richard Barnette3e8b2282018-05-15 20:42:20 +0000381 command_result = self._run(
382 '%s --last_attempt_error' % _UPDATER_BIN)
383 return command_result.stdout.strip().replace('\n', ', ')
Shuqian Zhaod9992722016-02-29 12:26:38 -0800384
385
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800386 def _base_update_handler_no_retry(self, run_args):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800387 """Base function to handle a remote update ssh call.
388
389 @param run_args: Dictionary of args passed to ssh_host.run function.
Shuqian Zhaod9992722016-02-29 12:26:38 -0800390
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800391 @throws: intercepts and re-throws all exceptions
Shuqian Zhaod9992722016-02-29 12:26:38 -0800392 """
Shuqian Zhaod9992722016-02-29 12:26:38 -0800393 try:
394 self.host.run(**run_args)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800395 except Exception as e:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800396 logging.debug('exception in update handler: %s', e)
397 raise e
Shuqian Zhaod9992722016-02-29 12:26:38 -0800398
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800399
400 def _base_update_handler(self, run_args, err_msg_prefix=None):
401 """Handle a remote update ssh call, possibly with retries.
402
403 @param run_args: Dictionary of args passed to ssh_host.run function.
404 @param err_msg_prefix: Prefix of the exception error message.
405 """
406 def exception_handler(e):
407 """Examines exceptions and returns True if the update handler
408 should be retried.
409
410 @param e: the exception intercepted by the retry util.
411 """
412 return (isinstance(e, error.AutoservSSHTimeout) or
413 (isinstance(e, error.GenericHostRunError) and
414 hasattr(e, 'description') and
415 (re.search('ERROR_CODE=37', e.description) or
416 re.search('generic error .255.', e.description))))
417
418 try:
419 # Try the update twice (arg 2 is max_retry, not including the first
420 # call). Some exceptions may be caught by the retry handler.
421 retry_util.GenericRetry(exception_handler, 1,
422 self._base_update_handler_no_retry,
423 run_args)
424 except Exception as e:
425 message = err_msg_prefix + ': ' + str(e)
426 raise RootFSUpdateError(message)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800427
428
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800429 def _wait_for_update_service(self):
430 """Ensure that the update engine daemon is running, possibly
431 by waiting for it a bit in case the DUT just rebooted and the
432 service hasn't started yet.
433 """
434 def handler(e):
435 """Retry exception handler.
436
437 Assumes that the error is due to the update service not having
438 started yet.
439
440 @param e: the exception intercepted by the retry util.
441 """
442 if isinstance(e, error.AutoservRunError):
443 logging.debug('update service check exception: %s\n'
444 'retrying...', e)
445 return True
446 else:
447 return False
448
449 # Retry at most three times, every 5s.
450 status = retry_util.GenericRetry(handler, 3,
451 self.check_update_status,
452 sleep=5)
453
454 # Expect the update engine to be idle.
455 if status != UPDATER_IDLE:
Richard Barnette9d43e562018-06-05 17:20:10 +0000456 raise RootFSUpdateError(
457 'Update engine status is %s (%s was expected).'
458 % (status, UPDATER_IDLE))
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800459
460
Richard Barnette55d1af82018-05-22 23:40:14 +0000461 def _reset_update_engine(self):
462 """Resets the host to prepare for a clean update regardless of state."""
463 self._run('stop ui || true')
464 self._run('stop update-engine || true')
465 self._run('start update-engine')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800466 self._wait_for_update_service()
467
Richard Barnette55d1af82018-05-22 23:40:14 +0000468
469 def _reset_stateful_partition(self):
470 """Clear any pending stateful update request."""
Richard Barnette18fd5842018-05-25 18:21:14 +0000471 self._run('%s --stateful_change=reset 2>&1'
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700472 % self._get_stateful_update_script())
Richard Barnette3ef29a82018-06-28 13:52:54 -0700473 self._run('rm -f %s' % _TARGET_VERSION)
474
475
476 def _set_target_version(self):
477 """Set the "target version" for the update."""
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700478 # Version strings that come from release buckets do not have RXX- at the
479 # beginning. So remove this prefix only if the version has it.
480 version_number = (self.update_version.split('-')[1]
481 if '-' in self.update_version
482 else self.update_version)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700483 self._run('echo %s > %s' % (version_number, _TARGET_VERSION))
Richard Barnette55d1af82018-05-22 23:40:14 +0000484
485
486 def _revert_boot_partition(self):
487 """Revert the boot partition."""
488 part = self._rootdev('-s')
489 logging.warning('Reverting update; Boot partition will be %s', part)
490 return self._run('/postinst %s 2>&1' % part)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700491
492
493 def _verify_update_completed(self):
494 """Verifies that an update has completed.
495
Richard Barnette9d43e562018-06-05 17:20:10 +0000496 @raise RootFSUpdateError if the DUT doesn't indicate that
497 download is complete and the DUT is ready for reboot.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700498 """
499 status = self.check_update_status()
500 if status != UPDATER_NEED_REBOOT:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800501 error_msg = ''
502 if status == UPDATER_IDLE:
Richard Barnette3e8b2282018-05-15 20:42:20 +0000503 error_msg = 'Update error: %s' % self._get_last_update_error()
Richard Barnette9d43e562018-06-05 17:20:10 +0000504 raise RootFSUpdateError(
505 'Update engine status is %s (%s was expected). %s'
506 % (status, UPDATER_NEED_REBOOT, error_msg))
David Haddock77b75c32020-05-14 01:56:32 -0700507 return kernel_utils.verify_kernel_state_after_update(self.host)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700508
509
Richard Barnette55d1af82018-05-22 23:40:14 +0000510 def trigger_update(self):
Richard Barnette9d43e562018-06-05 17:20:10 +0000511 """Triggers a background update."""
512 # If this function is called immediately after reboot (which it
513 # can be), there is no guarantee that the update engine is up
514 # and running yet, so wait for it.
Richard Barnette55d1af82018-05-22 23:40:14 +0000515 self._wait_for_update_service()
516
517 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
518 (_UPDATER_BIN, self.update_url))
519 run_args = {'command': autoupdate_cmd}
520 err_prefix = 'Failed to trigger an update on %s. ' % self.host.hostname
521 logging.info('Triggering update via: %s', autoupdate_cmd)
522 metric_fields = {'success': False}
523 try:
524 self._base_update_handler(run_args, err_prefix)
525 metric_fields['success'] = True
526 finally:
527 c = metrics.Counter('chromeos/autotest/autoupdater/trigger')
Richard Barnette621a8e42018-06-25 17:34:11 -0700528 metric_fields.update(_get_metric_fields(self.update_url))
Richard Barnette55d1af82018-05-22 23:40:14 +0000529 c.increment(fields=metric_fields)
530
531
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700532 def update_image(self):
Richard Barnette18fd5842018-05-25 18:21:14 +0000533 """Updates the device root FS and kernel and verifies success."""
Shuqian Zhaofe4d62e2016-06-23 14:46:45 -0700534 autoupdate_cmd = ('%s --update --omaha_url=%s' %
Richard Barnette3e8b2282018-05-15 20:42:20 +0000535 (_UPDATER_BIN, self.update_url))
David Haddock76a4c882017-12-13 18:50:09 -0800536 if not self.interactive:
537 autoupdate_cmd = '%s --interactive=false' % autoupdate_cmd
Shuqian Zhaod9992722016-02-29 12:26:38 -0800538 run_args = {'command': autoupdate_cmd, 'timeout': 3600}
539 err_prefix = ('Failed to install device image using payload at %s '
540 'on %s. ' % (self.update_url, self.host.hostname))
541 logging.info('Updating image via: %s', autoupdate_cmd)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700542 metric_fields = {'success': False}
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800543 try:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800544 self._base_update_handler(run_args, err_prefix)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700545 metric_fields['success'] = True
546 finally:
Allen Li1a5cc0a2017-06-20 14:08:59 -0700547 c = metrics.Counter('chromeos/autotest/autoupdater/update')
Richard Barnette621a8e42018-06-25 17:34:11 -0700548 metric_fields.update(_get_metric_fields(self.update_url))
Allen Li1a5cc0a2017-06-20 14:08:59 -0700549 c.increment(fields=metric_fields)
Richard Barnette4d211c92018-05-24 18:56:08 +0000550 return self._verify_update_completed()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700551
552
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700553 def _get_remote_script(self, script_name):
554 """Ensure that `script_name` is present on the DUT.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700555
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700556 The given script (e.g. `stateful_update`) may be present in the
557 stateful partition under /usr/local/bin, or we may have to
558 download it from the devserver.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700559
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700560 Determine whether the script is present or must be downloaded
561 and download if necessary. Then, return a command fragment
562 sufficient to run the script from whereever it now lives on the
563 DUT.
Richard Barnette9d43e562018-06-05 17:20:10 +0000564
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700565 @param script_name The name of the script as expected in
566 /usr/local/bin and on the devserver.
567 @return A string with the command (minus arguments) that will
568 run the target script.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700569 """
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700570 remote_script = '/usr/local/bin/%s' % script_name
571 if self.host.path_exists(remote_script):
572 return remote_script
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800573 self.host.run('mkdir -p -m 1777 /usr/local/tmp')
574 remote_tmp_script = '/usr/local/tmp/%s' % script_name
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700575 server_name = urlparse.urlparse(self.update_url)[1]
576 script_url = 'http://%s/static/%s' % (server_name, script_name)
Dana Goyette353d1d92019-06-27 10:43:59 -0700577 fetch_script = 'curl -Ss -o %s %s && head -1 %s' % (
578 remote_tmp_script, script_url, remote_tmp_script)
Chris Sosa5e4246b2012-05-22 18:05:22 -0700579
Dana Goyette353d1d92019-06-27 10:43:59 -0700580 first_line = self._run(fetch_script).stdout.strip()
581
582 if first_line and first_line.startswith('#!'):
583 script_interpreter = first_line.lstrip('#!')
584 if script_interpreter:
585 return '%s %s' % (script_interpreter, remote_tmp_script)
586 return None
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700587
588 def _get_stateful_update_script(self):
589 """Returns a command to run the stateful update script.
590
591 Find `stateful_update` on the target or install it, as
592 necessary. If installation fails, raise an exception.
593
594 @raise StatefulUpdateError if the script can't be found or
595 installed.
596 @return A string that can be joined with arguments to run the
597 `stateful_update` command on the DUT.
598 """
599 script_command = self._get_remote_script(_STATEFUL_UPDATE_SCRIPT)
600 if not script_command:
601 raise StatefulUpdateError('Could not install %s on DUT'
Richard Barnette9d43e562018-06-05 17:20:10 +0000602 % _STATEFUL_UPDATE_SCRIPT)
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700603 return script_command
Chris Sosa5e4246b2012-05-22 18:05:22 -0700604
605
Chris Sosa72312602013-04-16 15:01:56 -0700606 def update_stateful(self, clobber=True):
607 """Updates the stateful partition.
608
609 @param clobber: If True, a clean stateful installation.
Richard Barnette9d43e562018-06-05 17:20:10 +0000610
611 @raise StatefulUpdateError if the update script fails to
612 complete successfully.
Chris Sosa72312602013-04-16 15:01:56 -0700613 """
Chris Sosa77556d82012-04-05 15:23:14 -0700614 logging.info('Updating stateful partition...')
Richard Barnette18fd5842018-05-25 18:21:14 +0000615 statefuldev_url = self.update_url.replace('update', 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700616
Dale Curtis5c32c722011-05-04 19:24:23 -0700617 # Attempt stateful partition update; this must succeed so that the newly
618 # installed host is testable after update.
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700619 statefuldev_cmd = [self._get_stateful_update_script(), statefuldev_url]
Chris Sosa72312602013-04-16 15:01:56 -0700620 if clobber:
621 statefuldev_cmd.append('--stateful_change=clean')
622
623 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700624 try:
Dan Shi205b8732016-01-25 10:56:22 -0800625 self._run(' '.join(statefuldev_cmd), timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700626 except error.AutoservRunError:
Richard Barnette18fd5842018-05-25 18:21:14 +0000627 raise StatefulUpdateError(
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700628 'Failed to perform stateful update on %s' %
629 self.host.hostname)
Dale Curtis5c32c722011-05-04 19:24:23 -0700630
Chris Sosaa3ac2152012-05-23 22:23:13 -0700631
Richard Barnette14ee84c2018-05-18 20:23:42 +0000632 def _prepare_host(self):
633 """Make sure the target DUT is working and ready for update.
634
635 Initially, the target DUT's state is unknown. The DUT is
636 expected to be online, but we strive to be forgiving if Chrome
637 and/or the update engine aren't fully functional.
638 """
639 # Summary of work, and the rationale:
640 # 1. Reboot, because it's a good way to clear out problems.
641 # 2. Touch the PROVISION_FAILED file, to allow repair to detect
642 # failure later.
643 # 3. Run the hook for host class specific preparation.
644 # 4. Stop Chrome, because the system is designed to eventually
645 # reboot if Chrome is stuck in a crash loop.
646 # 5. Force `update-engine` to start, because if Chrome failed
647 # to start properly, the status of the `update-engine` job
648 # will be uncertain.
Richard Barnette5adb6d42018-06-28 15:52:32 -0700649 if not self.host.is_up():
650 raise HostUpdateError(self.host.hostname,
651 HostUpdateError.DUT_DOWN)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000652 self._reset_stateful_partition()
653 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
654 self._run('touch %s' % PROVISION_FAILED)
655 self.host.prepare_for_update()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700656 self._reset_update_engine()
Richard Barnette14ee84c2018-05-18 20:23:42 +0000657 logging.info('Updating from version %s to %s.',
658 self.host.get_release_version(),
659 self.update_version)
660
Amin Hassani95f86e02020-07-14 13:06:03 -0700661 def _install_via_update_engine(self, devserver_name, image_name):
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700662 """Install an updating using the production AU flow.
663
Amin Hassani95f86e02020-07-14 13:06:03 -0700664 This uses the standard AU flow and the `stateful_update` script to
665 download and install a root FS, kernel and stateful filesystem content.
666
667 @param devserver_name: The devserver name and port (optional).
668 @param image_name: The image to be installed.
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700669
670 @return The kernel expected to be booted next.
671 """
672 logging.info('Installing image using update_engine.')
Amin Hassani95f86e02020-07-14 13:06:03 -0700673 ds = dev_server.ImageServer('http://%s' % devserver_name)
674 try:
675 ds.stage_artifacts(image_name, ['full_payload', 'stateful',
676 'autotest_packages'])
677 except dev_server.DevServerException as e:
678 raise error.TestFail, str(e), sys.exc_info()[2]
679
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700680 expected_kernel = self.update_image()
681 self.update_stateful()
Richard Barnette3ef29a82018-06-28 13:52:54 -0700682 self._set_target_version()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700683 return expected_kernel
684
685
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700686 def _quick_provision_with_gs_cache(self, provision_command, devserver_name,
687 image_name):
688 """Run quick_provision using GsCache server.
689
690 @param provision_command: The path of quick_provision command.
691 @param devserver_name: The devserver name and port (optional).
692 @param image_name: The image to be installed.
693 """
694 logging.info('Try quick provision with gs_cache.')
695 # If enabled, GsCache server listion on different port on the
696 # devserver.
697 gs_cache_server = devserver_name.replace(DEVSERVER_PORT, GS_CACHE_PORT)
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700698 gs_cache_url = ('http://%s/download/%s'
699 % (gs_cache_server,
700 'chromeos-releases' if self._is_release_bucket
701 else 'chromeos-image-archive'))
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700702
703 # Check if GS_Cache server is enabled on the server.
Congbin Guo4a2a6642019-08-12 15:03:01 -0700704 self._run('curl -s -o /dev/null %s' % gs_cache_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700705
706 command = '%s --noreboot %s %s' % (provision_command, image_name,
707 gs_cache_url)
708 self._run(command)
709 metrics.Counter(_metric_name('quick_provision')).increment(
710 fields={'devserver': devserver_name, 'gs_cache': True})
711
712
713 def _quick_provision_with_devserver(self, provision_command,
714 devserver_name, image_name):
715 """Run quick_provision using legacy devserver.
716
717 @param provision_command: The path of quick_provision command.
718 @param devserver_name: The devserver name and port (optional).
719 @param image_name: The image to be installed.
720 """
Congbin Guo63ae0302019-08-12 16:37:49 -0700721 logging.info('Try quick provision with devserver.')
722 ds = dev_server.ImageServer('http://%s' % devserver_name)
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700723 archive_url = ('gs://chromeos-releases/%s' % image_name
724 if self._is_release_bucket else None)
Congbin Guo63ae0302019-08-12 16:37:49 -0700725 try:
Amin Hassani95f86e02020-07-14 13:06:03 -0700726 ds.stage_artifacts(image_name, ['quick_provision', 'stateful',
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700727 'autotest_packages'],
728 archive_url=archive_url)
Congbin Guo63ae0302019-08-12 16:37:49 -0700729 except dev_server.DevServerException as e:
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -0700730 six.reraise(error.TestFail, str(e), sys.exc_info()[2])
Congbin Guo63ae0302019-08-12 16:37:49 -0700731
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700732 static_url = 'http://%s/static' % devserver_name
733 command = '%s --noreboot %s %s' % (provision_command, image_name,
734 static_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700735 self._run(command)
736 metrics.Counter(_metric_name('quick_provision')).increment(
737 fields={'devserver': devserver_name, 'gs_cache': False})
738
739
Amin Hassani95f86e02020-07-14 13:06:03 -0700740 def _install_via_quick_provision(self, server_name, image_name):
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700741 """Install an updating using the `quick-provision` script.
742
743 This uses the `quick-provision` script to download and install
744 a root FS, kernel and stateful filesystem content.
745
Amin Hassani95f86e02020-07-14 13:06:03 -0700746 @param server_name: The devserver name and port (optional).
747 @param image_name: The image to be installed.
748
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700749 @return The kernel expected to be booted next.
750 """
Amin Hassanib04420b2020-07-08 18:46:11 +0000751 logging.info('Installing image using quick-provision.')
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700752 provision_command = self._get_remote_script(_QUICK_PROVISION_SCRIPT)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700753 try:
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700754 try:
755 self._quick_provision_with_gs_cache(provision_command,
756 server_name, image_name)
Amin Hassani95f86e02020-07-14 13:06:03 -0700757 except Exception as e:
758 logging.error('Failed to quick-provision with gscache with '
759 'error %s', e)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700760 self._quick_provision_with_devserver(provision_command,
761 server_name, image_name)
762
Richard Barnette3ef29a82018-06-28 13:52:54 -0700763 self._set_target_version()
David Haddock77b75c32020-05-14 01:56:32 -0700764 return kernel_utils.verify_kernel_state_after_update(self.host)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700765 except Exception:
766 # N.B. We handle only `Exception` here. Non-Exception
767 # classes (such as KeyboardInterrupt) are handled by our
768 # caller.
769 logging.exception('quick-provision script failed; '
770 'will fall back to update_engine.')
771 self._revert_boot_partition()
772 self._reset_stateful_partition()
773 self._reset_update_engine()
774 return None
775
776
Richard Barnette54d14f52018-05-18 16:39:49 +0000777 def _install_update(self):
Richard Barnette0beb14b2018-05-15 18:07:52 +0000778 """Install the requested image on the DUT, but don't start it.
779
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700780 This downloads and installs a root FS, kernel and stateful
781 filesystem content. This does not reboot the DUT, so the update
782 is merely pending when the method returns.
783
784 @return The kernel expected to be booted next.
Dan Shi0f466e82013-02-22 15:44:58 -0800785 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000786 logging.info('Installing image at %s onto %s',
787 self.update_url, self.host.hostname)
Amin Hassani95f86e02020-07-14 13:06:03 -0700788 server_name = urlparse.urlparse(self.update_url)[1]
789 image_name = url_to_image_name(self.update_url)
790
Amin Hassani78377a82020-07-24 14:04:13 -0700791 if self._use_quick_provision:
792 return self._install_via_quick_provision(server_name, image_name)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200793 try:
Amin Hassani78377a82020-07-24 14:04:13 -0700794 return self._install_via_update_engine(server_name, image_name)
Dale Curtis1e973182011-07-12 18:21:36 -0700795 except:
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700796 # N.B. This handling code includes non-Exception classes such
797 # as KeyboardInterrupt. We need to clean up, but we also must
798 # re-raise.
Richard Barnette14ee84c2018-05-18 20:23:42 +0000799 self._revert_boot_partition()
800 self._reset_stateful_partition()
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700801 self._reset_update_engine()
Dale Curtis1e973182011-07-12 18:21:36 -0700802 # Collect update engine logs in the event of failure.
803 if self.host.job:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700804 logging.info('Collecting update engine logs due to failure...')
Dale Curtis1e973182011-07-12 18:21:36 -0700805 self.host.get_file(
Richard Barnette3e8b2282018-05-15 20:42:20 +0000806 _UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
Gilad Arnold0c0df732015-09-21 06:37:59 -0700807 preserve_perm=False)
Richard Barnette3e8b2282018-05-15 20:42:20 +0000808 _list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700809 raise
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200810
811
Richard Barnette14ee84c2018-05-18 20:23:42 +0000812 def _complete_update(self, expected_kernel):
813 """Finish the update, and confirm that it succeeded.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000814
Richard Barnette14ee84c2018-05-18 20:23:42 +0000815 Initial condition is that the target build has been downloaded
816 and installed on the DUT, but has not yet been booted. This
817 function is responsible for rebooting the DUT, and checking that
818 the new build is running successfully.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000819
Richard Barnette14ee84c2018-05-18 20:23:42 +0000820 @param expected_kernel: kernel expected to be active after reboot.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000821 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000822 # Regarding the 'crossystem' command below: In some cases,
823 # the update flow puts the TPM into a state such that it
824 # fails verification. We don't know why. However, this
825 # call papers over the problem by clearing the TPM during
826 # the reboot.
827 #
828 # We ignore failures from 'crossystem'. Although failure
829 # here is unexpected, and could signal a bug, the point of
830 # the exercise is to paper over problems; allowing this to
831 # fail would defeat the purpose.
832 self._run('crossystem clear_tpm_owner_request=1',
833 ignore_status=True)
834 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
835
Richard Barnette0beb14b2018-05-15 18:07:52 +0000836 # Touch the lab machine file to leave a marker that
837 # distinguishes this image from other test images.
838 # Afterwards, we must re-run the autoreboot script because
839 # it depends on the _LAB_MACHINE_FILE.
840 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
841 '( touch "$FILE" ; start autoreboot )')
Richard Barnette3e8b2282018-05-15 20:42:20 +0000842 self._run(autoreboot_cmd % _LAB_MACHINE_FILE)
David Haddock3446a642020-05-26 03:26:49 -0700843 kernel_utils.verify_boot_expectations(
844 expected_kernel, NewBuildUpdateError.ROLLBACK_FAILURE, self.host)
Richard Barnette0beb14b2018-05-15 18:07:52 +0000845
846 logging.debug('Cleaning up old autotest directories.')
847 try:
848 installed_autodir = autotest.Autotest.get_installed_autodir(
849 self.host)
850 self._run('rm -rf ' + installed_autodir)
851 except autotest.AutodirNotFoundError:
852 logging.debug('No autotest installed directory found.')
853
854
Richard Barnette4c81b972018-07-18 12:35:16 -0700855 def run_update(self):
856 """Perform a full update of a DUT in the test lab.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000857
Richard Barnette4c81b972018-07-18 12:35:16 -0700858 This downloads and installs the root FS and stateful partition
859 content needed for the update specified in `self.host` and
860 `self.update_url`. The update is performed according to the
861 requirements for provisioning a DUT for testing the requested
862 build.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000863
Richard Barnette4c81b972018-07-18 12:35:16 -0700864 At the end of the procedure, metrics are reported describing the
865 outcome of the operation.
866
867 @returns A tuple of the form `(image_name, attributes)`, where
868 `image_name` is the name of the image installed, and
869 `attributes` is new attributes to be applied to the DUT.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000870 """
Richard Barnette4c81b972018-07-18 12:35:16 -0700871 server_name = dev_server.get_resolved_hostname(self.update_url)
872 metrics.Counter(_metric_name('install')).increment(
873 fields={'devserver': server_name})
874
Richard Barnette9d43e562018-06-05 17:20:10 +0000875 try:
876 self._prepare_host()
877 except _AttributedUpdateError:
878 raise
879 except Exception as e:
880 logging.exception('Failure preparing host prior to update.')
881 raise HostUpdateError(self.host.hostname, str(e))
882
883 try:
884 expected_kernel = self._install_update()
885 except _AttributedUpdateError:
886 raise
887 except Exception as e:
888 logging.exception('Failure during download and install.')
889 raise ImageInstallError(self.host.hostname, server_name, str(e))
890
891 try:
892 self._complete_update(expected_kernel)
893 except _AttributedUpdateError:
894 raise
895 except Exception as e:
896 logging.exception('Failure from build after update.')
897 raise NewBuildUpdateError(self.update_version, str(e))
Richard Barnette0beb14b2018-05-15 18:07:52 +0000898
Richard Barnette0beb14b2018-05-15 18:07:52 +0000899 image_name = url_to_image_name(self.update_url)
900 # update_url is different from devserver url needed to stage autotest
901 # packages, therefore, resolve a new devserver url here.
902 devserver_url = dev_server.ImageServer.resolve(
903 image_name, self.host.hostname).url()
904 repo_url = tools.get_package_url(devserver_url, image_name)
905 return image_name, {ds_constants.JOB_REPO_URL: repo_url}