blob: a2c65d8d780d4c0e8f90b10cfa44eb84973a3c1e [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
12
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
Sean O'Connor5346e4e2010-08-12 18:49:24 +020015
Dale Curtis5c32c722011-05-04 19:24:23 -070016# Local stateful update path is relative to the CrOS source directory.
17LOCAL_STATEFUL_UPDATE_PATH = 'src/platform/dev/stateful_update'
Chris Sosaa3ac2152012-05-23 22:23:13 -070018LOCAL_CHROOT_STATEFUL_UPDATE_PATH = '/usr/bin/stateful_update'
Dale Curtis5c32c722011-05-04 19:24:23 -070019REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update'
20STATEFUL_UPDATE = '/tmp/stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020021UPDATER_BIN = '/usr/bin/update_engine_client'
22UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020023UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
Darin Petkov7d572992010-09-23 10:11:05 -070024UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
Dale Curtis1e973182011-07-12 18:21:36 -070025UPDATER_LOGS = '/var/log/messages /var/log/update_engine'
beeps5e8c45a2013-12-17 22:05:11 -080026# A list of update engine client states that occur after an update is triggered.
27UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FORUPDATE',
28 'UPDATE_STATUS_UPDATE_AVAILABLE',
29 'UPDATE_STATUS_DOWNLOADING',
30 'UPDATE_STATUS_FINALIZING']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020031
32class ChromiumOSError(error.InstallError):
33 """Generic error for ChromiumOS-specific exceptions."""
34 pass
35
36
Chris Sosa77556d82012-04-05 15:23:14 -070037class RootFSUpdateError(ChromiumOSError):
38 """Raised when the RootFS fails to update."""
39 pass
40
41
42class StatefulUpdateError(ChromiumOSError):
43 """Raised when the stateful partition fails to update."""
44 pass
45
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
Sean O'Connor5346e4e2010-08-12 18:49:24 +020075class ChromiumOSUpdater():
Dan Shi0f466e82013-02-22 15:44:58 -080076 """Helper class used to update DUT with image of desired version."""
Dale Curtisa94c19c2011-05-02 15:05:17 -070077 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
78 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
Chris Sosa65425082013-10-16 13:26:22 -070079 # Time to wait for new kernel to be marked successful after
80 # auto update.
81 KERNEL_UPDATE_TIMEOUT = 120
Dale Curtisa94c19c2011-05-02 15:05:17 -070082
83
Chris Sosaa3ac2152012-05-23 22:23:13 -070084 def __init__(self, update_url, host=None, local_devserver=False):
Sean O'Connor5346e4e2010-08-12 18:49:24 +020085 self.host = host
86 self.update_url = update_url
Chris Sosa77556d82012-04-05 15:23:14 -070087 self._update_error_queue = multiprocessing.Queue(2)
Chris Sosaa3ac2152012-05-23 22:23:13 -070088 self.local_devserver = local_devserver
89 if not local_devserver:
90 self.update_version = url_to_version(update_url)
91 else:
92 self.update_version = None
Sean Oc053dfe2010-08-23 18:22:26 +020093
Sean O'Connor5346e4e2010-08-12 18:49:24 +020094 def check_update_status(self):
Dale Curtis5c32c722011-05-04 19:24:23 -070095 """Return current status from update-engine."""
96 update_status = self._run(
97 '%s -status 2>&1 | grep CURRENT_OP' % UPDATER_BIN)
Sean O'Connor5346e4e2010-08-12 18:49:24 +020098 return update_status.stdout.strip().split('=')[-1]
99
Sean Oc053dfe2010-08-23 18:22:26 +0200100
101 def reset_update_engine(self):
Dale Curtis5c32c722011-05-04 19:24:23 -0700102 """Restarts the update-engine service."""
Darin Petkov7d572992010-09-23 10:11:05 -0700103 self._run('rm -f %s' % UPDATED_MARKER)
Sean O267c00b2010-08-31 15:54:55 +0200104 try:
105 self._run('initctl stop update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700106 except error.AutoservRunError:
Sean O267c00b2010-08-31 15:54:55 +0200107 logging.warn('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +0200108 self._run('initctl start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700109
Sean Oc053dfe2010-08-23 18:22:26 +0200110 if self.check_update_status() != UPDATER_IDLE:
111 raise ChromiumOSError('%s is not in an installable state' %
112 self.host.hostname)
113
114
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200115 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700116 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200117 return self.host.run(cmd, *args, **kwargs)
118
Sean Oc053dfe2010-08-23 18:22:26 +0200119
Dale Curtisa94c19c2011-05-02 15:05:17 -0700120 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800121 """Returns the stripped output of rootdev <options>.
122
123 @param options: options to run rootdev.
124
125 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700126 return self._run('rootdev %s' % options).stdout.strip()
127
128
129 def get_kernel_state(self):
130 """Returns the (<active>, <inactive>) kernel state as a pair."""
131 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
132 if active_root == self.KERNEL_A['root']:
133 return self.KERNEL_A, self.KERNEL_B
134 elif active_root == self.KERNEL_B['root']:
135 return self.KERNEL_B, self.KERNEL_A
136 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700137 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700138 active_root)
139
140
141 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
142 """Return numeric cgpt value for the specified flag, kernel, device. """
143 return int(self._run('cgpt show -n -i %d %s %s' % (
144 kernel['kernel'], flag, dev)).stdout.strip())
145
146
147 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800148 """Return numeric priority for the specified kernel.
149
150 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
151
152 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700153 return self._cgpt('-P', kernel)
154
155
156 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800157 """Return boolean success flag for the specified kernel.
158
159 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
160
161 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700162 return self._cgpt('-S', kernel) != 0
163
164
165 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800166 """Return tries count for the specified kernel.
167
168 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
169
170 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700171 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200172
173
Chris Sosa5e4246b2012-05-22 18:05:22 -0700174 def get_stateful_update_script(self):
175 """Returns the path to the stateful update script on the target."""
Chris Sosaa3ac2152012-05-23 22:23:13 -0700176 # We attempt to load the local stateful update path in 3 different
177 # ways. First we use the location specified in the autotest global
178 # config. If this doesn't exist, we attempt to use the Chromium OS
179 # Chroot path to the installed script. If all else fails, we use the
180 # stateful update script on the host.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700181 stateful_update_path = os.path.join(
182 global_config.global_config.get_config_value(
183 'CROS', 'source_tree', default=''),
184 LOCAL_STATEFUL_UPDATE_PATH)
185
Chris Sosaa3ac2152012-05-23 22:23:13 -0700186 if not os.path.exists(stateful_update_path):
187 logging.warn('Could not find Chrome OS source location for '
188 'stateful_update script at %s, falling back to chroot '
189 'copy.', stateful_update_path)
190 stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
191
192 if not os.path.exists(stateful_update_path):
193 logging.warn('Could not chroot stateful_update script, falling '
194 'back on client copy.')
195 statefuldev_script = REMOTE_STATEUL_UPDATE_PATH
196 else:
Chris Sosa5e4246b2012-05-22 18:05:22 -0700197 self.host.send_file(
198 stateful_update_path, STATEFUL_UPDATE, delete_dest=True)
199 statefuldev_script = STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700200
201 return statefuldev_script
202
203
204 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800205 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700206 statefuldev_cmd = [self.get_stateful_update_script()]
207 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700208 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700209
210
Sean O267c00b2010-08-31 15:54:55 +0200211 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800212 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700213 part = self.rootdev('-s')
Sean O267c00b2010-08-31 15:54:55 +0200214 logging.warn('Reverting update; Boot partition will be %s', part)
215 return self._run('/postinst %s 2>&1' % part)
216
217
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800218 def trigger_update(self):
219 """Triggers a background update on a test image.
220
221 @raise RootFSUpdateError if anything went wrong.
222
223 """
224 autoupdate_cmd = '%s --check_for_update --omaha_url=%s' % (
225 UPDATER_BIN, self.update_url)
Gilad Arnold0338ff32013-10-02 12:16:26 -0700226 logging.info('Triggering update via: %s', autoupdate_cmd)
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800227 try:
Chris Sosa66d74072013-09-19 11:21:29 -0700228 self._run(autoupdate_cmd)
Don Garrettdf8aef72013-12-16 11:12:41 -0800229 except (error.AutoservSshPermissionDeniedError,
230 error.AutoservSSHTimeout) as e:
231 raise RootFSUpdateError('SSH on %s is seeing %s' %
232 (self.host.hostname, type(e).__name__))
233 except error.AutoservRunError as e:
234
235 # Check if the exit code is 255, if so it's probably a generic
236 # SSH error.
237 result = e.args[1]
238 if result.exit_status == 255:
239 raise RootFSUpdateError('SSH on %s is seeing a generic error.' %
240 self.host.hostname)
241
242 # We have ruled out all SSH cases, the error code is from
243 # update_engine_client, though we still don't know why.
244 raise RootFSUpdateError(
245 'devserver unreachable, payload unavailable, '
246 'or AU bug (unlikely) on %s: %s' %
247 (self.host.hostname, type(e).__name__))
248
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800249
Chris Sosac1932172013-10-16 13:28:53 -0700250 def _verify_update_completed(self):
251 """Verifies that an update has completed.
252
253 @raise RootFSUpdateError: if verification fails.
254 """
255 status = self.check_update_status()
256 if status != UPDATER_NEED_REBOOT:
257 raise RootFSUpdateError('Update did not complete with correct '
258 'status. Expecting %s, actual %s' %
259 (UPDATER_NEED_REBOOT, status))
260
261
262 def rollback_rootfs(self, powerwash):
263 """Triggers rollback and waits for it to complete.
264
265 @param powerwash: If true, powerwash as part of rollback.
266
267 @raise RootFSUpdateError if anything went wrong.
268
269 """
270 #TODO(sosa): crbug.com/309051 - Make this one update_engine_client call.
271 rollback_cmd = '%s --rollback' % (UPDATER_BIN)
272 wait_for_update_to_complete_cmd = '%s --update' % (UPDATER_BIN)
273 if not powerwash:
274 rollback_cmd += ' --nopowerwash'
275
276 logging.info('Triggering rollback.')
277 try:
278 self._run(rollback_cmd)
279 self._run(wait_for_update_to_complete_cmd)
280 except error.AutoservRunError as e:
281 raise RootFSUpdateError('Rollback failed on %s: %s' %
282 (self.host.hostname, str(e)))
283
284 self._verify_update_completed()
285
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800286
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700287 def update_rootfs(self):
288 """Updates the rootfs partition only."""
Chris Sosa77556d82012-04-05 15:23:14 -0700289 logging.info('Updating root partition...')
Dale Curtis5c32c722011-05-04 19:24:23 -0700290
291 # Run update_engine using the specified URL.
292 try:
293 autoupdate_cmd = '%s --update --omaha_url=%s 2>&1' % (
294 UPDATER_BIN, self.update_url)
295 self._run(autoupdate_cmd, timeout=900)
296 except error.AutoservRunError:
Chris Sosa77556d82012-04-05 15:23:14 -0700297 update_error = RootFSUpdateError('update-engine failed on %s' %
298 self.host.hostname)
299 self._update_error_queue.put(update_error)
300 raise update_error
Dale Curtis5c32c722011-05-04 19:24:23 -0700301
Chris Sosac1932172013-10-16 13:28:53 -0700302 try:
303 self._verify_update_completed()
304 except RootFSUpdateError as e:
305 self._update_error_queue.put(e)
306 raise
Dale Curtis5c32c722011-05-04 19:24:23 -0700307
308
Chris Sosa72312602013-04-16 15:01:56 -0700309 def update_stateful(self, clobber=True):
310 """Updates the stateful partition.
311
312 @param clobber: If True, a clean stateful installation.
313 """
Chris Sosa77556d82012-04-05 15:23:14 -0700314 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700315 statefuldev_url = self.update_url.replace('update',
316 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700317
Dale Curtis5c32c722011-05-04 19:24:23 -0700318 # Attempt stateful partition update; this must succeed so that the newly
319 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700320 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
321 if clobber:
322 statefuldev_cmd.append('--stateful_change=clean')
323
324 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700325 try:
326 self._run(' '.join(statefuldev_cmd), timeout=600)
327 except error.AutoservRunError:
Chris Sosa77556d82012-04-05 15:23:14 -0700328 update_error = StatefulUpdateError('stateful_update failed on %s' %
329 self.host.hostname)
330 self._update_error_queue.put(update_error)
331 raise update_error
Dale Curtis5c32c722011-05-04 19:24:23 -0700332
333
Dan Shi0f466e82013-02-22 15:44:58 -0800334 def run_update(self, force_update, update_root=True):
335 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700336
Dan Shi0f466e82013-02-22 15:44:58 -0800337 @param force_update: True to update DUT even if it's running the same
338 version already.
339 @param update_root: True to force a kernel update. If it's False and
340 force_update is True, stateful update will be used to clean up
341 the DUT.
342
343 """
344 booted_version = self.get_build_id()
345 if (self.check_version() and not force_update):
Dale Curtisa94c19c2011-05-02 15:05:17 -0700346 logging.info('System is already up to date. Skipping update.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200347 return False
348
Chris Sosaa3ac2152012-05-23 22:23:13 -0700349 if self.update_version:
350 logging.info('Updating from version %s to %s.',
351 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700352
Dale Curtis5c32c722011-05-04 19:24:23 -0700353 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200354 # If we can't talk to it, the machine host probably can't either.
355 auserver_host = urlparse.urlparse(self.update_url)[1]
356 try:
357 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700358 except IOError:
359 raise ChromiumOSError(
360 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200361
Chris Sosaa3ac2152012-05-23 22:23:13 -0700362 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700363 self.host.hostname)
364
Chris Sosa5e4246b2012-05-22 18:05:22 -0700365 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700366 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700367 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200368
Dale Curtis1e973182011-07-12 18:21:36 -0700369 try:
Chris Sosa77556d82012-04-05 15:23:14 -0700370 updaters = [
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700371 multiprocessing.process.Process(target=self.update_rootfs),
Chris Sosa72312602013-04-16 15:01:56 -0700372 multiprocessing.process.Process(target=self.update_stateful)
Chris Sosa77556d82012-04-05 15:23:14 -0700373 ]
Dan Shi0f466e82013-02-22 15:44:58 -0800374 if not update_root:
375 logging.info('Root update is skipped.')
376 updaters = updaters[1:]
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200377
Chris Sosa77556d82012-04-05 15:23:14 -0700378 # Run the updaters in parallel.
379 for updater in updaters: updater.start()
380 for updater in updaters: updater.join()
381
382 # Re-raise the first error that occurred.
383 if not self._update_error_queue.empty():
384 update_error = self._update_error_queue.get()
385 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700386 self.reset_stateful_partition()
Chris Sosa77556d82012-04-05 15:23:14 -0700387 raise update_error
Sean Oc053dfe2010-08-23 18:22:26 +0200388
Dale Curtis1e973182011-07-12 18:21:36 -0700389 logging.info('Update complete.')
390 return True
391 except:
392 # Collect update engine logs in the event of failure.
393 if self.host.job:
394 logging.info('Collecting update engine logs...')
395 self.host.get_file(
396 UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
397 preserve_perm=False)
398 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700399 finally:
400 self.host.show_update_engine_log()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200401
402
Dale Curtisa94c19c2011-05-02 15:05:17 -0700403 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800404 """Check the image running in DUT has the desired version.
405
406 @returns: True if the DUT's image version matches the version that
407 the autoupdater tries to update to.
408
409 """
Dale Curtisf57a25f2011-05-24 14:40:55 -0700410 booted_version = self.get_build_id()
Dan Shib95bb862013-03-22 16:29:28 -0700411 return (self.update_version and
412 self.update_version.endswith(booted_version))
413
414
415 def check_version_to_confirm_install(self):
416 """Check image running in DUT has the desired version to be installed.
417
418 The method should not be used to check if DUT needs to have a full
419 reimage. Only use it to confirm a image is installed.
420
Dan Shi190c7802013-04-04 13:05:30 -0700421 The method is designed to verify version for following 4 scenarios with
422 samples of version to update to and expected booted version:
423 1. trybot paladin build.
424 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
425 booted version: 3837.0.2013_03_21_1340
426
427 2. trybot release build.
428 update version: trybot-lumpy-release/R27-3837.0.0-b456
429 booted version: 3837.0.0
430
431 3. buildbot official release build.
432 update version: lumpy-release/R27-3837.0.0
433 booted version: 3837.0.0
434
435 4. non-official paladin rc build.
436 update version: lumpy-paladin/R27-3878.0.0-rc7
437 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700438
Dan Shi7f795512013-04-12 10:08:17 -0700439 5. chrome-perf build.
440 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
441 booted version: 3837.0.0
442
Dan Shi73aa2902013-05-03 11:22:11 -0700443 6. pgo-generate build.
444 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
445 booted version: 3837.0.0-pgo-generate
446
Dan Shib95bb862013-03-22 16:29:28 -0700447 When we are checking if a DUT needs to do a full install, we should NOT
448 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700449 it may return false positive for a DUT running trybot paladin build to
450 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700451
Dan Shi190c7802013-04-04 13:05:30 -0700452 TODO: This logic has a bug if a trybot paladin build failed to be
453 installed in a DUT running an older trybot paladin build with same
454 platform number, but different build number (-b###). So to conclusively
455 determine if a tryjob paladin build is imaged successfully, we may need
456 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700457
458 @returns: True if the DUT's image version (without the date string if
459 the image is a trybot build), matches the version that the
460 autoupdater is trying to update to.
461
462 """
J. Richard Barnetteec1de422013-06-26 15:44:07 -0700463 # In the local_devserver case, we can't know the expected
464 # build, so just pass.
465 if not self.update_version:
466 return True
467
Dan Shib95bb862013-03-22 16:29:28 -0700468 # Always try the default check_version method first, this prevents
469 # any backward compatibility issue.
470 if self.check_version():
471 return True
472
Dan Shi190c7802013-04-04 13:05:30 -0700473 # Remove R#- and -b# at the end of build version
474 stripped_version = re.sub(r'(R\d+-|-b\d+)', '', self.update_version)
475
Dan Shib95bb862013-03-22 16:29:28 -0700476 booted_version = self.get_build_id()
Dan Shi190c7802013-04-04 13:05:30 -0700477
Dan Shi7f795512013-04-12 10:08:17 -0700478 is_trybot_paladin_build = re.match(r'.+trybot-.+-paladin',
479 self.update_url)
Dan Shi190c7802013-04-04 13:05:30 -0700480
Dan Shi7f795512013-04-12 10:08:17 -0700481 # Replace date string with 0 in booted_version
482 booted_version_no_date = re.sub(r'\d{4}_\d{2}_\d{2}_\d+', '0',
483 booted_version)
484 has_date_string = booted_version != booted_version_no_date
485
Dan Shi73aa2902013-05-03 11:22:11 -0700486 is_pgo_generate_build = re.match(r'.+-pgo-generate',
487 self.update_url)
488
489 # Remove |-pgo-generate| in booted_version
490 booted_version_no_pgo = booted_version.replace('-pgo-generate', '')
491 has_pgo_generate = booted_version != booted_version_no_pgo
492
Dan Shi7f795512013-04-12 10:08:17 -0700493 if is_trybot_paladin_build:
494 if not has_date_string:
495 logging.error('A trybot paladin build is expected. Version ' +
496 '"%s" is not a paladin build.', booted_version)
Dan Shi190c7802013-04-04 13:05:30 -0700497 return False
498 return stripped_version == booted_version_no_date
Dan Shi73aa2902013-05-03 11:22:11 -0700499 elif is_pgo_generate_build:
500 if not has_pgo_generate:
501 logging.error('A pgo-generate build is expected. Version ' +
502 '"%s" is not a pgo-generate build.',
503 booted_version)
504 return False
505 return stripped_version == booted_version_no_pgo
Dan Shi7f795512013-04-12 10:08:17 -0700506 else:
507 if has_date_string:
508 logging.error('Unexpected date found in a non trybot paladin' +
509 ' build.')
510 return False
511 # Versioned build, i.e., rc or release build.
512 return stripped_version == booted_version
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200513
Sean Oc053dfe2010-08-23 18:22:26 +0200514
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200515 def get_build_id(self):
Dale Curtis793f9122011-02-04 15:00:52 -0800516 """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release."""
517 return self._run('grep CHROMEOS_RELEASE_VERSION'
518 ' /etc/lsb-release').stdout.split('=')[1].strip()
Chris Sosa65425082013-10-16 13:26:22 -0700519
520
521 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
522 """Verifies that we fully booted given expected kernel state.
523
524 This method both verifies that we booted using the correct kernel
525 state and that the OS has marked the kernel as good.
526
527 @param expected_kernel_state: kernel state that we are verifying with
528 i.e. I expect to be booted onto partition 4 etc. See output of
529 get_kernel_state.
530 @param rollback_message: string to raise as a ChromiumOSError
531 if we booted with the wrong partition.
532
533 @raises ChromiumOSError: If we didn't.
534 """
535 # Figure out the newly active kernel.
536 active_kernel_state = self.get_kernel_state()[0]
537
538 # Check for rollback due to a bad build.
539 if (expected_kernel_state and
540 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800541
542 # Kernel crash reports should be wiped between test runs, but
543 # may persist from earlier parts of the test, or from problems
544 # with provisioning.
545 #
546 # Kernel crash reports will NOT be present if the crash happened
547 # before encrypted stateful is mounted.
548 #
549 # TODO(dgarrett): Integrate with server/crashcollect.py at some
550 # point.
551 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
552 if kernel_crashes:
553 rollback_message += ': kernel_crash'
554 logging.debug('Found %d kernel crash reports:',
555 len(kernel_crashes))
556 # The crash names contain timestamps that may be useful:
557 # kernel.20131207.005945.0.kcrash
558 for crash in kernel_crashes:
559 logging.debug(' %s', os.path.basename(crash))
560
Chris Sosa65425082013-10-16 13:26:22 -0700561 # Print out some information to make it easier to debug
562 # the rollback.
563 logging.debug('Dumping partition table.')
564 self._run('cgpt show $(rootdev -s -d)')
565 logging.debug('Dumping crossystem for firmware debugging.')
566 self._run('crossystem --all')
567 raise ChromiumOSError(rollback_message)
568
569 # Make sure chromeos-setgoodkernel runs.
570 try:
571 utils.poll_for_condition(
572 lambda: (self.get_kernel_tries(active_kernel_state) == 0
573 and self.get_kernel_success(active_kernel_state)),
574 exception=ChromiumOSError(),
575 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
576 except ChromiumOSError:
577 services_status = self._run('status system-services').stdout
578 if services_status != 'system-services start/running\n':
579 event = ('Chrome failed to reach login screen')
580 else:
581 event = ('update-engine failed to call '
582 'chromeos-setgoodkernel')
583 raise ChromiumOSError(
584 'After update and reboot, %s '
585 'within %d seconds' % (event,
586 self.KERNEL_UPDATE_TIMEOUT))