Add rollback integration test autoupdate_Rollback.

This CL adds a rollback test that updates the DUT to a new image
and verifies that we can rollback to whatever we had installed
by inspecting the kernel state at each phase.

BUG=chromium:260568
TEST=Ran it several times.

Change-Id: Ie7f3c8ee7f0cfb4889989735f17870da19368da3
Reviewed-on: https://chromium-review.googlesource.com/173320
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Queue: Chris Sosa <sosa@chromium.org>
Tested-by: Chris Sosa <sosa@chromium.org>
diff --git a/server/cros/autoupdate_utils.py b/server/cros/autoupdate_utils.py
new file mode 100644
index 0000000..9e8e39b
--- /dev/null
+++ b/server/cros/autoupdate_utils.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.   1
+# Use of this source code is governed by a BSD-style license that can be   2
+# found in the LICENSE file.
+
+"Module containing common utilities for server-side autoupdate tests."
+
+import logging
+
+import common
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.cros import autoupdater, dev_server
+from autotest_lib.server.cros.dynamic_suite import tools
+
+
+def get_updater_from_repo_url(host, job_repo_url=None):
+    """Returns the autoupdater instance to use for a given autoupdate test.
+
+    All server-side tests that run in the lab have an associated job_repo_url
+    assigned to their host that is associated with the version of the build that
+    is currently installed on them. Given most autoupdate tests need to
+    update to some build as part of the test, we conveniently re-update to the
+    same version installed. This method serves as a helper to get the
+    instantiated autoupdater instance for that build.
+
+    This method guarantees that the devserver associated with the autoupdater
+    has already staged the necessary files for autoupdate.
+
+    @param host: The host for the DUT of the server-side test.
+    @param job_repo_url: If set, the job_repo_url to use.
+
+    @raise error.TestError: If we fail to get a job_repo_url.
+    """
+    # Get the job_repo_url -- if not present, attempt to use the one
+    # specified in the host attributes for the host.
+    if not job_repo_url:
+        try:
+            job_repo_url = host.lookup_job_repo_url()
+        except KeyError:
+            logging.fatal('Could not lookup job_repo_url from afe.')
+
+        if not job_repo_url:
+            raise error.TestError(
+                    'Could not find a job_repo_url for the given host.')
+
+    # Get the devserver url and build (image) from the repo url e.g.
+    # 'http://mydevserver:8080', 'x86-alex-release/R27-123.0.0'
+    ds_url, build = tools.get_devserver_build_from_package_url(job_repo_url)
+    devserver = dev_server.ImageServer(ds_url)
+    devserver.stage_artifacts(build, ['full_payload', 'stateful'])
+
+    # We only need to update stateful to do this test.
+    return autoupdater.ChromiumOSUpdater(devserver.get_update_url(build),
+                                         host=host)
diff --git a/server/site_tests/autoupdate_Rollback/autoupdate_Rollback.py b/server/site_tests/autoupdate_Rollback/autoupdate_Rollback.py
new file mode 100755
index 0000000..e6ccd56
--- /dev/null
+++ b/server/site_tests/autoupdate_Rollback/autoupdate_Rollback.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2013 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 logging
+
+from autotest_lib.server import test
+from autotest_lib.server.cros import autoupdate_utils
+
+
+class autoupdate_Rollback(test.test):
+    """Test that updates the machine and performs rollback."""
+    version = 1
+
+
+    def run_once(self, host, job_repo_url=None):
+        """Runs the test.
+
+        @param host: A host object representing the DUT.
+        @param job_repo_url: URL to get the image.
+
+        @raise error.TestError if anything went wrong with setting up the test;
+               error.TestFail if any part of the test has failed.
+
+        """
+        updater = autoupdate_utils.get_updater_from_repo_url(host, job_repo_url)
+
+        initial_kernel, updated_kernel = updater.get_kernel_state()
+        logging.info('Initial device state: active kernel %s, '
+                     'inactive kernel %s.', initial_kernel, updated_kernel)
+
+        logging.info('Performing an update.')
+        updater.update_rootfs()
+        host.reboot()
+
+        # We should be booting from the new partition.
+        error_message = 'Failed to set up test by updating DUT.'
+        updater.verify_boot_expectations(expected_kernel_state=updated_kernel,
+                                         rollback_message=error_message)
+        logging.info('Update verified, initiating rollback.')
+
+        # Powerwash is tested separately from rollback.
+        updater.rollback_rootfs(powerwash=False)
+        host.reboot()
+
+        # We should be back on our initial partition.
+        error_message = ('Autoupdate reported that rollback succeeded but we '
+                         'did not boot into the correct partition.')
+        updater.verify_boot_expectations(expected_kernel_state=initial_kernel,
+                                         rollback_message=error_message)
+        logging.info('We successfully rolled back to initial kernel.')
diff --git a/server/site_tests/autoupdate_Rollback/control b/server/site_tests/autoupdate_Rollback/control
new file mode 100644
index 0000000..bf14dda
--- /dev/null
+++ b/server/site_tests/autoupdate_Rollback/control
@@ -0,0 +1,55 @@
+# Copyright (c) 2013 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.
+
+from autotest_lib.client.common_lib import utils
+
+AUTHOR = "Chromium OS"
+NAME = "autoupdate_Rollback"
+TIME = "MEDIUM"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "platform"
+TEST_TYPE = "server"
+
+DOC = """
+This is a rollback test for Chrome OS releases. It first updates a machine and
+then invokes rollback to boot from its previously booted partition. It tests
+rollback using the update_engine_client rather than manipulating the UI.
+
+Setup for this test is exactly like platform_RebootAfterUpdate. Namely:
+
+Arg:
+  job_repo_url: repo url to use to find image to update from -- assumes caller
+                has staged image. By default if host[repo_job_url] is set, it'll
+                use that. This overrides that value. This value must follow
+                the package_url_pattern in the global config.
+
+To run locally:
+  1) Setup your devserver in your shadow config that your DUT can reach.
+  2) Start your devserver and stage the image you want for example:
+     http://localhost:8080/download?archive_url=\
+     gs://chromeos-image-archive/parrot-release/R32-4793.0.0 (and leave it on).
+  3) Run with test_that etc passing
+     args="job_repo_url=http://<your_hostname>:8080/static/\
+     parrot-release/R32-4793.0.0/autotest/packages"
+
+For example:
+
+test_that -b parrot --args="job_repo_url=http://<your_machine>:8080/static/\
+parrot-release/R32-4793.0.0/autotest/packages" --fast \
+<dut_ip> autoupdate_Rollback
+"""
+
+args_dict = utils.args_to_dict(args)
+job_repo_url = args_dict.get('job_repo_url')
+
+
+def run_test(machine):
+    """Execute a test configuration on a given machine."""
+    host = hosts.create_host(machine)
+    job.run_test("autoupdate_Rollback", host=host,
+                 job_repo_url=job_repo_url)
+
+
+# Invoke parallel tests.
+parallel_simple(run_test, machines)
diff --git a/server/site_tests/platform_RebootAfterUpdate/control b/server/site_tests/platform_RebootAfterUpdate/control
index e4a62aa..eea060f 100644
--- a/server/site_tests/platform_RebootAfterUpdate/control
+++ b/server/site_tests/platform_RebootAfterUpdate/control
@@ -30,7 +30,7 @@
      http://localhost:8080/download?archive_url=\
      gs://chromeos-image-archive/x86-alex/R29-4165.0.0
   3) Run with run_remote_test etc passing
