devserver: support for serving remotely hosted payloads

This feature is aimed to allow one instance of a devserver to respond to
an update check with a link to a payload that's hosted (statically
staged) on another devserver. Specifically, we plan on using it in the
test lab to spawn test-private devserver instances that will direct DUTs
to obtain their payloads from a central devserver. To use this feature,
the test-private instance should be invoked with the following switches:

 --archive_dir=static/  (avoids payload generation, value insignificant)
 --urlbase=http://<central-devserver-hostname>:<port>
 --payload=static/<path>/<to>/<payload>/<file>
 --remote_payload  (triggers proper handling of the remote payload)

Note that the --payload value is optional, and is only used if the
update check issued by the client does not contain a payload URL, for
example: .../update/static/<path>/<to>/<payload>/<file>

* Adds a new option --remote_payload for triggering special handling of
  remotely hosted payload files. This is hard to infer based on existing
  options (such as --urlbase) and disambiguates the behavior.

* Added functionality for retrieving necessary attributes of the remote
  payload file, such as size and hashes, which need to be send back to
  the DUT in the update response.

* The payload file name is assumed to be a devserver constant
  (update.gz).

* The remote payload URL is assumed to have a /static prefix to it. This
  invariant must be preserved by both the client (request) and the
  backend devserver (provisioning).

* Added unit test to cover remote payload logic.

BUG=chromium-os:33762
TEST=Devserver responds with remote payload data; passes unit tests.

Change-Id: Ief34bbd18d9046460f2b2a7a6c88b465d65426e8
Reviewed-on: https://gerrit.chromium.org/gerrit/34499
Reviewed-by: Chris Sosa <sosa@chromium.org>
Reviewed-by: Scott Zawalski <scottz@chromium.org>
Commit-Ready: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
diff --git a/autoupdate_unittest.py b/autoupdate_unittest.py
index 2fd88a7..f06b981 100755
--- a/autoupdate_unittest.py
+++ b/autoupdate_unittest.py
@@ -34,6 +34,7 @@
     self.mox.StubOutWithMock(common_util, 'GetFileSha256')
     self.mox.StubOutWithMock(autoupdate.Autoupdate, 'GetUpdatePayload')
     self.mox.StubOutWithMock(autoupdate.Autoupdate, '_GetLatestImageDir')
+    self.mox.StubOutWithMock(autoupdate.Autoupdate, '_GetRemotePayloadAttrs')
     self.port = 8080
     self.test_board = 'test-board'
     self.build_root = '/src_path/build/images'
@@ -51,18 +52,18 @@
     }
     self.test_data = _TEST_REQUEST % self.test_dict
     self.forced_image_path = '/path_to_force/chromiumos_image.bin'
-    self.hash = 12345
+    self.sha1 = 12345
     self.size = 54321
     self.url = 'http://%s/static/update.gz' % self.hostname
     self.payload = 'My payload'
     self.sha256 = 'SHA LA LA'
     cherrypy.request.base = 'http://%s' % self.hostname
 
-  def _DummyAutoupdateConstructor(self):
+  def _DummyAutoupdateConstructor(self, **kwargs):
     """Creates a dummy autoupdater.  Used to avoid using constructor."""
     dummy = autoupdate.Autoupdate(root_dir=None,
                                   static_dir=self.static_image_dir,
-                                  port=self.port)
+                                  **kwargs)
     return dummy
 
   def testGetRightSignedDeltaPayloadDir(self):
@@ -115,13 +116,13 @@
         self.forced_image_path,
         static_image_dir=self.static_image_dir).AndReturn('update.gz')
     common_util.GetFileSha1(os.path.join(
-        self.static_image_dir, 'update.gz')).AndReturn(self.hash)
+        self.static_image_dir, 'update.gz')).AndReturn(self.sha1)
     common_util.GetFileSha256(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.sha256)
     common_util.GetFileSize(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.size)
     autoupdate.Autoupdate.GetUpdatePayload(
-        self.hash, self.sha256, self.size, self.url, False).AndReturn(
+        self.sha1, self.sha256, self.size, self.url, False).AndReturn(
             self.payload)
 
     self.mox.ReplayAll()
@@ -139,13 +140,13 @@
         self.test_board, 'ForcedUpdate', self.static_image_dir).AndReturn(
             'update.gz')
     common_util.GetFileSha1(os.path.join(
-        self.static_image_dir, 'update.gz')).AndReturn(self.hash)
+        self.static_image_dir, 'update.gz')).AndReturn(self.sha1)
     common_util.GetFileSha256(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.sha256)
     common_util.GetFileSize(os.path.join(
         self.static_image_dir, 'update.gz')).AndReturn(self.size)
     autoupdate.Autoupdate.GetUpdatePayload(
-        self.hash, self.sha256, self.size, self.url, False).AndReturn(
+        self.sha1, self.sha256, self.size, self.url, False).AndReturn(
             self.payload)
 
     self.mox.ReplayAll()
@@ -211,13 +212,13 @@
         self.test_board, 'ForcedUpdate', new_image_dir).AndReturn(
             'update.gz')
     common_util.GetFileSha1(os.path.join(
-        new_image_dir, 'update.gz')).AndReturn(self.hash)
+        new_image_dir, 'update.gz')).AndReturn(self.sha1)
     common_util.GetFileSha256(os.path.join(
         new_image_dir, 'update.gz')).AndReturn(self.sha256)
     common_util.GetFileSize(os.path.join(
         new_image_dir, 'update.gz')).AndReturn(self.size)
     autoupdate.Autoupdate.GetUpdatePayload(
-        self.hash, self.sha256, self.size, new_url, False).AndReturn(
+        self.sha1, self.sha256, self.size, new_url, False).AndReturn(
             self.payload)
 
     self.mox.ReplayAll()
@@ -270,6 +271,26 @@
     self.assertTrue(au._CanUpdate('0.16.892.0', '0.16.892.1'))
     self.assertFalse(au._CanUpdate('0.16.892.0', '0.16.892.0'))
 
+  def testHandleUpdatePingRemotePayload(self):
+    remote_urlbase = 'http://remotehost:6666'
+    remote_payload_path = 'static/path/to/update.gz'
+    remote_url = '/'.join([remote_urlbase, remote_payload_path, 'update.gz'])
+
+    test_data = _TEST_REQUEST % self.test_dict
+
+    autoupdate.Autoupdate._GetRemotePayloadAttrs(remote_url).AndReturn(
+            (self.sha1, self.sha256, self.size, False))
+    autoupdate.Autoupdate.GetUpdatePayload(
+        self.sha1, self.sha256, self.size, remote_url, False).AndReturn(
+            self.payload)
+
+    self.mox.ReplayAll()
+    au_mock = self._DummyAutoupdateConstructor(urlbase=remote_urlbase,
+                                               payload_path=remote_payload_path,
+                                               remote_payload=True)
+    self.assertEqual(au_mock.HandleUpdatePing(test_data), self.payload)
+    self.mox.VerifyAll()
+
 
 if __name__ == '__main__':
   unittest.main()