blob: f23a2848d399361266d05e297e828fcdf699bddc [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
Eric Li9da65c42010-12-07 10:49:18 -080012from autotest_lib.client.cros import constants as chromeos_constants
Sean O'Connor5346e4e2010-08-12 18:49:24 +020013
Dale Curtis1f288ee2011-03-15 12:44:35 -070014# TODO(dalecurtis): HACK to bootstrap stateful updater until crosbug.com/8960 is
15# fixed.
16LOCAL_STATEFULDEV_UPDATER = ('/home/chromeos-test/chromeos-src/chromeos/src'
17 '/platform/dev/stateful_update')
18STATEFULDEV_UPDATER = '/tmp/stateful_update'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020019UPDATER_BIN = '/usr/bin/update_engine_client'
20UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020021UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
Darin Petkov7d572992010-09-23 10:11:05 -070022UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020023
24
25class ChromiumOSError(error.InstallError):
26 """Generic error for ChromiumOS-specific exceptions."""
27 pass
28
29
30def url_to_version(update_url):
31 # The ChromiumOS updater respects the last element in the path as
32 # the requested version. Parse it out.
33 return urlparse.urlparse(update_url).path.split('/')[-1]
34
35
36class ChromiumOSUpdater():
37 def __init__(self, host=None, update_url=None):
38 self.host = host
39 self.update_url = update_url
40 self.update_version = url_to_version(update_url)
41
Sean Oc053dfe2010-08-23 18:22:26 +020042
Sean O'Connor5346e4e2010-08-12 18:49:24 +020043 def check_update_status(self):
44 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1',
45 '| grep CURRENT_OP'])
46 update_status = self._run(update_status_cmd)
47 return update_status.stdout.strip().split('=')[-1]
48
Sean Oc053dfe2010-08-23 18:22:26 +020049
50 def reset_update_engine(self):
Sean O267c00b2010-08-31 15:54:55 +020051 logging.info('Resetting update-engine.')
Darin Petkov7d572992010-09-23 10:11:05 -070052 self._run('rm -f %s' % UPDATED_MARKER)
Sean O267c00b2010-08-31 15:54:55 +020053 try:
54 self._run('initctl stop update-engine')
55 except error.AutoservRunError, e:
56 logging.warn('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +020057 self._run('initctl start update-engine')
58 # May need to wait if service becomes slow to restart.
59 if self.check_update_status() != UPDATER_IDLE:
60 raise ChromiumOSError('%s is not in an installable state' %
61 self.host.hostname)
62
63
Sean O'Connor5346e4e2010-08-12 18:49:24 +020064 def _run(self, cmd, *args, **kwargs):
65 return self.host.run(cmd, *args, **kwargs)
66
Sean Oc053dfe2010-08-23 18:22:26 +020067
Sean O267c00b2010-08-31 15:54:55 +020068 def rootdev(self):
69 return self._run('rootdev').stdout.strip()
70
71
72 def revert_boot_partition(self):
73 part = self.rootdev()
74 logging.warn('Reverting update; Boot partition will be %s', part)
75 return self._run('/postinst %s 2>&1' % part)
76
77
Sean O'Connor5346e4e2010-08-12 18:49:24 +020078 def run_update(self):
Sean O'Connor5346e4e2010-08-12 18:49:24 +020079 if not self.update_url:
80 return False
81
82 # Check that devserver is accepting connections (from autoserv's host)
83 # If we can't talk to it, the machine host probably can't either.
84 auserver_host = urlparse.urlparse(self.update_url)[1]
85 try:
86 httplib.HTTPConnection(auserver_host).connect()
87 except socket.error:
88 raise ChromiumOSError('Update server at %s not available' %
89 auserver_host)
90
91 logging.info('Installing from %s to: %s' % (self.update_url,
92 self.host.hostname))
Sean O267c00b2010-08-31 15:54:55 +020093 # Reset update_engine's state & check that update_engine is idle.
94 self.reset_update_engine()
Sean Oc053dfe2010-08-23 18:22:26 +020095
96 # Run autoupdate command. This tells the autoupdate process on
97 # the host to look for an update at a specific URL and version
98 # string.
99 autoupdate_cmd = ' '.join([UPDATER_BIN,
100 '--update',
101 '--omaha_url=%s' % self.update_url,
Sean Oc053dfe2010-08-23 18:22:26 +0200102 ' 2>&1'])
103 logging.info(autoupdate_cmd)
104 try:
105 self._run(autoupdate_cmd, timeout=900)
106 except error.AutoservRunError, e:
107 # Either a runtime error occurred on the host, or
108 # update_engine_client exited with > 0.
109 raise ChromiumOSError('update_engine failed on %s' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200110 self.host.hostname)
111
Sean Oc053dfe2010-08-23 18:22:26 +0200112 # Check that the installer completed as expected.
113 status = self.check_update_status()
114 if status != UPDATER_NEED_REBOOT:
Sean Oc053dfe2010-08-23 18:22:26 +0200115 raise ChromiumOSError('update-engine error on %s: '
116 '"%s" from update-engine' %
117 (self.host.hostname, status))
118
119 # Attempt dev & test tools update (which don't live on the
120 # rootfs). This must succeed so that the newly installed host
121 # is testable after we run the autoupdater.
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200122 statefuldev_url = self.update_url.replace('update', 'static/archive')
123
Dale Curtis1f288ee2011-03-15 12:44:35 -0700124 # TODO(dalecurtis): HACK to bootstrap stateful updater until
125 # crosbug.com/8960 is fixed.
126 self.host.send_file(LOCAL_STATEFULDEV_UPDATER, STATEFULDEV_UPDATER,
127 delete_dest=True)
128 statefuldev_cmd = [STATEFULDEV_UPDATER, statefuldev_url]
129
130 # TODO(dalecurtis): HACK necessary until R10 builds are out of testing.
131 if int(self.update_version.split('.')[1]) > 10:
132 statefuldev_cmd.append('--stateful_change=clean')
133
134 statefuldev_cmd.append('2>&1')
135 statefuldev_cmd = ' '.join(statefuldev_cmd)
136
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200137 logging.info(statefuldev_cmd)
138 try:
Sean Oc053dfe2010-08-23 18:22:26 +0200139 self._run(statefuldev_cmd, timeout=600)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200140 except error.AutoservRunError, e:
Sean Oc053dfe2010-08-23 18:22:26 +0200141 # TODO(seano): If statefuldev update failed, we must mark
142 # the update as failed, and keep the same rootfs after
143 # reboot.
Sean O267c00b2010-08-31 15:54:55 +0200144 self.revert_boot_partition()
145 raise ChromiumOSError('stateful_update failed on %s.' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200146 self.host.hostname)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200147 return True
148
149
150 def check_version(self):
151 booted_version = self.get_build_id()
Dale Curtis793f9122011-02-04 15:00:52 -0800152 if not booted_version:
153 booted_version = self.get_dev_build_id()
Sean O267c00b2010-08-31 15:54:55 +0200154 if not booted_version in self.update_version:
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200155 logging.error('Expected Chromium OS version: %s.'
156 'Found Chromium OS %s',
Sean O267c00b2010-08-31 15:54:55 +0200157 self.update_version, booted_version)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200158 raise ChromiumOSError('Updater failed on host %s' %
159 self.host.hostname)
160 else:
161 return True
162
Sean Oc053dfe2010-08-23 18:22:26 +0200163
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200164 def get_build_id(self):
165 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that
166 matches the build ID."""
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200167 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION'
168 ' /etc/lsb-release').stdout
169 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION='
170 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)')
171 version_match = re.match(build_re, version)
Dale Curtis793f9122011-02-04 15:00:52 -0800172 if version_match:
173 version, build_id, builder = version_match.groups()
174 build_match = re.match(r'.*: (\d+)', builder)
175 if build_match:
176 builder_num = '-b%s' % build_match.group(1)
177 else:
178 builder_num = ''
179 return '%s-r%s%s' % (version, build_id, builder_num)
180
181
182 def get_dev_build_id(self):
183 """Pulls the CHROMEOS_RELEASE_VERSION string from /etc/lsb-release."""
184 return self._run('grep CHROMEOS_RELEASE_VERSION'
185 ' /etc/lsb-release').stdout.split('=')[1].strip()