[autotest] Deployment should use existing servo port.

If a deploy with servo v4 fails on a dut after the servo host and port get saved
into the afe for that particular dut, subsequent deploys for that same
dut will grab new unused ports instead of using the port already
reserved for it.  That seems senseless so let's use the port already
chosen for that dut.  A sanity check is done to make sure the port is
reserved on the same servo host.

BUG=None
TEST=deployed a dut that already had an entry in the AFE and used the
same port.

Change-Id: Ia7dce2249a076ad8d70322677158a7650007479c
Reviewed-on: https://chromium-review.googlesource.com/463788
Commit-Ready: Kevin Cheng <kevcheng@chromium.org>
Tested-by: Kevin Cheng <kevcheng@chromium.org>
Reviewed-by: Prathmesh Prabhu <pprabhu@chromium.org>
Reviewed-by: Allen Li <ayatane@chromium.org>
diff --git a/site_utils/deployment/install.py b/site_utils/deployment/install.py
index f57b598..0eb7e07 100644
--- a/site_utils/deployment/install.py
+++ b/site_utils/deployment/install.py
@@ -95,6 +95,10 @@
 _ReportResult = namedtuple('_ReportResult', ['hostname', 'message'])
 
 
+class _NoAFEServoPortError(Exception):
+    """Exception when there is no servo port stored in the AFE."""
+
+
 class _MultiFileWriter(object):
 
     """Group file objects for writing at once."""
@@ -636,6 +640,33 @@
     return servo_port
 
 
+def _get_afe_servo_port(host_info, afe):
+    """
+    Get the servo port from the afe if it matches the same servo host hostname.
+
+    @param host_info   HostInfo tuple (hostname, host_attr_dict).
+
+    @returns Servo port (int) if servo host hostname matches the one specified
+    host_info.host_attr_dict, otherwise None.
+
+    @raises _NoAFEServoPortError: When there is no stored host info or servo
+        port host attribute in the AFE for the given host.
+    """
+    afe_hosts = afe.get_hosts(hostname=host_info.hostname)
+    if not afe_hosts:
+        raise _NoAFEServoPortError
+
+    servo_port = afe_hosts[0].attributes.get(servo_host.SERVO_PORT_ATTR)
+    afe_servo_host = afe_hosts[0].attributes.get(servo_host.SERVO_HOST_ATTR)
+    host_info_servo_host = host_info.host_attr_dict.get(
+        servo_host.SERVO_HOST_ATTR)
+
+    if afe_servo_host == host_info_servo_host and servo_port:
+        return int(servo_port)
+    else:
+        raise _NoAFEServoPortError
+
+
 def _get_host_attributes(host_info_list, afe):
     """
     Get host attributes if a hostname_file was supplied.
@@ -652,8 +683,14 @@
     used_servo_ports = {}
     for host_info in host_info_list:
         host_attr_dict = host_info.host_attr_dict
-        host_attr_dict[servo_host.SERVO_PORT_ATTR] = _get_free_servo_port(
-                host_attr_dict[servo_host.SERVO_HOST_ATTR],used_servo_ports,
+        # If the host already has an entry in the AFE that matches the same
+        # servo host hostname and the servo port is set, use that port.
+        try:
+            host_attr_dict[servo_host.SERVO_PORT_ATTR] = _get_afe_servo_port(
+                host_info, afe)
+        except _NoAFEServoPortError:
+            host_attr_dict[servo_host.SERVO_PORT_ATTR] = _get_free_servo_port(
+                host_attr_dict[servo_host.SERVO_HOST_ATTR], used_servo_ports,
                 afe)
         host_attributes[host_info.hostname] = host_attr_dict
     return host_attributes