blob: d16ab199b01ad8d5224cef2942317938953eaf09 [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'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020026
27
28class ChromiumOSError(error.InstallError):
29 """Generic error for ChromiumOS-specific exceptions."""
30 pass
31
32
Chris Sosa77556d82012-04-05 15:23:14 -070033class RootFSUpdateError(ChromiumOSError):
34 """Raised when the RootFS fails to update."""
35 pass
36
37
38class StatefulUpdateError(ChromiumOSError):
39 """Raised when the stateful partition fails to update."""
40 pass
41
42
Sean O'Connor5346e4e2010-08-12 18:49:24 +020043def url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -080044 """Return the version based on update_url.
45
46 @param update_url: url to the image to update to.
47
48 """
Dale Curtisddfdb942011-07-14 13:59:24 -070049 # The Chrome OS version is generally the last element in the URL. The only
50 # exception is delta update URLs, which are rooted under the version; e.g.,
51 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
52 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -070053 return re.sub('/au/.*', '',
54 urlparse.urlparse(update_url).path).split('/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +020055
56
Scott Zawalskieadbf702013-03-14 09:23:06 -040057def url_to_image_name(update_url):
58 """Return the image name based on update_url.
59
60 From a URL like:
61 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
62 return lumpy-release/R27-3837.0.0
63
64 @param update_url: url to the image to update to.
65 @returns a string representing the image name in the update_url.
66
67 """
68 return '/'.join(urlparse.urlparse(update_url).path.split('/')[-2:])
69
70
Sean O'Connor5346e4e2010-08-12 18:49:24 +020071class ChromiumOSUpdater():
Dan Shi0f466e82013-02-22 15:44:58 -080072 """Helper class used to update DUT with image of desired version."""
Dale Curtisa94c19c2011-05-02 15:05:17 -070073 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
74 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
Chris Sosa65425082013-10-16 13:26:22 -070075 # Time to wait for new kernel to be marked successful after
76 # auto update.
77 KERNEL_UPDATE_TIMEOUT = 120
Dale Curtisa94c19c2011-05-02 15:05:17 -070078
79
Chris Sosaa3ac2152012-05-23 22:23:13 -070080 def __init__(self, update_url, host=None, local_devserver=False):
Sean O'Connor5346e4e2010-08-12 18:49:24 +020081 self.host = host
82 self.update_url = update_url
Chris Sosa77556d82012-04-05 15:23:14 -070083 self._update_error_queue = multiprocessing.Queue(2)
Chris Sosaa3ac2152012-05-23 22:23:13 -070084 self.local_devserver = local_devserver
85 if not local_devserver:
86 self.update_version = url_to_version(update_url)
87 else:
88 self.update_version = None
Sean Oc053dfe2010-08-23 18:22:26 +020089
Sean O'Connor5346e4e2010-08-12 18:49:24 +020090 def check_update_status(self):
Dale Curtis5c32c722011-05-04 19:24:23 -070091 """Return current status from update-engine."""
92 update_status = self._run(
93 '%s -status 2>&1 | grep CURRENT_OP' % UPDATER_BIN)
Sean O'Connor5346e4e2010-08-12 18:49:24 +020094 return update_status.stdout.strip().split('=')[-1]
95
Sean Oc053dfe2010-08-23 18:22:26 +020096
97 def reset_update_engine(self):
Dale Curtis5c32c722011-05-04 19:24:23 -070098 """Restarts the update-engine service."""
Darin Petkov7d572992010-09-23 10:11:05 -070099 self._run('rm -f %s' % UPDATED_MARKER)
Sean O267c00b2010-08-31 15:54:55 +0200100 try:
101 self._run('initctl stop update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700102 except error.AutoservRunError:
Sean O267c00b2010-08-31 15:54:55 +0200103 logging.warn('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +0200104 self._run('initctl start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700105
Sean Oc053dfe2010-08-23 18:22:26 +0200106 if self.check_update_status() != UPDATER_IDLE:
107 raise ChromiumOSError('%s is not in an installable state' %
108 self.host.hostname)
109
110
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200111 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700112 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200113 return self.host.run(cmd, *args, **kwargs)
114
Sean Oc053dfe2010-08-23 18:22:26 +0200115
Dale Curtisa94c19c2011-05-02 15:05:17 -0700116 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800117 """Returns the stripped output of rootdev <options>.
118
119 @param options: options to run rootdev.
120
121 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700122 return self._run('rootdev %s' % options).stdout.strip()
123
124
125 def get_kernel_state(self):
126 """Returns the (<active>, <inactive>) kernel state as a pair."""
127 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
128 if active_root == self.KERNEL_A['root']:
129 return self.KERNEL_A, self.KERNEL_B
130 elif active_root == self.KERNEL_B['root']:
131 return self.KERNEL_B, self.KERNEL_A
132 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700133 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700134 active_root)
135
136
137 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
138 """Return numeric cgpt value for the specified flag, kernel, device. """
139 return int(self._run('cgpt show -n -i %d %s %s' % (
140 kernel['kernel'], flag, dev)).stdout.strip())
141
142
143 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800144 """Return numeric priority for the specified kernel.
145
146 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
147
148 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700149 return self._cgpt('-P', kernel)
150
151
152 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800153 """Return boolean success flag for the specified kernel.
154
155 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
156
157 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700158 return self._cgpt('-S', kernel) != 0
159
160
161 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800162 """Return tries count for the specified kernel.
163
164 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
165
166 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700167 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200168
169
Chris Sosa5e4246b2012-05-22 18:05:22 -0700170 def get_stateful_update_script(self):
171 """Returns the path to the stateful update script on the target."""
Chris Sosaa3ac2152012-05-23 22:23:13 -0700172 # We attempt to load the local stateful update path in 3 different
173 # ways. First we use the location specified in the autotest global
174 # config. If this doesn't exist, we attempt to use the Chromium OS
175 # Chroot path to the installed script. If all else fails, we use the
176 # stateful update script on the host.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700177 stateful_update_path = os.path.join(
178 global_config.global_config.get_config_value(
179 'CROS', 'source_tree', default=''),
180 LOCAL_STATEFUL_UPDATE_PATH)
181
Chris Sosaa3ac2152012-05-23 22:23:13 -0700182 if not os.path.exists(stateful_update_path):
183 logging.warn('Could not find Chrome OS source location for '
184 'stateful_update script at %s, falling back to chroot '
185 'copy.', stateful_update_path)
186 stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
187
188 if not os.path.exists(stateful_update_path):
189 logging.warn('Could not chroot stateful_update script, falling '
190 'back on client copy.')
191 statefuldev_script = REMOTE_STATEUL_UPDATE_PATH
192 else:
Chris Sosa5e4246b2012-05-22 18:05:22 -0700193 self.host.send_file(
194 stateful_update_path, STATEFUL_UPDATE, delete_dest=True)
195 statefuldev_script = STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700196
197 return statefuldev_script
198
199
200 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800201 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700202 statefuldev_cmd = [self.get_stateful_update_script()]
203 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700204 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700205
206
Sean O267c00b2010-08-31 15:54:55 +0200207 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800208 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700209 part = self.rootdev('-s')
Sean O267c00b2010-08-31 15:54:55 +0200210 logging.warn('Reverting update; Boot partition will be %s', part)
211 return self._run('/postinst %s 2>&1' % part)
212
213
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800214 def trigger_update(self):
215 """Triggers a background update on a test image.
216
217 @raise RootFSUpdateError if anything went wrong.
218
219 """
220 autoupdate_cmd = '%s --check_for_update --omaha_url=%s' % (
221 UPDATER_BIN, self.update_url)
Gilad Arnold0338ff32013-10-02 12:16:26 -0700222 logging.info('Triggering update via: %s', autoupdate_cmd)
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800223 try:
Chris Sosa66d74072013-09-19 11:21:29 -0700224 self._run(autoupdate_cmd)
Don Garrettdf8aef72013-12-16 11:12:41 -0800225 except (error.AutoservSshPermissionDeniedError,
226 error.AutoservSSHTimeout) as e:
227 raise RootFSUpdateError('SSH on %s is seeing %s' %
228 (self.host.hostname, type(e).__name__))
229 except error.AutoservRunError as e:
230
231 # Check if the exit code is 255, if so it's probably a generic
232 # SSH error.
233 result = e.args[1]
234 if result.exit_status == 255:
235 raise RootFSUpdateError('SSH on %s is seeing a generic error.' %
236 self.host.hostname)
237
238 # We have ruled out all SSH cases, the error code is from
239 # update_engine_client, though we still don't know why.
240 raise RootFSUpdateError(
241 'devserver unreachable, payload unavailable, '
242 'or AU bug (unlikely) on %s: %s' %
243 (self.host.hostname, type(e).__name__))
244
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800245
Chris Sosac1932172013-10-16 13:28:53 -0700246 def _verify_update_completed(self):
247 """Verifies that an update has completed.
248
249 @raise RootFSUpdateError: if verification fails.
250 """
251 status = self.check_update_status()
252 if status != UPDATER_NEED_REBOOT:
253 raise RootFSUpdateError('Update did not complete with correct '
254 'status. Expecting %s, actual %s' %
255 (UPDATER_NEED_REBOOT, status))
256
257
258 def rollback_rootfs(self, powerwash):
259 """Triggers rollback and waits for it to complete.
260
261 @param powerwash: If true, powerwash as part of rollback.
262
263 @raise RootFSUpdateError if anything went wrong.
264
265 """
266 #TODO(sosa): crbug.com/309051 - Make this one update_engine_client call.
267 rollback_cmd = '%s --rollback' % (UPDATER_BIN)
268 wait_for_update_to_complete_cmd = '%s --update' % (UPDATER_BIN)
269 if not powerwash:
270 rollback_cmd += ' --nopowerwash'
271
272 logging.info('Triggering rollback.')
273 try:
274 self._run(rollback_cmd)
275 self._run(wait_for_update_to_complete_cmd)
276 except error.AutoservRunError as e:
277 raise RootFSUpdateError('Rollback failed on %s: %s' %
278 (self.host.hostname, str(e)))
279
280 self._verify_update_completed()
281
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800282
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700283 def update_rootfs(self):
284 """Updates the rootfs partition only."""
Chris Sosa77556d82012-04-05 15:23:14 -0700285 logging.info('Updating root partition...')
Dale Curtis5c32c722011-05-04 19:24:23 -0700286
287 # Run update_engine using the specified URL.
288 try:
289 autoupdate_cmd = '%s --update --omaha_url=%s 2>&1' % (
290 UPDATER_BIN, self.update_url)
291 self._run(autoupdate_cmd, timeout=900)
292 except error.AutoservRunError:
Chris Sosa77556d82012-04-05 15:23:14 -0700293 update_error = RootFSUpdateError('update-engine failed on %s' %
294 self.host.hostname)
295 self._update_error_queue.put(update_error)
296 raise update_error
Dale Curtis5c32c722011-05-04 19:24:23 -0700297
Chris Sosac1932172013-10-16 13:28:53 -0700298 try:
299 self._verify_update_completed()
300 except RootFSUpdateError as e:
301 self._update_error_queue.put(e)
302 raise
Dale Curtis5c32c722011-05-04 19:24:23 -0700303
304
Chris Sosa72312602013-04-16 15:01:56 -0700305 def update_stateful(self, clobber=True):
306 """Updates the stateful partition.
307
308 @param clobber: If True, a clean stateful installation.
309 """
Chris Sosa77556d82012-04-05 15:23:14 -0700310 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700311 statefuldev_url = self.update_url.replace('update',
312 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700313
Dale Curtis5c32c722011-05-04 19:24:23 -0700314 # Attempt stateful partition update; this must succeed so that the newly
315 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700316 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
317 if clobber:
318 statefuldev_cmd.append('--stateful_change=clean')
319
320 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700321 try:
322 self._run(' '.join(statefuldev_cmd), timeout=600)
323 except error.AutoservRunError:
Chris Sosa77556d82012-04-05 15:23:14 -0700324 update_error = StatefulUpdateError('stateful_update failed on %s' %
325 self.host.hostname)
326 self._update_error_queue.put(update_error)
327 raise update_error
Dale Curtis5c32c722011-05-04 19:24:23 -0700328
329
Dan Shi0f466e82013-02-22 15:44:58 -0800330 def run_update(self, force_update, update_root=True):
331 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700332
Dan Shi0f466e82013-02-22 15:44:58 -0800333 @param force_update: True to update DUT even if it's running the same
334 version already.
335 @param update_root: True to force a kernel update. If it's False and
336 force_update is True, stateful update will be used to clean up
337 the DUT.
338
339 """
340 booted_version = self.get_build_id()
341 if (self.check_version() and not force_update):
Dale Curtisa94c19c2011-05-02 15:05:17 -0700342 logging.info('System is already up to date. Skipping update.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200343 return False
344
Chris Sosaa3ac2152012-05-23 22:23:13 -0700345 if self.update_version:
346 logging.info('Updating from version %s to %s.',
347 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700348
Dale Curtis5c32c722011-05-04 19:24:23 -0700349 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200350 # If we can't talk to it, the machine host probably can't either.
351 auserver_host = urlparse.urlparse(self.update_url)[1]
352 try:
353 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700354 except IOError:
355 raise ChromiumOSError(
356 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200357
Chris Sosaa3ac2152012-05-23 22:23:13 -0700358 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700359 self.host.hostname)
360
Chris Sosa5e4246b2012-05-22 18:05:22 -0700361 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700362 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700363 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200364
Dale Curtis1e973182011-07-12 18:21:36 -0700365 try:
Chris Sosa77556d82012-04-05 15:23:14 -0700366 updaters = [
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700367 multiprocessing.process.Process(target=self.update_rootfs),
Chris Sosa72312602013-04-16 15:01:56 -0700368 multiprocessing.process.Process(target=self.update_stateful)
Chris Sosa77556d82012-04-05 15:23:14 -0700369 ]
Dan Shi0f466e82013-02-22 15:44:58 -0800370 if not update_root:
371 logging.info('Root update is skipped.')
372 updaters = updaters[1:]
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200373
Chris Sosa77556d82012-04-05 15:23:14 -0700374 # Run the updaters in parallel.
375 for updater in updaters: updater.start()
376 for updater in updaters: updater.join()
377
378 # Re-raise the first error that occurred.
379 if not self._update_error_queue.empty():
380 update_error = self._update_error_queue.get()
381 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700382 self.reset_stateful_partition()
Chris Sosa77556d82012-04-05 15:23:14 -0700383 raise update_error
Sean Oc053dfe2010-08-23 18:22:26 +0200384
Dale Curtis1e973182011-07-12 18:21:36 -0700385 logging.info('Update complete.')
386 return True
387 except:
388 # Collect update engine logs in the event of failure.
389 if self.host.job:
390 logging.info('Collecting update engine logs...')
391 self.host.get_file(
392 UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
393 preserve_perm=False)
394 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700395 finally:
396 self.host.show_update_engine_log()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200397
398
Dale Curtisa94c19c2011-05-02 15:05:17 -0700399 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800400 """Check the image running in DUT has the desired version.
401
402 @returns: True if the DUT's image version matches the version that
403 the autoupdater tries to update to.
404
405 """
Dale Curtisf57a25f2011-05-24 14:40:55 -0700406 booted_version = self.get_build_id()
Dan Shib95bb862013-03-22 16:29:28 -0700407 return (self.update_version and
408 self.update_version.endswith(booted_version))
409
410
411 def check_version_to_confirm_install(self):
412 """Check image running in DUT has the desired version to be installed.
413
414 The method should not be used to check if DUT needs to have a full
415 reimage. Only use it to confirm a image is installed.
416
Dan Shi190c7802013-04-04 13:05:30 -0700417 The method is designed to verify version for following 4 scenarios with
418 samples of version to update to and expected booted version:
419 1. trybot paladin build.
420 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
421 booted version: 3837.0.2013_03_21_1340
422
423 2. trybot release build.
424 update version: trybot-lumpy-release/R27-3837.0.0-b456
425 booted version: 3837.0.0
426
427 3. buildbot official release build.
428 update version: lumpy-release/R27-3837.0.0
429 booted version: 3837.0.0
430
431 4. non-official paladin rc build.
432 update version: lumpy-paladin/R27-3878.0.0-rc7
433 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700434
Dan Shi7f795512013-04-12 10:08:17 -0700435 5. chrome-perf build.
436 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
437 booted version: 3837.0.0
438
Dan Shi73aa2902013-05-03 11:22:11 -0700439 6. pgo-generate build.
440 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
441 booted version: 3837.0.0-pgo-generate
442
Dan Shib95bb862013-03-22 16:29:28 -0700443 When we are checking if a DUT needs to do a full install, we should NOT
444 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700445 it may return false positive for a DUT running trybot paladin build to
446 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700447
Dan Shi190c7802013-04-04 13:05:30 -0700448 TODO: This logic has a bug if a trybot paladin build failed to be
449 installed in a DUT running an older trybot paladin build with same
450 platform number, but different build number (-b###). So to conclusively
451 determine if a tryjob paladin build is imaged successfully, we may need
452 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700453
454 @returns: True if the DUT's image version (without the date string if
455 the image is a trybot build), matches the version that the
456 autoupdater is trying to update to.
457
458 """
J. Richard Barnetteec1de422013-06-26 15:44:07 -0700459 # In the local_devserver case, we can't know the expected
460 # build, so just pass.
461 if not self.update_version:
462 return True
463
Dan Shib95bb862013-03-22 16:29:28 -0700464 # Always try the default check_version method first, this prevents
465 # any backward compatibility issue.
466 if self.check_version():
467 return True
468
Dan Shi190c7802013-04-04 13:05:30 -0700469 # Remove R#- and -b# at the end of build version
470 stripped_version = re.sub(r'(R\d+-|-b\d+)', '', self.update_version)
471
Dan Shib95bb862013-03-22 16:29:28 -0700472 booted_version = self.get_build_id()
Dan Shi190c7802013-04-04 13:05:30 -0700473
Dan Shi7f795512013-04-12 10:08:17 -0700474 is_trybot_paladin_build = re.match(r'.+trybot-.+-paladin',
475 self.update_url)
Dan Shi190c7802013-04-04 13:05:30 -0700476
Dan Shi7f795512013-04-12 10:08:17 -0700477 # Replace date string with 0 in booted_version
478 booted_version_no_date = re.sub(r'\d{4}_\d{2}_\d{2}_\d+', '0',
479 booted_version)
480 has_date_string = booted_version != booted_version_no_date
481
Dan Shi73aa2902013-05-03 11:22:11 -0700482 is_pgo_generate_build = re.match(r'.+-pgo-generate',
483 self.update_url)
484
485 # Remove |-pgo-generate| in booted_version
486 booted_version_no_pgo = booted_version.replace('-pgo-generate', '')
487 has_pgo_generate = booted_version != booted_version_no_pgo
488
Dan Shi7f795512013-04-12 10:08:17 -0700489 if is_trybot_paladin_build:
490 if not has_date_string:
491 logging.error('A trybot paladin build is expected. Version ' +
492 '"%s" is not a paladin build.', booted_version)
Dan Shi190c7802013-04-04 13:05:30 -0700493 return False
494 return stripped_version == booted_version_no_date
Dan Shi73aa2902013-05-03 11:22:11 -0700495 elif is_pgo_generate_build:
496 if not has_pgo_generate:
497 logging.error('A pgo-generate build is expected. Version ' +
498 '"%s" is not a pgo-generate build.',
499 booted_version)
500 return False
501 return stripped_version == booted_version_no_pgo
Dan Shi7f795512013-04-12 10:08:17 -0700502 else:
503 if has_date_string:
504 logging.error('Unexpected date found in a non trybot paladin' +
505 ' build.')
506 return False
507 # Versioned build, i.e., rc or release build.
508 return stripped_version == booted_version
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200509
Sean Oc053dfe2010-08-23 18:22:26 +0200510
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200511 def get_build_id(self):
Dale Curtis793f9122011-02-04 15:00:52 -0800512 """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release."""
513 return self._run('grep CHROMEOS_RELEASE_VERSION'
514 ' /etc/lsb-release').stdout.split('=')[1].strip()
Chris Sosa65425082013-10-16 13:26:22 -0700515
516
517 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
518 """Verifies that we fully booted given expected kernel state.
519
520 This method both verifies that we booted using the correct kernel
521 state and that the OS has marked the kernel as good.
522
523 @param expected_kernel_state: kernel state that we are verifying with
524 i.e. I expect to be booted onto partition 4 etc. See output of
525 get_kernel_state.
526 @param rollback_message: string to raise as a ChromiumOSError
527 if we booted with the wrong partition.
528
529 @raises ChromiumOSError: If we didn't.
530 """
531 # Figure out the newly active kernel.
532 active_kernel_state = self.get_kernel_state()[0]
533
534 # Check for rollback due to a bad build.
535 if (expected_kernel_state and
536 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800537
538 # Kernel crash reports should be wiped between test runs, but
539 # may persist from earlier parts of the test, or from problems
540 # with provisioning.
541 #
542 # Kernel crash reports will NOT be present if the crash happened
543 # before encrypted stateful is mounted.
544 #
545 # TODO(dgarrett): Integrate with server/crashcollect.py at some
546 # point.
547 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
548 if kernel_crashes:
549 rollback_message += ': kernel_crash'
550 logging.debug('Found %d kernel crash reports:',
551 len(kernel_crashes))
552 # The crash names contain timestamps that may be useful:
553 # kernel.20131207.005945.0.kcrash
554 for crash in kernel_crashes:
555 logging.debug(' %s', os.path.basename(crash))
556
Chris Sosa65425082013-10-16 13:26:22 -0700557 # Print out some information to make it easier to debug
558 # the rollback.
559 logging.debug('Dumping partition table.')
560 self._run('cgpt show $(rootdev -s -d)')
561 logging.debug('Dumping crossystem for firmware debugging.')
562 self._run('crossystem --all')
563 raise ChromiumOSError(rollback_message)
564
565 # Make sure chromeos-setgoodkernel runs.
566 try:
567 utils.poll_for_condition(
568 lambda: (self.get_kernel_tries(active_kernel_state) == 0
569 and self.get_kernel_success(active_kernel_state)),
570 exception=ChromiumOSError(),
571 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
572 except ChromiumOSError:
573 services_status = self._run('status system-services').stdout
574 if services_status != 'system-services start/running\n':
575 event = ('Chrome failed to reach login screen')
576 else:
577 event = ('update-engine failed to call '
578 'chromeos-setgoodkernel')
579 raise ChromiumOSError(
580 'After update and reboot, %s '
581 'within %d seconds' % (event,
582 self.KERNEL_UPDATE_TIMEOUT))