acloud: Support acloud reconnect command.

Bug: 118648283
Test: m acloud && atest acloud_test && acloud reconnect
1. remote instance, no ssh tunnel established.
   - tunnel established , adb connect and vnc started.
2. remote instance, ssh tunnel established, no adb connect/vnc started.
   - adb connect and vnc started.
3. remote instance, ssh tunnel established, adb connected but no vnc started.
   - vnc started.
4. remote instance, ssh tunnel established, no connected but vnc started.
   - adb started.
5. local instance, no adb connected/vnc started.
   - adb connect and vnc started.
6. local instance, no adb connected but vnc started.
   - adb connect.
7.local instance, adb connected but no vnc started.
   - vnc started.
Change-Id: I19e3fea1d312666907deeb3e2069cae2458107ea
diff --git a/list/instance.py b/list/instance.py
index 87e4efd..1e202ce 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -35,7 +35,6 @@
 
 logger = logging.getLogger(__name__)
 
-_COMMAND_PS = ["ps", "aux"]
 _RE_GROUP_ADB = "local_adb_port"
 _RE_GROUP_VNC = "local_vnc_port"
 _RE_SSH_TUNNEL_PATTERN = (r"((.*\s*-L\s)(?P<%s>\d+):127.0.0.1:%s)"
@@ -65,7 +64,7 @@
         self._ip = None
         self._adb_port = None  # adb port which is forwarding to remote
         self._vnc_port = None  # vnc port which is forwarding to remote
-        self._is_ssh_tunnel_connected = None  # True if ssh tunnel is still connected
+        self._ssh_tunnel_is_connected = None  # True if ssh tunnel is still connected
         self._createtime = None
         self._avd_type = None
         self._avd_flavor = None
@@ -131,9 +130,9 @@
         return self._vnc_port
 
     @property
-    def is_ssh_tunnel_connected(self):
+    def ssh_tunnel_is_connected(self):
         """Return the connect status."""
-        return self._is_ssh_tunnel_connected
+        return self._ssh_tunnel_is_connected
 
     @property
     def createtime(self):
@@ -190,7 +189,7 @@
                 local_instance._vnc_port = constants.DEFAULT_VNC_PORT
                 local_instance._display = ("%sx%s (%s)" % (x_res, y_res, dpi))
                 local_instance._is_local = True
-                local_instance._is_ssh_tunnel_connected = True
+                local_instance._ssh_tunnel_is_connected = True
                 return local_instance
         return None
 
@@ -239,12 +238,12 @@
             self._adb_port = forwarded_ports.adb_port
             self._vnc_port = forwarded_ports.vnc_port
             if self._adb_port:
-                self._is_ssh_tunnel_connected = True
+                self._ssh_tunnel_is_connected = True
                 self._fullname = (_FULL_NAME_STRING %
                                   {"device_serial": "127.0.0.1:%d" % self._adb_port,
                                    "instance_name": self._name})
             else:
-                self._is_ssh_tunnel_connected = False
+                self._ssh_tunnel_is_connected = False
                 self._fullname = (_FULL_NAME_STRING %
                                   {"device_serial": "not connected",
                                    "instance_name": self._name})
@@ -271,7 +270,7 @@
             NamedTuple ForwardedPorts(vnc_port, adb_port) holding the ports
             used in the ssh forwarded call. Both fields are integers.
         """
-        process_output = subprocess.check_output(_COMMAND_PS)
+        process_output = subprocess.check_output(constants.COMMAND_PS)
         re_pattern = re.compile(_RE_SSH_TUNNEL_PATTERN %
                                 (_RE_GROUP_VNC, constants.DEFAULT_VNC_PORT,
                                  _RE_GROUP_ADB, constants.DEFAULT_ADB_PORT, ip))
diff --git a/list/instance_test.py b/list/instance_test.py
index 6010d5c..5fd1963 100644
--- a/list/instance_test.py
+++ b/list/instance_test.py
@@ -13,7 +13,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-"""Tests for host_setup_runner."""
+"""Tests for instance class."""
 import collections
 import subprocess
 
@@ -87,22 +87,22 @@
             "GetAdbVncPortFromSSHTunnel",
             return_value=forwarded_ports(vnc_port=fake_vnc, adb_port=fake_adb))
 
-        # test is_ssh_tunnel_connected will be true if ssh tunnel connection is found
+        # test ssh_tunnel_is_connected will be true if ssh tunnel connection is found
         instance_info = instance.RemoteInstance(gce_instance)
-        self.assertTrue(instance_info.is_ssh_tunnel_connected)
+        self.assertTrue(instance_info.ssh_tunnel_is_connected)
         self.assertEqual(instance_info.forwarding_adb_port, fake_adb)
         self.assertEqual(instance_info.forwarding_vnc_port, fake_vnc)
         expected_full_name = "device serial: 127.0.0.1:%s (%s)" % (fake_adb,
                                                                    instance_info.name)
         self.assertEqual(expected_full_name, instance_info.fullname)
 
-        # test is_ssh_tunnel_connected will be false if ssh tunnel connection is not found
+        # test ssh_tunnel_is_connected will be false if ssh tunnel connection is not found
         self.Patch(
             instance.RemoteInstance,
             "GetAdbVncPortFromSSHTunnel",
             return_value=forwarded_ports(vnc_port=None, adb_port=None))
         instance_info = instance.RemoteInstance(gce_instance)
-        self.assertFalse(instance_info.is_ssh_tunnel_connected)
+        self.assertFalse(instance_info.ssh_tunnel_is_connected)
         expected_full_name = ("device serial: not connected (%s)" %
                               instance_info.name)
         self.assertEqual(expected_full_name, instance_info.fullname)
diff --git a/list/list.py b/list/list.py
index ee47c58..9354a99 100644
--- a/list/list.py
+++ b/list/list.py
@@ -21,6 +21,7 @@
 import getpass
 import logging
 
+from acloud import errors
 from acloud.internal import constants
 from acloud.internal.lib import auth
 from acloud.internal.lib import gcompute_client
@@ -128,8 +129,8 @@
 def ChooseInstances(cfg, select_all_instances=False):
     """Get instances.
 
-    Get remote and local instances and ask user to choose them to use.
-    (delete or reconnect)
+    Retrieve all remote/local instances and if there is more than 1 instance
+    found, ask user which instance they'd like.
 
     Args:
         cfg: AcloudConfig object.
@@ -149,6 +150,40 @@
     return instances_list
 
 
+def GetInstancesFromInstanceNames(cfg, instance_names):
+    """Get instances from instance names.
+
+    Turn a list of instance names into a list of Instance().
+
+    Args:
+        cfg: AcloudConfig object.
+        instance_names: list of instance name.
+
+    Returns:
+        List of Instance() object.
+
+    Raises:
+        errors.NoInstancesFound: No instances found.
+    """
+    instance_list = []
+    full_list_of_instance = GetInstances(cfg)
+    for instance_object in full_list_of_instance:
+        if instance_object.name in instance_names:
+            instance_list.append(instance_object)
+
+    #find the missing instance.
+    missing_instances = []
+    instance_list_names = [instance_object.name for instance_object in instance_list]
+    missing_instances = [
+        instance_name for instance_name in instance_names
+        if instance_name not in instance_list_names
+    ]
+    if missing_instances:
+        raise errors.NoInstancesFound("Did not find the following instances: %s" %
+                                      ' '.join(missing_instances))
+    return instance_list
+
+
 def Run(args):
     """Run list.
 
diff --git a/list/list_test.py b/list/list_test.py
new file mode 100644
index 0000000..5cbe02a
--- /dev/null
+++ b/list/list_test.py
@@ -0,0 +1,66 @@
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests for list."""
+import unittest
+
+import mock
+
+from acloud import errors
+from acloud.internal.lib import driver_test_lib
+from acloud.list import list as list_instance
+
+
+class InstanceObject(object):
+    """Mock to store data of instance."""
+
+    def __init__(self, name):
+        self.name = name
+
+
+class ListTest(driver_test_lib.BaseDriverTest):
+    """Test list."""
+
+    def testGetInstancesFromInstanceNames(self):
+        """test get instances from instance names."""
+        cfg = mock.MagicMock()
+        instance_names = ["alive_instance1", "alive_local_instance"]
+
+        alive_instance1 = InstanceObject("alive_instance1")
+        alive_instance2 = InstanceObject("alive_instance2")
+        alive_local_instance = InstanceObject("alive_local_instance")
+
+        instance_alive = [alive_instance1, alive_instance2, alive_local_instance]
+        self.Patch(list_instance, "GetInstances", return_value=instance_alive)
+        instances_list = list_instance.GetInstancesFromInstanceNames(cfg, instance_names)
+        instances_name_in_list = [instance_object.name for instance_object in instances_list]
+        self.assertEqual(instances_name_in_list.sort(), instance_names.sort())
+
+        instance_names = ["alive_instance1", "alive_local_instance", "alive_local_instance"]
+        instances_list = list_instance.GetInstancesFromInstanceNames(cfg, instance_names)
+        instances_name_in_list = [instance_object.name for instance_object in instances_list]
+        self.assertEqual(instances_name_in_list.sort(), instance_names.sort())
+
+        # test get instance from instance name error with invalid input.
+        instance_names = ["miss2_local_instance", "alive_instance1"]
+        miss_instance_names = ["miss2_local_instance"]
+        self.assertRaisesRegexp(
+            errors.NoInstancesFound,
+            "Did not find the following instances: %s" % ' '.join(miss_instance_names),
+            list_instance.GetInstancesFromInstanceNames,
+            cfg=cfg,
+            instance_names=instance_names)
+
+
+if __name__ == "__main__":
+    unittest.main()