blob: 7748bb1009bc893a39cf8258f9607ac53d6af4c2 [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
Sean O'Connor5346e4e2010-08-12 18:49:24 +020017
Dale Curtis5c32c722011-05-04 19:24:23 -070018# Local stateful update path is relative to the CrOS source directory.
19LOCAL_STATEFUL_UPDATE_PATH = 'src/platform/dev/stateful_update'
Chris Sosaa3ac2152012-05-23 22:23:13 -070020LOCAL_CHROOT_STATEFUL_UPDATE_PATH = '/usr/bin/stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020021UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020022UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
beeps5e8c45a2013-12-17 22:05:11 -080023# A list of update engine client states that occur after an update is triggered.
24UPDATER_PROCESSING_UPDATE = ['UPDATE_STATUS_CHECKING_FORUPDATE',
25 'UPDATE_STATUS_UPDATE_AVAILABLE',
26 'UPDATE_STATUS_DOWNLOADING',
27 'UPDATE_STATUS_FINALIZING']
Sean O'Connor5346e4e2010-08-12 18:49:24 +020028
29class ChromiumOSError(error.InstallError):
30 """Generic error for ChromiumOS-specific exceptions."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -070031
32
33class BrilloError(error.InstallError):
34 """Generic error for Brillo-specific exceptions."""
Sean O'Connor5346e4e2010-08-12 18:49:24 +020035
36
Chris Sosa77556d82012-04-05 15:23:14 -070037class RootFSUpdateError(ChromiumOSError):
38 """Raised when the RootFS fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070039
40
41class StatefulUpdateError(ChromiumOSError):
42 """Raised when the stateful partition fails to update."""
Chris Sosa77556d82012-04-05 15:23:14 -070043
44
Sean O'Connor5346e4e2010-08-12 18:49:24 +020045def url_to_version(update_url):
Dan Shi0f466e82013-02-22 15:44:58 -080046 """Return the version based on update_url.
47
48 @param update_url: url to the image to update to.
49
50 """
Dale Curtisddfdb942011-07-14 13:59:24 -070051 # The Chrome OS version is generally the last element in the URL. The only
52 # exception is delta update URLs, which are rooted under the version; e.g.,
53 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
54 # strip off the au section of the path before reading the version.
Dan Shi5002cfc2013-04-29 10:45:05 -070055 return re.sub('/au/.*', '',
56 urlparse.urlparse(update_url).path).split('/')[-1].strip()
Sean O'Connor5346e4e2010-08-12 18:49:24 +020057
58
Scott Zawalskieadbf702013-03-14 09:23:06 -040059def url_to_image_name(update_url):
60 """Return the image name based on update_url.
61
62 From a URL like:
63 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
64 return lumpy-release/R27-3837.0.0
65
66 @param update_url: url to the image to update to.
67 @returns a string representing the image name in the update_url.
68
69 """
70 return '/'.join(urlparse.urlparse(update_url).path.split('/')[-2:])
71
72
Prashanth B32baa9b2014-03-13 13:23:01 -070073def _get_devserver_build_from_update_url(update_url):
74 """Get the devserver and build from the update url.
75
76 @param update_url: The url for update.
77 Eg: http://devserver:port/update/build.
78
79 @return: A tuple of (devserver url, build) or None if the update_url
80 doesn't match the expected pattern.
81
82 @raises ValueError: If the update_url doesn't match the expected pattern.
83 @raises ValueError: If no global_config was found, or it doesn't contain an
84 image_url_pattern.
85 """
86 pattern = global_config.global_config.get_config_value(
87 'CROS', 'image_url_pattern', type=str, default='')
88 if not pattern:
89 raise ValueError('Cannot parse update_url, the global config needs '
90 'an image_url_pattern.')
91 re_pattern = pattern.replace('%s', '(\S+)')
92 parts = re.search(re_pattern, update_url)
93 if not parts or len(parts.groups()) < 2:
94 raise ValueError('%s is not an update url' % update_url)
95 return parts.groups()
96
97
98def list_image_dir_contents(update_url):
99 """Lists the contents of the devserver for a given build/update_url.
100
101 @param update_url: An update url. Eg: http://devserver:port/update/build.
102 """
103 if not update_url:
104 logging.warning('Need update_url to list contents of the devserver.')
105 return
106 error_msg = 'Cannot check contents of devserver, update url %s' % update_url
107 try:
108 devserver_url, build = _get_devserver_build_from_update_url(update_url)
109 except ValueError as e:
110 logging.warning('%s: %s', error_msg, e)
111 return
112 devserver = dev_server.ImageServer(devserver_url)
113 try:
114 devserver.list_image_dir(build)
115 # The devserver will retry on URLError to avoid flaky connections, but will
116 # eventually raise the URLError if it persists. All HTTPErrors get
117 # converted to DevServerExceptions.
118 except (dev_server.DevServerException, urllib2.URLError) as e:
119 logging.warning('%s: %s', error_msg, e)
120
121
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700122# TODO(garnold) This implements shared updater functionality needed for
123# supporting the autoupdate_EndToEnd server-side test. We should probably
124# migrate more of the existing ChromiumOSUpdater functionality to it as we
125# expand non-CrOS support in other tests.
126class BaseUpdater(object):
127 """Platform-agnostic DUT update functionality."""
128
129 def __init__(self, updater_ctrl_bin, update_url, host):
130 """Initializes the object.
131
132 @param updater_ctrl_bin: Path to update_engine_client.
133 @param update_url: The URL we want the update to use.
134 @param host: A client.common_lib.hosts.Host implementation.
135 """
136 self.updater_ctrl_bin = updater_ctrl_bin
137 self.update_url = update_url
138 self.host = host
139 self._update_error_queue = multiprocessing.Queue(2)
140
141
142 def check_update_status(self):
143 """Returns the current update engine state.
144
145 We use the `update_engine_client -status' command and parse the line
146 indicating the update state, e.g. "CURRENT_OP=UPDATE_STATUS_IDLE".
147 """
148 update_status = self.host.run(
149 '%s -status 2>&1 | grep CURRENT_OP' % self.updater_ctrl_bin)
150 return update_status.stdout.strip().split('=')[-1]
151
152
153 def trigger_update(self):
154 """Triggers a background update.
155
156 @raise RootFSUpdateError if anything went wrong.
157 """
158 autoupdate_cmd = ('%s --check_for_update --omaha_url=%s' %
159 (self.updater_ctrl_bin, self.update_url))
160 err_msg = 'Failed to trigger an update on %s.' % self.host.hostname
161 logging.info('Triggering update via: %s', autoupdate_cmd)
162 try:
163 self.host.run(autoupdate_cmd)
164 except (error.AutoservSshPermissionDeniedError,
165 error.AutoservSSHTimeout) as e:
166 err_msg += ' SSH reports an error: %s' % type(e).__name__
167 raise RootFSUpdateError(err_msg)
168 except error.AutoservRunError as e:
169 # Check if the exit code is 255, if so it's probably a generic
170 # SSH error.
171 result = e.args[1]
172 if result.exit_status == 255:
173 err_msg += (' SSH reports a generic error (255), which could '
174 'indicate a problem with underlying connectivity '
175 'layers.')
176 raise RootFSUpdateError(err_msg)
177
178 # We have ruled out all SSH cases, the error code is from
179 # update_engine_client, though we still don't know why.
180 list_image_dir_contents(self.update_url)
181 err_msg += (' It could be that the devserver is unreachable, the '
182 'payload unavailable, or there is a bug in the update '
183 'engine (unlikely). Reported error: %s' %
184 type(e).__name__)
185 raise RootFSUpdateError(err_msg)
186
187
188 def _verify_update_completed(self):
189 """Verifies that an update has completed.
190
191 @raise RootFSUpdateError: if verification fails.
192 """
193 status = self.check_update_status()
194 if status != UPDATER_NEED_REBOOT:
195 raise RootFSUpdateError('Update did not complete with correct '
196 'status. Expecting %s, actual %s' %
197 (UPDATER_NEED_REBOOT, status))
198
199
200 def update_image(self):
201 """Updates the device image and verifies success."""
202 try:
203 autoupdate_cmd = ('%s --update --omaha_url=%s 2>&1' %
204 (self.updater_ctrl_bin, self.update_url))
205 self.host.run(autoupdate_cmd, timeout=1200)
206 except error.AutoservRunError:
207 list_image_dir_contents(self.update_url)
208 update_error = RootFSUpdateError(
209 'Failed to install device image using update engine on %s' %
210 self.host.hostname)
211 self._update_error_queue.put(update_error)
212 raise update_error
213 except Exception as e:
214 # Don't allow other exceptions to not be caught.
215 self._update_error_queue.put(e)
216 raise e
217
218 try:
219 self._verify_update_completed()
220 except RootFSUpdateError as e:
221 self._update_error_queue.put(e)
222 raise
223
224
225class ChromiumOSUpdater(BaseUpdater):
Dan Shi0f466e82013-02-22 15:44:58 -0800226 """Helper class used to update DUT with image of desired version."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700227 REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update'
228 UPDATER_BIN = '/usr/bin/update_engine_client'
229 STATEFUL_UPDATE = '/tmp/stateful_update'
230 UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
231 UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
232
Dale Curtisa94c19c2011-05-02 15:05:17 -0700233 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
234 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
Chris Sosa65425082013-10-16 13:26:22 -0700235 # Time to wait for new kernel to be marked successful after
236 # auto update.
237 KERNEL_UPDATE_TIMEOUT = 120
Dale Curtisa94c19c2011-05-02 15:05:17 -0700238
239
Chris Sosaa3ac2152012-05-23 22:23:13 -0700240 def __init__(self, update_url, host=None, local_devserver=False):
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700241 super(ChromiumOSUpdater, self).__init__(self.UPDATER_BIN, update_url,
242 host)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700243 self.local_devserver = local_devserver
244 if not local_devserver:
245 self.update_version = url_to_version(update_url)
246 else:
247 self.update_version = None
Sean Oc053dfe2010-08-23 18:22:26 +0200248
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700249
Sean Oc053dfe2010-08-23 18:22:26 +0200250 def reset_update_engine(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700251 """Resets the host to prepare for a clean update regardless of state."""
Gilad Arnold0c0df732015-09-21 06:37:59 -0700252 self._run('rm -f %s' % self.UPDATED_MARKER)
Chris Sosae92399e2015-04-24 11:32:59 -0700253 self._run('stop ui || true')
254 self._run('stop update-engine || true')
255 self._run('start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -0700256
Sean Oc053dfe2010-08-23 18:22:26 +0200257 if self.check_update_status() != UPDATER_IDLE:
258 raise ChromiumOSError('%s is not in an installable state' %
259 self.host.hostname)
260
261
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200262 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -0700263 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200264 return self.host.run(cmd, *args, **kwargs)
265
Sean Oc053dfe2010-08-23 18:22:26 +0200266
Dale Curtisa94c19c2011-05-02 15:05:17 -0700267 def rootdev(self, options=''):
Dan Shi0f466e82013-02-22 15:44:58 -0800268 """Returns the stripped output of rootdev <options>.
269
270 @param options: options to run rootdev.
271
272 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700273 return self._run('rootdev %s' % options).stdout.strip()
274
275
276 def get_kernel_state(self):
277 """Returns the (<active>, <inactive>) kernel state as a pair."""
278 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
279 if active_root == self.KERNEL_A['root']:
280 return self.KERNEL_A, self.KERNEL_B
281 elif active_root == self.KERNEL_B['root']:
282 return self.KERNEL_B, self.KERNEL_A
283 else:
Dale Curtis5c32c722011-05-04 19:24:23 -0700284 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -0700285 active_root)
286
287
288 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
289 """Return numeric cgpt value for the specified flag, kernel, device. """
290 return int(self._run('cgpt show -n -i %d %s %s' % (
291 kernel['kernel'], flag, dev)).stdout.strip())
292
293
294 def get_kernel_priority(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800295 """Return numeric priority for the specified kernel.
296
297 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
298
299 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700300 return self._cgpt('-P', kernel)
301
302
303 def get_kernel_success(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800304 """Return boolean success flag for the specified kernel.
305
306 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
307
308 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700309 return self._cgpt('-S', kernel) != 0
310
311
312 def get_kernel_tries(self, kernel):
Dan Shi0f466e82013-02-22 15:44:58 -0800313 """Return tries count for the specified kernel.
314
315 @param kernel: information of the given kernel, KERNEL_A or KERNEL_B.
316
317 """
Dale Curtisa94c19c2011-05-02 15:05:17 -0700318 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200319
320
Chris Sosa5e4246b2012-05-22 18:05:22 -0700321 def get_stateful_update_script(self):
322 """Returns the path to the stateful update script on the target."""
Chris Sosaa3ac2152012-05-23 22:23:13 -0700323 # We attempt to load the local stateful update path in 3 different
324 # ways. First we use the location specified in the autotest global
325 # config. If this doesn't exist, we attempt to use the Chromium OS
326 # Chroot path to the installed script. If all else fails, we use the
327 # stateful update script on the host.
Chris Sosa5e4246b2012-05-22 18:05:22 -0700328 stateful_update_path = os.path.join(
329 global_config.global_config.get_config_value(
330 'CROS', 'source_tree', default=''),
331 LOCAL_STATEFUL_UPDATE_PATH)
332
Chris Sosaa3ac2152012-05-23 22:23:13 -0700333 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700334 logging.warning('Could not find Chrome OS source location for '
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700335 'stateful_update script at %s, falling back to '
336 'chroot copy.', stateful_update_path)
Chris Sosaa3ac2152012-05-23 22:23:13 -0700337 stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
338
339 if not os.path.exists(stateful_update_path):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700340 logging.warning('Could not chroot stateful_update script, falling '
Gilad Arnold5f2ff442015-09-21 07:06:40 -0700341 'back on client copy.')
Gilad Arnold0c0df732015-09-21 06:37:59 -0700342 statefuldev_script = self.REMOTE_STATEUL_UPDATE_PATH
Chris Sosaa3ac2152012-05-23 22:23:13 -0700343 else:
Chris Sosa5e4246b2012-05-22 18:05:22 -0700344 self.host.send_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700345 stateful_update_path, self.STATEFUL_UPDATE,
346 delete_dest=True)
347 statefuldev_script = self.STATEFUL_UPDATE
Chris Sosa5e4246b2012-05-22 18:05:22 -0700348
349 return statefuldev_script
350
351
352 def reset_stateful_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800353 """Clear any pending stateful update request."""
Chris Sosa5e4246b2012-05-22 18:05:22 -0700354 statefuldev_cmd = [self.get_stateful_update_script()]
355 statefuldev_cmd += ['--stateful_change=reset', '2>&1']
Chris Sosa66d74072013-09-19 11:21:29 -0700356 self._run(' '.join(statefuldev_cmd))
Chris Sosa5e4246b2012-05-22 18:05:22 -0700357
358
Sean O267c00b2010-08-31 15:54:55 +0200359 def revert_boot_partition(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800360 """Revert the boot partition."""
Dale Curtisd9b26b92011-10-24 13:34:46 -0700361 part = self.rootdev('-s')
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700362 logging.warning('Reverting update; Boot partition will be %s', part)
Sean O267c00b2010-08-31 15:54:55 +0200363 return self._run('/postinst %s 2>&1' % part)
364
365
Chris Sosac1932172013-10-16 13:28:53 -0700366 def rollback_rootfs(self, powerwash):
367 """Triggers rollback and waits for it to complete.
368
369 @param powerwash: If true, powerwash as part of rollback.
370
371 @raise RootFSUpdateError if anything went wrong.
372
373 """
Dan Shi549fb822015-03-24 18:01:11 -0700374 version = self.host.get_release_version()
Chris Sosac8617522014-06-09 23:22:26 +0000375 # Introduced can_rollback in M36 (build 5772). # etc/lsb-release matches
376 # X.Y.Z. This version split just pulls the first part out.
377 try:
378 build_number = int(version.split('.')[0])
379 except ValueError:
380 logging.error('Could not parse build number.')
381 build_number = 0
382
383 if build_number >= 5772:
Gilad Arnold0c0df732015-09-21 06:37:59 -0700384 can_rollback_cmd = '%s --can_rollback' % self.UPDATER_BIN
Chris Sosac8617522014-06-09 23:22:26 +0000385 logging.info('Checking for rollback.')
386 try:
387 self._run(can_rollback_cmd)
388 except error.AutoservRunError as e:
389 raise RootFSUpdateError("Rollback isn't possible on %s: %s" %
390 (self.host.hostname, str(e)))
391
Gilad Arnold0c0df732015-09-21 06:37:59 -0700392 rollback_cmd = '%s --rollback --follow' % self.UPDATER_BIN
Chris Sosac1932172013-10-16 13:28:53 -0700393 if not powerwash:
394 rollback_cmd += ' --nopowerwash'
395
Chris Sosac8617522014-06-09 23:22:26 +0000396 logging.info('Performing rollback.')
Chris Sosac1932172013-10-16 13:28:53 -0700397 try:
398 self._run(rollback_cmd)
Chris Sosac1932172013-10-16 13:28:53 -0700399 except error.AutoservRunError as e:
400 raise RootFSUpdateError('Rollback failed on %s: %s' %
401 (self.host.hostname, str(e)))
402
403 self._verify_update_completed()
404
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800405
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700406 # TODO(garnold) This is here for backward compatibility and should be
407 # deprecated once we shift to using update_image() everywhere.
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700408 def update_rootfs(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700409 """Run the standard command to force an update."""
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700410 return self.update_image()
Dale Curtis5c32c722011-05-04 19:24:23 -0700411
412
Chris Sosa72312602013-04-16 15:01:56 -0700413 def update_stateful(self, clobber=True):
414 """Updates the stateful partition.
415
416 @param clobber: If True, a clean stateful installation.
417 """
Chris Sosa77556d82012-04-05 15:23:14 -0700418 logging.info('Updating stateful partition...')
joychen03eaad92013-06-26 09:55:21 -0700419 statefuldev_url = self.update_url.replace('update',
420 'static')
Chris Sosaa3ac2152012-05-23 22:23:13 -0700421
Dale Curtis5c32c722011-05-04 19:24:23 -0700422 # Attempt stateful partition update; this must succeed so that the newly
423 # installed host is testable after update.
Chris Sosa72312602013-04-16 15:01:56 -0700424 statefuldev_cmd = [self.get_stateful_update_script(), statefuldev_url]
425 if clobber:
426 statefuldev_cmd.append('--stateful_change=clean')
427
428 statefuldev_cmd.append('2>&1')
Dale Curtis5c32c722011-05-04 19:24:23 -0700429 try:
430 self._run(' '.join(statefuldev_cmd), timeout=600)
431 except error.AutoservRunError:
Gilad Arnold62cf3a42015-10-01 09:15:25 -0700432 update_error = StatefulUpdateError(
433 'Failed to perform stateful update on %s' %
434 self.host.hostname)
Chris Sosa77556d82012-04-05 15:23:14 -0700435 self._update_error_queue.put(update_error)
436 raise update_error
Simran Basi3b858a22015-03-17 16:23:24 -0700437 except Exception as e:
438 # Don't allow other exceptions to not be caught.
439 self._update_error_queue.put(e)
440 raise e
Dale Curtis5c32c722011-05-04 19:24:23 -0700441
442
Chris Sosae92399e2015-04-24 11:32:59 -0700443 def run_update(self, update_root=True):
Dan Shi0f466e82013-02-22 15:44:58 -0800444 """Update the DUT with image of specific version.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700445
Chris Sosae92399e2015-04-24 11:32:59 -0700446 @param update_root: True to force a rootfs update.
Dan Shi0f466e82013-02-22 15:44:58 -0800447 """
Dan Shi549fb822015-03-24 18:01:11 -0700448 booted_version = self.host.get_release_version()
Chris Sosaa3ac2152012-05-23 22:23:13 -0700449 if self.update_version:
450 logging.info('Updating from version %s to %s.',
451 booted_version, self.update_version)
Dale Curtis53d55862011-05-16 12:17:59 -0700452
Dale Curtis5c32c722011-05-04 19:24:23 -0700453 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200454 # If we can't talk to it, the machine host probably can't either.
455 auserver_host = urlparse.urlparse(self.update_url)[1]
456 try:
457 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700458 except IOError:
459 raise ChromiumOSError(
460 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200461
Chris Sosaa3ac2152012-05-23 22:23:13 -0700462 logging.info('Installing from %s to %s', self.update_url,
Chris Sosa77556d82012-04-05 15:23:14 -0700463 self.host.hostname)
464
Chris Sosa5e4246b2012-05-22 18:05:22 -0700465 # Reset update state.
Chris Sosa77556d82012-04-05 15:23:14 -0700466 self.reset_update_engine()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700467 self.reset_stateful_partition()
Sean Oc053dfe2010-08-23 18:22:26 +0200468
Dale Curtis1e973182011-07-12 18:21:36 -0700469 try:
Chris Sosa77556d82012-04-05 15:23:14 -0700470 updaters = [
Chris Sosa2f1ae9f2013-08-13 10:00:15 -0700471 multiprocessing.process.Process(target=self.update_rootfs),
Chris Sosa72312602013-04-16 15:01:56 -0700472 multiprocessing.process.Process(target=self.update_stateful)
Chris Sosa77556d82012-04-05 15:23:14 -0700473 ]
Dan Shi0f466e82013-02-22 15:44:58 -0800474 if not update_root:
475 logging.info('Root update is skipped.')
476 updaters = updaters[1:]
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200477
Chris Sosa77556d82012-04-05 15:23:14 -0700478 # Run the updaters in parallel.
479 for updater in updaters: updater.start()
480 for updater in updaters: updater.join()
481
482 # Re-raise the first error that occurred.
483 if not self._update_error_queue.empty():
484 update_error = self._update_error_queue.get()
485 self.revert_boot_partition()
Chris Sosa5e4246b2012-05-22 18:05:22 -0700486 self.reset_stateful_partition()
Chris Sosa77556d82012-04-05 15:23:14 -0700487 raise update_error
Sean Oc053dfe2010-08-23 18:22:26 +0200488
Dale Curtis1e973182011-07-12 18:21:36 -0700489 logging.info('Update complete.')
Dale Curtis1e973182011-07-12 18:21:36 -0700490 except:
491 # Collect update engine logs in the event of failure.
492 if self.host.job:
493 logging.info('Collecting update engine logs...')
494 self.host.get_file(
Gilad Arnold0c0df732015-09-21 06:37:59 -0700495 self.UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
496 preserve_perm=False)
Prashanth B32baa9b2014-03-13 13:23:01 -0700497 list_image_dir_contents(self.update_url)
Dale Curtis1e973182011-07-12 18:21:36 -0700498 raise
Dan Shi10e992b2013-08-30 11:02:59 -0700499 finally:
500 self.host.show_update_engine_log()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200501
502
Dale Curtisa94c19c2011-05-02 15:05:17 -0700503 def check_version(self):
Dan Shi0f466e82013-02-22 15:44:58 -0800504 """Check the image running in DUT has the desired version.
505
506 @returns: True if the DUT's image version matches the version that
507 the autoupdater tries to update to.
508
509 """
Dan Shi549fb822015-03-24 18:01:11 -0700510 booted_version = self.host.get_release_version()
Dan Shib95bb862013-03-22 16:29:28 -0700511 return (self.update_version and
512 self.update_version.endswith(booted_version))
513
514
515 def check_version_to_confirm_install(self):
516 """Check image running in DUT has the desired version to be installed.
517
518 The method should not be used to check if DUT needs to have a full
519 reimage. Only use it to confirm a image is installed.
520
Dan Shi549fb822015-03-24 18:01:11 -0700521 The method is designed to verify version for following 6 scenarios with
Dan Shi190c7802013-04-04 13:05:30 -0700522 samples of version to update to and expected booted version:
523 1. trybot paladin build.
524 update version: trybot-lumpy-paladin/R27-3837.0.0-b123
525 booted version: 3837.0.2013_03_21_1340
526
527 2. trybot release build.
528 update version: trybot-lumpy-release/R27-3837.0.0-b456
529 booted version: 3837.0.0
530
531 3. buildbot official release build.
532 update version: lumpy-release/R27-3837.0.0
533 booted version: 3837.0.0
534
535 4. non-official paladin rc build.
536 update version: lumpy-paladin/R27-3878.0.0-rc7
537 booted version: 3837.0.0-rc7
Dan Shib95bb862013-03-22 16:29:28 -0700538
Dan Shi7f795512013-04-12 10:08:17 -0700539 5. chrome-perf build.
540 update version: lumpy-chrome-perf/R28-3837.0.0-b2996
541 booted version: 3837.0.0
542
Dan Shi73aa2902013-05-03 11:22:11 -0700543 6. pgo-generate build.
544 update version: lumpy-release-pgo-generate/R28-3837.0.0-b2996
545 booted version: 3837.0.0-pgo-generate
546
Dan Shib95bb862013-03-22 16:29:28 -0700547 When we are checking if a DUT needs to do a full install, we should NOT
548 use this method to check if the DUT is running the same version, since
Dan Shi190c7802013-04-04 13:05:30 -0700549 it may return false positive for a DUT running trybot paladin build to
550 be updated to another trybot paladin build.
Dan Shib95bb862013-03-22 16:29:28 -0700551
Dan Shi190c7802013-04-04 13:05:30 -0700552 TODO: This logic has a bug if a trybot paladin build failed to be
553 installed in a DUT running an older trybot paladin build with same
554 platform number, but different build number (-b###). So to conclusively
555 determine if a tryjob paladin build is imaged successfully, we may need
556 to find out the date string from update url.
Dan Shib95bb862013-03-22 16:29:28 -0700557
558 @returns: True if the DUT's image version (without the date string if
559 the image is a trybot build), matches the version that the
560 autoupdater is trying to update to.
561
562 """
J. Richard Barnetteec1de422013-06-26 15:44:07 -0700563 # In the local_devserver case, we can't know the expected
564 # build, so just pass.
565 if not self.update_version:
566 return True
567
Dan Shib95bb862013-03-22 16:29:28 -0700568 # Always try the default check_version method first, this prevents
569 # any backward compatibility issue.
570 if self.check_version():
571 return True
572
Dan Shi549fb822015-03-24 18:01:11 -0700573 return utils.version_match(self.update_version,
574 self.host.get_release_version(),
575 self.update_url)
Chris Sosa65425082013-10-16 13:26:22 -0700576
577
578 def verify_boot_expectations(self, expected_kernel_state, rollback_message):
579 """Verifies that we fully booted given expected kernel state.
580
581 This method both verifies that we booted using the correct kernel
582 state and that the OS has marked the kernel as good.
583
584 @param expected_kernel_state: kernel state that we are verifying with
585 i.e. I expect to be booted onto partition 4 etc. See output of
586 get_kernel_state.
587 @param rollback_message: string to raise as a ChromiumOSError
588 if we booted with the wrong partition.
589
590 @raises ChromiumOSError: If we didn't.
591 """
592 # Figure out the newly active kernel.
593 active_kernel_state = self.get_kernel_state()[0]
594
595 # Check for rollback due to a bad build.
596 if (expected_kernel_state and
597 active_kernel_state != expected_kernel_state):
Don Garrett56b1cc82013-12-06 17:49:20 -0800598
599 # Kernel crash reports should be wiped between test runs, but
600 # may persist from earlier parts of the test, or from problems
601 # with provisioning.
602 #
603 # Kernel crash reports will NOT be present if the crash happened
604 # before encrypted stateful is mounted.
605 #
606 # TODO(dgarrett): Integrate with server/crashcollect.py at some
607 # point.
608 kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
609 if kernel_crashes:
610 rollback_message += ': kernel_crash'
611 logging.debug('Found %d kernel crash reports:',
612 len(kernel_crashes))
613 # The crash names contain timestamps that may be useful:
614 # kernel.20131207.005945.0.kcrash
615 for crash in kernel_crashes:
Dan Shi0942b1d2015-03-31 11:07:00 -0700616 logging.debug(' %s', os.path.basename(crash))
Don Garrett56b1cc82013-12-06 17:49:20 -0800617
Chris Sosa65425082013-10-16 13:26:22 -0700618 # Print out some information to make it easier to debug
619 # the rollback.
620 logging.debug('Dumping partition table.')
621 self._run('cgpt show $(rootdev -s -d)')
622 logging.debug('Dumping crossystem for firmware debugging.')
623 self._run('crossystem --all')
624 raise ChromiumOSError(rollback_message)
625
626 # Make sure chromeos-setgoodkernel runs.
627 try:
628 utils.poll_for_condition(
629 lambda: (self.get_kernel_tries(active_kernel_state) == 0
630 and self.get_kernel_success(active_kernel_state)),
631 exception=ChromiumOSError(),
632 timeout=self.KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
633 except ChromiumOSError:
634 services_status = self._run('status system-services').stdout
635 if services_status != 'system-services start/running\n':
636 event = ('Chrome failed to reach login screen')
637 else:
638 event = ('update-engine failed to call '
639 'chromeos-setgoodkernel')
640 raise ChromiumOSError(
641 'After update and reboot, %s '
642 'within %d seconds' % (event,
643 self.KERNEL_UPDATE_TIMEOUT))
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700644
645
646class BrilloUpdater(BaseUpdater):
647 """Helper class for updating a Brillo DUT."""
648
649 def __init__(self, update_url, host=None):
650 """Initialize the object.
651
652 @param update_url: The URL we want the update to use.
653 @param host: A client.common_lib.hosts.Host implementation.
654 """
Gilad Arnoldecf894b2015-10-07 08:48:22 -0700655 super(BrilloUpdater, self).__init__(
Gilad Arnoldd6adeb82015-09-21 07:10:03 -0700656 '/system/bin/update_engine_client', update_url, host)