faft: Start devserver to serve the image and ask Servod to dump it to USB

Originally we assume Servod runs in the same machine as Autoserv such that
we can directly install image by giving a local path. But it is not true
in Autotest lab. Servod runs in a different host and doesn't understand
the local path.

We should start a devserver to serve the image, which is similar to the
lab, and pass the URL to Servod. Then Servod directly dump it to its USB.

The image argument also supports an URL now. So change the comment.

If Servod and Autoserv run in the same machine. It increases ~1 minute in
addition after this change (~3min vs ~4min). It takes time to setup devserver
and transfer the data on network loop.

BUG=chrome-os-partner:14884,chromium-os:34395
TEST=manual

For a local file:
$ ./run_remote_tests.sh --board daisy --remote dut -a "image=/home/waihong/trunk
/src/build/images/daisy/R22-2723.88.0/chromiumos_test_image.bin" FAFTSetup
See:
19:30:00 INFO | Server proxy: http://localhost:9990
19:30:11 INFO | Processing build: 2723.88.0 dev-channel
19:30:11 INFO | Starting devserver to serve the image...
19:30:11 INFO | Ask Servo to install the image from http://10.0.0.1:8090/static/
archive/chromiumos_test_image.bin
...
19:30:17 INFO | Searching for usb device and copying image to it.
...

For an URL:
$ start_devserver
$ wget -O - -t 1 -q --timeout 0 'http://localhost:8080/stage_images?archive_url=
gs://chromeos-image-archive/daisy-release/R23-2913.84.0&image_types=test'
$ ./run_remote_tests.sh --board daisy --remote dut -a "image=http://localhost:80
80/static/images/daisy-release/R23-2913.84.0/chromiumos_test_image.bin" FAFTSetup

Change-Id: Iadf30d892e665f3a2898fb31860992b95caa402f
Reviewed-on: https://gerrit.chromium.org/gerrit/35689
Commit-Ready: Tom Wai-Hong Tam <waihong@chromium.org>
Reviewed-by: Tom Wai-Hong Tam <waihong@chromium.org>
Tested-by: Tom Wai-Hong Tam <waihong@chromium.org>
diff --git a/server/cros/faftsequence.py b/server/cros/faftsequence.py
index 06f2586..9460520 100644
--- a/server/cros/faftsequence.py
+++ b/server/cros/faftsequence.py
@@ -6,6 +6,7 @@
 import logging
 import os
 import re
+import subprocess
 import sys
 import tempfile
 import time
@@ -70,13 +71,13 @@
         _faft_sequence: The registered FAFT_SEQUENCE.
         _customized_key_commands: The dict of the customized key commands,
             including Ctrl-D, Ctrl-U, Enter, Space, and recovery reboot.
-        _install_image_path: The path of Chrome OS test image to be installed.
+        _install_image_path: The URL or the path on the host to the Chrome OS
+            test image to be installed.
         _firmware_update: Boolean. True if firmware update needed after
             installing the image.
     """
     version = 1
 
-
     # Mapping of partition number of kernel and rootfs.
     KERNEL_MAP = {'a':'2', 'b':'4', '2':'2', '4':'4', '3':'2', '5':'4'}
     ROOTFS_MAP = {'a':'3', 'b':'5', '2':'3', '4':'5', '3':'3', '5':'5'}
@@ -114,6 +115,9 @@
     CHROMEOS_MAGIC = "CHROMEOS"
     CORRUPTED_MAGIC = "CORRUPTD"
 
+    _HTTP_PREFIX = 'http://'
+    _DEVSERVER_PORT = '8090'
+
     _faft_template = {}
     _faft_sequence = ()
 
@@ -321,6 +325,16 @@
                     'The image in the USB disk should be a test image.')
 
 
+    def get_server_address(self):
+        """Get the server address seen from the client.
+
+        Returns:
+          A string of the server address.
+        """
+        r = self.faft_client.run_shell_command_get_output("echo $SSH_CLIENT")
+        return r[0].split()[0]
+
+
     def install_test_image(self, image_path=None, firmware_update=False):
         """Install the test image specied by the path onto the USB and DUT disk.
 
@@ -343,16 +357,42 @@
         4. network connected via usb dongle in the dut in usb 3.0 slot.
 
         Args:
-            image_path: Path on the host to the test image.
+            image_path: An URL or a path on the host to the test image.
             firmware_update: Also update the firmware after installing.
         """
-        build_ver, build_hash = lab_test.VerifyImageAndGetId(cros_dir,
-                                                             image_path)
-        logging.info('Processing build: %s %s' % (build_ver, build_hash))
+        if image_path.startswith(self._HTTP_PREFIX):
+            # TODO(waihong@chromium.org): Add the check of the URL to ensure
+            # it is a test image.
+            devserver = None
+            image_url = image_path
+        else:
+            build_ver, build_hash = lab_test.VerifyImageAndGetId(cros_dir,
+                                                                 image_path)
+            logging.info('Processing build: %s %s' % (build_ver, build_hash))
 
-        # Reuse the servo method that uses the servo USB key to install
-        # the test image.
-        self.servo.image_to_servo_usb(image_path)
+            image_dir, image_base = os.path.split(image_path)
+            logging.info('Starting devserver to serve the image...')
+            # The following stdout and stderr arguments should not be None,
+            # even we don't use them. Otherwise, the socket of devserve is
+            # created as fd 1 (as no stdout) but it still thinks stdout is fd
+            # 1 and dump the log to the socket. Wrong HTTP protocol happens.
+            devserver = subprocess.Popen(['start_devserver',
+                        '--archive_dir=%s' % image_dir,
+                        '--port=%s' % self._DEVSERVER_PORT],
+                        stdout=subprocess.PIPE,
+                        stderr=subprocess.PIPE)
+            image_url = '%s%s:%s/static/archive/%s' % (
+                        self._HTTP_PREFIX,
+                        self.get_server_address(),
+                        self._DEVSERVER_PORT,
+                        image_base)
+
+        logging.info('Ask Servo to install the image from %s' % image_url)
+        self.servo.image_to_servo_usb(image_url)
+
+        if devserver and devserver.poll() is None:
+            logging.info('Shutting down devserver...')
+            devserver.terminate()
 
         # DUT is powered off while imaging servo USB.
         # Now turn it on.