blob: 00594db39c1ca5459f871bb1dbef130d050ed772 [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:
177 err_msg += 'SSH reports an error: %s' % type(e).__name__
178 to_raise = RootFSUpdateError(err_msg)
179 except error.AutoservRunError as e:
180 # 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__))
194 logging.error(e)
195 to_raise = RootFSUpdateError(err_msg)
196 except Exception as e:
197 to_raise = e
198
199 return to_raise
200
201
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700202 def trigger_update(self):
203 """Triggers a background update.
204
Shuqian Zhaod9992722016-02-29 12:26:38 -0800205 @raise RootFSUpdateError or unknown Exception if anything went wrong.
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700206 """
207 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
208 (self.updater_ctrl_bin, self.update_url))
Shuqian Zhaod9992722016-02-29 12:26:38 -0800209 run_args = {'command': autoupdate_cmd}
210 err_prefix = 'Failed to trigger an update on %s. ' % self.host.hostname
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700211 logging.info('Triggering update via: %s', autoupdate_cmd)
Shuqian Zhaod9992722016-02-29 12:26:38 -0800212 to_raise = self._base_update_handler(run_args, err_prefix)
213 if to_raise:
214 raise to_raise
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700215
216
217 def _verify_update_completed(self):
218 """Verifies that an update has completed.
219
220 @raise RootFSUpdateError: if verification fails.
221 """
222 status = self.check_update_status()
223 if status != UPDATER_NEED_REBOOT:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800224 error_msg = ''
225 if status == UPDATER_IDLE:
226 error_msg = 'Update error: %s' % self.get_last_update_error()
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700227 raise RootFSUpdateError('Update did not complete with correct '
Shuqian Zhaod9992722016-02-29 12:26:38 -0800228 'status. Expecting %s, actual %s. %s' %
229 (UPDATER_NEED_REBOOT, status, error_msg))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700230
231
232 def update_image(self):
233 """Updates the device image and verifies success."""
Shuqian Zhaod9992722016-02-29 12:26:38 -0800234 autoupdate_cmd = ('%s --update --omaha_url=%s 2>&1' %
235 (self.updater_ctrl_bin, self.update_url))
236 run_args = {'command': autoupdate_cmd, 'timeout': 3600}
237 err_prefix = ('Failed to install device image using payload at %s '
238 'on %s. ' % (self.update_url, self.host.hostname))
239 logging.info('Updating image via: %s', autoupdate_cmd)
240 to_raise = self._base_update_handler(run_args, err_prefix)
241 if to_raise:
242 self._update_error_queue.put(to_raise)
243 raise to_raise
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700244
245 try:
246 self._verify_update_completed()
247 except RootFSUpdateError as e:
248 self._update_error_queue.put(e)
249 raise
250
251
252class ChromiumOSUpdater(BaseUpdater):
Dan Shi0f466e82013-02-22 15:44:58 -0800253 """Helper class used to update DUT with image of desired version."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700254 REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update'
255 UPDATER_BIN = '/usr/bin/update_engine_client'
256 STATEFUL_UPDATE = '/tmp/stateful_update'
257 UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
258 UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
259
Dale Curtisa94c19c2011-05-02 15:05:17 -0700260 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
261 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
Chris Sosa65425082013-10-16 13:26:22 -0700262 # Time to wait for new kernel to be marked successful after
263 # auto update.
264 KERNEL_UPDATE_TIMEOUT = 120
Dale Curtisa94c19c2011-05-02 15:05:17 -0700265
Dan Shif3a35f72016-01-25 11:18:14 -0800266 _timer = autotest_stats.Timer('cros_autoupdater')
Dale Curtisa94c19c2011-05-02 15:05:17 -0700267
Chris Sosaa3ac2152012-05-23 22:23:13 -0700268 def __init__(self, update_url, host=None, local_devserver=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700269 super(ChromiumOSUpdater, self).__init__(self.UPDATER_BIN, update_url,
270 host)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700271 self.local_devserver = local_devserver
272 if not local_devserver:
Dan Shif3a35f72016-01-25 11:18:14 -0800273 self.update_version = url_to_version(update_url)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700274 else:
Dan Shif3a35f72016-01-25 11:18:14 -0800275 self.update_version = None
Sean Oc053dfe2010-08-23 18:22:26 +0200276
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700277
Sean Oc053dfe2010-08-23 18:22:26 +0200278 def reset_update_engine(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700279 """Resets the host to prepare for a clean update regardless of state."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700280 self._run('rm -f %s' % self.UPDATED_MARKER)
Chris Sosae92399e2015-04-24 11:32:59 -0700281 self._run('stop ui || true')
282 self._run('stop update-engine || true')
283 self._run('start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700284
Sean Oc053dfe2010-08-23 18:22:26 +0200285 if self.check_update_status() != UPDATER_IDLE:
286 raise ChromiumOSError('%s is not in an installable state' %
287 self.host.hostname)
288
289
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200290 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700291 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200292 return self.host.run(cmd, *args, **kwargs)
293
Sean Oc053dfe2010-08-23 18:22:26 +0200294
Dale Curtisa94c19c2011-05-02 15:05:17 -0700295 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800296 """Returns the stripped output of rootdev <options>.
297
298 @param options: options to run rootdev.
299
300 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700301 return self._run('rootdev %s' % options).stdout.strip()
302
303
304 def get_kernel_state(self):
305 """Returns the (<active>, <inactive>) kernel state as a pair."""
306 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
307 if active_root == self.KERNEL_A['root']:
308 return self.KERNEL_A, self.KERNEL_B
309 elif active_root == self.KERNEL_B['root']:
310 return self.KERNEL_B, self.KERNEL_A
311 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700312 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700313 active_root)
314
315
316 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
317 """Return numeric cgpt value for the specified flag, kernel, device. """
318 return int(self._run('cgpt show -n -i %d %s %s' % (
319 kernel['kernel'], flag, dev)).stdout.strip())
320
321
322 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800323 """Return numeric priority for the specified kernel.
324
325 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
326
327 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700328 return self._cgpt('-P', kernel)
329
330
331 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800332 """Return boolean success flag for the specified kernel.
333
334 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
335
336 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700337 return self._cgpt('-S', kernel) != 0
338
339
340 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800341 """Return tries count for the specified kernel.
342
343 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
344
345 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700346 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200347
348
Chris Sosa5e4246b2012-05-22 18:05:22 -0700349 def get_stateful_update_script(self):
350 """Returns the path to the stateful update script on the target."""
Chris Sosaa3ac2152012-05-23 22:23:13 -0700351 # We attempt to load the local stateful update path in 3 different
352 # ways. First we use the location specified in the autotest global
353 # config. If this doesn't exist, we attempt to use the Chromium OS
354 # Chroot path to the installed script. If all else fails, we use the
355 # stateful update script on the host.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700356 stateful_update_path = os.path.join(
357 global_config.global_config.get_config_value(
358 'CROS', 'source_tree', default=''),
359 LOCAL_STATEFUL_UPDATE_PATH)
360
Chris Sosaa3ac2152012-05-23 22:23:13 -0700361 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700362 logging.warning('Could not find Chrome OS source location for '
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700363 'stateful_update script at %s, falling back to '
364 'chroot copy.', stateful_update_path)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700365 stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
366
367 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700368 logging.warning('Could not chroot stateful_update script, falling '
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700369 'back on client copy.')
Gilad Arnold0c0df732015-09-21 06:37:59 -0700370 statefuldev_script = self.REMOTE_STATEUL_UPDATE_PATH
Chris Sosaa3ac2152012-05-23 22:23:13 -0700371 else:
Chris Sosa5e4246b2012-05-22 18:05:22 -0700372 self.host.send_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700373 stateful_update_path, self.STATEFUL_UPDATE,
374 delete_dest=True)
375 statefuldev_script = self.STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700376
377 return statefuldev_script
378
379
380 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800381 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700382 statefuldev_cmd = [self.get_stateful_update_script()]
383 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700384 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700385
386
Sean O267c00b2010-08-31 15:54:55 +0200387 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800388 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700389 part = self.rootdev('-s')
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700390 logging.warning('Reverting update; Boot partition will be %s', part)
Sean O267c00b2010-08-31 15:54:55 +0200391 return self._run('/postinst %s 2>&1' % part)
392
393
Chris Sosac1932172013-10-16 13:28:53 -0700394 def rollback_rootfs(self, powerwash):
395 """Triggers rollback and waits for it to complete.
396
397 @param powerwash: If true, powerwash as part of rollback.
398
399 @raise RootFSUpdateError if anything went wrong.
400
401 """
Dan Shi549fb822015-03-24 18:01:11 -0700402 version = self.host.get_release_version()
Chris Sosac8617522014-06-09 23:22:26 +0000403 # Introduced can_rollback in M36 (build 5772). # etc/lsb-release matches
404 # X.Y.Z. This version split just pulls the first part out.
405 try:
406 build_number = int(version.split('.')[0])
407 except ValueError:
408 logging.error('Could not parse build number.')
409 build_number = 0
410
411 if build_number >= 5772:
Gilad Arnold0c0df732015-09-21 06:37:59 -0700412 can_rollback_cmd = '%s --can_rollback' % self.UPDATER_BIN
Chris Sosac8617522014-06-09 23:22:26 +0000413 logging.info('Checking for rollback.')
414 try:
415 self._run(can_rollback_cmd)
416 except error.AutoservRunError as e:
417 raise RootFSUpdateError("Rollback isn't possible on %s: %s" %
418 (self.host.hostname, str(e)))
419
Gilad Arnold0c0df732015-09-21 06:37:59 -0700420 rollback_cmd = '%s --rollback --follow' % self.UPDATER_BIN
Chris Sosac1932172013-10-16 13:28:53 -0700421 if not powerwash:
Dan Shif3a35f72016-01-25 11:18:14 -0800422 rollback_cmd += ' --nopowerwash'
Chris Sosac1932172013-10-16 13:28:53 -0700423
Chris Sosac8617522014-06-09 23:22:26 +0000424 logging.info('Performing rollback.')
Chris Sosac1932172013-10-16 13:28:53 -0700425 try:
426 self._run(rollback_cmd)
Chris Sosac1932172013-10-16 13:28:53 -0700427 except error.AutoservRunError as e:
428 raise RootFSUpdateError('Rollback failed on %s: %s' %
429 (self.host.hostname, str(e)))
430
431 self._verify_update_completed()
432
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800433
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700434 # TODO(garnold) This is here for backward compatibility and should be
435 # deprecated once we shift to using update_image() everywhere.
Dan Shif3a35f72016-01-25 11:18:14 -0800436 @_timer.decorate
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700437 def update_rootfs(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700438 """Run the standard command to force an update."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700439 return self.update_image()
Dale Curtis5c32c722011-05-04 19:24:23 -0700440
441
Dan Shif3a35f72016-01-25 11:18:14 -0800442 @_timer.decorate
Chris Sosa72312602013-04-16 15:01:56 -0700443 def update_stateful(self, clobber=True):
444 """Updates the stateful partition.
445
446 @param clobber: If True, a clean stateful installation.
447 """
Chris Sosa77556d82012-04-05 15:23:14 -0700448 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700449 statefuldev_url = self.update_url.replace('update',
450 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700451
Dale Curtis5c32c722011-05-04 19:24:23 -0700452 # Attempt stateful partition update; this must succeed so that the newly
453 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700454 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
455 if clobber:
456 statefuldev_cmd.append('--stateful_change=clean')
457
458 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700459 try:
Dan Shi205b8732016-01-25 10:56:22 -0800460 self._run(' '.join(statefuldev_cmd), timeout=1200)
Dale Curtis5c32c722011-05-04 19:24:23 -0700461 except error.AutoservRunError:
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700462 update_error = StatefulUpdateError(
463 'Failed to perform stateful update on %s' %
464 self.host.hostname)
Chris Sosa77556d82012-04-05 15:23:14 -0700465 self._update_error_queue.put(update_error)
466 raise update_error
Simran Basi3b858a22015-03-17 16:23:24 -0700467 except Exception as e:
468 # Don't allow other exceptions to not be caught.
469 self._update_error_queue.put(e)
470 raise e
Dale Curtis5c32c722011-05-04 19:24:23 -0700471
472
Dan Shif3a35f72016-01-25 11:18:14 -0800473 @_timer.decorate
Chris Sosae92399e2015-04-24 11:32:59 -0700474 def run_update(self, update_root=True):
Dan Shi0f466e82013-02-22 15:44:58 -0800475 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700476
Chris Sosae92399e2015-04-24 11:32:59 -0700477 @param update_root: True to force a rootfs update.
Dan Shi0f466e82013-02-22 15:44:58 -0800478 """
Dan Shi549fb822015-03-24 18:01:11 -0700479 booted_version = self.host.get_release_version()
Chris Sosaa3ac2152012-05-23 22:23:13 -0700480 if self.update_version:
481 logging.info('Updating from version %s to %s.',
482 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700483
Dale Curtis5c32c722011-05-04 19:24:23 -0700484 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200485 # If we can't talk to it, the machine host probably can't either.
486 auserver_host = urlparse.urlparse(self.update_url)[1]
487 try:
488 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700489 except IOError:
490 raise ChromiumOSError(
491 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200492
Chris Sosaa3ac2152012-05-23 22:23:13 -0700493 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700494 self.host.hostname)
495
Chris Sosa5e4246b2012-05-22 18:05:22 -0700496 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700497 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700498 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200499
Dale Curtis1e973182011-07-12 18:21:36 -0700500 try:
Chris Sosa77556d82012-04-05 15:23:14 -0700501 updaters = [
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700502 multiprocessing.process.Process(target=self.update_rootfs),
Chris Sosa72312602013-04-16 15:01:56 -0700503 multiprocessing.process.Process(target=self.update_stateful)
Chris Sosa77556d82012-04-05 15:23:14 -0700504 ]
Dan Shi0f466e82013-02-22 15:44:58 -0800505 if not update_root:
506 logging.info('Root update is skipped.')
507 updaters = updaters[1:]
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200508
Chris Sosa77556d82012-04-05 15:23:14 -0700509 # Run the updaters in parallel.
510 for updater in updaters: updater.start()
511 for updater in updaters: updater.join()
512
513 # Re-raise the first error that occurred.
514 if not self._update_error_queue.empty():
515 update_error = self._update_error_queue.get()
516 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700517 self.reset_stateful_partition()
Chris Sosa77556d82012-04-05 15:23:14 -0700518 raise update_error
Sean Oc053dfe2010-08-23 18:22:26 +0200519
Dale Curtis1e973182011-07-12 18:21:36 -0700520 logging.info('Update complete.')
Dale Curtis1e973182011-07-12 18:21:36 -0700521 except:
522 # Collect update engine logs in the event of failure.
523 if self.host.job:
524 logging.info('Collecting update engine logs...')
525 self.host.get_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700526 self.UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
527 preserve_perm=False)
Prashanth B32baa9b2014-03-13 13:23:01 -0700528 list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700529 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700530 finally:
Shuqian Zhaod9992722016-02-29 12:26:38 -0800531 logging.info('Update engine log has downloaded in '
532 'sysinfo/update_engine dir. Check the lastest.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200533
534
Dale Curtisa94c19c2011-05-02 15:05:17 -0700535 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800536 """Check the image running in DUT has the desired version.
537
538 @returns: True if the DUT's image version matches the version that
539 the autoupdater tries to update to.
540
541 """
Dan Shi549fb822015-03-24 18:01:11 -0700542 booted_version = self.host.get_release_version()
Dan Shib95bb862013-03-22 16:29:28 -0700543 return (self.update_version and
544 self.update_version.endswith(booted_version))
545
546
547 def check_version_to_confirm_install(self):
548 """Check image running in DUT has the desired version to be installed.
549
550 The method should not be used to check if DUT needs to have a full
551 reimage. Only use it to confirm a image is installed.
552
Dan Shi549fb822015-03-24 18:01:11 -0700553 The method is designed to verify version for following 6 scenarios with
Dan Shi190c7802013-04-04 13:05:30 -0700554 samples of version to update to and expected booted version:
555 1. trybot paladin build.
556 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
557 booted version: 3837.0.2013_03_21_1340
558
559 2. trybot release build.
560 update version: trybot-lumpy-release/R27-3837.0.0-b456
561 booted version: 3837.0.0
562
563 3. buildbot official release build.
564 update version: lumpy-release/R27-3837.0.0
565 booted version: 3837.0.0
566
567 4. non-official paladin rc build.
568 update version: lumpy-paladin/R27-3878.0.0-rc7
569 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700570
Dan Shi7f795512013-04-12 10:08:17 -0700571 5. chrome-perf build.
572 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
573 booted version: 3837.0.0
574
Dan Shi73aa2902013-05-03 11:22:11 -0700575 6. pgo-generate build.
576 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
577 booted version: 3837.0.0-pgo-generate
578
Dan Shib95bb862013-03-22 16:29:28 -0700579 When we are checking if a DUT needs to do a full install, we should NOT
580 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700581 it may return false positive for a DUT running trybot paladin build to
582 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700583
Dan Shi190c7802013-04-04 13:05:30 -0700584 TODO: This logic has a bug if a trybot paladin build failed to be
585 installed in a DUT running an older trybot paladin build with same
586 platform number, but different build number (-b###). So to conclusively
587 determine if a tryjob paladin build is imaged successfully, we may need
588 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700589
590 @returns: True if the DUT's image version (without the date string if
591 the image is a trybot build), matches the version that the
592 autoupdater is trying to update to.
593
594 """
J. Richard Barnetteec1de422013-06-26 15:44:07 -0700595 # In the local_devserver case, we can't know the expected
596 # build, so just pass.
597 if not self.update_version:
598 return True
599
Dan Shib95bb862013-03-22 16:29:28 -0700600 # Always try the default check_version method first, this prevents
601 # any backward compatibility issue.
602 if self.check_version():
603 return True
604
Dan Shi549fb822015-03-24 18:01:11 -0700605 return utils.version_match(self.update_version,
606 self.host.get_release_version(),
607 self.update_url)
Chris Sosa65425082013-10-16 13:26:22 -0700608
609
610 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
611 """Verifies that we fully booted given expected kernel state.
612
613 This method both verifies that we booted using the correct kernel
614 state and that the OS has marked the kernel as good.
615
616 @param expected_kernel_state: kernel state that we are verifying with
617 i.e. I expect to be booted onto partition 4 etc. See output of
618 get_kernel_state.
619 @param rollback_message: string to raise as a ChromiumOSError
620 if we booted with the wrong partition.
621
622 @raises ChromiumOSError: If we didn't.
623 """
624 # Figure out the newly active kernel.
625 active_kernel_state = self.get_kernel_state()[0]
626
627 # Check for rollback due to a bad build.
628 if (expected_kernel_state and
629 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800630
631 # Kernel crash reports should be wiped between test runs, but
632 # may persist from earlier parts of the test, or from problems
633 # with provisioning.
634 #
635 # Kernel crash reports will NOT be present if the crash happened
636 # before encrypted stateful is mounted.
637 #
638 # TODO(dgarrett): Integrate with server/crashcollect.py at some
639 # point.
640 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
641 if kernel_crashes:
642 rollback_message += ': kernel_crash'
643 logging.debug('Found %d kernel crash reports:',
644 len(kernel_crashes))
645 # The crash names contain timestamps that may be useful:
646 # kernel.20131207.005945.0.kcrash
647 for crash in kernel_crashes:
Dan Shi0942b1d2015-03-31 11:07:00 -0700648 logging.debug(' %s', os.path.basename(crash))
Don Garrett56b1cc82013-12-06 17:49:20 -0800649
Chris Sosa65425082013-10-16 13:26:22 -0700650 # Print out some information to make it easier to debug
651 # the rollback.
652 logging.debug('Dumping partition table.')
653 self._run('cgpt show $(rootdev -s -d)')
654 logging.debug('Dumping crossystem for firmware debugging.')
655 self._run('crossystem --all')
656 raise ChromiumOSError(rollback_message)
657
658 # Make sure chromeos-setgoodkernel runs.
659 try:
660 utils.poll_for_condition(
661 lambda: (self.get_kernel_tries(active_kernel_state) == 0
662 and self.get_kernel_success(active_kernel_state)),
663 exception=ChromiumOSError(),
664 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
665 except ChromiumOSError:
666 services_status = self._run('status system-services').stdout
667 if services_status != 'system-services start/running\n':
668 event = ('Chrome failed to reach login screen')
669 else:
670 event = ('update-engine failed to call '
671 'chromeos-setgoodkernel')
672 raise ChromiumOSError(
673 'After update and reboot, %s '
674 'within %d seconds' % (event,
675 self.KERNEL_UPDATE_TIMEOUT))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700676
677
678class BrilloUpdater(BaseUpdater):
679 """Helper class for updating a Brillo DUT."""
680
681 def __init__(self, update_url, host=None):
682 """Initialize the object.
683
684 @param update_url: The URL we want the update to use.
685 @param host: A client.common_lib.hosts.Host implementation.
686 """
Gilad Arnoldecf894b2015-10-07 08:48:22 -0700687 super(BrilloUpdater, self).__init__(
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700688 '/system/bin/update_engine_client', update_url, host)