blob: c183fe5f64089481fdc6261f596f94408682d325 [file] [log] [blame]
Dale Curtis5c32c722011-05-04 19:24:23 -07001# Copyright (c) 2011 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
5import httplib
6import logging
Dale Curtis5c32c722011-05-04 19:24:23 -07007import os
Sean O'Connor5346e4e2010-08-12 18:49:24 +02008import re
Sean O'Connor5346e4e2010-08-12 18:49:24 +02009import urlparse
10
Dale Curtis5c32c722011-05-04 19:24:23 -070011from autotest_lib.client.common_lib import error, global_config
Sean O'Connor5346e4e2010-08-12 18:49:24 +020012
Dale Curtis5c32c722011-05-04 19:24:23 -070013# Local stateful update path is relative to the CrOS source directory.
14LOCAL_STATEFUL_UPDATE_PATH = 'src/platform/dev/stateful_update'
15REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update'
16STATEFUL_UPDATE = '/tmp/stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020017UPDATER_BIN = '/usr/bin/update_engine_client'
18UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020019UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
Darin Petkov7d572992010-09-23 10:11:05 -070020UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
Dale Curtis1e973182011-07-12 18:21:36 -070021UPDATER_LOGS = '/var/log/messages /var/log/update_engine'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020022
23
24class ChromiumOSError(error.InstallError):
25 """Generic error for ChromiumOS-specific exceptions."""
26 pass
27
28
29def url_to_version(update_url):
Dale Curtisddfdb942011-07-14 13:59:24 -070030 # The Chrome OS version is generally the last element in the URL. The only
31 # exception is delta update URLs, which are rooted under the version; e.g.,
32 # http://.../update/.../0.14.755.0/au/0.14.754.0. In this case we want to
33 # strip off the au section of the path before reading the version.
34 return re.sub(
35 '/au/.*', '', urlparse.urlparse(update_url).path).split('/')[-1]
Sean O'Connor5346e4e2010-08-12 18:49:24 +020036
37
38class ChromiumOSUpdater():
Dale Curtisa94c19c2011-05-02 15:05:17 -070039 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
40 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
41
42
43 def __init__(self, update_url, host=None):
Sean O'Connor5346e4e2010-08-12 18:49:24 +020044 self.host = host
45 self.update_url = update_url
46 self.update_version = url_to_version(update_url)
47
Sean Oc053dfe2010-08-23 18:22:26 +020048
Sean O'Connor5346e4e2010-08-12 18:49:24 +020049 def check_update_status(self):
Dale Curtis5c32c722011-05-04 19:24:23 -070050 """Return current status from update-engine."""
51 update_status = self._run(
52 '%s -status 2>&1 | grep CURRENT_OP' % UPDATER_BIN)
Sean O'Connor5346e4e2010-08-12 18:49:24 +020053 return update_status.stdout.strip().split('=')[-1]
54
Sean Oc053dfe2010-08-23 18:22:26 +020055
56 def reset_update_engine(self):
Dale Curtis5c32c722011-05-04 19:24:23 -070057 """Restarts the update-engine service."""
Darin Petkov7d572992010-09-23 10:11:05 -070058 self._run('rm -f %s' % UPDATED_MARKER)
Sean O267c00b2010-08-31 15:54:55 +020059 try:
60 self._run('initctl stop update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -070061 except error.AutoservRunError:
Sean O267c00b2010-08-31 15:54:55 +020062 logging.warn('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +020063 self._run('initctl start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -070064
Sean Oc053dfe2010-08-23 18:22:26 +020065 if self.check_update_status() != UPDATER_IDLE:
66 raise ChromiumOSError('%s is not in an installable state' %
67 self.host.hostname)
68
69
Sean O'Connor5346e4e2010-08-12 18:49:24 +020070 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -070071 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +020072 return self.host.run(cmd, *args, **kwargs)
73
Sean Oc053dfe2010-08-23 18:22:26 +020074
Dale Curtisa94c19c2011-05-02 15:05:17 -070075 def rootdev(self, options=''):
Dale Curtis5c32c722011-05-04 19:24:23 -070076 """Returns the stripped output of rootdev <options>."""
Dale Curtisa94c19c2011-05-02 15:05:17 -070077 return self._run('rootdev %s' % options).stdout.strip()
78
79
80 def get_kernel_state(self):
81 """Returns the (<active>, <inactive>) kernel state as a pair."""
82 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
83 if active_root == self.KERNEL_A['root']:
84 return self.KERNEL_A, self.KERNEL_B
85 elif active_root == self.KERNEL_B['root']:
86 return self.KERNEL_B, self.KERNEL_A
87 else:
Dale Curtis5c32c722011-05-04 19:24:23 -070088 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -070089 active_root)
90
91
92 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
93 """Return numeric cgpt value for the specified flag, kernel, device. """
94 return int(self._run('cgpt show -n -i %d %s %s' % (
95 kernel['kernel'], flag, dev)).stdout.strip())
96
97
98 def get_kernel_priority(self, kernel):
99 """Return numeric priority for the specified kernel."""
100 return self._cgpt('-P', kernel)
101
102
103 def get_kernel_success(self, kernel):
104 """Return boolean success flag for the specified kernel."""
105 return self._cgpt('-S', kernel) != 0
106
107
108 def get_kernel_tries(self, kernel):
109 """Return tries count for the specified kernel."""
110 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200111
112
113 def revert_boot_partition(self):
114 part = self.rootdev()
115 logging.warn('Reverting update; Boot partition will be %s', part)
116 return self._run('/postinst %s 2>&1' % part)
117
118
Dale Curtis5c32c722011-05-04 19:24:23 -0700119 def _update_root(self):
120 # Reset update_engine's state & check that update_engine is idle.
121 self.reset_update_engine()
122
123 # Run update_engine using the specified URL.
124 try:
125 autoupdate_cmd = '%s --update --omaha_url=%s 2>&1' % (
126 UPDATER_BIN, self.update_url)
127 self._run(autoupdate_cmd, timeout=900)
128 except error.AutoservRunError:
129 raise ChromiumOSError('update-engine failed on %s' %
130 self.host.hostname)
131
132 # Check that the installer completed as expected.
133 status = self.check_update_status()
134 if status != UPDATER_NEED_REBOOT:
135 raise ChromiumOSError('update-engine error on %s: %s' %
136 (self.host.hostname, status))
137
138
139 def _update_stateful(self):
140 # Attempt stateful partition update; this must succeed so that the newly
141 # installed host is testable after update.
142 statefuldev_url = self.update_url.replace('update', 'static/archive')
143
144 # Load the Chrome OS source tree location.
145 stateful_update_path = os.path.join(
146 global_config.global_config.get_config_value(
147 'CROS', 'source_tree', default=''),
148 LOCAL_STATEFUL_UPDATE_PATH)
149
150 if os.path.exists(stateful_update_path):
151 self.host.send_file(
152 stateful_update_path, STATEFUL_UPDATE, delete_dest=True)
153 statefuldev_cmd = [STATEFUL_UPDATE]
154 else:
155 logging.warn('Could not find local stateful_update script, falling'
156 ' back on client copy.')
157 statefuldev_cmd = [REMOTE_STATEUL_UPDATE_PATH]
158
159 statefuldev_cmd += [statefuldev_url, '--stateful_change=clean', '2>&1']
160 try:
161 self._run(' '.join(statefuldev_cmd), timeout=600)
162 except error.AutoservRunError:
163 self.revert_boot_partition()
164 raise ChromiumOSError('stateful_update failed on %s' %
165 self.host.hostname)
166
167
Dale Curtisa94c19c2011-05-02 15:05:17 -0700168 def run_update(self, force_update):
Dale Curtisf57a25f2011-05-24 14:40:55 -0700169 booted_version = self.get_build_id()
Dale Curtisa94c19c2011-05-02 15:05:17 -0700170 if booted_version in self.update_version and not force_update:
171 logging.info('System is already up to date. Skipping update.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200172 return False
173
Dale Curtis53d55862011-05-16 12:17:59 -0700174 logging.info(
175 'Updating from version %s to %s.', booted_version,
176 self.update_version)
177
Dale Curtis5c32c722011-05-04 19:24:23 -0700178 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200179 # If we can't talk to it, the machine host probably can't either.
180 auserver_host = urlparse.urlparse(self.update_url)[1]
181 try:
182 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700183 except IOError:
184 raise ChromiumOSError(
185 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200186
Dale Curtis5c32c722011-05-04 19:24:23 -0700187 logging.info(
188 'Installing from %s to: %s', self.update_url, self.host.hostname)
Sean Oc053dfe2010-08-23 18:22:26 +0200189
Dale Curtis1e973182011-07-12 18:21:36 -0700190 try:
191 logging.info('Updating root partition...')
192 self._update_root()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200193
Dale Curtis1e973182011-07-12 18:21:36 -0700194 logging.info('Updating stateful partition...')
195 self._update_stateful()
Sean Oc053dfe2010-08-23 18:22:26 +0200196
Dale Curtis1e973182011-07-12 18:21:36 -0700197 logging.info('Update complete.')
198 return True
199 except:
200 # Collect update engine logs in the event of failure.
201 if self.host.job:
202 logging.info('Collecting update engine logs...')
203 self.host.get_file(
204 UPDATER_LOGS, self.host.job.sysinfo.sysinfodir,
205 preserve_perm=False)
206 raise
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200207
208
Dale Curtisa94c19c2011-05-02 15:05:17 -0700209 def check_version(self):
Dale Curtisf57a25f2011-05-24 14:40:55 -0700210 booted_version = self.get_build_id()
Sean O267c00b2010-08-31 15:54:55 +0200211 if not booted_version in self.update_version:
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200212 logging.error('Expected Chromium OS version: %s.'
213 'Found Chromium OS %s',
Sean O267c00b2010-08-31 15:54:55 +0200214 self.update_version, booted_version)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200215 raise ChromiumOSError('Updater failed on host %s' %
216 self.host.hostname)
217 else:
218 return True
219
Sean Oc053dfe2010-08-23 18:22:26 +0200220
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200221 def get_build_id(self):
Dale Curtis793f9122011-02-04 15:00:52 -0800222 """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release."""
223 return self._run('grep CHROMEOS_RELEASE_VERSION'
224 ' /etc/lsb-release').stdout.split('=')[1].strip()