blob: 91e45b5e76245dcbf5c3a4772603d74ac1d76673 [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 httplib
7import logging
Dale Curtis5c32c722011-05-04 19:24:23 -07008import os
Sean O'Connor5346e4e2010-08-12 18:49:24 +02009import re
Sean O'Connor5346e4e2010-08-12 18:49:24 +020010import urlparse
Prashanth B32baa9b2014-03-13 13:23:01 -070011import urllib2
Sean O'Connor5346e4e2010-08-12 18:49:24 +020012
Chris Sosa65425082013-10-16 13:26:22 -070013from autotest_lib.client.bin import utils
Dale Curtis5c32c722011-05-04 19:24:23 -070014from autotest_lib.client.common_lib import error, global_config
Prashanth B32baa9b2014-03-13 13:23:01 -070015from autotest_lib.client.common_lib.cros import dev_server
Dan Shif3a35f72016-01-25 11:18:14 -080016from autotest_lib.client.common_lib.cros.graphite import autotest_stats
17
Sean O'Connor5346e4e2010-08-12 18:49:24 +020018
Dale Curtis5c32c722011-05-04 19:24:23 -070019# Local stateful update path is relative to the CrOS source directory.
20LOCAL_STATEFUL_UPDATE_PATH = 'src/platform/dev/stateful_update'
Chris Sosaa3ac2152012-05-23 22:23:13 -070021LOCAL_CHROOT_STATEFUL_UPDATE_PATH = '/usr/bin/stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020022UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020023UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
beeps5e8c45a2013-12-17 22:05:11 -080024# A list of update engine client states that occur after an update is triggered.
25UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FORUPDATE',
26 'UPDATE_STATUS_UPDATE_AVAILABLE',
27 'UPDATE_STATUS_DOWNLOADING',
28 'UPDATE_STATUS_FINALIZING']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020029
30class ChromiumOSError(error.InstallError):
31 """Generic error for ChromiumOS-specific exceptions."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -070032
33
34class BrilloError(error.InstallError):
35 """Generic error for Brillo-specific exceptions."""
Sean O'Connor5346e4e2010-08-12 18:49:24 +020036
37
Chris Sosa77556d82012-04-05 15:23:14 -070038class RootFSUpdateError(ChromiumOSError):
39 """Raised when the RootFS fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070040
41
42class StatefulUpdateError(ChromiumOSError):
43 """Raised when the stateful partition fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070044
45
Sean O'Connor5346e4e2010-08-12 18:49:24 +020046def url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -080047 """Return the version based on update_url.
48
49 @param update_url: url to the image to update to.
50
51 """
Dale Curtisddfdb942011-07-14 13:59:24 -070052 # The Chrome OS version is generally the last element in the URL. The only
53 # exception is delta update URLs, which are rooted under the version; e.g.,
54 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
55 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -070056 return re.sub('/au/.*', '',
57 urlparse.urlparse(update_url).path).split('/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +020058
59
Scott Zawalskieadbf702013-03-14 09:23:06 -040060def url_to_image_name(update_url):
61 """Return the image name based on update_url.
62
63 From a URL like:
64 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
65 return lumpy-release/R27-3837.0.0
66
67 @param update_url: url to the image to update to.
68 @returns a string representing the image name in the update_url.
69
70 """
71 return '/'.join(urlparse.urlparse(update_url).path.split('/')[-2:])
72
73
Prashanth B32baa9b2014-03-13 13:23:01 -070074def _get_devserver_build_from_update_url(update_url):
75 """Get the devserver and build from the update url.
76
77 @param update_url: The url for update.
78 Eg: http://devserver:port/update/build.
79
80 @return: A tuple of (devserver url, build) or None if the update_url
81 doesn't match the expected pattern.
82
83 @raises ValueError: If the update_url doesn't match the expected pattern.
84 @raises ValueError: If no global_config was found, or it doesn't contain an
85 image_url_pattern.
86 """
87 pattern = global_config.global_config.get_config_value(
88 'CROS', 'image_url_pattern', type=str, default='')
89 if not pattern:
90 raise ValueError('Cannot parse update_url, the global config needs '
91 'an image_url_pattern.')
92 re_pattern = pattern.replace('%s', '(\S+)')
93 parts = re.search(re_pattern, update_url)
94 if not parts or len(parts.groups()) < 2:
95 raise ValueError('%s is not an update url' % update_url)
96 return parts.groups()
97
98
99def list_image_dir_contents(update_url):
100 """Lists the contents of the devserver for a given build/update_url.
101
102 @param update_url: An update url. Eg: http://devserver:port/update/build.
103 """
104 if not update_url:
105 logging.warning('Need update_url to list contents of the devserver.')
106 return
107 error_msg = 'Cannot check contents of devserver, update url %s' % update_url
108 try:
109 devserver_url, build = _get_devserver_build_from_update_url(update_url)
110 except ValueError as e:
111 logging.warning('%s: %s', error_msg, e)
112 return
113 devserver = dev_server.ImageServer(devserver_url)
114 try:
115 devserver.list_image_dir(build)
116 # The devserver will retry on URLError to avoid flaky connections, but will
117 # eventually raise the URLError if it persists. All HTTPErrors get
118 # converted to DevServerExceptions.
119 except (dev_server.DevServerException, urllib2.URLError) as e:
120 logging.warning('%s: %s', error_msg, e)
121
122
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700123# TODO(garnold) This implements shared updater functionality needed for
124# supporting the autoupdate_EndToEnd server-side test. We should probably
125# migrate more of the existing ChromiumOSUpdater functionality to it as we
126# expand non-CrOS support in other tests.
127class BaseUpdater(object):
128 """Platform-agnostic DUT update functionality."""
129
130 def __init__(self, updater_ctrl_bin, update_url, host):
131 """Initializes the object.
132
133 @param updater_ctrl_bin: Path to update_engine_client.
134 @param update_url: The URL we want the update to use.
135 @param host: A client.common_lib.hosts.Host implementation.
136 """
137 self.updater_ctrl_bin = updater_ctrl_bin
138 self.update_url = update_url
139 self.host = host
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700140
141
142 def check_update_status(self):
143 """Returns the current update engine state.
144
145 We use the `update_engine_client -status' command and parse the line
146 indicating the update state, e.g. "CURRENT_OP=UPDATE_STATUS_IDLE".
147 """
148 update_status = self.host.run(
149 '%s -status 2>&1 | grep CURRENT_OP' % self.updater_ctrl_bin)
150 return update_status.stdout.strip().split('=')[-1]
151
152
Shuqian Zhaod9992722016-02-29 12:26:38 -0800153 def get_last_update_error(self):
154 """Get the last autoupdate error code."""
155 error_msg = self.host.run(
156 '%s --last_attempt_error' % self.updater_ctrl_bin)
157 error_msg = (error_msg.stdout.strip()).replace('\n', ', ')
158 return error_msg
159
160
161 def _base_update_handler(self, run_args, err_msg_prefix=None):
162 """Base function to handle a remote update ssh call.
163
164 @param run_args: Dictionary of args passed to ssh_host.run function.
165 @param err_msg_prefix: Prefix of the exception error message.
166
167 @returns: The exception thrown, None if no exception.
168 """
169 to_raise = None
170 err_msg = err_msg_prefix
171 try:
172 self.host.run(**run_args)
173 except (error.AutoservSshPermissionDeniedError,
174 error.AutoservSSHTimeout) as e:
Shuqian Zhaoc2a15eb2016-03-31 14:03:27 -0700175 logging.exception(e)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800176 err_msg += 'SSH reports an error: %s' % type(e).__name__
177 to_raise = RootFSUpdateError(err_msg)
178 except error.AutoservRunError as e:
Shuqian Zhaoc2a15eb2016-03-31 14:03:27 -0700179 logging.exception(e)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800180 # Check if exit code is 255, if so it's probably a generic SSH error
181 result = e.args[1]
182 if result.exit_status == 255:
183 err_msg += ('SSH reports a generic error (255) which is '
184 'probably a lab network failure')
185 to_raise = RootFSUpdateError(err_msg)
186
187 # We have ruled out all SSH cases, the error code is from
188 # update_engine_client.
189 else:
190 list_image_dir_contents(self.update_url)
191 err_msg += ('Update failed. Returned update_engine error code: '
192 '%s. Reported error: %s' %
193 (self.get_last_update_error(), type(e).__name__))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800194 to_raise = RootFSUpdateError(err_msg)
195 except Exception as e:
196 to_raise = e
197
198 return to_raise
199
200
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700201 def trigger_update(self):
202 """Triggers a background update.
203
Shuqian Zhaod9992722016-02-29 12:26:38 -0800204 @raise RootFSUpdateError or unknown Exception if anything went wrong.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700205 """
206 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
207 (self.updater_ctrl_bin, self.update_url))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800208 run_args = {'command': autoupdate_cmd}
209 err_prefix = 'Failed to trigger an update on %s. ' % self.host.hostname
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700210 logging.info('Triggering update via: %s', autoupdate_cmd)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800211 to_raise = self._base_update_handler(run_args, err_prefix)
212 if to_raise:
213 raise to_raise
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700214
215
216 def _verify_update_completed(self):
217 """Verifies that an update has completed.
218
219 @raise RootFSUpdateError: if verification fails.
220 """
221 status = self.check_update_status()
222 if status != UPDATER_NEED_REBOOT:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800223 error_msg = ''
224 if status == UPDATER_IDLE:
225 error_msg = 'Update error: %s' % self.get_last_update_error()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700226 raise RootFSUpdateError('Update did not complete with correct '
Shuqian Zhaod9992722016-02-29 12:26:38 -0800227 'status. Expecting %s, actual %s. %s' %
228 (UPDATER_NEED_REBOOT, status, error_msg))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700229
230
231 def update_image(self):
232 """Updates the device image and verifies success."""
Shuqian Zhaod9992722016-02-29 12:26:38 -0800233 autoupdate_cmd = ('%s --update --omaha_url=%s 2>&1' %
234 (self.updater_ctrl_bin, self.update_url))
235 run_args = {'command': autoupdate_cmd, 'timeout': 3600}
236 err_prefix = ('Failed to install device image using payload at %s '
237 'on %s. ' % (self.update_url, self.host.hostname))
238 logging.info('Updating image via: %s', autoupdate_cmd)
239 to_raise = self._base_update_handler(run_args, err_prefix)
240 if to_raise:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800241 raise to_raise
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700242
Aviv Keshetf37b2d72016-06-01 19:27:59 -0700243 self._verify_update_completed()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700244
245
246class ChromiumOSUpdater(BaseUpdater):
Dan Shi0f466e82013-02-22 15:44:58 -0800247 """Helper class used to update DUT with image of desired version."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700248 REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update'
249 UPDATER_BIN = '/usr/bin/update_engine_client'
250 STATEFUL_UPDATE = '/tmp/stateful_update'
251 UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
252 UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
253
Dale Curtisa94c19c2011-05-02 15:05:17 -0700254 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
255 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
Chris Sosa65425082013-10-16 13:26:22 -0700256 # Time to wait for new kernel to be marked successful after
257 # auto update.
258 KERNEL_UPDATE_TIMEOUT = 120
Dale Curtisa94c19c2011-05-02 15:05:17 -0700259
Dan Shif3a35f72016-01-25 11:18:14 -0800260 _timer = autotest_stats.Timer('cros_autoupdater')
Dale Curtisa94c19c2011-05-02 15:05:17 -0700261
Chris Sosaa3ac2152012-05-23 22:23:13 -0700262 def __init__(self, update_url, host=None, local_devserver=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700263 super(ChromiumOSUpdater, self).__init__(self.UPDATER_BIN, update_url,
264 host)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700265 self.local_devserver = local_devserver
266 if not local_devserver:
Dan Shif3a35f72016-01-25 11:18:14 -0800267 self.update_version = url_to_version(update_url)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700268 else:
Dan Shif3a35f72016-01-25 11:18:14 -0800269 self.update_version = None
Sean Oc053dfe2010-08-23 18:22:26 +0200270
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700271
Sean Oc053dfe2010-08-23 18:22:26 +0200272 def reset_update_engine(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700273 """Resets the host to prepare for a clean update regardless of state."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700274 self._run('rm -f %s' % self.UPDATED_MARKER)
Chris Sosae92399e2015-04-24 11:32:59 -0700275 self._run('stop ui || true')
276 self._run('stop update-engine || true')
277 self._run('start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700278
Sean Oc053dfe2010-08-23 18:22:26 +0200279 if self.check_update_status() != UPDATER_IDLE:
280 raise ChromiumOSError('%s is not in an installable state' %
281 self.host.hostname)
282
283
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200284 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700285 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200286 return self.host.run(cmd, *args, **kwargs)
287
Sean Oc053dfe2010-08-23 18:22:26 +0200288
Dale Curtisa94c19c2011-05-02 15:05:17 -0700289 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800290 """Returns the stripped output of rootdev <options>.
291
292 @param options: options to run rootdev.
293
294 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700295 return self._run('rootdev %s' % options).stdout.strip()
296
297
298 def get_kernel_state(self):
299 """Returns the (<active>, <inactive>) kernel state as a pair."""
300 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
301 if active_root == self.KERNEL_A['root']:
302 return self.KERNEL_A, self.KERNEL_B
303 elif active_root == self.KERNEL_B['root']:
304 return self.KERNEL_B, self.KERNEL_A
305 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700306 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700307 active_root)
308
309
310 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
311 """Return numeric cgpt value for the specified flag, kernel, device. """
312 return int(self._run('cgpt show -n -i %d %s %s' % (
313 kernel['kernel'], flag, dev)).stdout.strip())
314
315
316 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800317 """Return numeric priority for the specified kernel.
318
319 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
320
321 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700322 return self._cgpt('-P', kernel)
323
324
325 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800326 """Return boolean success flag for the specified kernel.
327
328 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
329
330 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700331 return self._cgpt('-S', kernel) != 0
332
333
334 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800335 """Return tries count for the specified kernel.
336
337 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
338
339 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700340 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200341
342
Chris Sosa5e4246b2012-05-22 18:05:22 -0700343 def get_stateful_update_script(self):
344 """Returns the path to the stateful update script on the target."""
Chris Sosaa3ac2152012-05-23 22:23:13 -0700345 # We attempt to load the local stateful update path in 3 different
346 # ways. First we use the location specified in the autotest global
347 # config. If this doesn't exist, we attempt to use the Chromium OS
348 # Chroot path to the installed script. If all else fails, we use the
349 # stateful update script on the host.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700350 stateful_update_path = os.path.join(
351 global_config.global_config.get_config_value(
352 'CROS', 'source_tree', default=''),
353 LOCAL_STATEFUL_UPDATE_PATH)
354
Chris Sosaa3ac2152012-05-23 22:23:13 -0700355 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700356 logging.warning('Could not find Chrome OS source location for '
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700357 'stateful_update script at %s, falling back to '
358 'chroot copy.', stateful_update_path)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700359 stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
360
361 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700362 logging.warning('Could not chroot stateful_update script, falling '
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700363 'back on client copy.')
Gilad Arnold0c0df732015-09-21 06:37:59 -0700364 statefuldev_script = self.REMOTE_STATEUL_UPDATE_PATH
Chris Sosaa3ac2152012-05-23 22:23:13 -0700365 else:
Chris Sosa5e4246b2012-05-22 18:05:22 -0700366 self.host.send_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700367 stateful_update_path, self.STATEFUL_UPDATE,
368 delete_dest=True)
369 statefuldev_script = self.STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700370
371 return statefuldev_script
372
373
374 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800375 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700376 statefuldev_cmd = [self.get_stateful_update_script()]
377 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700378 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700379
380
Sean O267c00b2010-08-31 15:54:55 +0200381 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800382 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700383 part = self.rootdev('-s')
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700384 logging.warning('Reverting update; Boot partition will be %s', part)
Sean O267c00b2010-08-31 15:54:55 +0200385 return self._run('/postinst %s 2>&1' % part)
386
387
Chris Sosac1932172013-10-16 13:28:53 -0700388 def rollback_rootfs(self, powerwash):
389 """Triggers rollback and waits for it to complete.
390
391 @param powerwash: If true, powerwash as part of rollback.
392
393 @raise RootFSUpdateError if anything went wrong.
394
395 """
Dan Shi549fb822015-03-24 18:01:11 -0700396 version = self.host.get_release_version()
Chris Sosac8617522014-06-09 23:22:26 +0000397 # Introduced can_rollback in M36 (build 5772). # etc/lsb-release matches
398 # X.Y.Z. This version split just pulls the first part out.
399 try:
400 build_number = int(version.split('.')[0])
401 except ValueError:
402 logging.error('Could not parse build number.')
403 build_number = 0
404
405 if build_number >= 5772:
Gilad Arnold0c0df732015-09-21 06:37:59 -0700406 can_rollback_cmd = '%s --can_rollback' % self.UPDATER_BIN
Chris Sosac8617522014-06-09 23:22:26 +0000407 logging.info('Checking for rollback.')
408 try:
409 self._run(can_rollback_cmd)
410 except error.AutoservRunError as e:
411 raise RootFSUpdateError("Rollback isn't possible on %s: %s" %
412 (self.host.hostname, str(e)))
413
Gilad Arnold0c0df732015-09-21 06:37:59 -0700414 rollback_cmd = '%s --rollback --follow' % self.UPDATER_BIN
Chris Sosac1932172013-10-16 13:28:53 -0700415 if not powerwash:
Dan Shif3a35f72016-01-25 11:18:14 -0800416 rollback_cmd += ' --nopowerwash'
Chris Sosac1932172013-10-16 13:28:53 -0700417
Chris Sosac8617522014-06-09 23:22:26 +0000418 logging.info('Performing rollback.')
Chris Sosac1932172013-10-16 13:28:53 -0700419 try:
420 self._run(rollback_cmd)
Chris Sosac1932172013-10-16 13:28:53 -0700421 except error.AutoservRunError as e:
422 raise RootFSUpdateError('Rollback failed on %s: %s' %
423 (self.host.hostname, str(e)))
424
425 self._verify_update_completed()
426
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800427
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700428 # TODO(garnold) This is here for backward compatibility and should be
429 # deprecated once we shift to using update_image() everywhere.
Dan Shif3a35f72016-01-25 11:18:14 -0800430 @_timer.decorate
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700431 def update_rootfs(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700432 """Run the standard command to force an update."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700433 return self.update_image()
Dale Curtis5c32c722011-05-04 19:24:23 -0700434
435
Dan Shif3a35f72016-01-25 11:18:14 -0800436 @_timer.decorate
Chris Sosa72312602013-04-16 15:01:56 -0700437 def update_stateful(self, clobber=True):
438 """Updates the stateful partition.
439
440 @param clobber: If True, a clean stateful installation.
441 """
Chris Sosa77556d82012-04-05 15:23:14 -0700442 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700443 statefuldev_url = self.update_url.replace('update',
444 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700445
Dale Curtis5c32c722011-05-04 19:24:23 -0700446 # Attempt stateful partition update; this must succeed so that the newly
447 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700448 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
449 if clobber:
450 statefuldev_cmd.append('--stateful_change=clean')
451
452 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700453 try:
Dan Shi205b8732016-01-25 10:56:22 -0800454 self._run(' '.join(statefuldev_cmd), timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700455 except error.AutoservRunError:
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700456 update_error = StatefulUpdateError(
457 'Failed to perform stateful update on %s' %
458 self.host.hostname)
Chris Sosa77556d82012-04-05 15:23:14 -0700459 raise update_error
Dale Curtis5c32c722011-05-04 19:24:23 -0700460
Dan Shif3a35f72016-01-25 11:18:14 -0800461 @_timer.decorate
Chris Sosae92399e2015-04-24 11:32:59 -0700462 def run_update(self, update_root=True):
Dan Shi0f466e82013-02-22 15:44:58 -0800463 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700464
Chris Sosae92399e2015-04-24 11:32:59 -0700465 @param update_root: True to force a rootfs update.
Dan Shi0f466e82013-02-22 15:44:58 -0800466 """
Dan Shi549fb822015-03-24 18:01:11 -0700467 booted_version = self.host.get_release_version()
Chris Sosaa3ac2152012-05-23 22:23:13 -0700468 if self.update_version:
469 logging.info('Updating from version %s to %s.',
470 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700471
Dale Curtis5c32c722011-05-04 19:24:23 -0700472 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200473 # If we can't talk to it, the machine host probably can't either.
474 auserver_host = urlparse.urlparse(self.update_url)[1]
475 try:
476 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700477 except IOError:
478 raise ChromiumOSError(
479 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200480
Chris Sosaa3ac2152012-05-23 22:23:13 -0700481 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700482 self.host.hostname)
483
Chris Sosa5e4246b2012-05-22 18:05:22 -0700484 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700485 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700486 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200487
Dale Curtis1e973182011-07-12 18:21:36 -0700488 try:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700489 try:
490 if not update_root:
491 logging.info('Root update is skipped.')
492 else:
493 self.update_rootfs()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200494
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700495 self.update_stateful()
496 except:
Chris Sosa77556d82012-04-05 15:23:14 -0700497 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700498 self.reset_stateful_partition()
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700499 raise
Sean Oc053dfe2010-08-23 18:22:26 +0200500
Dale Curtis1e973182011-07-12 18:21:36 -0700501 logging.info('Update complete.')
Dale Curtis1e973182011-07-12 18:21:36 -0700502 except:
503 # Collect update engine logs in the event of failure.
504 if self.host.job:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700505 logging.info('Collecting update engine logs due to failure...')
Dale Curtis1e973182011-07-12 18:21:36 -0700506 self.host.get_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700507 self.UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
508 preserve_perm=False)
Prashanth B32baa9b2014-03-13 13:23:01 -0700509 list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700510 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700511 finally:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800512 logging.info('Update engine log has downloaded in '
513 'sysinfo/update_engine dir. Check the lastest.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200514
515
Dale Curtisa94c19c2011-05-02 15:05:17 -0700516 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800517 """Check the image running in DUT has the desired version.
518
519 @returns: True if the DUT's image version matches the version that
520 the autoupdater tries to update to.
521
522 """
Dan Shi549fb822015-03-24 18:01:11 -0700523 booted_version = self.host.get_release_version()
Dan Shib95bb862013-03-22 16:29:28 -0700524 return (self.update_version and
525 self.update_version.endswith(booted_version))
526
527
528 def check_version_to_confirm_install(self):
529 """Check image running in DUT has the desired version to be installed.
530
531 The method should not be used to check if DUT needs to have a full
532 reimage. Only use it to confirm a image is installed.
533
Dan Shi549fb822015-03-24 18:01:11 -0700534 The method is designed to verify version for following 6 scenarios with
Dan Shi190c7802013-04-04 13:05:30 -0700535 samples of version to update to and expected booted version:
536 1. trybot paladin build.
537 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
538 booted version: 3837.0.2013_03_21_1340
539
540 2. trybot release build.
541 update version: trybot-lumpy-release/R27-3837.0.0-b456
542 booted version: 3837.0.0
543
544 3. buildbot official release build.
545 update version: lumpy-release/R27-3837.0.0
546 booted version: 3837.0.0
547
548 4. non-official paladin rc build.
549 update version: lumpy-paladin/R27-3878.0.0-rc7
550 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700551
Dan Shi7f795512013-04-12 10:08:17 -0700552 5. chrome-perf build.
553 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
554 booted version: 3837.0.0
555
Dan Shi73aa2902013-05-03 11:22:11 -0700556 6. pgo-generate build.
557 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
558 booted version: 3837.0.0-pgo-generate
559
Dan Shib95bb862013-03-22 16:29:28 -0700560 When we are checking if a DUT needs to do a full install, we should NOT
561 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700562 it may return false positive for a DUT running trybot paladin build to
563 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700564
Dan Shi190c7802013-04-04 13:05:30 -0700565 TODO: This logic has a bug if a trybot paladin build failed to be
566 installed in a DUT running an older trybot paladin build with same
567 platform number, but different build number (-b###). So to conclusively
568 determine if a tryjob paladin build is imaged successfully, we may need
569 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700570
571 @returns: True if the DUT's image version (without the date string if
572 the image is a trybot build), matches the version that the
573 autoupdater is trying to update to.
574
575 """
J. Richard Barnetteec1de422013-06-26 15:44:07 -0700576 # In the local_devserver case, we can't know the expected
577 # build, so just pass.
578 if not self.update_version:
579 return True
580
Dan Shib95bb862013-03-22 16:29:28 -0700581 # Always try the default check_version method first, this prevents
582 # any backward compatibility issue.
583 if self.check_version():
584 return True
585
Dan Shi549fb822015-03-24 18:01:11 -0700586 return utils.version_match(self.update_version,
587 self.host.get_release_version(),
588 self.update_url)
Chris Sosa65425082013-10-16 13:26:22 -0700589
590
591 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
592 """Verifies that we fully booted given expected kernel state.
593
594 This method both verifies that we booted using the correct kernel
595 state and that the OS has marked the kernel as good.
596
597 @param expected_kernel_state: kernel state that we are verifying with
598 i.e. I expect to be booted onto partition 4 etc. See output of
599 get_kernel_state.
600 @param rollback_message: string to raise as a ChromiumOSError
601 if we booted with the wrong partition.
602
603 @raises ChromiumOSError: If we didn't.
604 """
605 # Figure out the newly active kernel.
606 active_kernel_state = self.get_kernel_state()[0]
607
608 # Check for rollback due to a bad build.
609 if (expected_kernel_state and
610 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800611
612 # Kernel crash reports should be wiped between test runs, but
613 # may persist from earlier parts of the test, or from problems
614 # with provisioning.
615 #
616 # Kernel crash reports will NOT be present if the crash happened
617 # before encrypted stateful is mounted.
618 #
619 # TODO(dgarrett): Integrate with server/crashcollect.py at some
620 # point.
621 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
622 if kernel_crashes:
623 rollback_message += ': kernel_crash'
624 logging.debug('Found %d kernel crash reports:',
625 len(kernel_crashes))
626 # The crash names contain timestamps that may be useful:
627 # kernel.20131207.005945.0.kcrash
628 for crash in kernel_crashes:
Dan Shi0942b1d2015-03-31 11:07:00 -0700629 logging.debug(' %s', os.path.basename(crash))
Don Garrett56b1cc82013-12-06 17:49:20 -0800630
Chris Sosa65425082013-10-16 13:26:22 -0700631 # Print out some information to make it easier to debug
632 # the rollback.
633 logging.debug('Dumping partition table.')
634 self._run('cgpt show $(rootdev -s -d)')
635 logging.debug('Dumping crossystem for firmware debugging.')
636 self._run('crossystem --all')
637 raise ChromiumOSError(rollback_message)
638
639 # Make sure chromeos-setgoodkernel runs.
640 try:
641 utils.poll_for_condition(
642 lambda: (self.get_kernel_tries(active_kernel_state) == 0
643 and self.get_kernel_success(active_kernel_state)),
644 exception=ChromiumOSError(),
645 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
646 except ChromiumOSError:
647 services_status = self._run('status system-services').stdout
648 if services_status != 'system-services start/running\n':
649 event = ('Chrome failed to reach login screen')
650 else:
651 event = ('update-engine failed to call '
652 'chromeos-setgoodkernel')
653 raise ChromiumOSError(
654 'After update and reboot, %s '
655 'within %d seconds' % (event,
656 self.KERNEL_UPDATE_TIMEOUT))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700657
658
659class BrilloUpdater(BaseUpdater):
660 """Helper class for updating a Brillo DUT."""
661
662 def __init__(self, update_url, host=None):
663 """Initialize the object.
664
665 @param update_url: The URL we want the update to use.
666 @param host: A client.common_lib.hosts.Host implementation.
667 """
Gilad Arnoldecf894b2015-10-07 08:48:22 -0700668 super(BrilloUpdater, self).__init__(
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700669 '/system/bin/update_engine_client', update_url, host)