This is the first CL to refactor autotest to move all cros related modules into a cros dir.
Change-Id: Ib729f30aea39f9fa6ba6519b5baec6babea1f01d
BUG=7326
TEST=run server/autoserv --image= with both client and server sleeptest.
Review URL: http://codereview.chromium.org/5626007
diff --git a/client/common_lib/cros/__init__.py b/client/common_lib/cros/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/client/common_lib/cros/__init__.py
diff --git a/client/common_lib/cros/autoupdater.py b/client/common_lib/cros/autoupdater.py
new file mode 100644
index 0000000..311bd58
--- /dev/null
+++ b/client/common_lib/cros/autoupdater.py
@@ -0,0 +1,165 @@
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import httplib
+import logging
+import re
+import socket
+import urlparse
+
+from autotest_lib.client.bin import chromeos_constants
+from autotest_lib.client.common_lib import error
+
+STATEFULDEV_UPDATER = '/usr/local/bin/stateful_update'
+UPDATER_BIN = '/usr/bin/update_engine_client'
+UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
+UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
+UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
+
+
+class ChromiumOSError(error.InstallError):
+ """Generic error for ChromiumOS-specific exceptions."""
+ pass
+
+
+def url_to_version(update_url):
+ # The ChromiumOS updater respects the last element in the path as
+ # the requested version. Parse it out.
+ return urlparse.urlparse(update_url).path.split('/')[-1]
+
+
+class ChromiumOSUpdater():
+ def __init__(self, host=None, update_url=None):
+ self.host = host
+ self.update_url = update_url
+ self.update_version = url_to_version(update_url)
+
+
+ def check_update_status(self):
+ update_status_cmd = ' '.join([UPDATER_BIN, '-status', '2>&1',
+ '| grep CURRENT_OP'])
+ update_status = self._run(update_status_cmd)
+ return update_status.stdout.strip().split('=')[-1]
+
+
+ def reset_update_engine(self):
+ logging.info('Resetting update-engine.')
+ self._run('rm -f %s' % UPDATED_MARKER)
+ try:
+ self._run('initctl stop update-engine')
+ except error.AutoservRunError, e:
+ logging.warn('Stopping update-engine service failed. Already dead?')
+ self._run('initctl start update-engine')
+ # May need to wait if service becomes slow to restart.
+ if self.check_update_status() != UPDATER_IDLE:
+ raise ChromiumOSError('%s is not in an installable state' %
+ self.host.hostname)
+
+
+ def _run(self, cmd, *args, **kwargs):
+ return self.host.run(cmd, *args, **kwargs)
+
+
+ def rootdev(self):
+ return self._run('rootdev').stdout.strip()
+
+
+ def revert_boot_partition(self):
+ part = self.rootdev()
+ logging.warn('Reverting update; Boot partition will be %s', part)
+ return self._run('/postinst %s 2>&1' % part)
+
+
+ def run_update(self):
+ if not self.update_url:
+ return False
+
+ # Check that devserver is accepting connections (from autoserv's host)
+ # If we can't talk to it, the machine host probably can't either.
+ auserver_host = urlparse.urlparse(self.update_url)[1]
+ try:
+ httplib.HTTPConnection(auserver_host).connect()
+ except socket.error:
+ raise ChromiumOSError('Update server at %s not available' %
+ auserver_host)
+
+ logging.info('Installing from %s to: %s' % (self.update_url,
+ self.host.hostname))
+ # Reset update_engine's state & check that update_engine is idle.
+ self.reset_update_engine()
+
+ # Run autoupdate command. This tells the autoupdate process on
+ # the host to look for an update at a specific URL and version
+ # string.
+ autoupdate_cmd = ' '.join([UPDATER_BIN,
+ '--update',
+ '--omaha_url=%s' % self.update_url,
+ ' 2>&1'])
+ logging.info(autoupdate_cmd)
+ try:
+ self._run(autoupdate_cmd, timeout=900)
+ except error.AutoservRunError, e:
+ # Either a runtime error occurred on the host, or
+ # update_engine_client exited with > 0.
+ raise ChromiumOSError('update_engine failed on %s' %
+ self.host.hostname)
+
+ # Check that the installer completed as expected.
+ status = self.check_update_status()
+ if status != UPDATER_NEED_REBOOT:
+ raise ChromiumOSError('update-engine error on %s: '
+ '"%s" from update-engine' %
+ (self.host.hostname, status))
+
+ # Attempt dev & test tools update (which don't live on the
+ # rootfs). This must succeed so that the newly installed host
+ # is testable after we run the autoupdater.
+ statefuldev_url = self.update_url.replace('update', 'static/archive')
+
+ statefuldev_cmd = ' '.join([STATEFULDEV_UPDATER, statefuldev_url,
+ '2>&1'])
+ logging.info(statefuldev_cmd)
+ try:
+ self._run(statefuldev_cmd, timeout=600)
+ except error.AutoservRunError, e:
+ # TODO(seano): If statefuldev update failed, we must mark
+ # the update as failed, and keep the same rootfs after
+ # reboot.
+ self.revert_boot_partition()
+ raise ChromiumOSError('stateful_update failed on %s.' %
+ self.host.hostname)
+ return True
+
+
+ def check_version(self):
+ booted_version = self.get_build_id()
+ if not booted_version in self.update_version:
+ logging.error('Expected Chromium OS version: %s.'
+ 'Found Chromium OS %s',
+ self.update_version, booted_version)
+ raise ChromiumOSError('Updater failed on host %s' %
+ self.host.hostname)
+ else:
+ return True
+
+
+ def get_build_id(self):
+ """Turns the CHROMEOS_RELEASE_DESCRIPTION into a string that
+ matches the build ID."""
+ # TODO(seano): handle dev build naming schemes.
+ version = self._run('grep CHROMEOS_RELEASE_DESCRIPTION'
+ ' /etc/lsb-release').stdout
+ build_re = (r'CHROMEOS_RELEASE_DESCRIPTION='
+ '(\d+\.\d+\.\d+\.\d+) \(\w+ \w+ (\w+)(.*)\)')
+ version_match = re.match(build_re, version)
+ if not version_match:
+ raise ChromiumOSError('Unable to get build ID from %s. Found "%s"',
+ self.host.hostname, version)
+ version, build_id, builder = version_match.groups()
+ build_match = re.match(r'.*: (\d+)', builder)
+ if build_match:
+ builder_num = '-b%s' % build_match.group(1)
+ else:
+ builder_num = ''
+ return '%s-r%s%s' % (version, build_id, builder_num)
diff --git a/client/common_lib/cros/common.py b/client/common_lib/cros/common.py
new file mode 100644
index 0000000..9574299
--- /dev/null
+++ b/client/common_lib/cros/common.py
@@ -0,0 +1,8 @@
+import os, sys
+dirname = os.path.dirname(sys.modules[__name__].__file__)
+client_dir = os.path.abspath(os.path.join(dirname, "../.."))
+sys.path.insert(0, client_dir)
+import setup_modules
+sys.path.pop(0)
+setup_modules.setup(base_path=client_dir,
+ root_module_name="autotest_lib.client.common_lib")