OLD | NEW |
---|---|
1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 1 # 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import httplib | 5 import httplib |
6 import logging | 6 import logging |
7 import re | 7 import re |
8 import socket | 8 import socket |
9 import urlparse | 9 import urlparse |
10 | 10 |
11 from autotest_lib.client.bin import site_utils | |
12 from autotest_lib.client.common_lib import error | 11 from autotest_lib.client.common_lib import error |
13 | 12 |
14 | |
15 STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update' | 13 STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update' |
16 UPDATER_BIN = '/usr/bin/update_engine_client' | 14 UPDATER_BIN = '/usr/bin/update_engine_client' |
17 UPDATER_IDLE = 'UPDATE_STATUS_IDLE' | 15 UPDATER_IDLE = 'UPDATE_STATUS_IDLE' |
18 UPDATER_NEED_REBOOT = 'UPDATED_NEED_REBOOT' | 16 UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT' |
19 | 17 |
20 | 18 |
21 class ChromiumOSError(error.InstallError): | 19 class ChromiumOSError(error.InstallError): |
22 """Generic error for ChromiumOS-specific exceptions.""" | 20 """Generic error for ChromiumOS-specific exceptions.""" |
23 pass | 21 pass |
24 | 22 |
25 | 23 |
26 def url_to_version(update_url): | 24 def url_to_version(update_url): |
27 # The ChromiumOS updater respects the last element in the path as | 25 # The ChromiumOS updater respects the last element in the path as |
28 # the requested version. Parse it out. | 26 # the requested version. Parse it out. |
29 return urlparse.urlparse(update_url).path.split('/')[-1] | 27 return urlparse.urlparse(update_url).path.split('/')[-1] |
30 | 28 |
31 | 29 |
32 class ChromiumOSUpdater(): | 30 class ChromiumOSUpdater(): |
33 def __init__(self, host=None, update_url=None): | 31 def __init__(self, host=None, update_url=None): |
34 self.host = host | 32 self.host = host |
35 self.update_url = update_url | 33 self.update_url = update_url |
36 self.update_version = url_to_version(update_url) | 34 self.update_version = url_to_version(update_url) |
37 | 35 |
36 | |
38 def check_update_status(self): | 37 def check_update_status(self): |
39 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1', | 38 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1', |
40 '| grep CURRENT_OP']) | 39 '| grep CURRENT_OP']) |
41 update_status = self._run(update_status_cmd) | 40 update_status = self._run(update_status_cmd) |
42 return update_status.stdout.strip().split('=')[-1] | 41 return update_status.stdout.strip().split('=')[-1] |
43 | 42 |
43 | |
44 def reset_update_engine(self): | |
45 self._run('initctl stop update-engine') | |
petkov
2010/08/23 19:20:31
Does this throw an exception if update-engine is n
| |
46 self._run('rm -f /tmp/update_engine_autoupdate_completed') | |
47 self._run('initctl start update-engine') | |
48 # May need to wait if service becomes slow to restart. | |
49 if self.check_update_status() != UPDATER_IDLE: | |
50 raise ChromiumOSError('%s is not in an installable state' % | |
51 self.host.hostname) | |
52 | |
53 | |
44 def _run(self, cmd, *args, **kwargs): | 54 def _run(self, cmd, *args, **kwargs): |
45 return self.host.run(cmd, *args, **kwargs) | 55 return self.host.run(cmd, *args, **kwargs) |
46 | 56 |
57 | |
47 def run_update(self): | 58 def run_update(self): |
48 # TODO(seano): Retrieve update_engine.log from target host. | 59 # TODO(seano): Retrieve update_engine.log from target host. |
49 if not self.update_url: | 60 if not self.update_url: |
50 return False | 61 return False |
51 | 62 |
52 # Check that devserver is accepting connections (from autoserv's host) | 63 # Check that devserver is accepting connections (from autoserv's host) |
53 # If we can't talk to it, the machine host probably can't either. | 64 # If we can't talk to it, the machine host probably can't either. |
54 auserver_host = urlparse.urlparse(self.update_url)[1] | 65 auserver_host = urlparse.urlparse(self.update_url)[1] |
55 try: | 66 try: |
56 httplib.HTTPConnection(auserver_host).connect() | 67 httplib.HTTPConnection(auserver_host).connect() |
57 except socket.error: | 68 except socket.error: |
58 raise ChromiumOSError('Update server at %s not available' % | 69 raise ChromiumOSError('Update server at %s not available' % |
59 auserver_host) | 70 auserver_host) |
60 | 71 |
61 logging.info('Installing from %s to: %s' % (self.update_url, | 72 logging.info('Installing from %s to: %s' % (self.update_url, |
62 self.host.hostname)) | 73 self.host.hostname)) |
63 # If we find the system an updated-but-not-rebooted state, | 74 # If we find the system an updated-but-not-rebooted state, |
64 # that's probably bad and we shouldn't trust that the previous | 75 # that's probably bad and we shouldn't trust that the previous |
65 # update left the machine in a good state. Reset update_engine's | 76 # update left the machine in a good state. Reset update_engine's |
66 # state & ensure that update_engine is idle. | 77 # state & ensure that update_engine is idle. |
67 if self.check_update_status() != UPDATER_IDLE: | 78 if self.check_update_status() != UPDATER_IDLE: |
petkov
2010/08/23 19:20:31
This may throw an exception if update-engine is no
| |
68 self._run('initctl stop update-engine') | 79 self.reset_update_engine() |
69 self._run('rm -f /tmp/update_engine_autoupdate_completed') | 80 |
70 self._run('initctl start update-engine') | 81 # Run autoupdate command. This tells the autoupdate process on |
71 # May need to wait if service becomes slow to restart. | 82 # the host to look for an update at a specific URL and version |
72 if self.check_update_status() != UPDATER_IDLE: | 83 # string. |
73 raise ChromiumOSError('%s is not in an installable state' % | 84 autoupdate_cmd = ' '.join([UPDATER_BIN, |
85 '--update', | |
86 '--omaha_url=%s' % self.update_url, | |
87 '--app_version ForcedUpdate', | |
petkov
2010/08/23 19:20:31
FYI, currently --update implies --app_version=Forc
| |
88 ' 2>&1']) | |
89 logging.info(autoupdate_cmd) | |
90 try: | |
91 self._run(autoupdate_cmd, timeout=900) | |
92 except error.AutoservRunError, e: | |
93 # Either a runtime error occurred on the host, or | |
94 # update_engine_client exited with > 0. | |
95 raise ChromiumOSError('update_engine failed on %s' % | |
74 self.host.hostname) | 96 self.host.hostname) |
75 | 97 |
76 # First, attempt dev & test tools update (which don't live on | 98 # Check that the installer completed as expected. |
77 # the rootfs). This must succeed so that the newly installed | 99 status = self.check_update_status() |
78 # host is testable after we run the autoupdater. | 100 if status != UPDATER_NEED_REBOOT: |
101 # TODO(seano): should we aggressively reset update-engine here? | |
petkov
2010/08/23 19:20:31
It might be good to somehow log /var/log/update_en
| |
102 raise ChromiumOSError('update-engine error on %s: ' | |
103 '"%s" from update-engine' % | |
104 (self.host.hostname, status)) | |
105 | |
106 # Attempt dev & test tools update (which don't live on the | |
107 # rootfs). This must succeed so that the newly installed host | |
108 # is testable after we run the autoupdater. | |
79 statefuldev_url = self.update_url.replace('update', 'static/archive') | 109 statefuldev_url = self.update_url.replace('update', 'static/archive') |
80 | 110 |
81 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url, | 111 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url, |
82 '2>&1']) | 112 '2>&1']) |
83 logging.info(statefuldev_cmd) | 113 logging.info(statefuldev_cmd) |
84 try: | 114 try: |
85 self._run(statefuldev_cmd, timeout=1200) | 115 self._run(statefuldev_cmd, timeout=600) |
86 except error.AutoservRunError, e: | 116 except error.AutoservRunError, e: |
117 # TODO(seano): If statefuldev update failed, we must mark | |
118 # the update as failed, and keep the same rootfs after | |
119 # reboot. | |
87 raise ChromiumOSError('stateful_update failed on %s' % | 120 raise ChromiumOSError('stateful_update failed on %s' % |
88 self.host.hostname) | 121 self.host.hostname) |
89 | |
90 # Run autoupdate command. This tells the autoupdate process on | |
91 # the host to look for an update at a specific URL and version | |
92 # string. | |
93 autoupdate_cmd = ' '.join([UPDATER_BIN, | |
94 '--omaha_url=%s' % self.update_url, | |
95 '--app_version ForcedUpdate']) | |
96 logging.info(autoupdate_cmd) | |
97 try: | |
98 self._run(autoupdate_cmd, timeout=60) | |
99 except error.AutoservRunError, e: | |
100 raise ChromiumOSError('unable to run updater on %s' % | |
101 self.host.hostname) | |
102 | |
103 # Check that the installer completed as expected. | |
104 def update_successful(): | |
105 status = self.check_update_status() | |
106 if status == UPDATER_IDLE: | |
107 raise ChromiumOSError('update-engine error on %s' % | |
108 self.host.hostname) | |
109 else: | |
110 return 'UPDATED_NEED_REBOOT' in status | |
111 | |
112 site_utils.poll_for_condition(update_successful, | |
113 ChromiumOSError('Updater failed'), | |
114 900, 10) | |
115 return True | 122 return True |
116 | 123 |
117 | 124 |
118 def check_version(self): | 125 def check_version(self): |
119 booted_version = self.get_build_id() | 126 booted_version = self.get_build_id() |
120 if booted_version != self.update_version: | 127 if booted_version != self.update_version: |
121 logging.error('Expected Chromium OS version: %s.' | 128 logging.error('Expected Chromium OS version: %s.' |
122 'Found Chromium OS %s', | 129 'Found Chromium OS %s', |
123 (self.update_version, booted_version)) | 130 (self.update_version, booted_version)) |
124 raise ChromiumOSError('Updater failed on host %s' % | 131 raise ChromiumOSError('Updater failed on host %s' % |
125 self.host.hostname) | 132 self.host.hostname) |
126 else: | 133 else: |
127 return True | 134 return True |
128 | 135 |
136 | |
129 def get_build_id(self): | 137 def get_build_id(self): |
130 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that | 138 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that |
131 matches the build ID.""" | 139 matches the build ID.""" |
132 # TODO(seano): handle dev build naming schemes. | 140 # TODO(seano): handle dev build naming schemes. |
133 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION' | 141 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION' |
134 ' /etc/lsb-release').stdout | 142 ' /etc/lsb-release').stdout |
135 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION=' | 143 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION=' |
136 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)') | 144 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)') |
137 version_match = re.match(build_re, version) | 145 version_match = re.match(build_re, version) |
138 if not version_match: | 146 if not version_match: |
139 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"', | 147 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"', |
140 self.host.hostname, version) | 148 self.host.hostname, version) |
141 version, build_id, builder = version_match.groups() | 149 version, build_id, builder = version_match.groups() |
142 # Continuous builds have an extra "builder number" on the end. | 150 # Continuous builds have an extra "builder number" on the end. |
143 # Report it if this looks like one. | 151 # Report it if this looks like one. |
144 build_match = re.match(r'.*: (\d+)', builder) | 152 build_match = re.match(r'.*: (\d+)', builder) |
145 if build_match: | 153 if build_match: |
146 builder_num = '-b%s' % build_match.group(1) | 154 builder_num = '-b%s' % build_match.group(1) |
147 else: | 155 else: |
148 builder_num = '' | 156 builder_num = '' |
149 return '%s-r%s%s' % (version, build_id, builder_num) | 157 return '%s-r%s%s' % (version, build_id, builder_num) |
OLD | NEW |