blob: f2d89c6f0587896a0c1a9cc1fb15bb535f026acb [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
Chris Sosa77556d82012-04-05 15:23:14 -07008import multiprocessing
Dale Curtis5c32c722011-05-04 19:24:23 -07009import os
Sean O'Connor5346e4e2010-08-12 18:49:24 +020010import re
Sean O'Connor5346e4e2010-08-12 18:49:24 +020011import urlparse
Prashanth B32baa9b2014-03-13 13:23:01 -070012import urllib2
Sean O'Connor5346e4e2010-08-12 18:49:24 +020013
Chris Sosa65425082013-10-16 13:26:22 -070014from autotest_lib.client.bin import utils
Dale Curtis5c32c722011-05-04 19:24:23 -070015from autotest_lib.client.common_lib import error, global_config
Prashanth B32baa9b2014-03-13 13:23:01 -070016from autotest_lib.client.common_lib.cros import dev_server
Dan Shif3a35f72016-01-25 11:18:14 -080017from autotest_lib.client.common_lib.cros.graphite import autotest_stats
18
Sean O'Connor5346e4e2010-08-12 18:49:24 +020019
Dale Curtis5c32c722011-05-04 19:24:23 -070020# Local stateful update path is relative to the CrOS source directory.
21LOCAL_STATEFUL_UPDATE_PATH = 'src/platform/dev/stateful_update'
Chris Sosaa3ac2152012-05-23 22:23:13 -070022LOCAL_CHROOT_STATEFUL_UPDATE_PATH = '/usr/bin/stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020023UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020024UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
beeps5e8c45a2013-12-17 22:05:11 -080025# A list of update engine client states that occur after an update is triggered.
26UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FORUPDATE',
27 'UPDATE_STATUS_UPDATE_AVAILABLE',
28 'UPDATE_STATUS_DOWNLOADING',
29 'UPDATE_STATUS_FINALIZING']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020030
31class ChromiumOSError(error.InstallError):
32 """Generic error for ChromiumOS-specific exceptions."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -070033
34
35class BrilloError(error.InstallError):
36 """Generic error for Brillo-specific exceptions."""
Sean O'Connor5346e4e2010-08-12 18:49:24 +020037
38
Chris Sosa77556d82012-04-05 15:23:14 -070039class RootFSUpdateError(ChromiumOSError):
40 """Raised when the RootFS fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070041
42
43class StatefulUpdateError(ChromiumOSError):
44 """Raised when the stateful partition fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070045
46
Sean O'Connor5346e4e2010-08-12 18:49:24 +020047def url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -080048 """Return the version based on update_url.
49
50 @param update_url: url to the image to update to.
51
52 """
Dale Curtisddfdb942011-07-14 13:59:24 -070053 # The Chrome OS version is generally the last element in the URL. The only
54 # exception is delta update URLs, which are rooted under the version; e.g.,
55 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
56 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -070057 return re.sub('/au/.*', '',
58 urlparse.urlparse(update_url).path).split('/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +020059
60
Scott Zawalskieadbf702013-03-14 09:23:06 -040061def url_to_image_name(update_url):
62 """Return the image name based on update_url.
63
64 From a URL like:
65 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
66 return lumpy-release/R27-3837.0.0
67
68 @param update_url: url to the image to update to.
69 @returns a string representing the image name in the update_url.
70
71 """
72 return '/'.join(urlparse.urlparse(update_url).path.split('/')[-2:])
73
74
Prashanth B32baa9b2014-03-13 13:23:01 -070075def _get_devserver_build_from_update_url(update_url):
76 """Get the devserver and build from the update url.
77
78 @param update_url: The url for update.
79 Eg: http://devserver:port/update/build.
80
81 @return: A tuple of (devserver url, build) or None if the update_url
82 doesn't match the expected pattern.
83
84 @raises ValueError: If the update_url doesn't match the expected pattern.
85 @raises ValueError: If no global_config was found, or it doesn't contain an
86 image_url_pattern.
87 """
88 pattern = global_config.global_config.get_config_value(
89 'CROS', 'image_url_pattern', type=str, default='')
90 if not pattern:
91 raise ValueError('Cannot parse update_url, the global config needs '
92 'an image_url_pattern.')
93 re_pattern = pattern.replace('%s', '(\S+)')
94 parts = re.search(re_pattern, update_url)
95 if not parts or len(parts.groups()) < 2:
96 raise ValueError('%s is not an update url' % update_url)
97 return parts.groups()
98
99
100def list_image_dir_contents(update_url):
101 """Lists the contents of the devserver for a given build/update_url.
102
103 @param update_url: An update url. Eg: http://devserver:port/update/build.
104 """
105 if not update_url:
106 logging.warning('Need update_url to list contents of the devserver.')
107 return
108 error_msg = 'Cannot check contents of devserver, update url %s' % update_url
109 try:
110 devserver_url, build = _get_devserver_build_from_update_url(update_url)
111 except ValueError as e:
112 logging.warning('%s: %s', error_msg, e)
113 return
114 devserver = dev_server.ImageServer(devserver_url)
115 try:
116 devserver.list_image_dir(build)
117 # The devserver will retry on URLError to avoid flaky connections, but will
118 # eventually raise the URLError if it persists. All HTTPErrors get
119 # converted to DevServerExceptions.
120 except (dev_server.DevServerException, urllib2.URLError) as e:
121 logging.warning('%s: %s', error_msg, e)
122
123
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700124# TODO(garnold) This implements shared updater functionality needed for
125# supporting the autoupdate_EndToEnd server-side test. We should probably
126# migrate more of the existing ChromiumOSUpdater functionality to it as we
127# expand non-CrOS support in other tests.
128class BaseUpdater(object):
129 """Platform-agnostic DUT update functionality."""
130
131 def __init__(self, updater_ctrl_bin, update_url, host):
132 """Initializes the object.
133
134 @param updater_ctrl_bin: Path to update_engine_client.
135 @param update_url: The URL we want the update to use.
136 @param host: A client.common_lib.hosts.Host implementation.
137 """
138 self.updater_ctrl_bin = updater_ctrl_bin
139 self.update_url = update_url
140 self.host = host
141 self._update_error_queue = multiprocessing.Queue(2)
142
143
144 def check_update_status(self):
145 """Returns the current update engine state.
146
147 We use the `update_engine_client -status' command and parse the line
148 indicating the update state, e.g. "CURRENT_OP=UPDATE_STATUS_IDLE".
149 """
150 update_status = self.host.run(
151 '%s -status 2>&1 | grep CURRENT_OP' % self.updater_ctrl_bin)
152 return update_status.stdout.strip().split('=')[-1]
153
154
Shuqian Zhaod9992722016-02-29 12:26:38 -0800155 def get_last_update_error(self):
156 """Get the last autoupdate error code."""
157 error_msg = self.host.run(
158 '%s --last_attempt_error' % self.updater_ctrl_bin)
159 error_msg = (error_msg.stdout.strip()).replace('\n', ', ')
160 return error_msg
161
162
163 def _base_update_handler(self, run_args, err_msg_prefix=None):
164 """Base function to handle a remote update ssh call.
165
166 @param run_args: Dictionary of args passed to ssh_host.run function.
167 @param err_msg_prefix: Prefix of the exception error message.
168
169 @returns: The exception thrown, None if no exception.
170 """
171 to_raise = None
172 err_msg = err_msg_prefix
173 try:
174 self.host.run(**run_args)
175 except (error.AutoservSshPermissionDeniedError,
176 error.AutoservSSHTimeout) as e:
Shuqian Zhaoc2a15eb2016-03-31 14:03:27 -0700177 logging.exception(e)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800178 err_msg += 'SSH reports an error: %s' % type(e).__name__
179 to_raise = RootFSUpdateError(err_msg)
180 except error.AutoservRunError as e:
Shuqian Zhaoc2a15eb2016-03-31 14:03:27 -0700181 logging.exception(e)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800182 # Check if exit code is 255, if so it's probably a generic SSH error
183 result = e.args[1]
184 if result.exit_status == 255:
185 err_msg += ('SSH reports a generic error (255) which is '
186 'probably a lab network failure')
187 to_raise = RootFSUpdateError(err_msg)
188
189 # We have ruled out all SSH cases, the error code is from
190 # update_engine_client.
191 else:
192 list_image_dir_contents(self.update_url)
193 err_msg += ('Update failed. Returned update_engine error code: '
194 '%s. Reported error: %s' %
195 (self.get_last_update_error(), type(e).__name__))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800196 to_raise = RootFSUpdateError(err_msg)
197 except Exception as e:
198 to_raise = e
199
200 return to_raise
201
202
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700203 def trigger_update(self):
204 """Triggers a background update.
205
Shuqian Zhaod9992722016-02-29 12:26:38 -0800206 @raise RootFSUpdateError or unknown Exception if anything went wrong.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700207 """
208 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
209 (self.updater_ctrl_bin, self.update_url))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800210 run_args = {'command': autoupdate_cmd}
211 err_prefix = 'Failed to trigger an update on %s. ' % self.host.hostname
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700212 logging.info('Triggering update via: %s', autoupdate_cmd)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800213 to_raise = self._base_update_handler(run_args, err_prefix)
214 if to_raise:
215 raise to_raise
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700216
217
218 def _verify_update_completed(self):
219 """Verifies that an update has completed.
220
221 @raise RootFSUpdateError: if verification fails.
222 """
223 status = self.check_update_status()
224 if status != UPDATER_NEED_REBOOT:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800225 error_msg = ''
226 if status == UPDATER_IDLE:
227 error_msg = 'Update error: %s' % self.get_last_update_error()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700228 raise RootFSUpdateError('Update did not complete with correct '
Shuqian Zhaod9992722016-02-29 12:26:38 -0800229 'status. Expecting %s, actual %s. %s' %
230 (UPDATER_NEED_REBOOT, status, error_msg))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700231
232
233 def update_image(self):
234 """Updates the device image and verifies success."""
Shuqian Zhaod9992722016-02-29 12:26:38 -0800235 autoupdate_cmd = ('%s --update --omaha_url=%s 2>&1' %
236 (self.updater_ctrl_bin, self.update_url))
237 run_args = {'command': autoupdate_cmd, 'timeout': 3600}
238 err_prefix = ('Failed to install device image using payload at %s '
239 'on %s. ' % (self.update_url, self.host.hostname))
240 logging.info('Updating image via: %s', autoupdate_cmd)
241 to_raise = self._base_update_handler(run_args, err_prefix)
242 if to_raise:
243 self._update_error_queue.put(to_raise)
244 raise to_raise
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700245
246 try:
247 self._verify_update_completed()
248 except RootFSUpdateError as e:
249 self._update_error_queue.put(e)
250 raise
251
252
253class ChromiumOSUpdater(BaseUpdater):
Dan Shi0f466e82013-02-22 15:44:58 -0800254 """Helper class used to update DUT with image of desired version."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700255 REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update'
256 UPDATER_BIN = '/usr/bin/update_engine_client'
257 STATEFUL_UPDATE = '/tmp/stateful_update'
258 UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
259 UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
260
Dale Curtisa94c19c2011-05-02 15:05:17 -0700261 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
262 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
Chris Sosa65425082013-10-16 13:26:22 -0700263 # Time to wait for new kernel to be marked successful after
264 # auto update.
265 KERNEL_UPDATE_TIMEOUT = 120
Dale Curtisa94c19c2011-05-02 15:05:17 -0700266
Dan Shif3a35f72016-01-25 11:18:14 -0800267 _timer = autotest_stats.Timer('cros_autoupdater')
Dale Curtisa94c19c2011-05-02 15:05:17 -0700268
Chris Sosaa3ac2152012-05-23 22:23:13 -0700269 def __init__(self, update_url, host=None, local_devserver=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700270 super(ChromiumOSUpdater, self).__init__(self.UPDATER_BIN, update_url,
271 host)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700272 self.local_devserver = local_devserver
273 if not local_devserver:
Dan Shif3a35f72016-01-25 11:18:14 -0800274 self.update_version = url_to_version(update_url)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700275 else:
Dan Shif3a35f72016-01-25 11:18:14 -0800276 self.update_version = None
Sean Oc053dfe2010-08-23 18:22:26 +0200277
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700278
Sean Oc053dfe2010-08-23 18:22:26 +0200279 def reset_update_engine(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700280 """Resets the host to prepare for a clean update regardless of state."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700281 self._run('rm -f %s' % self.UPDATED_MARKER)
Chris Sosae92399e2015-04-24 11:32:59 -0700282 self._run('stop ui || true')
283 self._run('stop update-engine || true')
284 self._run('start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700285
Sean Oc053dfe2010-08-23 18:22:26 +0200286 if self.check_update_status() != UPDATER_IDLE:
287 raise ChromiumOSError('%s is not in an installable state' %
288 self.host.hostname)
289
290
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200291 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700292 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200293 return self.host.run(cmd, *args, **kwargs)
294
Sean Oc053dfe2010-08-23 18:22:26 +0200295
Dale Curtisa94c19c2011-05-02 15:05:17 -0700296 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800297 """Returns the stripped output of rootdev <options>.
298
299 @param options: options to run rootdev.
300
301 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700302 return self._run('rootdev %s' % options).stdout.strip()
303
304
305 def get_kernel_state(self):
306 """Returns the (<active>, <inactive>) kernel state as a pair."""
307 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
308 if active_root == self.KERNEL_A['root']:
309 return self.KERNEL_A, self.KERNEL_B
310 elif active_root == self.KERNEL_B['root']:
311 return self.KERNEL_B, self.KERNEL_A
312 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700313 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700314 active_root)
315
316
317 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
318 """Return numeric cgpt value for the specified flag, kernel, device. """
319 return int(self._run('cgpt show -n -i %d %s %s' % (
320 kernel['kernel'], flag, dev)).stdout.strip())
321
322
323 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800324 """Return numeric priority for the specified kernel.
325
326 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
327
328 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700329 return self._cgpt('-P', kernel)
330
331
332 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800333 """Return boolean success flag for the specified kernel.
334
335 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
336
337 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700338 return self._cgpt('-S', kernel) != 0
339
340
341 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800342 """Return tries count for the specified kernel.
343
344 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
345
346 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700347 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200348
349
Chris Sosa5e4246b2012-05-22 18:05:22 -0700350 def get_stateful_update_script(self):
351 """Returns the path to the stateful update script on the target."""
Chris Sosaa3ac2152012-05-23 22:23:13 -0700352 # We attempt to load the local stateful update path in 3 different
353 # ways. First we use the location specified in the autotest global
354 # config. If this doesn't exist, we attempt to use the Chromium OS
355 # Chroot path to the installed script. If all else fails, we use the
356 # stateful update script on the host.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700357 stateful_update_path = os.path.join(
358 global_config.global_config.get_config_value(
359 'CROS', 'source_tree', default=''),
360 LOCAL_STATEFUL_UPDATE_PATH)
361
Chris Sosaa3ac2152012-05-23 22:23:13 -0700362 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700363 logging.warning('Could not find Chrome OS source location for '
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700364 'stateful_update script at %s, falling back to '
365 'chroot copy.', stateful_update_path)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700366 stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
367
368 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700369 logging.warning('Could not chroot stateful_update script, falling '
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700370 'back on client copy.')
Gilad Arnold0c0df732015-09-21 06:37:59 -0700371 statefuldev_script = self.REMOTE_STATEUL_UPDATE_PATH
Chris Sosaa3ac2152012-05-23 22:23:13 -0700372 else:
Chris Sosa5e4246b2012-05-22 18:05:22 -0700373 self.host.send_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700374 stateful_update_path, self.STATEFUL_UPDATE,
375 delete_dest=True)
376 statefuldev_script = self.STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700377
378 return statefuldev_script
379
380
381 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800382 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700383 statefuldev_cmd = [self.get_stateful_update_script()]
384 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700385 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700386
387
Sean O267c00b2010-08-31 15:54:55 +0200388 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800389 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700390 part = self.rootdev('-s')
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700391 logging.warning('Reverting update; Boot partition will be %s', part)
Sean O267c00b2010-08-31 15:54:55 +0200392 return self._run('/postinst %s 2>&1' % part)
393
394
Chris Sosac1932172013-10-16 13:28:53 -0700395 def rollback_rootfs(self, powerwash):
396 """Triggers rollback and waits for it to complete.
397
398 @param powerwash: If true, powerwash as part of rollback.
399
400 @raise RootFSUpdateError if anything went wrong.
401
402 """
Dan Shi549fb822015-03-24 18:01:11 -0700403 version = self.host.get_release_version()
Chris Sosac8617522014-06-09 23:22:26 +0000404 # Introduced can_rollback in M36 (build 5772). # etc/lsb-release matches
405 # X.Y.Z. This version split just pulls the first part out.
406 try:
407 build_number = int(version.split('.')[0])
408 except ValueError:
409 logging.error('Could not parse build number.')
410 build_number = 0
411
412 if build_number >= 5772:
Gilad Arnold0c0df732015-09-21 06:37:59 -0700413 can_rollback_cmd = '%s --can_rollback' % self.UPDATER_BIN
Chris Sosac8617522014-06-09 23:22:26 +0000414 logging.info('Checking for rollback.')
415 try:
416 self._run(can_rollback_cmd)
417 except error.AutoservRunError as e:
418 raise RootFSUpdateError("Rollback isn't possible on %s: %s" %
419 (self.host.hostname, str(e)))
420
Gilad Arnold0c0df732015-09-21 06:37:59 -0700421 rollback_cmd = '%s --rollback --follow' % self.UPDATER_BIN
Chris Sosac1932172013-10-16 13:28:53 -0700422 if not powerwash:
Dan Shif3a35f72016-01-25 11:18:14 -0800423 rollback_cmd += ' --nopowerwash'
Chris Sosac1932172013-10-16 13:28:53 -0700424
Chris Sosac8617522014-06-09 23:22:26 +0000425 logging.info('Performing rollback.')
Chris Sosac1932172013-10-16 13:28:53 -0700426 try:
427 self._run(rollback_cmd)
Chris Sosac1932172013-10-16 13:28:53 -0700428 except error.AutoservRunError as e:
429 raise RootFSUpdateError('Rollback failed on %s: %s' %
430 (self.host.hostname, str(e)))
431
432 self._verify_update_completed()
433
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800434
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700435 # TODO(garnold) This is here for backward compatibility and should be
436 # deprecated once we shift to using update_image() everywhere.
Dan Shif3a35f72016-01-25 11:18:14 -0800437 @_timer.decorate
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700438 def update_rootfs(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700439 """Run the standard command to force an update."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700440 return self.update_image()
Dale Curtis5c32c722011-05-04 19:24:23 -0700441
442
Dan Shif3a35f72016-01-25 11:18:14 -0800443 @_timer.decorate
Chris Sosa72312602013-04-16 15:01:56 -0700444 def update_stateful(self, clobber=True):
445 """Updates the stateful partition.
446
447 @param clobber: If True, a clean stateful installation.
448 """
Chris Sosa77556d82012-04-05 15:23:14 -0700449 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700450 statefuldev_url = self.update_url.replace('update',
451 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700452
Dale Curtis5c32c722011-05-04 19:24:23 -0700453 # Attempt stateful partition update; this must succeed so that the newly
454 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700455 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
456 if clobber:
457 statefuldev_cmd.append('--stateful_change=clean')
458
459 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700460 try:
Dan Shi205b8732016-01-25 10:56:22 -0800461 self._run(' '.join(statefuldev_cmd), timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700462 except error.AutoservRunError:
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700463 update_error = StatefulUpdateError(
464 'Failed to perform stateful update on %s' %
465 self.host.hostname)
Chris Sosa77556d82012-04-05 15:23:14 -0700466 self._update_error_queue.put(update_error)
467 raise update_error
Simran Basi3b858a22015-03-17 16:23:24 -0700468 except Exception as e:
469 # Don't allow other exceptions to not be caught.
470 self._update_error_queue.put(e)
471 raise e
Dale Curtis5c32c722011-05-04 19:24:23 -0700472
473
Dan Shif3a35f72016-01-25 11:18:14 -0800474 @_timer.decorate
Chris Sosae92399e2015-04-24 11:32:59 -0700475 def run_update(self, update_root=True):
Dan Shi0f466e82013-02-22 15:44:58 -0800476 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700477
Chris Sosae92399e2015-04-24 11:32:59 -0700478 @param update_root: True to force a rootfs update.
Dan Shi0f466e82013-02-22 15:44:58 -0800479 """
Dan Shi549fb822015-03-24 18:01:11 -0700480 booted_version = self.host.get_release_version()
Chris Sosaa3ac2152012-05-23 22:23:13 -0700481 if self.update_version:
482 logging.info('Updating from version %s to %s.',
483 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700484
Dale Curtis5c32c722011-05-04 19:24:23 -0700485 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200486 # If we can't talk to it, the machine host probably can't either.
487 auserver_host = urlparse.urlparse(self.update_url)[1]
488 try:
489 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700490 except IOError:
491 raise ChromiumOSError(
492 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200493
Chris Sosaa3ac2152012-05-23 22:23:13 -0700494 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700495 self.host.hostname)
496
Chris Sosa5e4246b2012-05-22 18:05:22 -0700497 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700498 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700499 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200500
Dale Curtis1e973182011-07-12 18:21:36 -0700501 try:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700502 try:
503 if not update_root:
504 logging.info('Root update is skipped.')
505 else:
506 self.update_rootfs()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200507
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700508 self.update_stateful()
509 except:
Chris Sosa77556d82012-04-05 15:23:14 -0700510 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700511 self.reset_stateful_partition()
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700512 raise
Sean Oc053dfe2010-08-23 18:22:26 +0200513
Dale Curtis1e973182011-07-12 18:21:36 -0700514 logging.info('Update complete.')
Dale Curtis1e973182011-07-12 18:21:36 -0700515 except:
516 # Collect update engine logs in the event of failure.
517 if self.host.job:
Aviv Keshet2610d3e2016-06-01 16:37:01 -0700518 logging.info('Collecting update engine logs due to failure...')
Dale Curtis1e973182011-07-12 18:21:36 -0700519 self.host.get_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700520 self.UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
521 preserve_perm=False)
Prashanth B32baa9b2014-03-13 13:23:01 -0700522 list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700523 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700524 finally:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800525 logging.info('Update engine log has downloaded in '
526 'sysinfo/update_engine dir. Check the lastest.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200527
528
Dale Curtisa94c19c2011-05-02 15:05:17 -0700529 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800530 """Check the image running in DUT has the desired version.
531
532 @returns: True if the DUT's image version matches the version that
533 the autoupdater tries to update to.
534
535 """
Dan Shi549fb822015-03-24 18:01:11 -0700536 booted_version = self.host.get_release_version()
Dan Shib95bb862013-03-22 16:29:28 -0700537 return (self.update_version and
538 self.update_version.endswith(booted_version))
539
540
541 def check_version_to_confirm_install(self):
542 """Check image running in DUT has the desired version to be installed.
543
544 The method should not be used to check if DUT needs to have a full
545 reimage. Only use it to confirm a image is installed.
546
Dan Shi549fb822015-03-24 18:01:11 -0700547 The method is designed to verify version for following 6 scenarios with
Dan Shi190c7802013-04-04 13:05:30 -0700548 samples of version to update to and expected booted version:
549 1. trybot paladin build.
550 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
551 booted version: 3837.0.2013_03_21_1340
552
553 2. trybot release build.
554 update version: trybot-lumpy-release/R27-3837.0.0-b456
555 booted version: 3837.0.0
556
557 3. buildbot official release build.
558 update version: lumpy-release/R27-3837.0.0
559 booted version: 3837.0.0
560
561 4. non-official paladin rc build.
562 update version: lumpy-paladin/R27-3878.0.0-rc7
563 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700564
Dan Shi7f795512013-04-12 10:08:17 -0700565 5. chrome-perf build.
566 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
567 booted version: 3837.0.0
568
Dan Shi73aa2902013-05-03 11:22:11 -0700569 6. pgo-generate build.
570 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
571 booted version: 3837.0.0-pgo-generate
572
Dan Shib95bb862013-03-22 16:29:28 -0700573 When we are checking if a DUT needs to do a full install, we should NOT
574 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700575 it may return false positive for a DUT running trybot paladin build to
576 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700577
Dan Shi190c7802013-04-04 13:05:30 -0700578 TODO: This logic has a bug if a trybot paladin build failed to be
579 installed in a DUT running an older trybot paladin build with same
580 platform number, but different build number (-b###). So to conclusively
581 determine if a tryjob paladin build is imaged successfully, we may need
582 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700583
584 @returns: True if the DUT's image version (without the date string if
585 the image is a trybot build), matches the version that the
586 autoupdater is trying to update to.
587
588 """
J. Richard Barnetteec1de422013-06-26 15:44:07 -0700589 # In the local_devserver case, we can't know the expected
590 # build, so just pass.
591 if not self.update_version:
592 return True
593
Dan Shib95bb862013-03-22 16:29:28 -0700594 # Always try the default check_version method first, this prevents
595 # any backward compatibility issue.
596 if self.check_version():
597 return True
598
Dan Shi549fb822015-03-24 18:01:11 -0700599 return utils.version_match(self.update_version,
600 self.host.get_release_version(),
601 self.update_url)
Chris Sosa65425082013-10-16 13:26:22 -0700602
603
604 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
605 """Verifies that we fully booted given expected kernel state.
606
607 This method both verifies that we booted using the correct kernel
608 state and that the OS has marked the kernel as good.
609
610 @param expected_kernel_state: kernel state that we are verifying with
611 i.e. I expect to be booted onto partition 4 etc. See output of
612 get_kernel_state.
613 @param rollback_message: string to raise as a ChromiumOSError
614 if we booted with the wrong partition.
615
616 @raises ChromiumOSError: If we didn't.
617 """
618 # Figure out the newly active kernel.
619 active_kernel_state = self.get_kernel_state()[0]
620
621 # Check for rollback due to a bad build.
622 if (expected_kernel_state and
623 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800624
625 # Kernel crash reports should be wiped between test runs, but
626 # may persist from earlier parts of the test, or from problems
627 # with provisioning.
628 #
629 # Kernel crash reports will NOT be present if the crash happened
630 # before encrypted stateful is mounted.
631 #
632 # TODO(dgarrett): Integrate with server/crashcollect.py at some
633 # point.
634 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
635 if kernel_crashes:
636 rollback_message += ': kernel_crash'
637 logging.debug('Found %d kernel crash reports:',
638 len(kernel_crashes))
639 # The crash names contain timestamps that may be useful:
640 # kernel.20131207.005945.0.kcrash
641 for crash in kernel_crashes:
Dan Shi0942b1d2015-03-31 11:07:00 -0700642 logging.debug(' %s', os.path.basename(crash))
Don Garrett56b1cc82013-12-06 17:49:20 -0800643
Chris Sosa65425082013-10-16 13:26:22 -0700644 # Print out some information to make it easier to debug
645 # the rollback.
646 logging.debug('Dumping partition table.')
647 self._run('cgpt show $(rootdev -s -d)')
648 logging.debug('Dumping crossystem for firmware debugging.')
649 self._run('crossystem --all')
650 raise ChromiumOSError(rollback_message)
651
652 # Make sure chromeos-setgoodkernel runs.
653 try:
654 utils.poll_for_condition(
655 lambda: (self.get_kernel_tries(active_kernel_state) == 0
656 and self.get_kernel_success(active_kernel_state)),
657 exception=ChromiumOSError(),
658 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
659 except ChromiumOSError:
660 services_status = self._run('status system-services').stdout
661 if services_status != 'system-services start/running\n':
662 event = ('Chrome failed to reach login screen')
663 else:
664 event = ('update-engine failed to call '
665 'chromeos-setgoodkernel')
666 raise ChromiumOSError(
667 'After update and reboot, %s '
668 'within %d seconds' % (event,
669 self.KERNEL_UPDATE_TIMEOUT))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700670
671
672class BrilloUpdater(BaseUpdater):
673 """Helper class for updating a Brillo DUT."""
674
675 def __init__(self, update_url, host=None):
676 """Initialize the object.
677
678 @param update_url: The URL we want the update to use.
679 @param host: A client.common_lib.hosts.Host implementation.
680 """
Gilad Arnoldecf894b2015-10-07 08:48:22 -0700681 super(BrilloUpdater, self).__init__(
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700682 '/system/bin/update_engine_client', update_url, host)