Allow async call to stage artifacts.

Add async argument to allow async call to stage artifacts. Default to False. In
async mode, client will poll devserver.is_staged method to check if artifacts
are staged.

BUG=chromium:249426
TEST=local devserver with link like:
http://localhost:8093/stage?archive_url=gs://chromeos-image-archive/lumpy-release/R30-4408.0.0/&artifacts=autotest,test_suites&async=True
http://localhost:8093/is_staged?archive_url=gs://chromeos-image-archive/lumpy-release/R28-4100.7.0&artifacts=autotest,test_suites
run_suite from local setup to stage a build that's not in the devserver.

Change-Id: I7777d5d25e8870fef7edf8a9084bca18f3624c46
Reviewed-on: https://gerrit.chromium.org/gerrit/64310
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Queue: Dan Shi <dshi@chromium.org>
Reviewed-by: Dan Shi <dshi@chromium.org>
Tested-by: Dan Shi <dshi@chromium.org>
diff --git a/devserver.py b/devserver.py
index 259b1a2..e176699 100755
--- a/devserver.py
+++ b/devserver.py
@@ -102,7 +102,6 @@
   Returns:
     number of white space chars before characters start.
   """
-  # pylint: disable=W1401
   matched = re.match('^\s+', string)
   if matched:
     return len(matched.group())
@@ -403,14 +402,37 @@
 
     Args:
       archive_url: Google Storage URL for the build.
+      artifacts: Comma separated list of artifacts to download.
 
     Example URL:
       http://myhost/download?archive_url=gs://chromeos-image-archive/
-      x86-generic/R17-1208.0.0-a1-b338
+      x86-generic/R17-1208.0.0-a1-b338&artifacts=full_payload,test_suites,
+      stateful
     """
+    async = kwargs.get('async', False)
     return self.stage(archive_url=kwargs.get('archive_url'),
-                      artifacts='full_payload,test_suites,stateful')
+                      artifacts=kwargs.get('artifacts'),
+                      async=async)
 
+  @cherrypy.expose
+  def is_staged(self, **kwargs):
+    """Check if artifacts have been downloaded.
+
+    @param archive_url: Google Storage URL for the build.
+    @param artifacts: Comma separated list of artifacts to download.
+    @returns: True of all artifacts are staged.
+
+    Example:
+      To check if autotest and test_suites are staged:
+        http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
+            artifacts=autotest,test_suites
+    """
+    archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
+    artifacts = kwargs.get('artifacts', '')
+    if not artifacts:
+      raise DevServerError('No artifacts specified.')
+    return str(downloader.Downloader(updater.static_dir, archive_url).IsStaged(
+        artifacts.split(',')))
 
   @cherrypy.expose
   def stage(self, **kwargs):
@@ -428,6 +450,7 @@
     Args:
       archive_url: Google Storage URL for the build.
       artifacts: Comma separated list of artifacts to download.
+      async: True to return without waiting for download to complete.
 
     Example:
       To download the autotest and test suites tarballs:
@@ -451,20 +474,20 @@
     """
     archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
     artifacts = kwargs.get('artifacts', '')
+    async = kwargs.get('async', False)
     if not artifacts:
       raise DevServerError('No artifacts specified.')
 
     with DevServerRoot._staging_thread_count_lock:
       DevServerRoot._staging_thread_count += 1
     try:
-      downloader.Downloader(updater.static_dir, archive_url).Download(
-          artifacts.split(','))
+      downloader.Downloader(updater.static_dir,
+          archive_url).Download(artifacts.split(','), async=async)
     finally:
       with DevServerRoot._staging_thread_count_lock:
         DevServerRoot._staging_thread_count -= 1
     return 'Success'
 
-
   @cherrypy.expose
   def setup_telemetry(self, **kwargs):
     """Extracts and sets up telemetry
@@ -532,7 +555,7 @@
       x86-generic/R17-1208.0.0-a1-b338
     """
     return self.stage(archive_url=kwargs.get('archive_url'),
-                      artifacts='full_payload,test_suites,autotest,stateful')
+                      artifacts=kwargs.get('artifacts'))
 
   @cherrypy.expose
   def stage_debug(self, **kwargs):