blob: ac577ba857206e95739d7f2701c5b55478c41bd8 [file] [log] [blame]
Derek Beckett5fb683c2020-08-19 15:24:13 -07001# Lint as: python2, python3
Chris Sosa5e4246b2012-05-22 18:05:22 -07002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Sean O'Connor5346e4e2010-08-12 18:49:24 +02003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -07006from __future__ import print_function
7
Sean O'Connor5346e4e2010-08-12 18:49:24 +02008import logging
Amin Hassani5cda21d2020-08-10 15:24:44 -07009import os
Sean O'Connor5346e4e2010-08-12 18:49:24 +020010import re
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -070011import six
Congbin Guo63ae0302019-08-12 16:37:49 -070012import sys
Derek Beckett5fb683c2020-08-19 15:24:13 -070013import six.moves.urllib.parse
Sean O'Connor5346e4e2010-08-12 18:49:24 +020014
Chris Sosa65425082013-10-16 13:26:22 -070015from autotest_lib.client.bin import utils
Amin Hassani18e39882020-08-10 15:32:10 -070016from autotest_lib.client.common_lib import error
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
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
Dan Shif3a35f72016-01-25 11:18:14 -080022
Richard Barnette621a8e42018-06-25 17:34:11 -070023
Richard Barnettee86b1ce2018-06-07 10:37:23 -070024_QUICK_PROVISION_SCRIPT = 'quick-provision'
Richard Barnette3e8b2282018-05-15 20:42:20 +000025
Richard Barnette0beb14b2018-05-15 18:07:52 +000026# PROVISION_FAILED - A flag file to indicate provision failures. The
27# file is created at the start of any AU procedure (see
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -070028# `ChromiumOSProvisioner._prepare_host()`). The file's location in
Richard Barnette0beb14b2018-05-15 18:07:52 +000029# stateful means that on successul update it will be removed. Thus, if
30# this file exists, it indicates that we've tried and failed in a
31# previous attempt to update.
32PROVISION_FAILED = '/var/tmp/provision_failed'
33
Richard Barnette3e8b2282018-05-15 20:42:20 +000034# A flag file used to enable special handling in lab DUTs. Some
35# parts of the system in Chromium OS test images will behave in ways
36# convenient to the test lab when this file is present. Generally,
37# we create this immediately after any update completes.
Brian Norrisc4f20552021-02-12 11:14:33 -080038LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Richard Barnette3e8b2282018-05-15 20:42:20 +000039
Richard Barnette3ef29a82018-06-28 13:52:54 -070040# _TARGET_VERSION - A file containing the new version to which we plan
41# to update. This file is used by the CrOS shutdown code to detect and
42# handle certain version downgrade cases. Specifically: Downgrading
43# may trigger an unwanted powerwash in the target build when the
44# following conditions are met:
45# * Source build is a v4.4 kernel with R69-10756.0.0 or later.
46# * Target build predates the R69-10756.0.0 cutoff.
47# When this file is present and indicates a downgrade, the OS shutdown
48# code on the DUT knows how to prevent the powerwash.
49_TARGET_VERSION = '/run/update_target_version'
50
Richard Barnette5adb6d42018-06-28 15:52:32 -070051# _REBOOT_FAILURE_MESSAGE - This is the standard message text returned
52# when the Host.reboot() method fails. The source of this text comes
53# from `wait_for_restart()` in client/common_lib/hosts/base_classes.py.
54
55_REBOOT_FAILURE_MESSAGE = 'Host did not return from reboot'
56
Congbin Guoeb7aa2d2019-07-15 16:10:44 -070057DEVSERVER_PORT = '8082'
58GS_CACHE_PORT = '8888'
59
60
Richard Barnette9d43e562018-06-05 17:20:10 +000061class _AttributedUpdateError(error.TestFail):
62 """Update failure with an attributed cause."""
63
64 def __init__(self, attribution, msg):
Jae Hoon Kim3f004992020-09-10 17:48:33 -070065 super(_AttributedUpdateError,
66 self).__init__('%s: %s' % (attribution, msg))
Richard Barnette5adb6d42018-06-28 15:52:32 -070067 self._message = msg
68
69 def _classify(self):
70 for err_pattern, classification in self._CLASSIFIERS:
71 if re.match(err_pattern, self._message):
72 return classification
73 return None
74
75 @property
76 def failure_summary(self):
77 """Summarize this error for metrics reporting."""
78 classification = self._classify()
79 if classification:
80 return '%s: %s' % (self._SUMMARY, classification)
81 else:
82 return self._SUMMARY
Richard Barnette9d43e562018-06-05 17:20:10 +000083
84
85class HostUpdateError(_AttributedUpdateError):
86 """Failure updating a DUT attributable to the DUT.
87
88 This class of exception should be raised when the most likely cause
89 of failure was a condition existing on the DUT prior to the update,
90 such as a hardware problem, or a bug in the software on the DUT.
91 """
92
Richard Barnette5adb6d42018-06-28 15:52:32 -070093 DUT_DOWN = 'No answer to ssh'
94
95 _SUMMARY = 'DUT failed prior to update'
96 _CLASSIFIERS = [
Jae Hoon Kim3f004992020-09-10 17:48:33 -070097 (DUT_DOWN, DUT_DOWN),
98 (_REBOOT_FAILURE_MESSAGE, 'Reboot failed'),
Richard Barnette5adb6d42018-06-28 15:52:32 -070099 ]
100
Richard Barnette9d43e562018-06-05 17:20:10 +0000101 def __init__(self, hostname, msg):
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700102 super(HostUpdateError,
103 self).__init__('Error on %s prior to update' % hostname, msg)
Richard Barnette9d43e562018-06-05 17:20:10 +0000104
105
Richard Barnette9d43e562018-06-05 17:20:10 +0000106class ImageInstallError(_AttributedUpdateError):
107 """Failure updating a DUT when installing from the devserver.
108
109 This class of exception should be raised when the target DUT fails
110 to download and install the target image from the devserver, and
111 either the devserver or the DUT might be at fault.
112 """
113
Richard Barnette5adb6d42018-06-28 15:52:32 -0700114 _SUMMARY = 'Image failed to download and install'
115 _CLASSIFIERS = []
116
Richard Barnette9d43e562018-06-05 17:20:10 +0000117 def __init__(self, hostname, devserver, msg):
118 super(ImageInstallError, self).__init__(
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700119 'Download and install failed from %s onto %s' %
120 (devserver, hostname), msg)
Richard Barnette9d43e562018-06-05 17:20:10 +0000121
122
123class NewBuildUpdateError(_AttributedUpdateError):
124 """Failure updating a DUT attributable to the target build.
125
126 This class of exception should be raised when updating to a new
127 build fails, and the most likely cause of the failure is a bug in
128 the newly installed target build.
129 """
130
Richard Barnette5adb6d42018-06-28 15:52:32 -0700131 CHROME_FAILURE = 'Chrome failed to reach login screen'
Richard Barnette5adb6d42018-06-28 15:52:32 -0700132 ROLLBACK_FAILURE = 'System rolled back to previous build'
133
134 _SUMMARY = 'New build failed'
135 _CLASSIFIERS = [
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700136 (CHROME_FAILURE, 'Chrome did not start'),
137 (ROLLBACK_FAILURE, ROLLBACK_FAILURE),
Richard Barnette5adb6d42018-06-28 15:52:32 -0700138 ]
139
Richard Barnette9d43e562018-06-05 17:20:10 +0000140 def __init__(self, update_version, msg):
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700141 super(NewBuildUpdateError,
142 self).__init__('Failure in build %s' % update_version, msg)
Richard Barnette9d43e562018-06-05 17:20:10 +0000143
Richard Barnette621a8e42018-06-25 17:34:11 -0700144 @property
145 def failure_summary(self):
146 #pylint: disable=missing-docstring
147 return 'Build failed to work after installing'
148
Richard Barnette9d43e562018-06-05 17:20:10 +0000149
Richard Barnette3e8b2282018-05-15 20:42:20 +0000150def _url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -0800151 """Return the version based on update_url.
152
153 @param update_url: url to the image to update to.
154
155 """
Dale Curtisddfdb942011-07-14 13:59:24 -0700156 # The Chrome OS version is generally the last element in the URL. The only
157 # exception is delta update URLs, which are rooted under the version; e.g.,
158 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
159 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -0700160 return re.sub('/au/.*', '',
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700161 six.moves.urllib.parse.urlparse(update_url).path).split(
162 '/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200163
164
Scott Zawalskieadbf702013-03-14 09:23:06 -0400165def url_to_image_name(update_url):
166 """Return the image name based on update_url.
167
168 From a URL like:
169 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
170 return lumpy-release/R27-3837.0.0
171
172 @param update_url: url to the image to update to.
173 @returns a string representing the image name in the update_url.
174
175 """
Derek Beckett5fb683c2020-08-19 15:24:13 -0700176 return six.moves.urllib.parse.urlparse(update_url).path[len('/update/'):]
Scott Zawalskieadbf702013-03-14 09:23:06 -0400177
178
Richard Barnette4c81b972018-07-18 12:35:16 -0700179def get_update_failure_reason(exception):
180 """Convert an exception into a failure reason for metrics.
181
182 The passed in `exception` should be one raised by failure of
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700183 `ChromiumOSProvisioner.run_provision`. The returned string will describe
Richard Barnette4c81b972018-07-18 12:35:16 -0700184 the failure. If the input exception value is not a truish value
185 the return value will be `None`.
186
187 The number of possible return strings is restricted to a limited
188 enumeration of values so that the string may be safely used in
189 Monarch metrics without worrying about cardinality of the range of
190 string values.
191
192 @param exception Exception to be converted to a failure reason.
193
194 @return A string suitable for use in Monarch metrics, or `None`.
195 """
196 if exception:
197 if isinstance(exception, _AttributedUpdateError):
198 return exception.failure_summary
199 else:
200 return 'Unknown Error: %s' % type(exception).__name__
201 return None
202
203
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700204class ChromiumOSProvisioner(object):
Richard Barnette3e8b2282018-05-15 20:42:20 +0000205 """Chromium OS specific DUT update functionality."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700206
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700207 def __init__(self,
208 update_url,
209 host=None,
210 interactive=True,
211 is_release_bucket=None,
212 is_servohost=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700213 """Initializes the object.
214
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700215 @param update_url: The URL we want the update to use.
216 @param host: A client.common_lib.hosts.Host implementation.
David Haddock76a4c882017-12-13 18:50:09 -0800217 @param interactive: Bool whether we are doing an interactive update.
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700218 @param is_release_bucket: If True, use release bucket
219 gs://chromeos-releases.
Garry Wang01a1d482020-08-02 20:46:53 -0700220 @param is_servohost: Bool whether the update target is a servohost.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700221 """
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700222 self.update_url = update_url
223 self.host = host
David Haddock76a4c882017-12-13 18:50:09 -0800224 self.interactive = interactive
Richard Barnette3e8b2282018-05-15 20:42:20 +0000225 self.update_version = _url_to_version(update_url)
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700226 self._is_release_bucket = is_release_bucket
Garry Wang01a1d482020-08-02 20:46:53 -0700227 self._is_servohost = is_servohost
228
Richard Barnette3e8b2282018-05-15 20:42:20 +0000229 def _run(self, cmd, *args, **kwargs):
230 """Abbreviated form of self.host.run(...)"""
231 return self.host.run(cmd, *args, **kwargs)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700232
Richard Barnette55d1af82018-05-22 23:40:14 +0000233 def _rootdev(self, options=''):
234 """Returns the stripped output of rootdev <options>.
235
236 @param options: options to run rootdev.
237
238 """
239 return self._run('rootdev %s' % options).stdout.strip()
240
Richard Barnette55d1af82018-05-22 23:40:14 +0000241 def _reset_update_engine(self):
242 """Resets the host to prepare for a clean update regardless of state."""
243 self._run('stop ui || true')
Brian Norris7c3b0cd2020-09-28 12:47:13 -0700244 self._run('stop update-engine || true; start update-engine')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800245
Richard Barnette55d1af82018-05-22 23:40:14 +0000246 def _reset_stateful_partition(self):
247 """Clear any pending stateful update request."""
Amin Hassani5cda21d2020-08-10 15:24:44 -0700248 cmd = ['rm', '-rf']
249 for f in ('var_new', 'dev_image_new', '.update_available'):
250 cmd += [os.path.join('/mnt/stateful_partition', f)]
Amin Hassani7f68fea2020-08-17 13:52:10 -0700251 # TODO(b/165024723): This is a temporary measure until we figure out the
252 # root cause of this bug.
Jae Hoon Kim5dbb26f2020-12-03 22:38:43 +0000253 for f in ('dev_image/share/tast/data', 'dev_image/libexec/tast',
254 'dev_image/tmp/tast'):
255 cmd += [os.path.join('/mnt/stateful_partition', f)]
Amin Hassani5cda21d2020-08-10 15:24:44 -0700256 cmd += [_TARGET_VERSION, '2>&1']
257 self._run(cmd)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700258
Richard Barnette3ef29a82018-06-28 13:52:54 -0700259 def _set_target_version(self):
260 """Set the "target version" for the update."""
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700261 # Version strings that come from release buckets do not have RXX- at the
262 # beginning. So remove this prefix only if the version has it.
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700263 version_number = (self.update_version.split('-')[1] if
264 '-' in self.update_version else self.update_version)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700265 self._run('echo %s > %s' % (version_number, _TARGET_VERSION))
Richard Barnette55d1af82018-05-22 23:40:14 +0000266
Richard Barnette55d1af82018-05-22 23:40:14 +0000267 def _revert_boot_partition(self):
268 """Revert the boot partition."""
269 part = self._rootdev('-s')
270 logging.warning('Reverting update; Boot partition will be %s', part)
271 return self._run('/postinst %s 2>&1' % part)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700272
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700273 def _get_remote_script(self, script_name):
274 """Ensure that `script_name` is present on the DUT.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700275
Amin Hassani18e39882020-08-10 15:32:10 -0700276 The given script (e.g. `quick-provision`) may be present in the
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700277 stateful partition under /usr/local/bin, or we may have to
278 download it from the devserver.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700279
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700280 Determine whether the script is present or must be downloaded
281 and download if necessary. Then, return a command fragment
282 sufficient to run the script from whereever it now lives on the
283 DUT.
Richard Barnette9d43e562018-06-05 17:20:10 +0000284
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700285 @param script_name The name of the script as expected in
286 /usr/local/bin and on the devserver.
287 @return A string with the command (minus arguments) that will
288 run the target script.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700289 """
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700290 remote_script = '/usr/local/bin/%s' % script_name
291 if self.host.path_exists(remote_script):
292 return remote_script
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800293 self.host.run('mkdir -p -m 1777 /usr/local/tmp')
294 remote_tmp_script = '/usr/local/tmp/%s' % script_name
Derek Beckett5fb683c2020-08-19 15:24:13 -0700295 server_name = six.moves.urllib.parse.urlparse(self.update_url)[1]
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700296 script_url = 'http://%s/static/%s' % (server_name, script_name)
Dana Goyette353d1d92019-06-27 10:43:59 -0700297 fetch_script = 'curl -Ss -o %s %s && head -1 %s' % (
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700298 remote_tmp_script, script_url, remote_tmp_script)
Chris Sosa5e4246b2012-05-22 18:05:22 -0700299
Dana Goyette353d1d92019-06-27 10:43:59 -0700300 first_line = self._run(fetch_script).stdout.strip()
301
302 if first_line and first_line.startswith('#!'):
303 script_interpreter = first_line.lstrip('#!')
304 if script_interpreter:
305 return '%s %s' % (script_interpreter, remote_tmp_script)
306 return None
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700307
Richard Barnette14ee84c2018-05-18 20:23:42 +0000308 def _prepare_host(self):
309 """Make sure the target DUT is working and ready for update.
310
311 Initially, the target DUT's state is unknown. The DUT is
312 expected to be online, but we strive to be forgiving if Chrome
313 and/or the update engine aren't fully functional.
314 """
315 # Summary of work, and the rationale:
316 # 1. Reboot, because it's a good way to clear out problems.
317 # 2. Touch the PROVISION_FAILED file, to allow repair to detect
318 # failure later.
319 # 3. Run the hook for host class specific preparation.
320 # 4. Stop Chrome, because the system is designed to eventually
321 # reboot if Chrome is stuck in a crash loop.
322 # 5. Force `update-engine` to start, because if Chrome failed
323 # to start properly, the status of the `update-engine` job
324 # will be uncertain.
Richard Barnette5adb6d42018-06-28 15:52:32 -0700325 if not self.host.is_up():
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700326 raise HostUpdateError(self.host.hostname, HostUpdateError.DUT_DOWN)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000327 self._reset_stateful_partition()
Garry Wang01a1d482020-08-02 20:46:53 -0700328 # Servohost reboot logic is handled by themselves.
329 if not self._is_servohost:
330 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
331 self._run('touch %s' % PROVISION_FAILED)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000332 self.host.prepare_for_update()
Garry Wang01a1d482020-08-02 20:46:53 -0700333 # Servohost will only update via quick provision.
334 if not self._is_servohost:
335 self._reset_update_engine()
Richard Barnette14ee84c2018-05-18 20:23:42 +0000336 logging.info('Updating from version %s to %s.',
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700337 self.host.get_release_version(), self.update_version)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000338
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700339 def _quick_provision_with_gs_cache(self, provision_command, devserver_name,
340 image_name):
341 """Run quick_provision using GsCache server.
342
343 @param provision_command: The path of quick_provision command.
344 @param devserver_name: The devserver name and port (optional).
345 @param image_name: The image to be installed.
346 """
347 logging.info('Try quick provision with gs_cache.')
348 # If enabled, GsCache server listion on different port on the
349 # devserver.
350 gs_cache_server = devserver_name.replace(DEVSERVER_PORT, GS_CACHE_PORT)
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700351 gs_cache_url = (
352 'http://%s/download/%s' %
353 (gs_cache_server, 'chromeos-releases'
354 if self._is_release_bucket else 'chromeos-image-archive'))
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700355
356 # Check if GS_Cache server is enabled on the server.
Congbin Guo4a2a6642019-08-12 15:03:01 -0700357 self._run('curl -s -o /dev/null %s' % gs_cache_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700358
359 command = '%s --noreboot %s %s' % (provision_command, image_name,
360 gs_cache_url)
361 self._run(command)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700362
363 def _quick_provision_with_devserver(self, provision_command,
364 devserver_name, image_name):
365 """Run quick_provision using legacy devserver.
366
367 @param provision_command: The path of quick_provision command.
368 @param devserver_name: The devserver name and port (optional).
369 @param image_name: The image to be installed.
370 """
Congbin Guo63ae0302019-08-12 16:37:49 -0700371 logging.info('Try quick provision with devserver.')
372 ds = dev_server.ImageServer('http://%s' % devserver_name)
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700373 archive_url = ('gs://chromeos-releases/%s' %
374 image_name if self._is_release_bucket else None)
Congbin Guo63ae0302019-08-12 16:37:49 -0700375 try:
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700376 ds.stage_artifacts(
377 image_name,
378 ['quick_provision', 'stateful', 'autotest_packages'],
379 archive_url=archive_url)
Congbin Guo63ae0302019-08-12 16:37:49 -0700380 except dev_server.DevServerException as e:
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -0700381 six.reraise(error.TestFail, str(e), sys.exc_info()[2])
Congbin Guo63ae0302019-08-12 16:37:49 -0700382
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700383 static_url = 'http://%s/static' % devserver_name
384 command = '%s --noreboot %s %s' % (provision_command, image_name,
385 static_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700386 self._run(command)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700387
Amin Hassani18e39882020-08-10 15:32:10 -0700388 def _install_update(self):
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700389 """Install an updating using the `quick-provision` script.
390
391 This uses the `quick-provision` script to download and install
392 a root FS, kernel and stateful filesystem content.
393
394 @return The kernel expected to be booted next.
395 """
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700396 logging.info('Installing image at %s onto %s', self.update_url,
397 self.host.hostname)
Derek Beckett5fb683c2020-08-19 15:24:13 -0700398 server_name = six.moves.urllib.parse.urlparse(self.update_url)[1]
Amin Hassani18e39882020-08-10 15:32:10 -0700399 image_name = url_to_image_name(self.update_url)
400
Amin Hassanib04420b2020-07-08 18:46:11 +0000401 logging.info('Installing image using quick-provision.')
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700402 provision_command = self._get_remote_script(_QUICK_PROVISION_SCRIPT)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700403 try:
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700404 try:
405 self._quick_provision_with_gs_cache(provision_command,
406 server_name, image_name)
Amin Hassani95f86e02020-07-14 13:06:03 -0700407 except Exception as e:
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700408 logging.error(
409 'Failed to quick-provision with gscache with '
410 'error %s', e)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700411 self._quick_provision_with_devserver(provision_command,
412 server_name, image_name)
413
Richard Barnette3ef29a82018-06-28 13:52:54 -0700414 self._set_target_version()
David Haddock77b75c32020-05-14 01:56:32 -0700415 return kernel_utils.verify_kernel_state_after_update(self.host)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700416 except Exception:
417 # N.B. We handle only `Exception` here. Non-Exception
418 # classes (such as KeyboardInterrupt) are handled by our
419 # caller.
Amin Hassani18e39882020-08-10 15:32:10 -0700420 logging.exception('quick-provision script failed;')
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700421 self._revert_boot_partition()
422 self._reset_stateful_partition()
423 self._reset_update_engine()
424 return None
425
Richard Barnette14ee84c2018-05-18 20:23:42 +0000426 def _complete_update(self, expected_kernel):
427 """Finish the update, and confirm that it succeeded.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000428
Richard Barnette14ee84c2018-05-18 20:23:42 +0000429 Initial condition is that the target build has been downloaded
430 and installed on the DUT, but has not yet been booted. This
431 function is responsible for rebooting the DUT, and checking that
432 the new build is running successfully.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000433
Richard Barnette14ee84c2018-05-18 20:23:42 +0000434 @param expected_kernel: kernel expected to be active after reboot.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000435 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000436 # Regarding the 'crossystem' command below: In some cases,
437 # the update flow puts the TPM into a state such that it
438 # fails verification. We don't know why. However, this
439 # call papers over the problem by clearing the TPM during
440 # the reboot.
441 #
442 # We ignore failures from 'crossystem'. Although failure
443 # here is unexpected, and could signal a bug, the point of
444 # the exercise is to paper over problems; allowing this to
445 # fail would defeat the purpose.
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700446 self._run('crossystem clear_tpm_owner_request=1', ignore_status=True)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000447 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
448
Richard Barnette0beb14b2018-05-15 18:07:52 +0000449 # Touch the lab machine file to leave a marker that
450 # distinguishes this image from other test images.
451 # Afterwards, we must re-run the autoreboot script because
Brian Norrisc4f20552021-02-12 11:14:33 -0800452 # it depends on the LAB_MACHINE_FILE.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000453 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
454 '( touch "$FILE" ; start autoreboot )')
Brian Norrisc4f20552021-02-12 11:14:33 -0800455 self._run(autoreboot_cmd % LAB_MACHINE_FILE)
Sanika Kulkarnia9c4c332020-08-18 15:56:28 -0700456 try:
457 kernel_utils.verify_boot_expectations(
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700458 expected_kernel, NewBuildUpdateError.ROLLBACK_FAILURE,
459 self.host)
Sanika Kulkarnia9c4c332020-08-18 15:56:28 -0700460 except Exception:
461 # When the system is rolled back, the provision_failed file is
462 # removed. So add it back here and re-raise the exception.
463 self._run('touch %s' % PROVISION_FAILED)
464 raise
Richard Barnette0beb14b2018-05-15 18:07:52 +0000465
466 logging.debug('Cleaning up old autotest directories.')
467 try:
468 installed_autodir = autotest.Autotest.get_installed_autodir(
469 self.host)
470 self._run('rm -rf ' + installed_autodir)
471 except autotest.AutodirNotFoundError:
472 logging.debug('No autotest installed directory found.')
473
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700474 def run_provision(self):
475 """Perform a full provision of a DUT in the test lab.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000476
Richard Barnette4c81b972018-07-18 12:35:16 -0700477 This downloads and installs the root FS and stateful partition
478 content needed for the update specified in `self.host` and
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700479 `self.update_url`. The provision is performed according to the
Richard Barnette4c81b972018-07-18 12:35:16 -0700480 requirements for provisioning a DUT for testing the requested
481 build.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000482
Richard Barnette4c81b972018-07-18 12:35:16 -0700483 At the end of the procedure, metrics are reported describing the
484 outcome of the operation.
485
486 @returns A tuple of the form `(image_name, attributes)`, where
487 `image_name` is the name of the image installed, and
488 `attributes` is new attributes to be applied to the DUT.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000489 """
Richard Barnette4c81b972018-07-18 12:35:16 -0700490 server_name = dev_server.get_resolved_hostname(self.update_url)
Richard Barnette4c81b972018-07-18 12:35:16 -0700491
Richard Barnette9d43e562018-06-05 17:20:10 +0000492 try:
493 self._prepare_host()
494 except _AttributedUpdateError:
495 raise
496 except Exception as e:
497 logging.exception('Failure preparing host prior to update.')
498 raise HostUpdateError(self.host.hostname, str(e))
499
500 try:
501 expected_kernel = self._install_update()
502 except _AttributedUpdateError:
503 raise
504 except Exception as e:
505 logging.exception('Failure during download and install.')
506 raise ImageInstallError(self.host.hostname, server_name, str(e))
507
Garry Wang01a1d482020-08-02 20:46:53 -0700508 # Servohost will handle post update process themselves.
509 if not self._is_servohost:
510 try:
511 self._complete_update(expected_kernel)
512 except _AttributedUpdateError:
513 raise
514 except Exception as e:
515 logging.exception('Failure from build after update.')
516 raise NewBuildUpdateError(self.update_version, str(e))
Richard Barnette0beb14b2018-05-15 18:07:52 +0000517
Richard Barnette0beb14b2018-05-15 18:07:52 +0000518 image_name = url_to_image_name(self.update_url)
519 # update_url is different from devserver url needed to stage autotest
520 # packages, therefore, resolve a new devserver url here.
521 devserver_url = dev_server.ImageServer.resolve(
522 image_name, self.host.hostname).url()
523 repo_url = tools.get_package_url(devserver_url, image_name)
524 return image_name, {ds_constants.JOB_REPO_URL: repo_url}