-     args="job_repo_url=http://localhost:8080/static/\
+     args="job_repo_url=http://<your_hostname>:8080/static/\
      x86-alex/R29-4165.0.0/autotest/packages"
 """
 
diff --git a/server/site_tests/platform_RebootAfterUpdate/platform_RebootAfterUpdate.py b/server/site_tests/platform_RebootAfterUpdate/platform_RebootAfterUpdate.py
index 5710b6b..27199ba 100755
--- a/server/site_tests/platform_RebootAfterUpdate/platform_RebootAfterUpdate.py
+++ b/server/site_tests/platform_RebootAfterUpdate/platform_RebootAfterUpdate.py
@@ -6,9 +6,8 @@
 import time
 
 from autotest_lib.client.common_lib import error
-from autotest_lib.client.common_lib.cros import autoupdater, dev_server
 from autotest_lib.server import autotest, test
-from autotest_lib.server.cros.dynamic_suite import tools
+from autotest_lib.server.cros import autoupdate_utils
 
 
 class platform_RebootAfterUpdate(test.test):
@@ -68,28 +67,7 @@
                error.TestFail if any part of the test has failed.
 
         """
-        # Get the job_repo_url -- if not present, attempt to use the one
-        # specified in the host attributes for the host.
-        if not job_repo_url:
-            try:
-                job_repo_url = host.lookup_job_repo_url()
-            except KeyError:
-                logging.fatal('Could not lookup job_repo_url from afe.')
-
-            if not job_repo_url:
-                raise error.TestError(
-                        'Test could not be run. Missing the url with which to '
-                        're-image the device!')
-
-        # Get the devserver url and build (image) from the repo url e.g.
-        # 'http://mydevserver:8080', 'x86-alex-release/R27-123.0.0'
-        ds, build = tools.get_devserver_build_from_package_url(job_repo_url)
-        devserver = dev_server.ImageServer(ds)
-        devserver.stage_artifacts(build, ['stateful'])
-
-        # We only need to update stateful to do this test.
-        updater = autoupdater.ChromiumOSUpdater(
-                devserver.get_update_url(build), host=host)
+        updater = autoupdate_utils.get_updater_from_repo_url(host, job_repo_url)
         updater.update_stateful(clobber=True)
 
         logging.info('Rebooting after performing update.')