blob: a42943fa8db2a05f2b38d7bfb0f4f678f6db60e5 [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 O267c00b2010-08-31 15:54:55 +020011from autotest_lib.client.bin import chromeos_constants
Sean O'Connor5346e4e2010-08-12 18:49:24 +020012from autotest_lib.client.common_lib import error
13
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'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020018
19
20class ChromiumOSError(error.InstallError):
21 """Generic error for ChromiumOS-specific exceptions."""
22 pass
23
24
25def url_to_version(update_url):
26 # The ChromiumOS updater respects the last element in the path as
27 # the requested version. Parse it out.
28 return urlparse.urlparse(update_url).path.split('/')[-1]
29
30
31class ChromiumOSUpdater():
32 def __init__(self, host=None, update_url=None):
33 self.host = host
34 self.update_url = update_url
35 self.update_version = url_to_version(update_url)
36
Sean Oc053dfe2010-08-23 18:22:26 +020037
Sean O'Connor5346e4e2010-08-12 18:49:24 +020038 def check_update_status(self):
39 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1',
40 '| grep CURRENT_OP'])
41 update_status = self._run(update_status_cmd)
42 return update_status.stdout.strip().split('=')[-1]
43
Sean Oc053dfe2010-08-23 18:22:26 +020044
45 def reset_update_engine(self):
Sean O267c00b2010-08-31 15:54:55 +020046 logging.info('Resetting update-engine.')
Sean Oc053dfe2010-08-23 18:22:26 +020047 self._run('rm -f /tmp/update_engine_autoupdate_completed')
Sean O267c00b2010-08-31 15:54:55 +020048 try:
49 self._run('initctl stop update-engine')
50 except error.AutoservRunError, e:
51 logging.warn('Stopping update-engine service failed. Already dead?')
Sean Oc053dfe2010-08-23 18:22:26 +020052 self._run('initctl start update-engine')
53 # May need to wait if service becomes slow to restart.
54 if self.check_update_status() != UPDATER_IDLE:
55 raise ChromiumOSError('%s is not in an installable state' %
56 self.host.hostname)
57
58
Sean O'Connor5346e4e2010-08-12 18:49:24 +020059 def _run(self, cmd, *args, **kwargs):
60 return self.host.run(cmd, *args, **kwargs)
61
Sean Oc053dfe2010-08-23 18:22:26 +020062
Sean O267c00b2010-08-31 15:54:55 +020063 def rootdev(self):
64 return self._run('rootdev').stdout.strip()
65
66
67 def revert_boot_partition(self):
68 part = self.rootdev()
69 logging.warn('Reverting update; Boot partition will be %s', part)
70 return self._run('/postinst %s 2>&1' % part)
71
72
Sean O'Connor5346e4e2010-08-12 18:49:24 +020073 def run_update(self):
Sean O'Connor5346e4e2010-08-12 18:49:24 +020074 if not self.update_url:
75 return False
76
77 # Check that devserver is accepting connections (from autoserv's host)
78 # If we can't talk to it, the machine host probably can't either.
79 auserver_host = urlparse.urlparse(self.update_url)[1]
80 try:
81 httplib.HTTPConnection(auserver_host).connect()
82 except socket.error:
83 raise ChromiumOSError('Update server at %s not available' %
84 auserver_host)
85
86 logging.info('Installing from %s to: %s' % (self.update_url,
87 self.host.hostname))
Sean O267c00b2010-08-31 15:54:55 +020088 # Reset update_engine's state & check that update_engine is idle.
89 self.reset_update_engine()
Sean Oc053dfe2010-08-23 18:22:26 +020090
91 # Run autoupdate command. This tells the autoupdate process on
92 # the host to look for an update at a specific URL and version
93 # string.
94 autoupdate_cmd = ' '.join([UPDATER_BIN,
95 '--update',
96 '--omaha_url=%s' % self.update_url,
Sean Oc053dfe2010-08-23 18:22:26 +020097 ' 2>&1'])
98 logging.info(autoupdate_cmd)
99 try:
100 self._run(autoupdate_cmd, timeout=900)
101 except error.AutoservRunError, e:
102 # Either a runtime error occurred on the host, or
103 # update_engine_client exited with > 0.
104 raise ChromiumOSError('update_engine failed on %s' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200105 self.host.hostname)
106
Sean Oc053dfe2010-08-23 18:22:26 +0200107 # Check that the installer completed as expected.
108 status = self.check_update_status()
109 if status != UPDATER_NEED_REBOOT:
Sean Oc053dfe2010-08-23 18:22:26 +0200110 raise ChromiumOSError('update-engine error on %s: '
111 '"%s" from update-engine' %
112 (self.host.hostname, status))
113
114 # Attempt dev & test tools update (which don't live on the
115 # rootfs). This must succeed so that the newly installed host
116 # is testable after we run the autoupdater.
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200117 statefuldev_url = self.update_url.replace('update', 'static/archive')
118
119 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url,
120 '2>&1'])
121 logging.info(statefuldev_cmd)
122 try:
Sean Oc053dfe2010-08-23 18:22:26 +0200123 self._run(statefuldev_cmd, timeout=600)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200124 except error.AutoservRunError, e:
Sean Oc053dfe2010-08-23 18:22:26 +0200125 # TODO(seano): If statefuldev update failed, we must mark
126 # the update as failed, and keep the same rootfs after
127 # reboot.
Sean O267c00b2010-08-31 15:54:55 +0200128 self.revert_boot_partition()
129 raise ChromiumOSError('stateful_update failed on %s.' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200130 self.host.hostname)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200131 return True
132
133
134 def check_version(self):
135 booted_version = self.get_build_id()
Sean O267c00b2010-08-31 15:54:55 +0200136 if not booted_version in self.update_version:
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200137 logging.error('Expected Chromium OS version: %s.'
138 'Found Chromium OS %s',
Sean O267c00b2010-08-31 15:54:55 +0200139 self.update_version, booted_version)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200140 raise ChromiumOSError('Updater failed on host %s' %
141 self.host.hostname)
142 else:
143 return True
144
Sean Oc053dfe2010-08-23 18:22:26 +0200145
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200146 def get_build_id(self):
147 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that
148 matches the build ID."""
149 # TODO(seano): handle dev build naming schemes.
150 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION'
151 ' /etc/lsb-release').stdout
152 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION='
153 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)')
154 version_match = re.match(build_re, version)
155 if not version_match:
156 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"',
157 self.host.hostname, version)
158 version, build_id, builder = version_match.groups()
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200159 build_match = re.match(r'.*: (\d+)', builder)
160 if build_match:
161 builder_num = '-b%s' % build_match.group(1)
162 else:
163 builder_num = ''
164 return '%s-r%s%s' % (version, build_id, builder_num)