devserver: Add functionality to stage artifacts from a local directory.

This change updates the devserver so that local build and
test artifacts  can be staged onto the devserver.

The devserver stage function will now also accept a local_path
as an argument. local_path specifies a subdirectory of the
devserver's static directory that contains build artifacts we
want to stage. It's restricted to a subdirectory of the static
directory in order to not allow callers access to the whole
file system of the devserver.

This functionality will be used in supporting custom image
staging for the new 'cros stage-on-moblab' command.

BUG=chromium:370909
TEST=devserver_integration_test, unittests, & local moblab test run.
Change-Id: I617a61066e644657cdbfddee9762691c495d26b5
Reviewed-on: https://chromium-review.googlesource.com/239321
Reviewed-by: Simran Basi <sbasi@chromium.org>
Tested-by: Simran Basi <sbasi@chromium.org>
Commit-Queue: Simran Basi <sbasi@chromium.org>
diff --git a/downloader_unittest.py b/downloader_unittest.py
index 3c8083a..64ca12e 100755
--- a/downloader_unittest.py
+++ b/downloader_unittest.py
@@ -18,6 +18,7 @@
 
 # pylint: disable=W0212,E1120
 class DownloaderTestBase(mox.MoxTestBase):
+  """Downloader Unittests."""
 
   def setUp(self):
     mox.MoxTestBase.setUp(self)
@@ -26,18 +27,19 @@
     self.build = 'R17-1413.0.0-a1-b1346'
     self.archive_url = (
         'gs://chromeos-image-archive/%s/%s' % (self.board, self.build))
+    self.local_path = ('/local/path/x86-mario-release/R17-1413.0.0-a1-b1346')
 
   def tearDown(self):
     shutil.rmtree(self._work_dir, ignore_errors=True)
 
-  def testSimpleDownloadOfTestSuites(self):
-    """Basic test_suites test.
+  def _SimpleDownloadOfTestSuites(self, archive_path):
+    """Helper to verify test_suites are downloaded correctly.
 
-    Verifies that if we request the test_suites, it gets downloaded and
-    the autotest tarball is attempted in the background.
+    Args:
+      archive_path: Archive url or local path to test with.
     """
     downloader_instance = downloader.Downloader(self._work_dir,
-                                                self.archive_url)
+                                                archive_path)
     self.mox.StubOutWithMock(downloader.Downloader,
                              '_DownloadArtifactsSerially')
     self.mox.StubOutWithMock(downloader.Downloader,
@@ -46,7 +48,7 @@
     downloader.Downloader._DownloadArtifactsInBackground(mox.In(mox.IsA(
         build_artifact.AutotestTarballBuildArtifact)))
     downloader.Downloader._DownloadArtifactsSerially(
-        [mox.IsA(build_artifact.TarballBuildArtifact)], no_wait=True)
+        [mox.IsA(build_artifact.BundledBuildArtifact)], no_wait=True)
     self.mox.ReplayAll()
     downloader_instance.Download(artifacts=['test_suites'],
                                  files=None)
@@ -56,22 +58,45 @@
                      downloader.Downloader._TIMESTAMP_FILENAME)))
     self.mox.VerifyAll()
 
-  def testDownloadSymbols(self):
+  def testSimpleDownloadOfTestSuitesFromGS(self):
+    """Basic test_suites test.
+
+    Verifies that if we request the test_suites from Google Storage, it gets
+    downloaded and the autotest tarball is attempted in the background.
+    """
+    self._SimpleDownloadOfTestSuites(self.archive_url)
+
+  def testSimpleDownloadOfTestSuitesFromLocal(self):
+    """Basic test_suites test.
+
+    Verifies that if we request the test_suites from a local path, it gets
+    downloaded and the autotest tarball is attempted in the background.
+    """
+    self._SimpleDownloadOfTestSuites(self.local_path)
+
+  def _DownloadSymbolsHelper(self, archive_path):
     """Basic symbols download."""
-    downloader_instance = downloader.Downloader(self._work_dir,
-                                                self.archive_url)
+    downloader_instance = downloader.Downloader(self._work_dir, archive_path)
     self.mox.StubOutWithMock(downloader.Downloader,
                              '_DownloadArtifactsSerially')
     # Should not get called but mocking so that we know it wasn't called.
     self.mox.StubOutWithMock(downloader.Downloader,
                              '_DownloadArtifactsInBackground')
     downloader.Downloader._DownloadArtifactsSerially(
-        [mox.IsA(build_artifact.TarballBuildArtifact)], no_wait=True)
+        [mox.IsA(build_artifact.BundledBuildArtifact)], no_wait=True)
     self.mox.ReplayAll()
     downloader_instance.Download(artifacts=['symbols'],
                                  files=None)
     self.mox.VerifyAll()
 
+  def testDownloadSymbolsFromGS(self):
+    """Basic symbols download from Google Storage."""
+    self._DownloadSymbolsHelper(self.archive_url)
+
+  def testDownloadSymbolsFromLocal(self):
+    """Basic symbols download from a Local Path."""
+    self._DownloadSymbolsHelper(self.local_path)
+
 
 if __name__ == '__main__':
   unittest.main()