autoupdate: Install stateful_update from the right location

stateful_update can be locally stored at 2 locations:
/usr/sbin/stateful_update installed by package chromeos-base/devserver,
or by python modules: either during unit test or by build_externals.py on
the labserver.

BUG=chromium:689105
TEST=Unit test. Test stateful_update is executable.
Check powerwash machines are updating properly.

Change-Id: Ib43552651b7b1bae594254253b0d623f5e72f8a5
Signed-off-by: Gwendal Grignou <gwendal@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/527422
Reviewed-by: Richard Barnette <jrbarnette@google.com>
diff --git a/client/common_lib/cros/autoupdater.py b/client/common_lib/cros/autoupdater.py
index 3a36557..510091c 100644
--- a/client/common_lib/cros/autoupdater.py
+++ b/client/common_lib/cros/autoupdater.py
@@ -20,9 +20,14 @@
 except ImportError:
     metrics = utils.metrics_mock
 
+try:
+    import devserver
+    STATEFUL_UPDATE_PATH = devserver.__path__[0]
+except ImportError:
+    STATEFUL_UPDATE_PATH = '/usr/bin'
+
 # Local stateful update path is relative to the CrOS source directory.
-LOCAL_STATEFUL_UPDATE_PATH = 'src/platform/dev/stateful_update'
-LOCAL_CHROOT_STATEFUL_UPDATE_PATH = '/usr/bin/stateful_update'
+STATEFUL_UPDATE_SCRIPT = 'stateful_update'
 UPDATER_IDLE = 'UPDATE_STATUS_IDLE'
 UPDATER_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT'
 # A list of update engine client states that occur after an update is triggered.
@@ -325,9 +330,11 @@
 
 class ChromiumOSUpdater(BaseUpdater):
     """Helper class used to update DUT with image of desired version."""
-    REMOTE_STATEUL_UPDATE_PATH = '/usr/local/bin/stateful_update'
+    REMOTE_STATEFUL_UPDATE_PATH = os.path.join(
+            '/usr/local/bin', STATEFUL_UPDATE_SCRIPT)
+    REMOTE_TMP_STATEFUL_UPDATE = os.path.join(
+            '/tmp', STATEFUL_UPDATE_SCRIPT)
     UPDATER_BIN = '/usr/bin/update_engine_client'
-    STATEFUL_UPDATE = '/tmp/stateful_update'
     UPDATED_MARKER = '/var/run/update_engine_autoupdate_completed'
     UPDATER_LOGS = ['/var/log/messages', '/var/log/update_engine']
 
@@ -418,34 +425,31 @@
 
 
     def get_stateful_update_script(self):
-        """Returns the path to the stateful update script on the target."""
-        # We attempt to load the local stateful update path in 3 different
-        # ways. First we use the location specified in the autotest global
-        # config. If this doesn't exist, we attempt to use the Chromium OS
-        # Chroot path to the installed script. If all else fails, we use the
-        # stateful update script on the host.
-        stateful_update_path = os.path.join(
-                global_config.global_config.get_config_value(
-                        'CROS', 'source_tree', default=''),
-                LOCAL_STATEFUL_UPDATE_PATH)
+        """Returns the path to the stateful update script on the target.
 
-        if not os.path.exists(stateful_update_path):
-            logging.warning('Could not find Chrome OS source location for '
-                            'stateful_update script at %s, falling back to '
-                            'chroot copy.', stateful_update_path)
-            stateful_update_path = LOCAL_CHROOT_STATEFUL_UPDATE_PATH
+        When runnning test_that, stateful_update is in chroot /usr/sbin,
+        as installed by chromeos-base/devserver packages.
+        In the lab, it is installed with the python module devserver, by
+        build_externals.py command.
 
-        if not os.path.exists(stateful_update_path):
-            logging.warning('Could not chroot stateful_update script, falling '
-                            'back on client copy.')
-            statefuldev_script = self.REMOTE_STATEUL_UPDATE_PATH
-        else:
+        If we can find it, we hope it exists already on the DUT, we assert
+        otherwise.
+        """
+        stateful_update_file = os.path.join(STATEFUL_UPDATE_PATH,
+                                            STATEFUL_UPDATE_SCRIPT)
+        if os.path.exists(stateful_update_file):
             self.host.send_file(
-                    stateful_update_path, self.STATEFUL_UPDATE,
+                    stateful_update_file, self.REMOTE_TMP_STATEFUL_UPDATE,
                     delete_dest=True)
-            statefuldev_script = self.STATEFUL_UPDATE
+            return self.REMOTE_TMP_STATEFUL_UPDATE
 
