blob: f2abd31f2c7e098ba23b7db08e51d2a9d6e56663 [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'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020021
22
23class ChromiumOSError(error.InstallError):
24 """Generic error for ChromiumOS-specific exceptions."""
25 pass
26
27
28def url_to_version(update_url):
29 # The ChromiumOS updater respects the last element in the path as
30 # the requested version. Parse it out.
31 return urlparse.urlparse(update_url).path.split('/')[-1]
32
33
34class ChromiumOSUpdater():
Dale Curtisa94c19c2011-05-02 15:05:17 -070035 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
36 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
37
38
39 def __init__(self, update_url, host=None):
Sean O'Connor5346e4e2010-08-12 18:49:24 +020040 self.host = host
41 self.update_url = update_url
42 self.update_version = url_to_version(update_url)
43
Sean Oc053dfe2010-08-23 18:22:26 +020044
Sean O'Connor5346e4e2010-08-12 18:49:24 +020045 def check_update_status(self):
Dale Curtis5c32c722011-05-04 19:24:23 -070046 """Return current status from update-engine."""
47 update_status = self._run(
48 '%s -status 2>&1 | grep CURRENT_OP' % UPDATER_BIN)
Sean O'Connor5346e4e2010-08-12 18:49:24 +020049 return update_status.stdout.strip().split('=')[-1]
50
Sean Oc053dfe2010-08-23 18:22:26 +020051
52 def reset_update_engine(self):
Dale Curtis5c32c722011-05-04 19:24:23 -070053 """Restarts the update-engine service."""
Darin Petkov7d572992010-09-23 10:11:05 -070054 self._run('rm -f %s' % UPDATED_MARKER)
Sean O267c00b2010-08-31 15:54:55 +020055 try:
56 self._run('initctl stop update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -070057 except error.AutoservRunError:
Sean O267c00b2010-08-31 15:54:55 +020058 logging.warn('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +020059 self._run('initctl start update-engine')
Dale Curtis5c32c722011-05-04 19:24:23 -070060
Sean Oc053dfe2010-08-23 18:22:26 +020061 if self.check_update_status() != UPDATER_IDLE:
62 raise ChromiumOSError('%s is not in an installable state' %
63 self.host.hostname)
64
65
Sean O'Connor5346e4e2010-08-12 18:49:24 +020066 def _run(self, cmd, *args, **kwargs):
Dale Curtis5c32c722011-05-04 19:24:23 -070067 """Abbreviated form of self.host.run(...)"""
Sean O'Connor5346e4e2010-08-12 18:49:24 +020068 return self.host.run(cmd, *args, **kwargs)
69
Sean Oc053dfe2010-08-23 18:22:26 +020070
Dale Curtisa94c19c2011-05-02 15:05:17 -070071 def rootdev(self, options=''):
Dale Curtis5c32c722011-05-04 19:24:23 -070072 """Returns the stripped output of rootdev <options>."""
Dale Curtisa94c19c2011-05-02 15:05:17 -070073 return self._run('rootdev %s' % options).stdout.strip()
74
75
76 def get_kernel_state(self):
77 """Returns the (<active>, <inactive>) kernel state as a pair."""
78 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
79 if active_root == self.KERNEL_A['root']:
80 return self.KERNEL_A, self.KERNEL_B
81 elif active_root == self.KERNEL_B['root']:
82 return self.KERNEL_B, self.KERNEL_A
83 else:
Dale Curtis5c32c722011-05-04 19:24:23 -070084 raise ChromiumOSError('Encountered unknown root partition: %s' %
Dale Curtisa94c19c2011-05-02 15:05:17 -070085 active_root)
86
87
88 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
89 """Return numeric cgpt value for the specified flag, kernel, device. """
90 return int(self._run('cgpt show -n -i %d %s %s' % (
91 kernel['kernel'], flag, dev)).stdout.strip())
92
93
94 def get_kernel_priority(self, kernel):
95 """Return numeric priority for the specified kernel."""
96 return self._cgpt('-P', kernel)
97
98
99 def get_kernel_success(self, kernel):
100 """Return boolean success flag for the specified kernel."""
101 return self._cgpt('-S', kernel) != 0
102
103
104 def get_kernel_tries(self, kernel):
105 """Return tries count for the specified kernel."""
106 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200107
108
109 def revert_boot_partition(self):
110 part = self.rootdev()
111 logging.warn('Reverting update; Boot partition will be %s', part)
112 return self._run('/postinst %s 2>&1' % part)
113
114
Dale Curtis5c32c722011-05-04 19:24:23 -0700115 def _update_root(self):
116 # Reset update_engine's state & check that update_engine is idle.
117 self.reset_update_engine()
118
119 # Run update_engine using the specified URL.
120 try:
121 autoupdate_cmd = '%s --update --omaha_url=%s 2>&1' % (
122 UPDATER_BIN, self.update_url)
123 self._run(autoupdate_cmd, timeout=900)
124 except error.AutoservRunError:
125 raise ChromiumOSError('update-engine failed on %s' %
126 self.host.hostname)
127
128 # Check that the installer completed as expected.
129 status = self.check_update_status()
130 if status != UPDATER_NEED_REBOOT:
131 raise ChromiumOSError('update-engine error on %s: %s' %
132 (self.host.hostname, status))
133
134
135 def _update_stateful(self):
136 # Attempt stateful partition update; this must succeed so that the newly
137 # installed host is testable after update.
138 statefuldev_url = self.update_url.replace('update', 'static/archive')
139
140 # Load the Chrome OS source tree location.
141 stateful_update_path = os.path.join(
142 global_config.global_config.get_config_value(
143 'CROS', 'source_tree', default=''),
144 LOCAL_STATEFUL_UPDATE_PATH)
145
146 if os.path.exists(stateful_update_path):
147 self.host.send_file(
148 stateful_update_path, STATEFUL_UPDATE, delete_dest=True)
149 statefuldev_cmd = [STATEFUL_UPDATE]
150 else:
151 logging.warn('Could not find local stateful_update script, falling'
152 ' back on client copy.')
153 statefuldev_cmd = [REMOTE_STATEUL_UPDATE_PATH]
154
155 statefuldev_cmd += [statefuldev_url, '--stateful_change=clean', '2>&1']
156 try:
157 self._run(' '.join(statefuldev_cmd), timeout=600)
158 except error.AutoservRunError:
159 self.revert_boot_partition()
160 raise ChromiumOSError('stateful_update failed on %s' %
161 self.host.hostname)
162
163
Dale Curtisa94c19c2011-05-02 15:05:17 -0700164 def run_update(self, force_update):
Dale Curtisf57a25f2011-05-24 14:40:55 -0700165 booted_version = self.get_build_id()
Dale Curtisa94c19c2011-05-02 15:05:17 -0700166 if booted_version in self.update_version and not force_update:
167 logging.info('System is already up to date. Skipping update.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200168 return False
169
Dale Curtis53d55862011-05-16 12:17:59 -0700170 logging.info(
171 'Updating from version %s to %s.', booted_version,
172 self.update_version)
173
Dale Curtis5c32c722011-05-04 19:24:23 -0700174 # Check that Dev Server is accepting connections (from autoserv's host).
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200175 # If we can't talk to it, the machine host probably can't either.
176 auserver_host = urlparse.urlparse(self.update_url)[1]
177 try:
178 httplib.HTTPConnection(auserver_host).connect()
Dale Curtis5c32c722011-05-04 19:24:23 -0700179 except IOError:
180 raise ChromiumOSError(
181 'Update server at %s not available' % auserver_host)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200182
Dale Curtis5c32c722011-05-04 19:24:23 -0700183 logging.info(
184 'Installing from %s to: %s', self.update_url, self.host.hostname)
Sean Oc053dfe2010-08-23 18:22:26 +0200185
Dale Curtis5c32c722011-05-04 19:24:23 -0700186 logging.info('Updating root partition...')
187 self._update_root()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200188
Dale Curtis5c32c722011-05-04 19:24:23 -0700189 logging.info('Updating stateful partition...')
190 self._update_stateful()
Sean Oc053dfe2010-08-23 18:22:26 +0200191
Dale Curtis5c32c722011-05-04 19:24:23 -0700192 logging.info('Update complete.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200193 return True
194
195
Dale Curtisa94c19c2011-05-02 15:05:17 -0700196 def check_version(self):
Dale Curtisf57a25f2011-05-24 14:40:55 -0700197 booted_version = self.get_build_id()
Sean O267c00b2010-08-31 15:54:55 +0200198 if not booted_version in self.update_version:
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200199 logging.error('Expected Chromium OS version: %s.'
200 'Found Chromium OS %s',
Sean O267c00b2010-08-31 15:54:55 +0200201 self.update_version, booted_version)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200202 raise ChromiumOSError('Updater failed on host %s' %
203 self.host.hostname)
204 else:
205 return True
206
Sean Oc053dfe2010-08-23 18:22:26 +0200207
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200208 def get_build_id(self):
Dale Curtis793f9122011-02-04 15:00:52 -0800209 """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release."""
210 return self._run('grep CHROMEOS_RELEASE_VERSION'
211 ' /etc/lsb-release').stdout.split('=')[1].strip()