blob: 47bb1349eaef57de365f7390e303b9948b59c9f0 [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
Shelley Chen16b8df32016-10-27 16:24:21 -070023try:
Mike Frysinger714c5b02020-09-04 23:22:54 -040024 from autotest_lib.utils.frozen_chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080025except ImportError:
26 metrics = utils.metrics_mock
Sean O'Connor5346e4e2010-08-12 18:49:24 +020027
Gwendal Grignou3e96cc22017-06-07 16:22:51 -070028
Richard Barnette621a8e42018-06-25 17:34:11 -070029def _metric_name(base_name):
30 return 'chromeos/autotest/provision/' + base_name
31
32
Richard Barnettee86b1ce2018-06-07 10:37:23 -070033_QUICK_PROVISION_SCRIPT = 'quick-provision'
Richard Barnette3e8b2282018-05-15 20:42:20 +000034
Richard Barnette0beb14b2018-05-15 18:07:52 +000035# PROVISION_FAILED - A flag file to indicate provision failures. The
36# file is created at the start of any AU procedure (see
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -070037# `ChromiumOSProvisioner._prepare_host()`). The file's location in
Richard Barnette0beb14b2018-05-15 18:07:52 +000038# stateful means that on successul update it will be removed. Thus, if
39# this file exists, it indicates that we've tried and failed in a
40# previous attempt to update.
41PROVISION_FAILED = '/var/tmp/provision_failed'
42
Richard Barnette3e8b2282018-05-15 20:42:20 +000043# A flag file used to enable special handling in lab DUTs. Some
44# parts of the system in Chromium OS test images will behave in ways
45# convenient to the test lab when this file is present. Generally,
46# we create this immediately after any update completes.
47_LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
48
Richard Barnette3ef29a82018-06-28 13:52:54 -070049# _TARGET_VERSION - A file containing the new version to which we plan
50# to update. This file is used by the CrOS shutdown code to detect and
51# handle certain version downgrade cases. Specifically: Downgrading
52# may trigger an unwanted powerwash in the target build when the
53# following conditions are met:
54# * Source build is a v4.4 kernel with R69-10756.0.0 or later.
55# * Target build predates the R69-10756.0.0 cutoff.
56# When this file is present and indicates a downgrade, the OS shutdown
57# code on the DUT knows how to prevent the powerwash.
58_TARGET_VERSION = '/run/update_target_version'
59
Richard Barnette5adb6d42018-06-28 15:52:32 -070060# _REBOOT_FAILURE_MESSAGE - This is the standard message text returned
61# when the Host.reboot() method fails. The source of this text comes
62# from `wait_for_restart()` in client/common_lib/hosts/base_classes.py.
63
64_REBOOT_FAILURE_MESSAGE = 'Host did not return from reboot'
65
Congbin Guoeb7aa2d2019-07-15 16:10:44 -070066DEVSERVER_PORT = '8082'
67GS_CACHE_PORT = '8888'
68
69
Richard Barnette9d43e562018-06-05 17:20:10 +000070class _AttributedUpdateError(error.TestFail):
71 """Update failure with an attributed cause."""
72
73 def __init__(self, attribution, msg):
Jae Hoon Kim3f004992020-09-10 17:48:33 -070074 super(_AttributedUpdateError,
75 self).__init__('%s: %s' % (attribution, msg))
Richard Barnette5adb6d42018-06-28 15:52:32 -070076 self._message = msg
77
78 def _classify(self):
79 for err_pattern, classification in self._CLASSIFIERS:
80 if re.match(err_pattern, self._message):
81 return classification
82 return None
83
84 @property
85 def failure_summary(self):
86 """Summarize this error for metrics reporting."""
87 classification = self._classify()
88 if classification:
89 return '%s: %s' % (self._SUMMARY, classification)
90 else:
91 return self._SUMMARY
Richard Barnette9d43e562018-06-05 17:20:10 +000092
93
94class HostUpdateError(_AttributedUpdateError):
95 """Failure updating a DUT attributable to the DUT.
96
97 This class of exception should be raised when the most likely cause
98 of failure was a condition existing on the DUT prior to the update,
99 such as a hardware problem, or a bug in the software on the DUT.
100 """
101
Richard Barnette5adb6d42018-06-28 15:52:32 -0700102 DUT_DOWN = 'No answer to ssh'
103
104 _SUMMARY = 'DUT failed prior to update'
105 _CLASSIFIERS = [
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700106 (DUT_DOWN, DUT_DOWN),
107 (_REBOOT_FAILURE_MESSAGE, 'Reboot failed'),
Richard Barnette5adb6d42018-06-28 15:52:32 -0700108 ]
109
Richard Barnette9d43e562018-06-05 17:20:10 +0000110 def __init__(self, hostname, msg):
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700111 super(HostUpdateError,
112 self).__init__('Error on %s prior to update' % hostname, msg)
Richard Barnette9d43e562018-06-05 17:20:10 +0000113
114
Richard Barnette9d43e562018-06-05 17:20:10 +0000115class ImageInstallError(_AttributedUpdateError):
116 """Failure updating a DUT when installing from the devserver.
117
118 This class of exception should be raised when the target DUT fails
119 to download and install the target image from the devserver, and
120 either the devserver or the DUT might be at fault.
121 """
122
Richard Barnette5adb6d42018-06-28 15:52:32 -0700123 _SUMMARY = 'Image failed to download and install'
124 _CLASSIFIERS = []
125
Richard Barnette9d43e562018-06-05 17:20:10 +0000126 def __init__(self, hostname, devserver, msg):
127 super(ImageInstallError, self).__init__(
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700128 'Download and install failed from %s onto %s' %
129 (devserver, hostname), msg)
Richard Barnette9d43e562018-06-05 17:20:10 +0000130
131
132class NewBuildUpdateError(_AttributedUpdateError):
133 """Failure updating a DUT attributable to the target build.
134
135 This class of exception should be raised when updating to a new
136 build fails, and the most likely cause of the failure is a bug in
137 the newly installed target build.
138 """
139
Richard Barnette5adb6d42018-06-28 15:52:32 -0700140 CHROME_FAILURE = 'Chrome failed to reach login screen'
Richard Barnette5adb6d42018-06-28 15:52:32 -0700141 ROLLBACK_FAILURE = 'System rolled back to previous build'
142
143 _SUMMARY = 'New build failed'
144 _CLASSIFIERS = [
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700145 (CHROME_FAILURE, 'Chrome did not start'),
146 (ROLLBACK_FAILURE, ROLLBACK_FAILURE),
Richard Barnette5adb6d42018-06-28 15:52:32 -0700147 ]
148
Richard Barnette9d43e562018-06-05 17:20:10 +0000149 def __init__(self, update_version, msg):
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700150 super(NewBuildUpdateError,
151 self).__init__('Failure in build %s' % update_version, msg)
Richard Barnette9d43e562018-06-05 17:20:10 +0000152
Richard Barnette621a8e42018-06-25 17:34:11 -0700153 @property
154 def failure_summary(self):
155 #pylint: disable=missing-docstring
156 return 'Build failed to work after installing'
157
Richard Barnette9d43e562018-06-05 17:20:10 +0000158
Richard Barnette3e8b2282018-05-15 20:42:20 +0000159def _url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -0800160 """Return the version based on update_url.
161
162 @param update_url: url to the image to update to.
163
164 """
Dale Curtisddfdb942011-07-14 13:59:24 -0700165 # The Chrome OS version is generally the last element in the URL. The only
166 # exception is delta update URLs, which are rooted under the version; e.g.,
167 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
168 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -0700169 return re.sub('/au/.*', '',
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700170 six.moves.urllib.parse.urlparse(update_url).path).split(
171 '/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200172
173
Scott Zawalskieadbf702013-03-14 09:23:06 -0400174def url_to_image_name(update_url):
175 """Return the image name based on update_url.
176
177 From a URL like:
178 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
179 return lumpy-release/R27-3837.0.0
180
181 @param update_url: url to the image to update to.
182 @returns a string representing the image name in the update_url.
183
184 """
Derek Beckett5fb683c2020-08-19 15:24:13 -0700185 return six.moves.urllib.parse.urlparse(update_url).path[len('/update/'):]
Scott Zawalskieadbf702013-03-14 09:23:06 -0400186
187
Richard Barnette4c81b972018-07-18 12:35:16 -0700188def get_update_failure_reason(exception):
189 """Convert an exception into a failure reason for metrics.
190
191 The passed in `exception` should be one raised by failure of
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700192 `ChromiumOSProvisioner.run_provision`. The returned string will describe
Richard Barnette4c81b972018-07-18 12:35:16 -0700193 the failure. If the input exception value is not a truish value
194 the return value will be `None`.
195
196 The number of possible return strings is restricted to a limited
197 enumeration of values so that the string may be safely used in
198 Monarch metrics without worrying about cardinality of the range of
199 string values.
200
201 @param exception Exception to be converted to a failure reason.
202
203 @return A string suitable for use in Monarch metrics, or `None`.
204 """
205 if exception:
206 if isinstance(exception, _AttributedUpdateError):
207 return exception.failure_summary
208 else:
209 return 'Unknown Error: %s' % type(exception).__name__
210 return None
211
212
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700213class ChromiumOSProvisioner(object):
Richard Barnette3e8b2282018-05-15 20:42:20 +0000214 """Chromium OS specific DUT update functionality."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700215
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700216 def __init__(self,
217 update_url,
218 host=None,
219 interactive=True,
220 is_release_bucket=None,
221 is_servohost=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700222 """Initializes the object.
223
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700224 @param update_url: The URL we want the update to use.
225 @param host: A client.common_lib.hosts.Host implementation.
David Haddock76a4c882017-12-13 18:50:09 -0800226 @param interactive: Bool whether we are doing an interactive update.
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700227 @param is_release_bucket: If True, use release bucket
228 gs://chromeos-releases.
Garry Wang01a1d482020-08-02 20:46:53 -0700229 @param is_servohost: Bool whether the update target is a servohost.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700230 """
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700231 self.update_url = update_url
232 self.host = host
David Haddock76a4c882017-12-13 18:50:09 -0800233 self.interactive = interactive
Richard Barnette3e8b2282018-05-15 20:42:20 +0000234 self.update_version = _url_to_version(update_url)
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700235 self._is_release_bucket = is_release_bucket
Garry Wang01a1d482020-08-02 20:46:53 -0700236 self._is_servohost = is_servohost
237
Richard Barnette3e8b2282018-05-15 20:42:20 +0000238 def _run(self, cmd, *args, **kwargs):
239 """Abbreviated form of self.host.run(...)"""
240 return self.host.run(cmd, *args, **kwargs)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700241
Richard Barnette55d1af82018-05-22 23:40:14 +0000242 def _rootdev(self, options=''):
243 """Returns the stripped output of rootdev <options>.
244
245 @param options: options to run rootdev.
246
247 """
248 return self._run('rootdev %s' % options).stdout.strip()
249
Richard Barnette55d1af82018-05-22 23:40:14 +0000250 def _reset_update_engine(self):
251 """Resets the host to prepare for a clean update regardless of state."""
252 self._run('stop ui || true')
Brian Norris7c3b0cd2020-09-28 12:47:13 -0700253 self._run('stop update-engine || true; start update-engine')
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800254
Richard Barnette55d1af82018-05-22 23:40:14 +0000255 def _reset_stateful_partition(self):
256 """Clear any pending stateful update request."""
Amin Hassani5cda21d2020-08-10 15:24:44 -0700257 cmd = ['rm', '-rf']
258 for f in ('var_new', 'dev_image_new', '.update_available'):
259 cmd += [os.path.join('/mnt/stateful_partition', f)]
Amin Hassani7f68fea2020-08-17 13:52:10 -0700260 # TODO(b/165024723): This is a temporary measure until we figure out the
261 # root cause of this bug.
Jae Hoon Kim5dbb26f2020-12-03 22:38:43 +0000262 for f in ('dev_image/share/tast/data', 'dev_image/libexec/tast',
263 'dev_image/tmp/tast'):
264 cmd += [os.path.join('/mnt/stateful_partition', f)]
Amin Hassani5cda21d2020-08-10 15:24:44 -0700265 cmd += [_TARGET_VERSION, '2>&1']
266 self._run(cmd)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700267
Richard Barnette3ef29a82018-06-28 13:52:54 -0700268 def _set_target_version(self):
269 """Set the "target version" for the update."""
Amin Hassani1d6d3a72020-07-09 09:50:26 -0700270 # Version strings that come from release buckets do not have RXX- at the
271 # beginning. So remove this prefix only if the version has it.
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700272 version_number = (self.update_version.split('-')[1] if
273 '-' in self.update_version else self.update_version)
Richard Barnette3ef29a82018-06-28 13:52:54 -0700274 self._run('echo %s > %s' % (version_number, _TARGET_VERSION))
Richard Barnette55d1af82018-05-22 23:40:14 +0000275
Richard Barnette55d1af82018-05-22 23:40:14 +0000276 def _revert_boot_partition(self):
277 """Revert the boot partition."""
278 part = self._rootdev('-s')
279 logging.warning('Reverting update; Boot partition will be %s', part)
280 return self._run('/postinst %s 2>&1' % part)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700281
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700282 def _get_remote_script(self, script_name):
283 """Ensure that `script_name` is present on the DUT.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700284
Amin Hassani18e39882020-08-10 15:32:10 -0700285 The given script (e.g. `quick-provision`) may be present in the
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700286 stateful partition under /usr/local/bin, or we may have to
287 download it from the devserver.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700288
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700289 Determine whether the script is present or must be downloaded
290 and download if necessary. Then, return a command fragment
291 sufficient to run the script from whereever it now lives on the
292 DUT.
Richard Barnette9d43e562018-06-05 17:20:10 +0000293
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700294 @param script_name The name of the script as expected in
295 /usr/local/bin and on the devserver.
296 @return A string with the command (minus arguments) that will
297 run the target script.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700298 """
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700299 remote_script = '/usr/local/bin/%s' % script_name
300 if self.host.path_exists(remote_script):
301 return remote_script
Laurence Goodby06fb42c2020-02-29 17:14:42 -0800302 self.host.run('mkdir -p -m 1777 /usr/local/tmp')
303 remote_tmp_script = '/usr/local/tmp/%s' % script_name
Derek Beckett5fb683c2020-08-19 15:24:13 -0700304 server_name = six.moves.urllib.parse.urlparse(self.update_url)[1]
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700305 script_url = 'http://%s/static/%s' % (server_name, script_name)
Dana Goyette353d1d92019-06-27 10:43:59 -0700306 fetch_script = 'curl -Ss -o %s %s && head -1 %s' % (
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700307 remote_tmp_script, script_url, remote_tmp_script)
Chris Sosa5e4246b2012-05-22 18:05:22 -0700308
Dana Goyette353d1d92019-06-27 10:43:59 -0700309 first_line = self._run(fetch_script).stdout.strip()
310
311 if first_line and first_line.startswith('#!'):
312 script_interpreter = first_line.lstrip('#!')
313 if script_interpreter:
314 return '%s %s' % (script_interpreter, remote_tmp_script)
315 return None
Richard Barnettef00a2ee2018-06-08 11:51:38 -0700316
Richard Barnette14ee84c2018-05-18 20:23:42 +0000317 def _prepare_host(self):
318 """Make sure the target DUT is working and ready for update.
319
320 Initially, the target DUT's state is unknown. The DUT is
321 expected to be online, but we strive to be forgiving if Chrome
322 and/or the update engine aren't fully functional.
323 """
324 # Summary of work, and the rationale:
325 # 1. Reboot, because it's a good way to clear out problems.
326 # 2. Touch the PROVISION_FAILED file, to allow repair to detect
327 # failure later.
328 # 3. Run the hook for host class specific preparation.
329 # 4. Stop Chrome, because the system is designed to eventually
330 # reboot if Chrome is stuck in a crash loop.
331 # 5. Force `update-engine` to start, because if Chrome failed
332 # to start properly, the status of the `update-engine` job
333 # will be uncertain.
Richard Barnette5adb6d42018-06-28 15:52:32 -0700334 if not self.host.is_up():
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700335 raise HostUpdateError(self.host.hostname, HostUpdateError.DUT_DOWN)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000336 self._reset_stateful_partition()
Garry Wang01a1d482020-08-02 20:46:53 -0700337 # Servohost reboot logic is handled by themselves.
338 if not self._is_servohost:
339 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
340 self._run('touch %s' % PROVISION_FAILED)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000341 self.host.prepare_for_update()
Garry Wang01a1d482020-08-02 20:46:53 -0700342 # Servohost will only update via quick provision.
343 if not self._is_servohost:
344 self._reset_update_engine()
Richard Barnette14ee84c2018-05-18 20:23:42 +0000345 logging.info('Updating from version %s to %s.',
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700346 self.host.get_release_version(), self.update_version)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000347
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700348 def _quick_provision_with_gs_cache(self, provision_command, devserver_name,
349 image_name):
350 """Run quick_provision using GsCache server.
351
352 @param provision_command: The path of quick_provision command.
353 @param devserver_name: The devserver name and port (optional).
354 @param image_name: The image to be installed.
355 """
356 logging.info('Try quick provision with gs_cache.')
357 # If enabled, GsCache server listion on different port on the
358 # devserver.
359 gs_cache_server = devserver_name.replace(DEVSERVER_PORT, GS_CACHE_PORT)
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700360 gs_cache_url = (
361 'http://%s/download/%s' %
362 (gs_cache_server, 'chromeos-releases'
363 if self._is_release_bucket else 'chromeos-image-archive'))
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700364
365 # Check if GS_Cache server is enabled on the server.
Congbin Guo4a2a6642019-08-12 15:03:01 -0700366 self._run('curl -s -o /dev/null %s' % gs_cache_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700367
368 command = '%s --noreboot %s %s' % (provision_command, image_name,
369 gs_cache_url)
370 self._run(command)
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700371 metrics.Counter(
372 _metric_name('quick_provision')).increment(fields={
373 'devserver': devserver_name,
374 'gs_cache': True
375 })
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700376
377 def _quick_provision_with_devserver(self, provision_command,
378 devserver_name, image_name):
379 """Run quick_provision using legacy devserver.
380
381 @param provision_command: The path of quick_provision command.
382 @param devserver_name: The devserver name and port (optional).
383 @param image_name: The image to be installed.
384 """
Congbin Guo63ae0302019-08-12 16:37:49 -0700385 logging.info('Try quick provision with devserver.')
386 ds = dev_server.ImageServer('http://%s' % devserver_name)
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700387 archive_url = ('gs://chromeos-releases/%s' %
388 image_name if self._is_release_bucket else None)
Congbin Guo63ae0302019-08-12 16:37:49 -0700389 try:
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700390 ds.stage_artifacts(
391 image_name,
392 ['quick_provision', 'stateful', 'autotest_packages'],
393 archive_url=archive_url)
Congbin Guo63ae0302019-08-12 16:37:49 -0700394 except dev_server.DevServerException as e:
Gregory Nisbetcf8c2ed2020-07-14 18:35:49 -0700395 six.reraise(error.TestFail, str(e), sys.exc_info()[2])
Congbin Guo63ae0302019-08-12 16:37:49 -0700396
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700397 static_url = 'http://%s/static' % devserver_name
398 command = '%s --noreboot %s %s' % (provision_command, image_name,
399 static_url)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700400 self._run(command)
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700401 metrics.Counter(
402 _metric_name('quick_provision')).increment(fields={
403 'devserver': devserver_name,
404 'gs_cache': False
405 })
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700406
Amin Hassani18e39882020-08-10 15:32:10 -0700407 def _install_update(self):
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700408 """Install an updating using the `quick-provision` script.
409
410 This uses the `quick-provision` script to download and install
411 a root FS, kernel and stateful filesystem content.
412
413 @return The kernel expected to be booted next.
414 """
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700415 logging.info('Installing image at %s onto %s', self.update_url,
416 self.host.hostname)
Derek Beckett5fb683c2020-08-19 15:24:13 -0700417 server_name = six.moves.urllib.parse.urlparse(self.update_url)[1]
Amin Hassani18e39882020-08-10 15:32:10 -0700418 image_name = url_to_image_name(self.update_url)
419
Amin Hassanib04420b2020-07-08 18:46:11 +0000420 logging.info('Installing image using quick-provision.')
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700421 provision_command = self._get_remote_script(_QUICK_PROVISION_SCRIPT)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700422 try:
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700423 try:
424 self._quick_provision_with_gs_cache(provision_command,
425 server_name, image_name)
Amin Hassani95f86e02020-07-14 13:06:03 -0700426 except Exception as e:
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700427 logging.error(
428 'Failed to quick-provision with gscache with '
429 'error %s', e)
Congbin Guoeb7aa2d2019-07-15 16:10:44 -0700430 self._quick_provision_with_devserver(provision_command,
431 server_name, image_name)
432
Richard Barnette3ef29a82018-06-28 13:52:54 -0700433 self._set_target_version()
David Haddock77b75c32020-05-14 01:56:32 -0700434 return kernel_utils.verify_kernel_state_after_update(self.host)
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700435 except Exception:
436 # N.B. We handle only `Exception` here. Non-Exception
437 # classes (such as KeyboardInterrupt) are handled by our
438 # caller.
Amin Hassani18e39882020-08-10 15:32:10 -0700439 logging.exception('quick-provision script failed;')
Richard Barnettee86b1ce2018-06-07 10:37:23 -0700440 self._revert_boot_partition()
441 self._reset_stateful_partition()
442 self._reset_update_engine()
443 return None
444
Richard Barnette14ee84c2018-05-18 20:23:42 +0000445 def _complete_update(self, expected_kernel):
446 """Finish the update, and confirm that it succeeded.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000447
Richard Barnette14ee84c2018-05-18 20:23:42 +0000448 Initial condition is that the target build has been downloaded
449 and installed on the DUT, but has not yet been booted. This
450 function is responsible for rebooting the DUT, and checking that
451 the new build is running successfully.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000452
Richard Barnette14ee84c2018-05-18 20:23:42 +0000453 @param expected_kernel: kernel expected to be active after reboot.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000454 """
Richard Barnette14ee84c2018-05-18 20:23:42 +0000455 # Regarding the 'crossystem' command below: In some cases,
456 # the update flow puts the TPM into a state such that it
457 # fails verification. We don't know why. However, this
458 # call papers over the problem by clearing the TPM during
459 # the reboot.
460 #
461 # We ignore failures from 'crossystem'. Although failure
462 # here is unexpected, and could signal a bug, the point of
463 # the exercise is to paper over problems; allowing this to
464 # fail would defeat the purpose.
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700465 self._run('crossystem clear_tpm_owner_request=1', ignore_status=True)
Richard Barnette14ee84c2018-05-18 20:23:42 +0000466 self.host.reboot(timeout=self.host.REBOOT_TIMEOUT)
467
Richard Barnette0beb14b2018-05-15 18:07:52 +0000468 # Touch the lab machine file to leave a marker that
469 # distinguishes this image from other test images.
470 # Afterwards, we must re-run the autoreboot script because
471 # it depends on the _LAB_MACHINE_FILE.
472 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
473 '( touch "$FILE" ; start autoreboot )')
Richard Barnette3e8b2282018-05-15 20:42:20 +0000474 self._run(autoreboot_cmd % _LAB_MACHINE_FILE)
Sanika Kulkarnia9c4c332020-08-18 15:56:28 -0700475 try:
476 kernel_utils.verify_boot_expectations(
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700477 expected_kernel, NewBuildUpdateError.ROLLBACK_FAILURE,
478 self.host)
Sanika Kulkarnia9c4c332020-08-18 15:56:28 -0700479 except Exception:
480 # When the system is rolled back, the provision_failed file is
481 # removed. So add it back here and re-raise the exception.
482 self._run('touch %s' % PROVISION_FAILED)
483 raise
Richard Barnette0beb14b2018-05-15 18:07:52 +0000484
485 logging.debug('Cleaning up old autotest directories.')
486 try:
487 installed_autodir = autotest.Autotest.get_installed_autodir(
488 self.host)
489 self._run('rm -rf ' + installed_autodir)
490 except autotest.AutodirNotFoundError:
491 logging.debug('No autotest installed directory found.')
492
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700493 def run_provision(self):
494 """Perform a full provision of a DUT in the test lab.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000495
Richard Barnette4c81b972018-07-18 12:35:16 -0700496 This downloads and installs the root FS and stateful partition
497 content needed for the update specified in `self.host` and
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700498 `self.update_url`. The provision is performed according to the
Richard Barnette4c81b972018-07-18 12:35:16 -0700499 requirements for provisioning a DUT for testing the requested
500 build.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000501
Richard Barnette4c81b972018-07-18 12:35:16 -0700502 At the end of the procedure, metrics are reported describing the
503 outcome of the operation.
504
505 @returns A tuple of the form `(image_name, attributes)`, where
506 `image_name` is the name of the image installed, and
507 `attributes` is new attributes to be applied to the DUT.
Richard Barnette0beb14b2018-05-15 18:07:52 +0000508 """
Richard Barnette4c81b972018-07-18 12:35:16 -0700509 server_name = dev_server.get_resolved_hostname(self.update_url)
510 metrics.Counter(_metric_name('install')).increment(
511 fields={'devserver': server_name})
512
Richard Barnette9d43e562018-06-05 17:20:10 +0000513 try:
514 self._prepare_host()
515 except _AttributedUpdateError:
516 raise
517 except Exception as e:
518 logging.exception('Failure preparing host prior to update.')
519 raise HostUpdateError(self.host.hostname, str(e))
520
521 try:
522 expected_kernel = self._install_update()
523 except _AttributedUpdateError:
524 raise
525 except Exception as e:
526 logging.exception('Failure during download and install.')
527 raise ImageInstallError(self.host.hostname, server_name, str(e))
528
Garry Wang01a1d482020-08-02 20:46:53 -0700529 # Servohost will handle post update process themselves.
530 if not self._is_servohost:
531 try:
532 self._complete_update(expected_kernel)
533 except _AttributedUpdateError:
534 raise
535 except Exception as e:
536 logging.exception('Failure from build after update.')
537 raise NewBuildUpdateError(self.update_version, str(e))
Richard Barnette0beb14b2018-05-15 18:07:52 +0000538
Richard Barnette0beb14b2018-05-15 18:07:52 +0000539 image_name = url_to_image_name(self.update_url)
540 # update_url is different from devserver url needed to stage autotest
541 # packages, therefore, resolve a new devserver url here.
542 devserver_url = dev_server.ImageServer.resolve(
543 image_name, self.host.hostname).url()
544 repo_url = tools.get_package_url(devserver_url, image_name)
545 return image_name, {ds_constants.JOB_REPO_URL: repo_url}