blob: 7b1ead833cf367e0e58d30a96c612f28bf050344 [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
Sean O'Connor5346e4e2010-08-12 18:49:24 +020013STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update'
14UPDATER_BIN = '/usr/bin/update_engine_client'
15UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
Sean Oc053dfe2010-08-23 18:22:26 +020016UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
Sean O'Connor5346e4e2010-08-12 18:49:24 +020017
18
19class ChromiumOSError(error.InstallError):
20 """Generic error for ChromiumOS-specific exceptions."""
21 pass
22
23
24def url_to_version(update_url):
25 # The ChromiumOS updater respects the last element in the path as
26 # the requested version. Parse it out.
27 return urlparse.urlparse(update_url).path.split('/')[-1]
28
29
30class ChromiumOSUpdater():
31 def __init__(self, host=None, update_url=None):
32 self.host = host
33 self.update_url = update_url
34 self.update_version = url_to_version(update_url)
35
Sean Oc053dfe2010-08-23 18:22:26 +020036
Sean O'Connor5346e4e2010-08-12 18:49:24 +020037 def check_update_status(self):
38 update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1',
39 '| grep CURRENT_OP'])
40 update_status = self._run(update_status_cmd)
41 return update_status.stdout.strip().split('=')[-1]
42
Sean Oc053dfe2010-08-23 18:22:26 +020043
44 def reset_update_engine(self):
45 self._run('initctl stop update-engine')
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
Sean O'Connor5346e4e2010-08-12 18:49:24 +020054 def _run(self, cmd, *args, **kwargs):
55 return self.host.run(cmd, *args, **kwargs)
56
Sean Oc053dfe2010-08-23 18:22:26 +020057
Sean O'Connor5346e4e2010-08-12 18:49:24 +020058 def run_update(self):
59 # TODO(seano): Retrieve update_engine.log from target host.
60 if not self.update_url:
61 return False
62
63 # Check that devserver is accepting connections (from autoserv's host)
64 # If we can't talk to it, the machine host probably can't either.
65 auserver_host = urlparse.urlparse(self.update_url)[1]
66 try:
67 httplib.HTTPConnection(auserver_host).connect()
68 except socket.error:
69 raise ChromiumOSError('Update server at %s not available' %
70 auserver_host)
71
72 logging.info('Installing from %s to: %s' % (self.update_url,
73 self.host.hostname))
74 # If we find the system an updated-but-not-rebooted state,
75 # that's probably bad and we shouldn't trust that the previous
76 # update left the machine in a good state. Reset update_engine's
77 # state & ensure that update_engine is idle.
78 if self.check_update_status() != UPDATER_IDLE:
Sean Oc053dfe2010-08-23 18:22:26 +020079 self.reset_update_engine()
80
81 # Run autoupdate command. This tells the autoupdate process on
82 # the host to look for an update at a specific URL and version
83 # string.
84 autoupdate_cmd = ' '.join([UPDATER_BIN,
85 '--update',
86 '--omaha_url=%s' % self.update_url,
87 '--app_version ForcedUpdate',
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' %
Sean O'Connor5346e4e2010-08-12 18:49:24 +020096 self.host.hostname)
97
Sean Oc053dfe2010-08-23 18:22:26 +020098 # Check that the installer completed as expected.
99 status = self.check_update_status()
100 if status != UPDATER_NEED_REBOOT:
101 # TODO(seano): should we aggressively reset update-engine here?
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.
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200109 statefuldev_url = self.update_url.replace('update', 'static/archive')
110
111 statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url,
112 '2>&1'])
113 logging.info(statefuldev_cmd)
114 try:
Sean Oc053dfe2010-08-23 18:22:26 +0200115 self._run(statefuldev_cmd, timeout=600)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200116 except error.AutoservRunError, e:
Sean Oc053dfe2010-08-23 18:22:26 +0200117 # TODO(seano): If statefuldev update failed, we must mark
118 # the update as failed, and keep the same rootfs after
119 # reboot.
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200120 raise ChromiumOSError('stateful_update failed on %s' %
121 self.host.hostname)
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200122 return True
123
124
125 def check_version(self):
126 booted_version = self.get_build_id()
127 if booted_version != self.update_version:
128 logging.error('Expected Chromium OS version: %s.'
129 'Found Chromium OS %s',
130 (self.update_version, booted_version))
131 raise ChromiumOSError('Updater failed on host %s' %
132 self.host.hostname)
133 else:
134 return True
135
Sean Oc053dfe2010-08-23 18:22:26 +0200136
Sean O'Connor5346e4e2010-08-12 18:49:24 +0200137 def get_build_id(self):
138 """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that
139 matches the build ID."""
140 # TODO(seano): handle dev build naming schemes.
141 version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION'
142 ' /etc/lsb-release').stdout
143 build_re = (r'CHROMEOS_RELEASE_DESCRIPTION='
144 '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)')
145 version_match = re.match(build_re, version)
146 if not version_match:
147 raise ChromiumOSError('Unable to get build ID from %s. Found "%s"',
148 self.host.hostname, version)
149 version, build_id, builder = version_match.groups()
150 # Continuous builds have an extra "builder number" on the end.
151 # Report it if this looks like one.
152 build_match = re.match(r'.*: (\d+)', builder)
153 if build_match:
154 builder_num = '-b%s' % build_match.group(1)
155 else:
156 builder_num = ''
157 return '%s-r%s%s' % (version, build_id, builder_num)