blob: 31c4b7c995467deef90f308064898e225814f589 [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
Don Garrett56b1cc82013-12-06 17:49:20 -08005import glob
Sean O'Connor5346e4e2010-08-12 18:49:24 +02006import logging
Dale Curtis5c32c722011-05-04 19:24:23 -07007import os
Sean O'Connor5346e4e2010-08-12 18:49:24 +02008import re
Sean O'Connor5346e4e2010-08-12 18:49:24 +02009import urlparse
Prashanth B32baa9b2014-03-13 13:23:01 -070010import urllib2
Sean O'Connor5346e4e2010-08-12 18:49:24 +020011
Chris Sosa65425082013-10-16 13:26:22 -070012from autotest_lib.client.bin import utils
Dale Curtis5c32c722011-05-04 19:24:23 -070013from autotest_lib.client.common_lib import error, global_config
Prashanth B32baa9b2014-03-13 13:23:01 -070014from autotest_lib.client.common_lib.cros import dev_server
Shelley Chen61d28982016-10-28 09:40:20 -070015from autotest_lib.server import utils as server_utils
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -080016from chromite.lib import retry_util
Dan Shif3a35f72016-01-25 11:18:14 -080017
Shelley Chen16b8df32016-10-27 16:24:21 -070018try:
19 from chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080020except ImportError:
21 metrics = utils.metrics_mock
Sean O'Connor5346e4e2010-08-12 18:49:24 +020022
Gwendal Grignou3e96cc22017-06-07 16:22:51 -070023try:
24 import devserver
25 STATEFUL_UPDATE_PATH = devserver.__path__[0]
26except ImportError:
27 STATEFUL_UPDATE_PATH = '/usr/bin'
28
Dale Curtis5c32c722011-05-04 19:24:23 -070029# Local stateful update path is relative to the CrOS source directory.
Gwendal Grignou3e96cc22017-06-07 16:22:51 -070030STATEFUL_UPDATE_SCRIPT = 'stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020031UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020032UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
beeps5e8c45a2013-12-17 22:05:11 -080033# A list of update engine client states that occur after an update is triggered.
34UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FORUPDATE',
35 'UPDATE_STATUS_UPDATE_AVAILABLE',
36 'UPDATE_STATUS_DOWNLOADING',
37 'UPDATE_STATUS_FINALIZING']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020038
39class ChromiumOSError(error.InstallError):
40 """Generic error for ChromiumOS-specific exceptions."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -070041
42
Chris Sosa77556d82012-04-05 15:23:14 -070043class RootFSUpdateError(ChromiumOSError):
44 """Raised when the RootFS fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070045
46
47class StatefulUpdateError(ChromiumOSError):
48 """Raised when the stateful partition fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070049
50
Sean O'Connor5346e4e2010-08-12 18:49:24 +020051def url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -080052 """Return the version based on update_url.
53
54 @param update_url: url to the image to update to.
55
56 """
Dale Curtisddfdb942011-07-14 13:59:24 -070057 # The Chrome OS version is generally the last element in the URL. The only
58 # exception is delta update URLs, which are rooted under the version; e.g.,
59 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
60 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -070061 return re.sub('/au/.*', '',
62 urlparse.urlparse(update_url).path).split('/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +020063
64
Scott Zawalskieadbf702013-03-14 09:23:06 -040065def url_to_image_name(update_url):
66 """Return the image name based on update_url.
67
68 From a URL like:
69 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
70 return lumpy-release/R27-3837.0.0
71
72 @param update_url: url to the image to update to.
73 @returns a string representing the image name in the update_url.
74
75 """
76 return '/'.join(urlparse.urlparse(update_url).path.split('/')[-2:])
77
78
Prashanth B32baa9b2014-03-13 13:23:01 -070079def _get_devserver_build_from_update_url(update_url):
80 """Get the devserver and build from the update url.
81
82 @param update_url: The url for update.
83 Eg: http://devserver:port/update/build.
84
85 @return: A tuple of (devserver url, build) or None if the update_url
86 doesn't match the expected pattern.
87
88 @raises ValueError: If the update_url doesn't match the expected pattern.
89 @raises ValueError: If no global_config was found, or it doesn't contain an
90 image_url_pattern.
91 """
92 pattern = global_config.global_config.get_config_value(
93 'CROS', 'image_url_pattern', type=str, default='')
94 if not pattern:
95 raise ValueError('Cannot parse update_url, the global config needs '
96 'an image_url_pattern.')
97 re_pattern = pattern.replace('%s', '(\S+)')
98 parts = re.search(re_pattern, update_url)
99 if not parts or len(parts.groups()) < 2:
100 raise ValueError('%s is not an update url' % update_url)
101 return parts.groups()
102
103
104def list_image_dir_contents(update_url):
105 """Lists the contents of the devserver for a given build/update_url.
106
107 @param update_url: An update url. Eg: http://devserver:port/update/build.
108 """
109 if not update_url:
110 logging.warning('Need update_url to list contents of the devserver.')
111 return
112 error_msg = 'Cannot check contents of devserver, update url %s' % update_url
113 try:
114 devserver_url, build = _get_devserver_build_from_update_url(update_url)
115 except ValueError as e:
116 logging.warning('%s: %s', error_msg, e)
117 return
118 devserver = dev_server.ImageServer(devserver_url)
119 try:
120 devserver.list_image_dir(build)
121 # The devserver will retry on URLError to avoid flaky connections, but will
122 # eventually raise the URLError if it persists. All HTTPErrors get
123 # converted to DevServerExceptions.
124 except (dev_server.DevServerException, urllib2.URLError) as e:
125 logging.warning('%s: %s', error_msg, e)
126
127
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700128# TODO(garnold) This implements shared updater functionality needed for
129# supporting the autoupdate_EndToEnd server-side test. We should probably
130# migrate more of the existing ChromiumOSUpdater functionality to it as we
131# expand non-CrOS support in other tests.
132class BaseUpdater(object):
133 """Platform-agnostic DUT update functionality."""
134
David Haddock76a4c882017-12-13 18:50:09 -0800135 def __init__(self, updater_ctrl_bin, update_url, host, interactive=True):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700136 """Initializes the object.
137
138 @param updater_ctrl_bin: Path to update_engine_client.
139 @param update_url: The URL we want the update to use.
140 @param host: A client.common_lib.hosts.Host implementation.
David Haddock76a4c882017-12-13 18:50:09 -0800141 @param interactive: Bool whether we are doing an interactive update.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700142 """
143 self.updater_ctrl_bin = updater_ctrl_bin
144 self.update_url = update_url
145 self.host = host
David Haddock76a4c882017-12-13 18:50:09 -0800146 self.interactive = interactive
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700147
148
149 def check_update_status(self):
150 """Returns the current update engine state.
151
152 We use the `update_engine_client -status' command and parse the line
153 indicating the update state, e.g. "CURRENT_OP=UPDATE_STATUS_IDLE".
154 """
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800155 update_status = self.host.run(command='%s -status | grep CURRENT_OP' %
156 self.updater_ctrl_bin)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700157 return update_status.stdout.strip().split('=')[-1]
158
159
Shuqian Zhaod9992722016-02-29 12:26:38 -0800160 def get_last_update_error(self):
161 """Get the last autoupdate error code."""
162 error_msg = self.host.run(
163 '%s --last_attempt_error' % self.updater_ctrl_bin)
164 error_msg = (error_msg.stdout.strip()).replace('\n', ', ')
165 return error_msg
166
167
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800168 def _base_update_handler_no_retry(self, run_args):
Shuqian Zhaod9992722016-02-29 12:26:38 -0800169 """Base function to handle a remote update ssh call.
170
171 @param run_args: Dictionary of args passed to ssh_host.run function.
Shuqian Zhaod9992722016-02-29 12:26:38 -0800172
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800173 @throws: intercepts and re-throws all exceptions
Shuqian Zhaod9992722016-02-29 12:26:38 -0800174 """
Shuqian Zhaod9992722016-02-29 12:26:38 -0800175 try:
176 self.host.run(**run_args)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800177 except Exception as e:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800178 logging.debug('exception in update handler: %s', e)
179 raise e
Shuqian Zhaod9992722016-02-29 12:26:38 -0800180
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800181
182 def _base_update_handler(self, run_args, err_msg_prefix=None):
183 """Handle a remote update ssh call, possibly with retries.
184
185 @param run_args: Dictionary of args passed to ssh_host.run function.
186 @param err_msg_prefix: Prefix of the exception error message.
187 """
188 def exception_handler(e):
189 """Examines exceptions and returns True if the update handler
190 should be retried.
191
192 @param e: the exception intercepted by the retry util.
193 """
194 return (isinstance(e, error.AutoservSSHTimeout) or
195 (isinstance(e, error.GenericHostRunError) and
196 hasattr(e, 'description') and
197 (re.search('ERROR_CODE=37', e.description) or
198 re.search('generic error .255.', e.description))))
199
200 try:
201 # Try the update twice (arg 2 is max_retry, not including the first
202 # call). Some exceptions may be caught by the retry handler.
203 retry_util.GenericRetry(exception_handler, 1,
204 self._base_update_handler_no_retry,
205 run_args)
206 except Exception as e:
207 message = err_msg_prefix + ': ' + str(e)
208 raise RootFSUpdateError(message)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800209
210
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800211 def _wait_for_update_service(self):
212 """Ensure that the update engine daemon is running, possibly
213 by waiting for it a bit in case the DUT just rebooted and the
214 service hasn't started yet.
215 """
216 def handler(e):
217 """Retry exception handler.
218
219 Assumes that the error is due to the update service not having
220 started yet.
221
222 @param e: the exception intercepted by the retry util.
223 """
224 if isinstance(e, error.AutoservRunError):
225 logging.debug('update service check exception: %s\n'
226 'retrying...', e)
227 return True
228 else:
229 return False
230
231 # Retry at most three times, every 5s.
232 status = retry_util.GenericRetry(handler, 3,
233 self.check_update_status,
234 sleep=5)
235
236 # Expect the update engine to be idle.
237 if status != UPDATER_IDLE:
238 raise ChromiumOSError('%s is not in an installable state' %
239 self.host.hostname)
240
241
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700242 def trigger_update(self):
243 """Triggers a background update.
244
Shuqian Zhaod9992722016-02-29 12:26:38 -0800245 @raise RootFSUpdateError or unknown Exception if anything went wrong.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700246 """
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800247 # If this function is called immediately after reboot (which it is at
248 # this time), there is no guarantee that the update service is up and
249 # running yet, so wait for it.
250 self._wait_for_update_service()
251
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700252 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
253 (self.updater_ctrl_bin, self.update_url))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800254 run_args = {'command': autoupdate_cmd}
255 err_prefix = 'Failed to trigger an update on %s. ' % self.host.hostname
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700256 logging.info('Triggering update via: %s', autoupdate_cmd)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700257 metric_fields = {'success': False}
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800258 try:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800259 self._base_update_handler(run_args, err_prefix)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700260 metric_fields['success'] = True
261 finally:
Allen Li1a5cc0a2017-06-20 14:08:59 -0700262 c = metrics.Counter('chromeos/autotest/autoupdater/trigger')
Allen Lib5420a72017-06-20 14:14:07 -0700263 metric_fields.update(self._get_metric_fields())
Allen Li1a5cc0a2017-06-20 14:08:59 -0700264 c.increment(fields=metric_fields)
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700265
266
Allen Lib5420a72017-06-20 14:14:07 -0700267 def _get_metric_fields(self):
268 """Return a dict of metric fields.
269
270 This is used for sending autoupdate metrics for this instance.
271 """
272 build_name = url_to_image_name(self.update_url)
273 try:
274 board, build_type, milestone, _ = server_utils.ParseBuildName(
275 build_name)
276 except server_utils.ParseBuildNameException:
277 logging.warning('Unable to parse build name %s for metrics. '
278 'Continuing anyway.', build_name)
279 board, build_type, milestone = ('', '', '')
280 return {
281 'dev_server': dev_server.get_hostname(self.update_url),
282 'board': board,
283 'build_type': build_type,
284 'milestone': milestone,
285 }
286
287
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700288 def _verify_update_completed(self):
289 """Verifies that an update has completed.
290
291 @raise RootFSUpdateError: if verification fails.
292 """
293 status = self.check_update_status()
294 if status != UPDATER_NEED_REBOOT:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800295 error_msg = ''
296 if status == UPDATER_IDLE:
297 error_msg = 'Update error: %s' % self.get_last_update_error()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700298 raise RootFSUpdateError('Update did not complete with correct '
Shuqian Zhaod9992722016-02-29 12:26:38 -0800299 'status. Expecting %s, actual %s. %s' %
300 (UPDATER_NEED_REBOOT, status, error_msg))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700301
302
303 def update_image(self):
304 """Updates the device image and verifies success."""
Shuqian Zhaofe4d62e2016-06-23 14:46:45 -0700305 autoupdate_cmd = ('%s --update --omaha_url=%s' %
Shuqian Zhaod9992722016-02-29 12:26:38 -0800306 (self.updater_ctrl_bin, self.update_url))
David Haddock76a4c882017-12-13 18:50:09 -0800307 if not self.interactive:
308 autoupdate_cmd = '%s --interactive=false' % autoupdate_cmd
Shuqian Zhaod9992722016-02-29 12:26:38 -0800309 run_args = {'command': autoupdate_cmd, 'timeout': 3600}
310 err_prefix = ('Failed to install device image using payload at %s '
311 'on %s. ' % (self.update_url, self.host.hostname))
312 logging.info('Updating image via: %s', autoupdate_cmd)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700313 metric_fields = {'success': False}
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800314 try:
Luigi Semenzatoe76d9f82016-11-21 11:15:10 -0800315 self._base_update_handler(run_args, err_prefix)
Allen Li1a5cc0a2017-06-20 14:08:59 -0700316 metric_fields['success'] = True
317 finally:
Allen Li1a5cc0a2017-06-20 14:08:59 -0700318 c = metrics.Counter('chromeos/autotest/autoupdater/update')
Allen Lib5420a72017-06-20 14:14:07 -0700319 metric_fields.update(self._get_metric_fields())
Allen Li1a5cc0a2017-06-20 14:08:59 -0700320 c.increment(fields=metric_fields)
Dan Shi5e2efb72017-02-07 11:40:23 -0800321
Aviv Keshetf37b2d72016-06-01 19:27:59 -0700322 self._verify_update_completed()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700323
324
325class ChromiumOSUpdater(BaseUpdater):
Dan Shi0f466e82013-02-22 15:44:58 -0800326 """Helper class used to update DUT with image of desired version."""
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700327 REMOTE_STATEFUL_UPDATE_PATH = os.path.join(
328 '/usr/local/bin', STATEFUL_UPDATE_SCRIPT)
329 REMOTE_TMP_STATEFUL_UPDATE = os.path.join(
330 '/tmp', STATEFUL_UPDATE_SCRIPT)
Gilad Arnold0c0df732015-09-21 06:37:59 -0700331 UPDATER_BIN = '/usr/bin/update_engine_client'
Mike Frysinger898bd552017-04-10 23:56:36 -0400332 UPDATED_MARKER = '/run/update_engine_autoupdate_completed'
Gilad Arnold0c0df732015-09-21 06:37:59 -0700333 UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
334
Dale Curtisa94c19c2011-05-02 15:05:17 -0700335 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
336 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
Chris Sosa65425082013-10-16 13:26:22 -0700337 # Time to wait for new kernel to be marked successful after
338 # auto update.
339 KERNEL_UPDATE_TIMEOUT = 120
Dale Curtisa94c19c2011-05-02 15:05:17 -0700340
Richard Barnette0173ea82018-05-04 21:13:57 +0000341 def __init__(self, update_url, host=None, interactive=True):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700342 super(ChromiumOSUpdater, self).__init__(self.UPDATER_BIN, update_url,
David Haddock76a4c882017-12-13 18:50:09 -0800343 host, interactive=interactive)
Richard Barnette0173ea82018-05-04 21:13:57 +0000344 self.update_version = url_to_version(update_url)
Sean Oc053dfe2010-08-23 18:22:26 +0200345
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700346
Sean Oc053dfe2010-08-23 18:22:26 +0200347 def reset_update_engine(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700348 """Resets the host to prepare for a clean update regardless of state."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700349 self._run('rm -f %s' % self.UPDATED_MARKER)
Chris Sosae92399e2015-04-24 11:32:59 -0700350 self._run('stop ui || true')
351 self._run('stop update-engine || true')
352 self._run('start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700353
Shuqian Zhaofe4d62e2016-06-23 14:46:45 -0700354 # Wait for update engine to be ready.
Luigi Semenzatof15c8fc2017-03-03 14:12:40 -0800355 self._wait_for_update_service()
Sean Oc053dfe2010-08-23 18:22:26 +0200356
357
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200358 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700359 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200360 return self.host.run(cmd, *args, **kwargs)
361
Sean Oc053dfe2010-08-23 18:22:26 +0200362
Dale Curtisa94c19c2011-05-02 15:05:17 -0700363 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800364 """Returns the stripped output of rootdev <options>.
365
366 @param options: options to run rootdev.
367
368 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700369 return self._run('rootdev %s' % options).stdout.strip()
370
371
372 def get_kernel_state(self):
373 """Returns the (<active>, <inactive>) kernel state as a pair."""
374 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
375 if active_root == self.KERNEL_A['root']:
376 return self.KERNEL_A, self.KERNEL_B
377 elif active_root == self.KERNEL_B['root']:
378 return self.KERNEL_B, self.KERNEL_A
379 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700380 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700381 active_root)
382
383
384 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
385 """Return numeric cgpt value for the specified flag, kernel, device. """
386 return int(self._run('cgpt show -n -i %d %s %s' % (
387 kernel['kernel'], flag, dev)).stdout.strip())
388
389
390 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800391 """Return numeric priority for the specified kernel.
392
393 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
394
395 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700396 return self._cgpt('-P', kernel)
397
398
399 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800400 """Return boolean success flag for the specified kernel.
401
402 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
403
404 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700405 return self._cgpt('-S', kernel) != 0
406
407
408 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800409 """Return tries count for the specified kernel.
410
411 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
412
413 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700414 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200415
416
Chris Sosa5e4246b2012-05-22 18:05:22 -0700417 def get_stateful_update_script(self):
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700418 """Returns the path to the stateful update script on the target.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700419
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700420 When runnning test_that, stateful_update is in chroot /usr/sbin,
421 as installed by chromeos-base/devserver packages.
422 In the lab, it is installed with the python module devserver, by
423 build_externals.py command.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700424
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700425 If we can find it, we hope it exists already on the DUT, we assert
426 otherwise.
427 """
428 stateful_update_file = os.path.join(STATEFUL_UPDATE_PATH,
429 STATEFUL_UPDATE_SCRIPT)
430 if os.path.exists(stateful_update_file):
Chris Sosa5e4246b2012-05-22 18:05:22 -0700431 self.host.send_file(
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700432 stateful_update_file, self.REMOTE_TMP_STATEFUL_UPDATE,
Gilad Arnold0c0df732015-09-21 06:37:59 -0700433 delete_dest=True)
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700434 return self.REMOTE_TMP_STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700435
Gwendal Grignou3e96cc22017-06-07 16:22:51 -0700436 if self.host.path_exists(self.REMOTE_STATEFUL_UPDATE_PATH):
437 logging.warning('Could not chroot %s script, falling back on %s',
438 STATEFUL_UPDATE_SCRIPT, self.REMOTE_STATEFUL_UPDATE_PATH)
439 return self.REMOTE_STATEFUL_UPDATE_PATH
440 else:
441 raise ChromiumOSError('Could not locate %s',
442 STATEFUL_UPDATE_SCRIPT)
Chris Sosa5e4246b2012-05-22 18:05:22 -0700443
444
445 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800446 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700447 statefuldev_cmd = [self.get_stateful_update_script()]
448 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700449 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700450
451
Sean O267c00b2010-08-31 15:54:55 +0200452 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800453 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700454 part = self.rootdev('-s')
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700455 logging.warning('Reverting update; Boot partition will be %s', part)
Sean O267c00b2010-08-31 15:54:55 +0200456 return self._run('/postinst %s 2>&1' % part)
457
458
Chris Sosac1932172013-10-16 13:28:53 -0700459 def rollback_rootfs(self, powerwash):
460 """Triggers rollback and waits for it to complete.
461
462 @param powerwash: If true, powerwash as part of rollback.
463
464 @raise RootFSUpdateError if anything went wrong.
465
466 """
Dan Shi549fb822015-03-24 18:01:11 -0700467 version = self.host.get_release_version()
Chris Sosac8617522014-06-09 23:22:26 +0000468 # Introduced can_rollback in M36 (build 5772). # etc/lsb-release matches
469 # X.Y.Z. This version split just pulls the first part out.
470 try:
471 build_number = int(version.split('.')[0])
472 except ValueError:
473 logging.error('Could not parse build number.')
474 build_number = 0
475
476 if build_number >= 5772:
Gilad Arnold0c0df732015-09-21 06:37:59 -0700477 can_rollback_cmd = '%s --can_rollback' % self.UPDATER_BIN
Chris Sosac8617522014-06-09 23:22:26 +0000478 logging.info('Checking for rollback.')
479 try:
480 self._run(can_rollback_cmd)
481 except error.AutoservRunError as e:
482 raise RootFSUpdateError("Rollback isn't possible on %s: %s" %
483 (self.host.hostname, str(e)))
484
Gilad Arnold0c0df732015-09-21 06:37:59 -0700485 rollback_cmd = '%s --rollback --follow' % self.UPDATER_BIN
Chris Sosac1932172013-10-16 13:28:53 -0700486 if not powerwash:
Dan Shif3a35f72016-01-25 11:18:14 -0800487 rollback_cmd += ' --nopowerwash'
Chris Sosac1932172013-10-16 13:28:53 -0700488
Chris Sosac8617522014-06-09 23:22:26 +0000489 logging.info('Performing rollback.')
Chris Sosac1932172013-10-16 13:28:53 -0700490 try:
491 self._run(rollback_cmd)
Chris Sosac1932172013-10-16 13:28:53 -0700492 except error.AutoservRunError as e:
493 raise RootFSUpdateError('Rollback failed on %s: %s' %
494 (self.host.hostname, str(e)))
495
496 self._verify_update_completed()
497
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800498
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700499 # TODO(garnold) This is here for backward compatibility and should be
500 # deprecated once we shift to using update_image() everywhere.
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700501 def update_rootfs(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700502 """Run the standard command to force an update."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700503 return self.update_image()
Dale Curtis5c32c722011-05-04 19:24:23 -0700504
505
Chris Sosa72312602013-04-16 15:01:56 -0700506 def update_stateful(self, clobber=True):
507 """Updates the stateful partition.
508
509 @param clobber: If True, a clean stateful installation.
510 """
Chris Sosa77556d82012-04-05 15:23:14 -0700511 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700512 statefuldev_url = self.update_url.replace('update',
513 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700514
Dale Curtis5c32c722011-05-04 19:24:23 -0700515 # Attempt stateful partition update; this must succeed so that the newly
516 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700517 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
518 if clobber:
519 statefuldev_cmd.append('--stateful_change=clean')
520
521 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700522 try:
Dan Shi205b8732016-01-25 10:56:22 -0800523 self._run(' '.join(statefuldev_cmd), timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700524 except error.AutoservRunError:
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700525 update_error = StatefulUpdateError(
526 'Failed to perform stateful update on %s' %
527 self.host.hostname)
Chris Sosa77556d82012-04-05 15:23:14 -0700528 raise update_error
Dale Curtis5c32c722011-05-04 19:24:23 -0700529
Chris Sosae92399e2015-04-24 11:32:59 -0700530 def run_update(self, update_root=True):
Dan Shi0f466e82013-02-22 15:44:58 -0800531 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700532
Chris Sosae92399e2015-04-24 11:32:59 -0700533 @param update_root: True to force a rootfs update.
Dan Shi0f466e82013-02-22 15:44:58 -0800534 """
Dan Shi549fb822015-03-24 18:01:11 -0700535 booted_version = self.host.get_release_version()
Richard Barnette0173ea82018-05-04 21:13:57 +0000536 logging.info('Updating from version %s to %s.',
537 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700538
Dale Curtis5c32c722011-05-04 19:24:23 -0700539 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200540 # If we can't talk to it, the machine host probably can't either.
xixuanccf2e722016-06-10 16:42:38 -0700541 auserver_host = 'http://%s' % urlparse.urlparse(self.update_url)[1]
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200542 try:
xixuanccf2e722016-06-10 16:42:38 -0700543 if not dev_server.ImageServer.devserver_healthy(auserver_host):
544 raise ChromiumOSError(
545 'Update server at %s not healthy' % auserver_host)
546 except Exception as e:
547 logging.debug('Error happens in connection to devserver: %r', e)
Dale Curtis5c32c722011-05-04 19:24:23 -0700548 raise ChromiumOSError(
549 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200550
Chris Sosaa3ac2152012-05-23 22:23:13 -0700551 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700552 self.host.hostname)
553
Chris Sosa5e4246b2012-05-22 18:05:22 -0700554 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700555 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700556 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200557
Dale Curtis1e973182011-07-12 18:21:36 -0700558 try:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700559 try:
560 if not update_root:
561 logging.info('Root update is skipped.')
562 else:
563 self.update_rootfs()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200564
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700565 self.update_stateful()
566 except:
Chris Sosa77556d82012-04-05 15:23:14 -0700567 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700568 self.reset_stateful_partition()
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700569 raise
Sean Oc053dfe2010-08-23 18:22:26 +0200570
Dale Curtis1e973182011-07-12 18:21:36 -0700571 logging.info('Update complete.')
Dale Curtis1e973182011-07-12 18:21:36 -0700572 except:
573 # Collect update engine logs in the event of failure.
574 if self.host.job:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700575 logging.info('Collecting update engine logs due to failure...')
Dale Curtis1e973182011-07-12 18:21:36 -0700576 self.host.get_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700577 self.UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
578 preserve_perm=False)
Prashanth B32baa9b2014-03-13 13:23:01 -0700579 list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700580 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700581 finally:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800582 logging.info('Update engine log has downloaded in '
583 'sysinfo/update_engine dir. Check the lastest.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200584
585
Dale Curtisa94c19c2011-05-02 15:05:17 -0700586 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800587 """Check the image running in DUT has the desired version.
588
589 @returns: True if the DUT's image version matches the version that
590 the autoupdater tries to update to.
591
592 """
Dan Shi549fb822015-03-24 18:01:11 -0700593 booted_version = self.host.get_release_version()
Richard Barnette0173ea82018-05-04 21:13:57 +0000594 return self.update_version.endswith(booted_version)
Dan Shib95bb862013-03-22 16:29:28 -0700595
596
597 def check_version_to_confirm_install(self):
598 """Check image running in DUT has the desired version to be installed.
599
600 The method should not be used to check if DUT needs to have a full
601 reimage. Only use it to confirm a image is installed.
602
Dan Shi549fb822015-03-24 18:01:11 -0700603 The method is designed to verify version for following 6 scenarios with
Dan Shi190c7802013-04-04 13:05:30 -0700604 samples of version to update to and expected booted version:
605 1. trybot paladin build.
606 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
607 booted version: 3837.0.2013_03_21_1340
608
609 2. trybot release build.
610 update version: trybot-lumpy-release/R27-3837.0.0-b456
611 booted version: 3837.0.0
612
613 3. buildbot official release build.
614 update version: lumpy-release/R27-3837.0.0
615 booted version: 3837.0.0
616
617 4. non-official paladin rc build.
618 update version: lumpy-paladin/R27-3878.0.0-rc7
619 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700620
Dan Shi7f795512013-04-12 10:08:17 -0700621 5. chrome-perf build.
622 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
623 booted version: 3837.0.0
624
Dan Shi73aa2902013-05-03 11:22:11 -0700625 6. pgo-generate build.
626 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
627 booted version: 3837.0.0-pgo-generate
628
Dan Shib95bb862013-03-22 16:29:28 -0700629 When we are checking if a DUT needs to do a full install, we should NOT
630 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700631 it may return false positive for a DUT running trybot paladin build to
632 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700633
Dan Shi190c7802013-04-04 13:05:30 -0700634 TODO: This logic has a bug if a trybot paladin build failed to be
635 installed in a DUT running an older trybot paladin build with same
636 platform number, but different build number (-b###). So to conclusively
637 determine if a tryjob paladin build is imaged successfully, we may need
638 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700639
640 @returns: True if the DUT's image version (without the date string if
641 the image is a trybot build), matches the version that the
642 autoupdater is trying to update to.
643
644 """
645 # Always try the default check_version method first, this prevents
646 # any backward compatibility issue.
647 if self.check_version():
648 return True
649
Dan Shi549fb822015-03-24 18:01:11 -0700650 return utils.version_match(self.update_version,
651 self.host.get_release_version(),
652 self.update_url)
Chris Sosa65425082013-10-16 13:26:22 -0700653
654
655 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
656 """Verifies that we fully booted given expected kernel state.
657
658 This method both verifies that we booted using the correct kernel
659 state and that the OS has marked the kernel as good.
660
661 @param expected_kernel_state: kernel state that we are verifying with
662 i.e. I expect to be booted onto partition 4 etc. See output of
663 get_kernel_state.
664 @param rollback_message: string to raise as a ChromiumOSError
665 if we booted with the wrong partition.
666
667 @raises ChromiumOSError: If we didn't.
668 """
669 # Figure out the newly active kernel.
670 active_kernel_state = self.get_kernel_state()[0]
671
672 # Check for rollback due to a bad build.
673 if (expected_kernel_state and
674 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800675
676 # Kernel crash reports should be wiped between test runs, but
677 # may persist from earlier parts of the test, or from problems
678 # with provisioning.
679 #
680 # Kernel crash reports will NOT be present if the crash happened
681 # before encrypted stateful is mounted.
682 #
683 # TODO(dgarrett): Integrate with server/crashcollect.py at some
684 # point.
685 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
686 if kernel_crashes:
687 rollback_message += ': kernel_crash'
688 logging.debug('Found %d kernel crash reports:',
689 len(kernel_crashes))
690 # The crash names contain timestamps that may be useful:
691 # kernel.20131207.005945.0.kcrash
692 for crash in kernel_crashes:
Dan Shi0942b1d2015-03-31 11:07:00 -0700693 logging.debug(' %s', os.path.basename(crash))
Don Garrett56b1cc82013-12-06 17:49:20 -0800694
Chris Sosa65425082013-10-16 13:26:22 -0700695 # Print out some information to make it easier to debug
696 # the rollback.
697 logging.debug('Dumping partition table.')
698 self._run('cgpt show $(rootdev -s -d)')
699 logging.debug('Dumping crossystem for firmware debugging.')
700 self._run('crossystem --all')
701 raise ChromiumOSError(rollback_message)
702
703 # Make sure chromeos-setgoodkernel runs.
704 try:
705 utils.poll_for_condition(
706 lambda: (self.get_kernel_tries(active_kernel_state) == 0
707 and self.get_kernel_success(active_kernel_state)),
708 exception=ChromiumOSError(),
709 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
710 except ChromiumOSError:
711 services_status = self._run('status system-services').stdout
712 if services_status != 'system-services start/running\n':
713 event = ('Chrome failed to reach login screen')
714 else:
715 event = ('update-engine failed to call '
716 'chromeos-setgoodkernel')
717 raise ChromiumOSError(
718 'After update and reboot, %s '
719 'within %d seconds' % (event,
720 self.KERNEL_UPDATE_TIMEOUT))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700721