Find unused TCP port for client proxy

We currently use hardcoded port number and this causes a problem when we
run multiple FAFT instance on the same host. Let's change this to find
an unused port whenever we want to start a client proxy.

BUG=chrome-os-partner:19097
TEST=Run FAFT and see port number changes everytime.

Change-Id: I37e410a386d37dbd2800bcf15568a1a6dd21e087
Signed-off-by: Vic Yang <victoryang@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/49840
Reviewed-by: Tom Wai-Hong Tam <waihong@chromium.org>
diff --git a/server/cros/servo_test.py b/server/cros/servo_test.py
index c310f08..7769b9b 100644
--- a/server/cros/servo_test.py
+++ b/server/cros/servo_test.py
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import httplib, logging, os, socket, subprocess, sys, time, xmlrpclib
+import SocketServer
 
 from autotest_lib.client.common_lib import error
 from autotest_lib.server import autotest, test
@@ -18,7 +19,7 @@
     """
     version = 2
 
-    _PORT = 9990
+    _REMOTE_PORT = 9990
     _REMOTE_COMMAND = '/usr/local/autotest/cros/faft_client.py'
     _REMOTE_COMMAND_SHORT = 'faft_client'
     _REMOTE_LOG_FILE = '/tmp/faft_client.log'
@@ -39,6 +40,7 @@
         self._client = host
         self._ssh_tunnel = None
         self._remote_process = None
+        self._local_port = None
 
         # Initializes dut, may raise AssertionError if pre-defined gpio
         # sequence to set GPIO's fail.  Autotest does not handle exception
@@ -119,7 +121,7 @@
 
         # Connect to RPC object.
         logging.info('Connecting to client RPC server...')
-        remote_url = 'http://localhost:%s' % self._PORT
+        remote_url = 'http://localhost:%s' % self._local_port
         self.faft_client = xmlrpclib.ServerProxy(remote_url, allow_none=True)
         logging.info('Server proxy: %s', remote_url)
 
@@ -208,16 +210,25 @@
         """Delete the Servo object, call remote cleanup, and kill ssh."""
         self.kill_remote()
 
+    def _find_unused_port(self):
+        """Returns an unused TCP port."""
+        server = SocketServer.TCPServer(('localhost', 0),
+                                        SocketServer.BaseRequestHandler)
+        _, port = server.server_address
+        return port
+
     def _launch_ssh_tunnel(self):
         """Establish an ssh tunnel for connecting to the remote RPC server.
         """
+        if self._local_port is None:
+            self._local_port = self._find_unused_port()
         if not self._ssh_tunnel or self._ssh_tunnel.poll() is not None:
             self._ssh_tunnel = subprocess.Popen([
                 'ssh -N -n -q %s -L %s:localhost:%s root@%s' %
-                (self._SSH_CONFIG, self._PORT, self._PORT,
+                (self._SSH_CONFIG, self._local_port, self._REMOTE_PORT,
                 self._client.ip)], shell=True)
             assert self._ssh_tunnel.poll() is None, \
-                'The SSH tunnel on port %d is not up.' % self._PORT
+                'The SSH tunnel on port %d is not up.' % self._local_port
 
     def _kill_remote_process(self):
         """Ensure the remote process and local ssh process are terminated.