blob: 3ed610509e80eee0dba9dca2f5f03ebc0d517826 [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
Sean O'Connor5346e4e2010-08-12 18:49:24 +020014STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update'
15UPDATER_BIN = '/usr/bin/update_engine_client'
16UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020017UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
Darin Petkov7d572992010-09-23 10:11:05 -070018UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020019
20
21class ChromiumOSError(error.InstallError):
22 """Generic error for ChromiumOS-specific exceptions."""
23 pass
24
25
26def url_to_version(update_url):
27 # The ChromiumOS updater respects the last element in the path as
28 # the requested version. Parse it out.
29 return urlparse.urlparse(update_url).path.split('/')[-1]
30
31
32class ChromiumOSUpdater():
33 def __init__(self, host=None, update_url=None):
34 self.host = host
35 self.update_url = update_url
36 self.update_version = url_to_version(update_url)
37
Sean Oc053dfe2010-08-23 18:22:26 +020038
Sean O'Connor5346e4e2010-08-12 18:49:24 +020039 def check_update_status(self):
40 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1',
41 '| grep CURRENT_OP'])
42 update_status = self._run(update_status_cmd)
43 return update_status.stdout.strip().split('=')[-1]
44
Sean Oc053dfe2010-08-23 18:22:26 +020045
46 def reset_update_engine(self):
Sean O267c00b2010-08-31 15:54:55 +020047 logging.info('Resetting update-engine.')
Darin Petkov7d572992010-09-23 10:11:05 -070048 self._run('rm -f %s' % UPDATED_MARKER)
Sean O267c00b2010-08-31 15:54:55 +020049 try:
50 self._run('initctl stop update-engine')
51 except error.AutoservRunError, e:
52 logging.warn('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +020053 self._run('initctl start update-engine')
54 # May need to wait if service becomes slow to restart.
55 if self.check_update_status() != UPDATER_IDLE:
56 raise ChromiumOSError('%s is not in an installable state' %
57 self.host.hostname)
58
59
Sean O'Connor5346e4e2010-08-12 18:49:24 +020060 def _run(self, cmd, *args, **kwargs):
61 return self.host.run(cmd, *args, **kwargs)
62
Sean Oc053dfe2010-08-23 18:22:26 +020063
Sean O267c00b2010-08-31 15:54:55 +020064 def rootdev(self):
65 return self._run('rootdev').stdout.strip()
66
67
68 def revert_boot_partition(self):
69 part = self.rootdev()
70 logging.warn('Reverting update; Boot partition will be %s', part)
71 return self._run('/postinst %s 2>&1' % part)
72
73
Sean O'Connor5346e4e2010-08-12 18:49:24 +020074 def run_update(self):
Sean O'Connor5346e4e2010-08-12 18:49:24 +020075 if not self.update_url:
76 return False
77
78 # Check that devserver is accepting connections (from autoserv's host)
79 # If we can't talk to it, the machine host probably can't either.
80 auserver_host = urlparse.urlparse(self.update_url)[1]
81 try:
82 httplib.HTTPConnection(auserver_host).connect()
83 except socket.error:
84 raise ChromiumOSError('Update server at %s not available' %
85 auserver_host)
86
87 logging.info('Installing from %s to: %s' % (self.update_url,
88 self.host.hostname))
Sean O267c00b2010-08-31 15:54:55 +020089 # Reset update_engine's state & check that update_engine is idle.
90 self.reset_update_engine()
Sean Oc053dfe2010-08-23 18:22:26 +020091
92 # Run autoupdate command. This tells the autoupdate process on
93 # the host to look for an update at a specific URL and version
94 # string.
95 autoupdate_cmd = ' '.join([UPDATER_BIN,
96 '--update',
97 '--omaha_url=%s' % self.update_url,
Sean Oc053dfe2010-08-23 18:22:26 +020098 ' 2>&1'])
99 logging.info(autoupdate_cmd)
100 try:
101 self._run(autoupdate_cmd, timeout=900)
102 except error.AutoservRunError, e:
103 # Either a runtime error occurred on the host, or
104 # update_engine_client exited with > 0.
105 raise ChromiumOSError('update_engine failed on %s' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200106 self.host.hostname)
107
Sean Oc053dfe2010-08-23 18:22:26 +0200108 # Check that the installer completed as expected.
109 status = self.check_update_status()
110 if status != UPDATER_NEED_REBOOT:
Sean Oc053dfe2010-08-23 18:22:26 +0200111 raise ChromiumOSError('update-engine error on %s: '
112 '"%s" from update-engine' %
113 (self.host.hostname, status))
114
115 # Attempt dev & test tools update (which don't live on the
116 # rootfs). This must succeed so that the newly installed host
117 # is testable after we run the autoupdater.
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200118 statefuldev_url = self.update_url.replace('update', 'static/archive')
119
120 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url,
121 '2>&1'])
122 logging.info(statefuldev_cmd)
123 try:
Sean Oc053dfe2010-08-23 18:22:26 +0200124 self._run(statefuldev_cmd, timeout=600)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200125 except error.AutoservRunError, e:
Sean Oc053dfe2010-08-23 18:22:26 +0200126 # TODO(seano): If statefuldev update failed, we must mark
127 # the update as failed, and keep the same rootfs after
128 # reboot.
Sean O267c00b2010-08-31 15:54:55 +0200129 self.revert_boot_partition()
130 raise ChromiumOSError('stateful_update failed on %s.' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200131 self.host.hostname)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200132 return True
133
134
135 def check_version(self):
136 booted_version = self.get_build_id()
Sean O267c00b2010-08-31 15:54:55 +0200137 if not booted_version in self.update_version:
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200138 logging.error('Expected Chromium OS version: %s.'
139 'Found Chromium OS %s',
Sean O267c00b2010-08-31 15:54:55 +0200140 self.update_version, booted_version)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200141 raise ChromiumOSError('Updater failed on host %s' %
142 self.host.hostname)
143 else:
144 return True
145
Sean Oc053dfe2010-08-23 18:22:26 +0200146
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200147 def get_build_id(self):
148 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that
149 matches the build ID."""
150 # TODO(seano): handle dev build naming schemes.
151 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION'
152 ' /etc/lsb-release').stdout
153 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION='
154 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)')
155 version_match = re.match(build_re, version)
156 if not version_match:
157 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"',
158 self.host.hostname, version)
159 version, build_id, builder = version_match.groups()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200160 build_match = re.match(r'.*: (\d+)', builder)
161 if build_match:
162 builder_num = '-b%s' % build_match.group(1)
163 else:
164 builder_num = ''
165 return '%s-r%s%s' % (version, build_id, builder_num)