-        return statefuldev_script
+        if self.host.path_exists(self.REMOTE_STATEFUL_UPDATE_PATH):
+            logging.warning('Could not chroot %s script, falling back on %s',
+                   STATEFUL_UPDATE_SCRIPT, self.REMOTE_STATEFUL_UPDATE_PATH)
+            return self.REMOTE_STATEFUL_UPDATE_PATH
+        else:
+            raise ChromiumOSError('Could not locate %s',
+                                  STATEFUL_UPDATE_SCRIPT)
 
 
     def reset_stateful_partition(self):
diff --git a/client/common_lib/cros/autoupdater_unittest.py b/client/common_lib/cros/autoupdater_unittest.py
index 65bb520..d29dffd 100755
--- a/client/common_lib/cros/autoupdater_unittest.py
+++ b/client/common_lib/cros/autoupdater_unittest.py
@@ -4,6 +4,7 @@
 # found in the LICENSE file.
 
 import mox
+import os
 import unittest
 
 import common
@@ -11,6 +12,7 @@
 
 import autoupdater
 from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.test_utils import mock
 
 class TestAutoUpdater(mox.MoxTestBase):
     """Test autoupdater module."""
@@ -517,12 +519,12 @@
 
         # Test with clobber=False.
         autoupdater.ChromiumOSUpdater.get_stateful_update_script().AndReturn(
-                autoupdater.ChromiumOSUpdater.REMOTE_STATEUL_UPDATE_PATH)
+                autoupdater.ChromiumOSUpdater.REMOTE_STATEFUL_UPDATE_PATH)
         autoupdater.ChromiumOSUpdater._run(
                 mox.And(
                         mox.StrContains(
                                 autoupdater.ChromiumOSUpdater.
-                                REMOTE_STATEUL_UPDATE_PATH),
+                                REMOTE_STATEFUL_UPDATE_PATH),
                         mox.StrContains(static_update_url),
                         mox.Not(mox.StrContains('--stateful_change=clean'))),
                 timeout=mox.IgnoreArg())
@@ -535,12 +537,12 @@
         # Test with clobber=True.
         self.mox.ResetAll()
         autoupdater.ChromiumOSUpdater.get_stateful_update_script().AndReturn(
-                autoupdater.ChromiumOSUpdater.REMOTE_STATEUL_UPDATE_PATH)
+                autoupdater.ChromiumOSUpdater.REMOTE_STATEFUL_UPDATE_PATH)
         autoupdater.ChromiumOSUpdater._run(
                 mox.And(
                         mox.StrContains(
                                 autoupdater.ChromiumOSUpdater.
-                                REMOTE_STATEUL_UPDATE_PATH),
+                                REMOTE_STATEFUL_UPDATE_PATH),
                         mox.StrContains(static_update_url),
                         mox.StrContains('--stateful_change=clean')),
                 timeout=mox.IgnoreArg())
@@ -550,6 +552,44 @@
         self.mox.VerifyAll()
 
 
+    def testGetStatefulUpdateScript(self):
+        """ Test that get_stateful_update_script look for stateful_update.
+
+        Check get_stateful_update_script is trying hard to find
+        stateful_update and assert if it can't.
+
+        """
+        update_url = ('http://172.22.50.205:8082/update/lumpy-chrome-perf/'
+                      'R28-4444.0.0-b2996')
+        script_loc = os.path.join(autoupdater.STATEFUL_UPDATE_PATH,
+                                  autoupdater.STATEFUL_UPDATE_SCRIPT)
+        self.god = mock.mock_god()
+        self.god.stub_function(os.path, 'exists')
+        host = self.mox.CreateMockAnything()
+        updater = autoupdater.ChromiumOSUpdater(update_url, host=host)
+        os.path.exists.expect_call(script_loc).and_return(False)
+        host.path_exists('/usr/local/bin/stateful_update').AndReturn(False)
+
+        self.mox.ReplayAll()
+        # No existing files, no URL, we should assert.
+        self.assertRaises(
+                autoupdater.ChromiumOSError,
+                updater.get_stateful_update_script)
+        self.mox.VerifyAll()
+
+        # No existing files, but stateful URL, we will try.
+        self.mox.ResetAll()
+        os.path.exists.expect_call(script_loc).and_return(True)
+        host.send_file(
+                script_loc,
+                '/tmp/stateful_update', delete_dest=True).AndReturn(True)
+        self.mox.ReplayAll()
+        self.assertEqual(
+                updater.get_stateful_update_script(),
+                '/tmp/stateful_update')
+        self.mox.VerifyAll()
+
+
     def testRollbackRootfs(self):
         """Tests that we correctly rollback the rootfs when requested."""
         self.mox.StubOutWithMock(autoupdater.ChromiumOSUpdater, '_run')