blob: 2a75fe1b2861217a8c907aa9d54795741052aae8 [file] [log] [blame]
Sean O'Connor5346e4e2010-08-12 18:49:24 +02001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# 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
7import re
8import socket
9import urlparse
10
Sean O'Connor5346e4e2010-08-12 18:49:24 +020011from autotest_lib.client.common_lib import error
12
Dale Curtis1f288ee2011-03-15 12:44:35 -070013# TODO(dalecurtis): HACK to bootstrap stateful updater until crosbug.com/8960 is
14# fixed.
15LOCAL_STATEFULDEV_UPDATER = ('/home/chromeos-test/chromeos-src/chromeos/src'
16 '/platform/dev/stateful_update')
17STATEFULDEV_UPDATER = '/tmp/stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020018UPDATER_BIN = '/usr/bin/update_engine_client'
19UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020020UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
Darin Petkov7d572992010-09-23 10:11:05 -070021UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
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):
30 # The ChromiumOS updater respects the last element in the path as
31 # the requested version. Parse it out.
32 return urlparse.urlparse(update_url).path.split('/')[-1]
33
34
35class ChromiumOSUpdater():
Dale Curtisa94c19c2011-05-02 15:05:17 -070036 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
37 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
38
39
40 def __init__(self, update_url, host=None):
Sean O'Connor5346e4e2010-08-12 18:49:24 +020041 self.host = host
42 self.update_url = update_url
43 self.update_version = url_to_version(update_url)
44
Sean Oc053dfe2010-08-23 18:22:26 +020045
Sean O'Connor5346e4e2010-08-12 18:49:24 +020046 def check_update_status(self):
47 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1',
48 '| grep CURRENT_OP'])
49 update_status = self._run(update_status_cmd)
50 return update_status.stdout.strip().split('=')[-1]
51
Sean Oc053dfe2010-08-23 18:22:26 +020052
53 def reset_update_engine(self):
Sean O267c00b2010-08-31 15:54:55 +020054 logging.info('Resetting update-engine.')
Darin Petkov7d572992010-09-23 10:11:05 -070055 self._run('rm -f %s' % UPDATED_MARKER)
Sean O267c00b2010-08-31 15:54:55 +020056 try:
57 self._run('initctl stop update-engine')
58 except error.AutoservRunError, e:
59 logging.warn('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +020060 self._run('initctl start update-engine')
61 # May need to wait if service becomes slow to restart.
62 if self.check_update_status() != UPDATER_IDLE:
63 raise ChromiumOSError('%s is not in an installable state' %
64 self.host.hostname)
65
66
Sean O'Connor5346e4e2010-08-12 18:49:24 +020067 def _run(self, cmd, *args, **kwargs):
68 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=''):
72 return self._run('rootdev %s' % options).stdout.strip()
73
74
75 def get_kernel_state(self):
76 """Returns the (<active>, <inactive>) kernel state as a pair."""
77 active_root = int(re.findall('\d+\Z', self.rootdev('-s'))[0])
78 if active_root == self.KERNEL_A['root']:
79 return self.KERNEL_A, self.KERNEL_B
80 elif active_root == self.KERNEL_B['root']:
81 return self.KERNEL_B, self.KERNEL_A
82 else:
83 raise ChromiumOSError('Encountered unknown root partition: %s',
84 active_root)
85
86
87 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'):
88 """Return numeric cgpt value for the specified flag, kernel, device. """
89 return int(self._run('cgpt show -n -i %d %s %s' % (
90 kernel['kernel'], flag, dev)).stdout.strip())
91
92
93 def get_kernel_priority(self, kernel):
94 """Return numeric priority for the specified kernel."""
95 return self._cgpt('-P', kernel)
96
97
98 def get_kernel_success(self, kernel):
99 """Return boolean success flag for the specified kernel."""
100 return self._cgpt('-S', kernel) != 0
101
102
103 def get_kernel_tries(self, kernel):
104 """Return tries count for the specified kernel."""
105 return self._cgpt('-T', kernel)
Sean O267c00b2010-08-31 15:54:55 +0200106
107
108 def revert_boot_partition(self):
109 part = self.rootdev()
110 logging.warn('Reverting update; Boot partition will be %s', part)
111 return self._run('/postinst %s 2>&1' % part)
112
113
Dale Curtisa94c19c2011-05-02 15:05:17 -0700114 def run_update(self, force_update):
115 booted_version = self.get_booted_version()
116 if booted_version in self.update_version and not force_update:
117 logging.info('System is already up to date. Skipping update.')
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200118 return False
119
120 # Check that devserver is accepting connections (from autoserv's host)
121 # If we can't talk to it, the machine host probably can't either.
122 auserver_host = urlparse.urlparse(self.update_url)[1]
123 try:
124 httplib.HTTPConnection(auserver_host).connect()
125 except socket.error:
126 raise ChromiumOSError('Update server at %s not available' %
127 auserver_host)
128
129 logging.info('Installing from %s to: %s' % (self.update_url,
130 self.host.hostname))
Sean O267c00b2010-08-31 15:54:55 +0200131 # Reset update_engine's state & check that update_engine is idle.
132 self.reset_update_engine()
Sean Oc053dfe2010-08-23 18:22:26 +0200133
134 # Run autoupdate command. This tells the autoupdate process on
135 # the host to look for an update at a specific URL and version
136 # string.
137 autoupdate_cmd = ' '.join([UPDATER_BIN,
138 '--update',
139 '--omaha_url=%s' % self.update_url,
Sean Oc053dfe2010-08-23 18:22:26 +0200140 ' 2>&1'])
141 logging.info(autoupdate_cmd)
142 try:
143 self._run(autoupdate_cmd, timeout=900)
144 except error.AutoservRunError, e:
145 # Either a runtime error occurred on the host, or
146 # update_engine_client exited with > 0.
147 raise ChromiumOSError('update_engine failed on %s' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200148 self.host.hostname)
149
Sean Oc053dfe2010-08-23 18:22:26 +0200150 # Check that the installer completed as expected.
151 status = self.check_update_status()
152 if status != UPDATER_NEED_REBOOT:
Sean Oc053dfe2010-08-23 18:22:26 +0200153 raise ChromiumOSError('update-engine error on %s: '
154 '"%s" from update-engine' %
155 (self.host.hostname, status))
156
157 # Attempt dev & test tools update (which don't live on the
158 # rootfs). This must succeed so that the newly installed host
159 # is testable after we run the autoupdater.
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200160 statefuldev_url = self.update_url.replace('update', 'static/archive')
161
Dale Curtis1f288ee2011-03-15 12:44:35 -0700162 # TODO(dalecurtis): HACK to bootstrap stateful updater until
163 # crosbug.com/8960 is fixed.
164 self.host.send_file(LOCAL_STATEFULDEV_UPDATER, STATEFULDEV_UPDATER,
165 delete_dest=True)
166 statefuldev_cmd = [STATEFULDEV_UPDATER, statefuldev_url]
167
168 # TODO(dalecurtis): HACK necessary until R10 builds are out of testing.
169 if int(self.update_version.split('.')[1]) > 10:
170 statefuldev_cmd.append('--stateful_change=clean')
171
172 statefuldev_cmd.append('2>&1')
173 statefuldev_cmd = ' '.join(statefuldev_cmd)
174
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200175 logging.info(statefuldev_cmd)
176 try:
Sean Oc053dfe2010-08-23 18:22:26 +0200177 self._run(statefuldev_cmd, timeout=600)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200178 except error.AutoservRunError, e:
Sean Oc053dfe2010-08-23 18:22:26 +0200179 # TODO(seano): If statefuldev update failed, we must mark
180 # the update as failed, and keep the same rootfs after
181 # reboot.
Sean O267c00b2010-08-31 15:54:55 +0200182 self.revert_boot_partition()
183 raise ChromiumOSError('stateful_update failed on %s.' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200184 self.host.hostname)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200185 return True
186
187
Dale Curtisa94c19c2011-05-02 15:05:17 -0700188 def get_booted_version(self):
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200189 booted_version = self.get_build_id()
Dale Curtis793f9122011-02-04 15:00:52 -0800190 if not booted_version:
191 booted_version = self.get_dev_build_id()
Dale Curtisa94c19c2011-05-02 15:05:17 -0700192 return booted_version
193
194
195 def check_version(self):
196 booted_version = self.get_booted_version()
Sean O267c00b2010-08-31 15:54:55 +0200197 if not booted_version in self.update_version:
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200198 logging.error('Expected Chromium OS version: %s.'
199 'Found Chromium OS %s',
Sean O267c00b2010-08-31 15:54:55 +0200200 self.update_version, booted_version)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200201 raise ChromiumOSError('Updater failed on host %s' %
202 self.host.hostname)
203 else:
204 return True
205
Sean Oc053dfe2010-08-23 18:22:26 +0200206
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200207 def get_build_id(self):
208 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that
209 matches the build ID."""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200210 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION'
211 ' /etc/lsb-release').stdout
212 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION='
213 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)')
214 version_match = re.match(build_re, version)
Dale Curtis793f9122011-02-04 15:00:52 -0800215 if version_match:
216 version, build_id, builder = version_match.groups()
217 build_match = re.match(r'.*: (\d+)', builder)
218 if build_match:
219 builder_num = '-b%s' % build_match.group(1)
220 else:
221 builder_num = ''
222 return '%s-r%s%s' % (version, build_id, builder_num)
223
224
225 def get_dev_build_id(self):
226 """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release."""
227 return self._run('grep CHROMEOS_RELEASE_VERSION'
228 ' /etc/lsb-release').stdout.split('=')[1].strip()