Merge "Clean files and folders before reusing the existing instance."
diff --git a/README.md b/README.md
index 8fcbb7c..9bd3fed 100755
--- a/README.md
+++ b/README.md
@@ -29,9 +29,6 @@
 
 You should now be able to call acloud from anywhere.
 
-> If you're a googler, please check https://goto.google.com/acloud-googler-setup
-> to ensure a smooth setup experience.
-
 ### **Basic Usage**
 
 Acloud commands take the following form:
@@ -45,6 +42,7 @@
 * [delete](#delete)
 * [reconnect](#reconnect)
 * [setup](#setup)
+* [pull](#pull)
 
 #### **create**
 
@@ -170,6 +168,27 @@
 > $ acloud reconnect --instance-names [instance-name]
 
 
+### **pull**
+
+Pull will provide all log files to download or show in screen. It is helpful
+to debug about AVD boot up fail or AVD has abnromal behaviors.
+
+Cheatsheet:
+
+* Pull logs from a sole instance or prompt user to choose one to pull if where
+are more than one active instances.
+
+> $ acloud pull
+
+* Pull logs from the specific instance.
+
+> $ acloud pull --instance-name "instance-name"
+
+* Pull a specific log file from a specific instance
+
+> $ acloud pull --instance-name "instance-name" --file-name "file-name"
+
+
 #### **setup**
 
 Setup will walk you through the steps needed to set up your local host to
diff --git a/create/cheeps_remote_image_remote_instance_test.py b/create/cheeps_remote_image_remote_instance_test.py
index 0837401..626da4a 100644
--- a/create/cheeps_remote_image_remote_instance_test.py
+++ b/create/cheeps_remote_image_remote_instance_test.py
@@ -11,13 +11,13 @@
 from acloud.internal.lib import auth
 from acloud.internal.lib import cheeps_compute_client
 from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import gcompute_client
+from acloud.internal.lib import ssh
 
 
 class CheepsRemoteImageRemoteInstanceTest(driver_test_lib.BaseDriverTest):
     """Test cheeps_remote_image_remote_instance."""
 
-    IP = gcompute_client.IP(external="127.0.0.1", internal="10.0.0.1")
+    IP = ssh.IP(external="127.0.0.1", internal="10.0.0.1")
     INSTANCE = "fake-instance"
     IMAGE = "fake-image"
     GPU = "nvidia-tesla-k80"
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 2b9f96e..25871ed 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -38,11 +38,9 @@
 import json
 import logging
 import os
-import re
 import shutil
 import subprocess
 import sys
-import tempfile
 
 from acloud import errors
 from acloud.create import base_avd_create
@@ -50,13 +48,13 @@
 from acloud.internal import constants
 from acloud.internal.lib import utils
 from acloud.internal.lib.adb_tools import AdbTools
+from acloud.list import list as list_instance
 from acloud.list import instance
 from acloud.public import report
 
 
 logger = logging.getLogger(__name__)
 
-_ACLOUD_CVD_TEMP = os.path.join(tempfile.gettempdir(), "acloud_cvd_temp")
 _CMD_LAUNCH_CVD_ARGS = (" -daemon -cpus %s -x_res %s -y_res %s -dpi %s "
                         "-memory_mb %s -system_image_dir %s "
                         "-instance_dir %s")
@@ -65,16 +63,12 @@
 _CONFIRM_RELAUNCH = ("\nCuttlefish AVD[id:%d] is already running. \n"
                      "Enter 'y' to terminate current instance and launch a new "
                      "instance, enter anything else to exit out[y/N]: ")
-_CVD_RUNTIME_FOLDER_NAME = "cuttlefish_runtime"
-_CVD_CONFIG_NAME = "cuttlefish_config.json"
 _ENV_ANDROID_HOST_OUT = "ANDROID_HOST_OUT"
 _ENV_CVD_HOME = "HOME"
 _ENV_CUTTLEFISH_INSTANCE = "CUTTLEFISH_INSTANCE"
 _LAUNCH_CVD_TIMEOUT_SECS = 60  # default timeout as 60 seconds
 _LAUNCH_CVD_TIMEOUT_ERROR = ("Cuttlefish AVD launch timeout, did not complete "
                              "within %d secs.")
-_LOCAL_INSTANCE_HOME = "instance_home_%s"
-_RE_LOCAL_CVD_PORT = re.compile(r"^127\.0\.0\.1:65(?P<cvd_port_suffix>\d{2})\s+")
 _VIRTUAL_DISK_PATHS = "virtual_disk_paths"
 
 
@@ -157,7 +151,8 @@
 
         return avd_spec.local_image_dir, host_bins_path
 
-    def PrepareLaunchCVDCmd(self, launch_cvd_path, hw_property, system_image_dir,
+    @staticmethod
+    def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, system_image_dir,
                             local_instance_id):
         """Prepare launch_cvd command.
 
@@ -173,7 +168,7 @@
         Returns:
             String, launch_cvd cmd.
         """
-        instance_dir = self.GetLocalInstanceRuntimeDir(local_instance_id)
+        instance_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
         launch_cvd_w_args = launch_cvd_path + _CMD_LAUNCH_CVD_ARGS % (
             hw_property["cpu"], hw_property["x_res"], hw_property["y_res"],
             hw_property["dpi"], hw_property["memory"], system_image_dir,
@@ -231,7 +226,8 @@
                                                    _LAUNCH_CVD_TIMEOUT_ERROR % timeout_secs)
         timeout_exception(self._LaunchCvd)(cmd, local_instance_id)
 
-    def _StopCvd(self, host_bins_path, local_instance_id):
+    @staticmethod
+    def _StopCvd(host_bins_path, local_instance_id):
         """Execute stop_cvd to stop cuttlefish instance.
 
         Args:
@@ -244,7 +240,7 @@
         with open(os.devnull, "w") as dev_null:
             cvd_env = os.environ.copy()
             cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = os.path.join(
-                self.GetLocalInstanceRuntimeDir(local_instance_id),
+                instance.GetLocalInstanceRuntimeDir(local_instance_id),
                 constants.CUTTLEFISH_CONFIG_FILE)
             subprocess.check_call(
                 utils.AddUserGroupsToCmd(
@@ -260,8 +256,9 @@
         # sure adb device is completely gone since it will use the same adb port
         adb_cmd.DisconnectAdb(retry=True)
 
+    @staticmethod
     @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up")
-    def _LaunchCvd(self, cmd, local_instance_id):
+    def _LaunchCvd(cmd, local_instance_id):
         """Execute Launch CVD.
 
         Kick off the launch_cvd command and log the output.
@@ -275,8 +272,8 @@
         """
         # Delete the cvd home/runtime temp if exist. The runtime folder is
         # under the cvd home dir, so we only delete them from home dir.
-        cvd_home_dir = self.GetLocalInstanceHomeDir(local_instance_id)
-        cvd_runtime_dir = self.GetLocalInstanceRuntimeDir(local_instance_id)
+        cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id)
+        cvd_runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
         shutil.rmtree(cvd_home_dir, ignore_errors=True)
         os.makedirs(cvd_runtime_dir)
 
@@ -303,31 +300,6 @@
             utils.TextColors.WARNING)
 
     @staticmethod
-    def GetLocalInstanceHomeDir(local_instance_id):
-        """Get instance home dir
-
-        Args:
-            local_instance_id: Integer of instance id.
-
-        Return:
-            String, path of instance home dir.
-        """
-        return os.path.join(_ACLOUD_CVD_TEMP,
-                            _LOCAL_INSTANCE_HOME % local_instance_id)
-
-    def GetLocalInstanceRuntimeDir(self, local_instance_id):
-        """Get instance runtime dir
-
-        Args:
-            local_instance_id: Integer of instance id.
-
-        Return:
-            String, path of instance runtime dir.
-        """
-        return os.path.join(self.GetLocalInstanceHomeDir(local_instance_id),
-                            _CVD_RUNTIME_FOLDER_NAME)
-
-    @staticmethod
     def IsLocalCVDRunning(local_instance_id):
         """Check if the AVD with specific instance id is running
 
@@ -340,7 +312,8 @@
         local_ports = instance.GetLocalPortsbyInsId(local_instance_id)
         return AdbTools(local_ports.adb_port).IsAdbConnected()
 
-    def IsLocalImageOccupied(self, local_image_dir):
+    @staticmethod
+    def IsLocalImageOccupied(local_image_dir):
         """Check if the given image path is being used by a running CVD process.
 
         Args:
@@ -349,10 +322,10 @@
         Return:
             Integer of instance id which using the same image path.
         """
-        local_cvd_ids = self._GetActiveCVDIds()
+        local_cvd_ids = list_instance.GetActiveCVDIds()
         for cvd_id in local_cvd_ids:
-            cvd_config_path = os.path.join(self.GetLocalInstanceRuntimeDir(
-                cvd_id), _CVD_CONFIG_NAME)
+            cvd_config_path = os.path.join(instance.GetLocalInstanceRuntimeDir(
+                cvd_id), constants.CUTTLEFISH_CONFIG_FILE)
             if not os.path.isfile(cvd_config_path):
                 continue
             with open(cvd_config_path, "r") as config_file:
@@ -361,24 +334,3 @@
                     if local_image_dir in disk_path:
                         return cvd_id
         return None
-
-    @staticmethod
-    def _GetActiveCVDIds():
-        """Get active cvd ids from adb devices.
-
-        The adb port of local instance will be decided according to instance id.
-        The rule of adb port will be '6520 + [instance id] - 1'. So we grep last
-        two digits of port and calculate the instance id.
-
-        Return:
-            List of cvd id.
-        """
-        local_cvd_ids = []
-        adb_cmd = [constants.ADB_BIN, "devices"]
-        device_info = subprocess.check_output(adb_cmd)
-        for device in device_info.splitlines():
-            match = _RE_LOCAL_CVD_PORT.match(device)
-            if match:
-                cvd_serial = match.group("cvd_port_suffix")
-                local_cvd_ids.append(int(cvd_serial) - 19)
-        return local_cvd_ids
diff --git a/create/local_image_local_instance_test.py b/create/local_image_local_instance_test.py
index fdc7618..1007739 100644
--- a/create/local_image_local_instance_test.py
+++ b/create/local_image_local_instance_test.py
@@ -19,6 +19,7 @@
 import mock
 
 from acloud.create import local_image_local_instance
+from acloud.list import instance
 from acloud.internal import constants
 from acloud.internal.lib import utils
 
@@ -41,8 +42,7 @@
         self.local_image_local_instance = local_image_local_instance.LocalImageLocalInstance()
 
     # pylint: disable=protected-access
-    @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
-                       "GetLocalInstanceRuntimeDir")
+    @mock.patch.object(instance, "GetLocalInstanceRuntimeDir")
     @mock.patch.object(utils, "CheckUserInGroups")
     def testPrepareLaunchCVDCmd(self, mock_usergroups, mock_cvd_dir):
         """test PrepareLaunchCVDCmd."""
diff --git a/delete/delete.py b/delete/delete.py
index 8318c37..64ca254 100644
--- a/delete/delete.py
+++ b/delete/delete.py
@@ -28,7 +28,6 @@
 from acloud.internal import constants
 from acloud.internal.lib import utils
 from acloud.list import list as list_instances
-from acloud.list import instance as instance_class
 from acloud.public import config
 from acloud.public import device_driver
 from acloud.public import report
@@ -49,11 +48,11 @@
     Try to get directory of "run_cvd" by "ps -o command -p <pid>." command.
     For example: "/tmp/bin/run_cvd"
 
-    Raises:
-        errors.NoExecuteCmd: Can't find stop_cvd.
-
     Returns:
         String of stop_cvd file path.
+
+    Raises:
+        errors.NoExecuteCmd: Can't find stop_cvd.
     """
     process_id = subprocess.check_output(_COMMAND_GET_PROCESS_ID)
     process_info = subprocess.check_output(
@@ -103,7 +102,7 @@
     remote_instance_list = []
     for instance in instances_to_delete:
         if instance.islocal:
-            delete_report = DeleteLocalInstance()
+            delete_report = DeleteLocalInstance(instance, delete_report)
         else:
             remote_instance_list.append(instance.name)
         # Delete ssvnc viewer
@@ -150,32 +149,42 @@
 
 @utils.TimeExecute(function_description="Deleting local instances",
                    result_evaluator=utils.ReportEvaluator)
-def DeleteLocalInstance():
+def DeleteLocalInstance(instance, delete_report=None):
     """Delete local instance.
 
     Delete local instance with stop_cvd command and write delete instance
     information to report.
 
+    Args:
+        instance: instance.LocalInstance object.
+        delete_report: Report object.
+
     Returns:
         A Report instance.
     """
-    delete_report = report.Report(command="delete")
+    if not delete_report:
+        delete_report = report.Report(command="delete")
+
     try:
         with open(os.devnull, "w") as dev_null:
+            cvd_env = os.environ.copy()
+            if instance.instance_dir:
+                cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = os.path.join(
+                    instance.instance_dir, constants.CUTTLEFISH_CONFIG_FILE)
             subprocess.check_call(
                 utils.AddUserGroupsToCmd(_GetStopCvd(),
                                          constants.LIST_CF_USER_GROUPS),
-                stderr=dev_null, stdout=dev_null, shell=True)
+                stderr=dev_null, stdout=dev_null, shell=True, env=cvd_env)
             delete_report.SetStatus(report.Status.SUCCESS)
             device_driver.AddDeletionResultToReport(
-                delete_report, [constants.LOCAL_INS_NAME], failed=[],
+                delete_report, [instance.name], failed=[],
                 error_msgs=[],
                 resource_name="instance")
+            CleanupSSVncviewer(instance.vnc_port)
     except subprocess.CalledProcessError as e:
         delete_report.AddError(str(e))
         delete_report.SetStatus(report.Status.FAIL)
-    # Only CF supports local instances so assume it's a CF VNC port.
-    CleanupSSVncviewer(constants.CF_VNC_PORT)
+
     return delete_report
 
 
@@ -192,16 +201,12 @@
         A Report instance.
     """
     cfg = config.GetAcloudConfig(args)
-    remote_instances_to_delete = args.instance_names
+    instances_to_delete = args.instance_names
 
-    if remote_instances_to_delete:
-        return DeleteRemoteInstances(cfg, remote_instances_to_delete)
-
-    if args.local_instance:
-        if instance_class.LocalInstance():
-            return DeleteLocalInstance()
-        print("There is no local instance AVD to delete.")
-        return report.Report(command="delete")
+    if instances_to_delete:
+        return DeleteInstances(cfg,
+                               list_instances.GetInstancesFromInstanceNames(
+                                   cfg, instances_to_delete))
 
     if args.adb_port:
         return DeleteInstances(
diff --git a/delete/delete_args.py b/delete/delete_args.py
index dc9c106..9058d28 100644
--- a/delete/delete_args.py
+++ b/delete/delete_args.py
@@ -40,8 +40,8 @@
         dest="instance_names",
         nargs="+",
         required=False,
-        help="The names of the remote instances that need to delete, "
-        "separated by spaces, e.g. --instance-names instance-1 instance-2")
+        help="The names of the instances that need to delete, "
+        "separated by spaces, e.g. --instance-names instance-1 local-instance-1")
     delete_group.add_argument(
         "--all",
         action="store_true",
@@ -49,12 +49,6 @@
         required=False,
         help="If more than 1 AVD instance is found, delete them all.")
     delete_group.add_argument(
-        "--local-instance",
-        action="store_true",
-        dest="local_instance",
-        required=False,
-        help="Only delete the local instance.")
-    delete_group.add_argument(
         "--adb-port", "-p",
         type=int,
         dest="adb_port",
diff --git a/delete/delete_test.py b/delete/delete_test.py
index 916cd22..bb7c10e 100644
--- a/delete/delete_test.py
+++ b/delete/delete_test.py
@@ -31,7 +31,7 @@
     @mock.patch("subprocess.check_output")
     def testGetStopcvd(self, mock_subprocess, mock_path_exist):
         """Test _GetStopCvd."""
-        mock_subprocess.side_effect = ["fack_id",
+        mock_subprocess.side_effect = ["fake_id",
                                        "/tmp/bin/run_cvd"]
         expected_value = "/tmp/bin/stop_cvd"
         self.assertEqual(expected_value, delete._GetStopCvd())
@@ -41,7 +41,10 @@
     def testDeleteLocalInstance(self, mock_subprocess, mock_get_stopcvd):
         """Test DeleteLocalInstance."""
         mock_subprocess.return_value = True
-        delete_report = delete.DeleteLocalInstance()
+        instance_object = mock.MagicMock()
+        instance_object.instance_dir = "fake_instance_dir"
+        instance_object.name = "local-instance"
+        delete_report = delete.DeleteLocalInstance(instance_object)
         self.assertEqual(delete_report.data, {
             "deleted": [
                 {
diff --git a/errors.py b/errors.py
index d87bf83..d77ed28 100644
--- a/errors.py
+++ b/errors.py
@@ -32,6 +32,10 @@
     """Error raised when a GCE operation timedout."""
 
 
+class GetGceZoneError(DriverError):
+    """Can't get GCE zones info."""
+
+
 class HttpError(DriverError):
     """Error related to http requests."""
 
@@ -241,3 +245,7 @@
 
 class UnsupportedLocalInstanceId(Exception):
     """Unsupported local instance id."""
+
+
+class InvalidInstanceDir(Exception):
+    """Invalid instance dir."""
diff --git a/internal/constants.py b/internal/constants.py
index 895a312..9d91677 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -113,6 +113,7 @@
 
 COMMAND_PS = ["ps", "aux"]
 CMD_LAUNCH_CVD = "launch_cvd"
+CMD_PGREP = "pgrep"
 CMD_STOP_CVD = "stop_cvd"
 CMD_RUN_CVD = "run_cvd"
 ENV_ANDROID_BUILD_TOP = "ANDROID_BUILD_TOP"
@@ -144,6 +145,7 @@
 INS_KEY_IS_LOCAL = "remote"
 INS_STATUS_RUNNING = "RUNNING"
 LOCAL_INS_NAME = "local-instance"
+LOCAL_INS_HOME_PREFIX = "instance_home_"
 ENV_CUTTLEFISH_CONFIG_FILE = "CUTTLEFISH_CONFIG_FILE"
 CUTTLEFISH_CONFIG_FILE = "cuttlefish_config.json"
 
@@ -151,7 +153,6 @@
 TOOL_NAME = "acloud"
 EXIT_BY_USER = 1
 EXIT_BY_ERROR = -99
-RE_LAUNCH_CVD_PATTERN = "launch_cvd.*%(arg_name)s %(arg_value)s"
 
 # For reuse gce instance
 SELECT_ONE_GCE_INSTANCE = "select_one_gce_instance"
diff --git a/internal/lib/android_compute_client.py b/internal/lib/android_compute_client.py
index b32c1ab..1bc69bc 100755
--- a/internal/lib/android_compute_client.py
+++ b/internal/lib/android_compute_client.py
@@ -375,7 +375,7 @@
             zone: String, representing zone name, e.g. "us-central1-f"
 
         Returns:
-            NamedTuple of (internal, external) IP of the instance.
+            ssh.IP object, that stores internal and external ip of the instance.
         """
         return super(AndroidComputeClient, self).GetInstanceIP(
             instance, zone or self._zone)
diff --git a/internal/lib/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index 3d832d1..98783a4 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -49,7 +49,6 @@
 from acloud.internal.lib import gcompute_client
 from acloud.internal.lib import utils
 from acloud.internal.lib.ssh import Ssh
-from acloud.internal.lib.ssh import IP
 from acloud.pull import pull
 
 
@@ -166,7 +165,7 @@
             ip = self._CreateGceInstance(instance, image_name, image_project,
                                          extra_scopes, boot_disk_size_gb,
                                          avd_spec)
-        self._ssh = Ssh(ip=IP(internal=ip.internal, external=ip.external),
+        self._ssh = Ssh(ip=ip,
                         gce_user=constants.GCE_USER,
                         ssh_private_key_path=self._ssh_private_key_path,
                         extra_args_ssh_tunnel=self._extra_args_ssh_tunnel,
@@ -299,7 +298,7 @@
         """Launch CVD.
 
         Launch AVD with launch_cvd. If the process is failed, acloud would show
-        error messages and atuo download log files from remote instance.
+        error messages and auto download log files from remote instance.
 
         Args:
             instance: String, instance name.
@@ -380,7 +379,7 @@
             avd_spec: An AVDSpec instance.
 
         Returns:
-            Namedtuple of (internal, external) IP of the instance.
+            ssh.IP object, that stores internal and external ip of the instance.
         """
         timestart = time.time()
         metadata = self._metadata.copy()
diff --git a/internal/lib/gcompute_client.py b/internal/lib/gcompute_client.py
index 945a14c..5709445 100755
--- a/internal/lib/gcompute_client.py
+++ b/internal/lib/gcompute_client.py
@@ -26,17 +26,18 @@
 generic, and only knows how to talk to Compute Engine APIs.
 """
 # pylint: disable=too-many-lines
-import collections
 import copy
 import functools
 import getpass
 import logging
 import os
+import re
 
 from acloud import errors
 from acloud.internal import constants
 from acloud.internal.lib import base_cloud_client
 from acloud.internal.lib import utils
+from acloud.internal.lib.ssh import IP
 
 
 logger = logging.getLogger(__name__)
@@ -47,6 +48,7 @@
 _SSH_KEYS_NAME = "sshKeys"
 _ITEMS = "items"
 _METADATA = "metadata"
+_ZONE_RE = re.compile(r"^zones/(?P<zone>.+)")
 
 BASE_DISK_ARGS = {
     "type": "PERSISTENT",
@@ -56,8 +58,6 @@
     "initializeParams": {},
 }
 
-IP = collections.namedtuple("IP", ["external", "internal"])
-
 
 class OperationScope(object):
     """Represents operation scope enum."""
@@ -803,11 +803,41 @@
             "    onHostMaintenance: %s\n",
             str(automatic_restart).lower(), on_host_maintenance)
 
-    def ListInstances(self, zone, instance_filter=None):
-        """List instances.
+    def ListInstances(self, instance_filter=None):
+        """List instances cross all zones.
+
+        Gcompute response instance. For example:
+        {
+            'items':
+            {
+                'zones/europe-west3-b':
+                {
+                    'warning':
+                    {
+                        'message': "There are no results for scope
+                        'zones/europe-west3-b' on this page.",
+                        'code': 'NO_RESULTS_ON_PAGE',
+                        'data': [{'value': u'zones/europe-west3-b',
+                                  'key': u'scope'}]
+                    }
+                },
+                'zones/asia-east1-b':
+                {
+                    'instances': [
+                    {
+                        'name': 'ins-bc212dc8-userbuild-aosp-cf-x86-64-phone'
+                        'status': 'RUNNING',
+                        'cpuPlatform': 'Intel Broadwell',
+                        'startRestricted': False,
+                        'labels': {u'created_by': u'herbertxue'},
+                        'name': 'ins-bc212dc8-userbuild-aosp-cf-x86-64-phone',
+                        ...
+                    }]
+                }
+            }
+        }
 
         Args:
-            zone: A string, representing zone name. e.g. "us-central1-f"
             instance_filter: A string representing a filter in format of
                              FIELD_NAME COMPARISON_STRING LITERAL_STRING
                              e.g. "name ne example-instance"
@@ -816,11 +846,17 @@
         Returns:
             A list of instances.
         """
-        return self.ListWithMultiPages(
-            api_resource=self.service.instances().list,
+        api = self.service.instances().aggregatedList(
             project=self._project,
-            zone=zone,
             filter=instance_filter)
+        response = self.Execute(api)
+        instances_list = []
+        for instances_data in response["items"].values():
+            if "instances" in instances_data:
+                for instance in instances_data.get("instances"):
+                    instances_list.append(instance)
+
+        return instances_list
 
     def SetSchedulingInstances(self,
                                instances,
@@ -1348,7 +1384,7 @@
                 "Malformed response for GetSerialPortOutput: %s" % result)
         return result["contents"]
 
-    def GetInstanceNamesByIPs(self, ips, zone):
+    def GetInstanceNamesByIPs(self, ips):
         """Get Instance names by IPs.
 
         This function will go through all instances, which
@@ -1357,14 +1393,13 @@
 
         Args:
             ips: A set of IPs.
-            zone: String, name of the zone.
 
         Returns:
             A dictionary where key is IP and value is instance name or None
             if instance is not found for the given IP.
         """
         ip_name_map = dict.fromkeys(ips)
-        for instance in self.ListInstances(zone):
+        for instance in self.ListInstances():
             try:
                 ip = instance["networkInterfaces"][0]["accessConfigs"][0][
                     "natIP"]
@@ -1382,7 +1417,7 @@
             zone: String, name of the zone.
 
         Returns:
-            NamedTuple of (internal, external) IP of the instance.
+            ssh.IP object, that stores internal and external ip of the instance.
         """
         instance = self.GetInstance(instance, zone)
         internal_ip = instance["networkInterfaces"][0]["networkIP"]
@@ -1415,14 +1450,13 @@
         self.WaitOnOperation(
             operation, operation_scope=OperationScope.ZONE, scope_name=zone)
 
-    def AddSshRsaInstanceMetadata(self, zone, user, ssh_rsa_path, instance):
+    def AddSshRsaInstanceMetadata(self, user, ssh_rsa_path, instance):
         """Add the public rsa key to the instance's metadata.
 
         Confirm that the instance has this public key in the instance's
         metadata, if not we will add this public key.
 
         Args:
-            zone: String, name of zone.
             user: String, name of the user which the key belongs to.
             ssh_rsa_path: String, The absolute path to public rsa key.
             instance: String, representing instance name.
@@ -1432,11 +1466,87 @@
         entry = "%s:%s" % (user, rsa)
         logger.debug("New RSA entry: %s", entry)
 
+        zone = self.GetZoneByInstance(instance)
         gce_instance = self.GetInstance(instance, zone)
         metadata = gce_instance.get(_METADATA)
         if RsaNotInMetadata(metadata, entry):
             self.UpdateRsaInMetadata(zone, instance, metadata, entry)
 
+    def GetZoneByInstance(self, instance):
+        """Get the zone from instance name.
+
+        Gcompute response instance. For example:
+        {
+            'items':
+            {
+                'zones/europe-west3-b':
+                {
+                    'warning':
+                    {
+                        'message': "There are no results for scope
+                        'zones/europe-west3-b' on this page.",
+                        'code': 'NO_RESULTS_ON_PAGE',
+                        'data': [{'value': u'zones/europe-west3-b',
+                                  'key': u'scope'}]
+                    }
+                },
+                'zones/asia-east1-b':
+                {
+                    'instances': [
+                    {
+                        'name': 'ins-bc212dc8-userbuild-aosp-cf-x86-64-phone'
+                        'status': 'RUNNING',
+                        'cpuPlatform': 'Intel Broadwell',
+                        'startRestricted': False,
+                        'labels': {u'created_by': u'herbertxue'},
+                        'name': 'ins-bc212dc8-userbuild-aosp-cf-x86-64-phone',
+                        ...
+                    }]
+                }
+            }
+        }
+
+        Args:
+            instance: String, representing instance name.
+
+        Raises:
+            errors.GetGceZoneError: Can't get zone from instance name.
+
+        Returns:
+            String of zone name.
+        """
+        api = self.service.instances().aggregatedList(
+            project=self._project,
+            filter="name=%s" % instance)
+        response = self.Execute(api)
+        for zone, instance_data in response["items"].items():
+            if "instances" in instance_data:
+                zone_match = _ZONE_RE.match(zone)
+                if zone_match:
+                    return zone_match.group("zone")
+        raise errors.GetGceZoneError("Can't get zone from the instance name %s"
+                                     % instance)
+
+    def GetZonesByInstances(self, instances):
+        """Get the zone from instance name.
+
+        Args:
+            instances: List of strings, representing instance names.
+
+        Returns:
+            A dictionary that contains the name of all instances in the zone.
+            The key is the name of the zone, and the value is a list contains
+            the name of the instances.
+        """
+        zone_instances = {}
+        for instance in instances:
+            zone = self.GetZoneByInstance(instance)
+            if zone in zone_instances:
+                zone_instances[zone].append(instance)
+            else:
+                zone_instances[zone] = [instance]
+        return zone_instances
+
     def CheckAccess(self):
         """Check if the user has read access to the cloud project.
 
diff --git a/internal/lib/gcompute_client_test.py b/internal/lib/gcompute_client_test.py
index 5f14f7e..d4f225d 100644
--- a/internal/lib/gcompute_client_test.py
+++ b/internal/lib/gcompute_client_test.py
@@ -482,35 +482,69 @@
 
     def testListInstances(self):
         """Test ListInstances."""
-        fake_token = "fake_next_page_token"
         instance_1 = "instance_1"
         instance_2 = "instance_2"
-        response_1 = {"items": [instance_1], "nextPageToken": fake_token}
-        response_2 = {"items": [instance_2]}
+        response = {"items": {'zones/fake_zone': {"instances": [instance_1, instance_2]}}}
         self.Patch(
             gcompute_client.ComputeClient,
             "Execute",
-            side_effect=[response_1, response_2])
+            side_effect=[response])
         resource_mock = mock.MagicMock()
         self.compute_client._service.instances = mock.MagicMock(
             return_value=resource_mock)
-        resource_mock.list = mock.MagicMock()
-        instances = self.compute_client.ListInstances(self.ZONE)
+        resource_mock.aggregatedList = mock.MagicMock()
+        instances = self.compute_client.ListInstances()
         calls = [
             mock.call(
                 project=PROJECT,
-                zone=self.ZONE,
-                filter=None,
-                pageToken=None),
-            mock.call(
-                project=PROJECT,
-                zone=self.ZONE,
-                filter=None,
-                pageToken=fake_token),
+                filter=None),
         ]
-        resource_mock.list.assert_has_calls(calls)
+        resource_mock.aggregatedList.assert_has_calls(calls)
         self.assertEqual(instances, [instance_1, instance_2])
 
+    def testGetZoneByInstance(self):
+        """Test GetZoneByInstance."""
+        instance_1 = "instance_1"
+        response = {"items": {'zones/fake_zone': {"instances": [instance_1]}}}
+        self.Patch(
+            gcompute_client.ComputeClient,
+            "Execute",
+            side_effect=[response])
+        expected_zone = "fake_zone"
+        self.assertEqual(self.compute_client.GetZoneByInstance(instance_1),
+                         expected_zone)
+
+        # Test unable to find 'zone' from instance name.
+        response = {"items": {'zones/fake_zone': {"warning": "No instances."}}}
+        self.Patch(
+            gcompute_client.ComputeClient,
+            "Execute",
+            side_effect=[response])
+        with self.assertRaises(errors.GetGceZoneError):
+            self.compute_client.GetZoneByInstance(instance_1)
+
+    def testGetZonesByInstances(self):
+        """Test GetZonesByInstances."""
+        instances = ["instance_1", "instance_2"]
+        # Test instances in the same zone.
+        self.Patch(
+            gcompute_client.ComputeClient,
+            "GetZoneByInstance",
+            side_effect=["zone_1", "zone_1"])
+        expected_result = {"zone_1": ["instance_1", "instance_2"]}
+        self.assertEqual(self.compute_client.GetZonesByInstances(instances),
+                         expected_result)
+
+        # Test instances in different zones.
+        self.Patch(
+            gcompute_client.ComputeClient,
+            "GetZoneByInstance",
+            side_effect=["zone_1", "zone_2"])
+        expected_result = {"zone_1": ["instance_1"],
+                           "zone_2": ["instance_2"]}
+        self.assertEqual(self.compute_client.GetZonesByInstances(instances),
+                         expected_result)
+
     @mock.patch.object(gcompute_client.ComputeClient, "GetImage")
     @mock.patch.object(gcompute_client.ComputeClient, "GetNetworkUrl")
     @mock.patch.object(gcompute_client.ComputeClient, "GetSubnetworkUrl")
@@ -1122,7 +1156,7 @@
             "ListInstances",
             return_value=[good_instance, bad_instance])
         ip_name_map = self.compute_client.GetInstanceNamesByIPs(
-            ips=["172.22.22.22", "172.22.22.23"], zone=self.ZONE)
+            ips=["172.22.22.22", "172.22.22.23"])
         self.assertEqual(ip_name_map, {"172.22.22.22": "instance_1",
                                        "172.22.22.23": None})
 
@@ -1330,6 +1364,8 @@
         self.Patch(os.path, "exists", return_value=True)
         m = mock.mock_open(read_data=self.SSHKEY)
         self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
+        self.Patch(gcompute_client.ComputeClient, "GetZoneByInstance",
+                   return_value="fake_zone")
         resource_mock = mock.MagicMock()
         self.compute_client._service.instances = mock.MagicMock(
             return_value=resource_mock)
@@ -1341,7 +1377,6 @@
             return_value=instance_metadata_key_not_exist)
         with mock.patch("__builtin__.open", m):
             self.compute_client.AddSshRsaInstanceMetadata(
-                "fake_zone",
                 fake_user,
                 "/path/to/test_rsa.pub",
                 "fake_instance")
@@ -1358,7 +1393,6 @@
             return_value=instance_metadata_key_exist)
         with mock.patch("__builtin__.open", m):
             self.compute_client.AddSshRsaInstanceMetadata(
-                "fake_zone",
                 fake_user,
                 "/path/to/test_rsa.pub",
                 "fake_instance")
diff --git a/internal/lib/ssh.py b/internal/lib/ssh.py
index 92d94ab..590f1de 100755
--- a/internal/lib/ssh.py
+++ b/internal/lib/ssh.py
@@ -30,6 +30,7 @@
 _SSH_IDENTITY = "-l %(login_user)s %(ip_addr)s"
 _SSH_CMD_MAX_RETRY = 4
 _SSH_CMD_RETRY_SLEEP = 3
+_WAIT_FOR_SSH_MAX_TIMEOUT = 20
 
 
 def _SshCall(cmd, timeout=None):
@@ -217,9 +218,8 @@
 
         raise errors.UnknownType("Don't support the execute bin %s." % execute_bin)
 
-    @utils.TimeExecute(function_description="Waiting for SSH server")
-    def WaitForSsh(self, timeout=20, max_retry=_SSH_CMD_MAX_RETRY):
-        """Wait until the remote instance is ready to accept commands over SSH.
+    def CheckSshConnection(self, timeout):
+        """Run remote 'uptime' ssh command to check ssh connection.
 
         Args:
             timeout: Integer, the maximum time to wait for the command to respond.
@@ -229,18 +229,41 @@
         """
         remote_cmd = [self.GetBaseCmd(constants.SSH_BIN)]
         remote_cmd.append("uptime")
-        for _ in range(max_retry):
-            if _SshCall(" ".join(remote_cmd), timeout) == 0:
-                return
+
+        if _SshCall(" ".join(remote_cmd), timeout) == 0:
+            return
         raise errors.DeviceConnectionError(
             "Ssh isn't ready in the remote instance.")
 
+    @utils.TimeExecute(function_description="Waiting for SSH server")
+    def WaitForSsh(self, timeout=_WAIT_FOR_SSH_MAX_TIMEOUT,
+                   sleep_for_retry=_SSH_CMD_RETRY_SLEEP,
+                   max_retry=_SSH_CMD_MAX_RETRY):
+        """Wait until the remote instance is ready to accept commands over SSH.
+
+        Args:
+            timeout: Integer, the maximum time in seconds to wait for the
+                     command to respond.
+            sleep_for_retry: Integer, the sleep time in seconds for retry.
+            max_retry: Integer, the maximum number of retry.
+
+        Raises:
+            errors.DeviceConnectionError: Ssh isn't ready in the remote instance.
+        """
+        utils.RetryExceptionType(
+            exception_types=errors.DeviceConnectionError,
+            max_retries=max_retry,
+            functor=self.CheckSshConnection,
+            sleep_multiplier=sleep_for_retry,
+            retry_backoff_factor=utils.DEFAULT_RETRY_BACKOFF_FACTOR,
+            timeout=timeout)
+
     def ScpPushFile(self, src_file, dst_file):
         """Scp push file to remote.
 
         Args:
             src_file: The source file path to be pulled.
-            dst_file: The destiation file path the file is pulled to.
+            dst_file: The destination file path the file is pulled to.
         """
         scp_command = [self.GetBaseCmd(constants.SCP_BIN)]
         scp_command.append(src_file)
@@ -252,7 +275,7 @@
 
         Args:
             src_file: The source file path to be pulled.
-            dst_file: The destiation file path the file is pulled to.
+            dst_file: The destination file path the file is pulled to.
         """
         scp_command = [self.GetBaseCmd(constants.SCP_BIN)]
         scp_command.append("%s@%s:%s" %(self._gce_user, self._ip, src_file))
diff --git a/internal/lib/ssh_test.py b/internal/lib/ssh_test.py
index 5ec6a3a..bddefd5 100644
--- a/internal/lib/ssh_test.py
+++ b/internal/lib/ssh_test.py
@@ -193,6 +193,19 @@
         expected_ip = "1.1.1.1"
         self.assertEqual(ssh_object._ip, expected_ip)
 
+    def testWaitForSsh(self):
+        """Test WaitForSsh."""
+        ssh_object = ssh.Ssh(ip=self.FAKE_IP,
+                             gce_user=self.FAKE_SSH_USER,
+                             ssh_private_key_path=self.FAKE_SSH_PRIVATE_KEY_PATH,
+                             report_internal_ip=self.FAKE_REPORT_INTERNAL_IP)
+        self.Patch(ssh, "_SshCall", return_value=-1)
+        self.assertRaises(errors.DeviceConnectionError,
+                          ssh_object.WaitForSsh,
+                          timeout=1,
+                          sleep_for_retry=1,
+                          max_retry=1)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/internal/lib/utils.py b/internal/lib/utils.py
index af0d234..ff7e3a8 100755
--- a/internal/lib/utils.py
+++ b/internal/lib/utils.py
@@ -78,7 +78,6 @@
 
 _VNC_BIN = "ssvnc"
 _CMD_KILL = ["pkill", "-9", "-f"]
-_CMD_PGREP = "pgrep"
 _CMD_SG = "sg "
 _CMD_START_VNC = "%(bin)s vnc://127.0.0.1:%(port)d"
 _CMD_INSTALL_SSVNC = "sudo apt-get --assume-yes install ssvnc"
@@ -1028,7 +1027,7 @@
     """
     try:
         with open(os.devnull, "w") as dev_null:
-            subprocess.check_call([_CMD_PGREP, "-af", command],
+            subprocess.check_call([constants.CMD_PGREP, "-af", command],
                                   stderr=dev_null, stdout=dev_null)
         return True
     except subprocess.CalledProcessError:
diff --git a/list/instance.py b/list/instance.py
index 005abcf..7b58792 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -33,6 +33,7 @@
 import os
 import re
 import subprocess
+import tempfile
 
 # pylint: disable=import-error
 import dateutil.parser
@@ -46,6 +47,9 @@
 
 logger = logging.getLogger(__name__)
 
+_ACLOUD_CVD_TEMP = os.path.join(tempfile.gettempdir(), "acloud_cvd_temp")
+_CVD_RUNTIME_FOLDER_NAME = "cuttlefish_runtime"
+_LOCAL_INSTANCE_HOME = "instance_home_%s"
 _MSG_UNABLE_TO_CALCULATE = "Unable to calculate"
 _RE_GROUP_ADB = "local_adb_port"
 _RE_GROUP_VNC = "local_vnc_port"
@@ -62,6 +66,53 @@
                                                    constants.ADB_PORT])
 
 
+def GetLocalInstanceHomeDir(local_instance_id):
+    """Get local instance home dir accroding to instance id.
+
+    Args:
+        local_instance_id: Integer of instance id.
+
+    Return:
+        String, path of instance home dir.
+    """
+    return os.path.join(_ACLOUD_CVD_TEMP,
+                        _LOCAL_INSTANCE_HOME % local_instance_id)
+
+
+def GetLocalInstanceRuntimeDir(local_instance_id):
+    """Get instance runtime dir
+
+    Args:
+        local_instance_id: Integer of instance id.
+
+    Return:
+        String, path of instance runtime dir.
+    """
+    return os.path.join(GetLocalInstanceHomeDir(local_instance_id),
+                        _CVD_RUNTIME_FOLDER_NAME)
+
+
+def GetCuttlefishRuntimeConfig(local_instance_id):
+    """Get and parse cuttlefish_config.json.
+
+    Args:
+        local_instance_id: Integer of instance id.
+
+    Returns:
+        A dictionary that parsed from cuttlefish runtime config.
+
+    Raises:
+        errors.ConfigError: if file not found or config load failed.
+    """
+    runtime_cf_config_path = os.path.join(GetLocalInstanceRuntimeDir(
+        local_instance_id), constants.CUTTLEFISH_CONFIG_FILE)
+    if not os.path.exists(runtime_cf_config_path):
+        raise errors.ConfigError(
+            "file does not exist: %s" % runtime_cf_config_path)
+    with open(runtime_cf_config_path, "r") as cf_config:
+        return json.load(cf_config)
+
+
 def GetLocalPortsbyInsId(local_instance_id):
     """Get vnc and adb port by local instance id.
 
@@ -105,21 +156,25 @@
 class Instance(object):
     """Class to store data of instance."""
 
-    def __init__(self):
-        self._name = None
-        self._fullname = None
-        self._status = None
-        self._display = None  # Resolution and dpi
-        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._ssh_tunnel_is_connected = None  # True if ssh tunnel is still connected
-        self._createtime = None
-        self._elapsed_time = None
-        self._avd_type = None
-        self._avd_flavor = None
-        self._is_local = None  # True if this is a local instance
-        self._device_information = None
+    def __init__(self, name, fullname, display, ip, status=None, adb_port=None,
+                 vnc_port=None, ssh_tunnel_is_connected=None, createtime=None,
+                 elapsed_time=None, avd_type=None, avd_flavor=None,
+                 is_local=False, device_information=None):
+        self._name = name
+        self._fullname = fullname
+        self._status = status
+        self._display = display  # Resolution and dpi
+        self._ip = ip
+        self._adb_port = adb_port  # adb port which is forwarding to remote
+        self._vnc_port = vnc_port  # vnc port which is forwarding to remote
+        # True if ssh tunnel is still connected
+        self._ssh_tunnel_is_connected = ssh_tunnel_is_connected
+        self._createtime = createtime
+        self._elapsed_time = elapsed_time
+        self._avd_type = avd_type
+        self._avd_flavor = avd_flavor
+        self._is_local = is_local  # True if this is a local instance
+        self._device_information = device_information
 
     def __repr__(self):
         """Return full name property for print."""
@@ -214,95 +269,73 @@
         """Return if it is a local instance."""
         return self._is_local
 
+    @property
+    def adb_port(self):
+        """Return adb_port."""
+        return self._adb_port
+
+    @property
+    def vnc_port(self):
+        """Return vnc_port."""
+        return self._vnc_port
+
 
 class LocalInstance(Instance):
     """Class to store data of local instance."""
 
-    # pylint: disable=protected-access
-    def __new__(cls):
+    def __init__(self, local_instance_id, x_res, y_res, dpi, create_time,
+                 ins_dir=None):
         """Initialize a localInstance object.
 
-        Gather local instance information from launch_cvd process.
-
-        returns:
-            Instance object if launch_cvd process is found otherwise return None.
+        Args:
+            local_instance_id: Integer of instance id.
+            x_res: Integer of x dimension.
+            y_res: Integer of y dimension.
+            dpi: Integer of dpi.
+            date_str: String of create time.
+            ins_dir: String, path of instance idr.
         """
-        # Running instances on local is not supported on all OS.
-        if not utils.IsSupportedPlatform():
-            return None
+        display = ("%sx%s (%s)" % (x_res, y_res, dpi))
+        elapsed_time = _GetElapsedTime(create_time) if create_time else None
+        name = "%s-%d" % (constants.LOCAL_INS_NAME, local_instance_id)
+        local_ports = GetLocalPortsbyInsId(local_instance_id)
+        fullname = (_FULL_NAME_STRING %
+                    {"device_serial": "127.0.0.1:%d" % local_ports.adb_port,
+                     "instance_name": name,
+                     "elapsed_time": elapsed_time})
+        adb_device = AdbTools(local_ports.adb_port)
+        device_information = None
+        if adb_device.IsAdbConnected():
+            device_information = adb_device.device_information
 
-        process_output = subprocess.check_output(_COMMAND_PS_LAUNCH_CVD)
-        for line in process_output.splitlines():
-            match = _RE_RUN_CVD.match(line)
-            if match:
-                local_instance = Instance()
-                cf_runtime_config_dict = GetCuttlefishRuntimeConfig()
-                x_res = cf_runtime_config_dict["x_res"]
-                y_res = cf_runtime_config_dict["y_res"]
-                dpi = cf_runtime_config_dict["dpi"]
-                date_str = match.group("date_str").strip()
-                local_instance._name = constants.LOCAL_INS_NAME
-                local_instance._createtime = date_str
-                local_instance._elapsed_time = _GetElapsedTime(date_str)
-                local_instance._fullname = (_FULL_NAME_STRING %
-                                            {"device_serial": "127.0.0.1:%d" %
-                                                              constants.CF_ADB_PORT,
-                                             "instance_name": local_instance._name,
-                                             "elapsed_time": local_instance._elapsed_time})
-                local_instance._avd_type = constants.TYPE_CF
-                local_instance._ip = "127.0.0.1"
-                local_instance._status = constants.INS_STATUS_RUNNING
-                local_instance._adb_port = constants.CF_ADB_PORT
-                local_instance._vnc_port = constants.CF_VNC_PORT
-                local_instance._display = ("%sx%s (%s)" % (x_res, y_res, dpi))
-                local_instance._is_local = True
-                local_instance._ssh_tunnel_is_connected = True
+        super(LocalInstance, self).__init__(
+            name=name, fullname=fullname, display=display, ip="127.0.0.1",
+            status=constants.INS_STATUS_RUNNING, adb_port=local_ports.adb_port,
+            vnc_port=local_ports.vnc_port, createtime=create_time,
+            elapsed_time=elapsed_time, avd_type=constants.TYPE_CF,
+            is_local=True, device_information=device_information)
 
-                adb_device = AdbTools(constants.CF_ADB_PORT)
-                if adb_device.IsAdbConnected():
-                    local_instance._device_information = adb_device.device_information
-                return local_instance
-        return None
+        # LocalInstance class properties
+        self._instance_dir = ins_dir
 
-def GetCuttlefishRuntimeConfig():
-    """Get and parse cuttlefish_config.json.
+    @property
+    def instance_dir(self):
+        """Return _instance_dir."""
+        return self._instance_dir
 
-    Returns:
-        Dict parsing json file from cuttlefish runtime config.
-
-    Raises:
-        errors.ConfigError: if file not found or config load failed.
-    """
-    runtime_cf_config_path = os.path.join(os.path.expanduser("~"),
-                                          "cuttlefish_runtime",
-                                          "cuttlefish_config.json")
-    if os.path.exists(runtime_cf_config_path):
-        with open(runtime_cf_config_path, "r") as cf_config:
-            return json.load(cf_config)
-    else:
-        raise errors.ConfigError("file does not exist: %s" % runtime_cf_config_path)
-    raise errors.ConfigError("Could not load cuttlefish_config.json.")
 
 class RemoteInstance(Instance):
     """Class to store data of remote instance."""
 
+    # pylint: disable=too-many-locals
     def __init__(self, gce_instance):
         """Process the args into class vars.
 
-        RemoteInstace initialized by gce dict object.
+        RemoteInstace initialized by gce dict object. We parse the required data
+        from gce_instance to local variables.
         Reference:
         https://cloud.google.com/compute/docs/reference/rest/v1/instances/get
 
-        Args:
-            gce_instance: dict object queried from gce.
-        """
-        super(RemoteInstance, self).__init__()
-        self._ProcessGceInstance(gce_instance)
-        self._is_local = False
-
-    def _ProcessGceInstance(self, gce_instance):
-        """Parse the required data from gce_instance to local variables.
-
         We also gather more details on client side including the forwarding adb
         port and vnc port which will be used to determine the status of ssh
         tunnel connection.
@@ -314,13 +347,13 @@
         - Terminated: If we can't retrieve the public ip from gce instance.
 
         Args:
-           gce_instance: dict object queried from gce.
+            gce_instance: dict object queried from gce.
         """
-        self._name = gce_instance.get(constants.INS_KEY_NAME)
+        name = gce_instance.get(constants.INS_KEY_NAME)
 
-        self._createtime = gce_instance.get(constants.INS_KEY_CREATETIME)
-        self._elapsed_time = _GetElapsedTime(self._createtime)
-        self._status = gce_instance.get(constants.INS_KEY_STATUS)
+        create_time = gce_instance.get(constants.INS_KEY_CREATETIME)
+        elapsed_time = _GetElapsedTime(create_time)
+        status = gce_instance.get(constants.INS_KEY_STATUS)
 
         ip = None
         for network_interface in gce_instance.get("networkInterfaces"):
@@ -328,44 +361,56 @@
                 ip = access_config.get("natIP")
 
         # Get metadata
+        display = None
+        avd_type = None
+        avd_flavor = None
         for metadata in gce_instance.get("metadata", {}).get("items", []):
             key = metadata["key"]
             value = metadata["value"]
             if key == constants.INS_KEY_DISPLAY:
-                self._display = value
+                display = value
             elif key == constants.INS_KEY_AVD_TYPE:
-                self._avd_type = value
+                avd_type = value
             elif key == constants.INS_KEY_AVD_FLAVOR:
-                self._avd_flavor = value
+                avd_flavor = value
 
         # Find ssl tunnel info.
+        adb_port = None
+        vnc_port = None
+        device_information = None
         if ip:
-            forwarded_ports = self.GetAdbVncPortFromSSHTunnel(ip,
-                                                              self._avd_type)
-            self._ip = ip
-            self._adb_port = forwarded_ports.adb_port
-            self._vnc_port = forwarded_ports.vnc_port
-            self._ssh_tunnel_is_connected = self._adb_port is not None
+            forwarded_ports = self.GetAdbVncPortFromSSHTunnel(ip, avd_type)
+            adb_port = forwarded_ports.adb_port
+            vnc_port = forwarded_ports.vnc_port
+            ssh_tunnel_is_connected = adb_port is not None
 
-            adb_device = AdbTools(self._adb_port)
+            adb_device = AdbTools(adb_port)
             if adb_device.IsAdbConnected():
-                self._device_information = adb_device.device_information
-                self._fullname = (_FULL_NAME_STRING %
-                                  {"device_serial": "127.0.0.1:%d" % self._adb_port,
-                                   "instance_name": self._name,
-                                   "elapsed_time": self._elapsed_time})
+                device_information = adb_device.device_information
+                fullname = (_FULL_NAME_STRING %
+                            {"device_serial": "127.0.0.1:%d" % adb_port,
+                             "instance_name": name,
+                             "elapsed_time": elapsed_time})
             else:
-                self._fullname = (_FULL_NAME_STRING %
-                                  {"device_serial": "not connected",
-                                   "instance_name": self._name,
-                                   "elapsed_time": self._elapsed_time})
+                fullname = (_FULL_NAME_STRING %
+                            {"device_serial": "not connected",
+                             "instance_name": name,
+                             "elapsed_time": elapsed_time})
         # If instance is terminated, its ip is None.
         else:
-            self._ssh_tunnel_is_connected = False
-            self._fullname = (_FULL_NAME_STRING %
-                              {"device_serial": "terminated",
-                               "instance_name": self._name,
-                               "elapsed_time": self._elapsed_time})
+            ssh_tunnel_is_connected = False
+            fullname = (_FULL_NAME_STRING %
+                        {"device_serial": "terminated",
+                         "instance_name": name,
+                         "elapsed_time": elapsed_time})
+
+        super(RemoteInstance, self).__init__(
+            name=name, fullname=fullname, display=display, ip=ip, status=status,
+            adb_port=adb_port, vnc_port=vnc_port,
+            ssh_tunnel_is_connected=ssh_tunnel_is_connected,
+            createtime=create_time, elapsed_time=elapsed_time, avd_type=avd_type,
+            avd_flavor=avd_flavor, is_local=False,
+            device_information=device_information)
 
     @staticmethod
     def GetAdbVncPortFromSSHTunnel(ip, avd_type):
diff --git a/list/instance_test.py b/list/instance_test.py
index af1960b..8c9e1ca 100644
--- a/list/instance_test.py
+++ b/list/instance_test.py
@@ -61,21 +61,23 @@
         """"Test get local instance info from launch_cvd process."""
         self.Patch(subprocess, "check_output", return_value=self.PS_LAUNCH_CVD)
         self.Patch(instance, "_GetElapsedTime", return_value="fake_time")
-        self.Patch(instance, "GetCuttlefishRuntimeConfig",
-                   return_value=self.PS_RUNTIME_CF_CONFIG)
-        local_instance = instance.LocalInstance()
-        self.assertEqual(constants.LOCAL_INS_NAME, local_instance.name)
+        local_instance = instance.LocalInstance(2,
+                                                "1080",
+                                                "1920",
+                                                "480",
+                                                "Sat Nov 10 21:55:10 2018",
+                                                "fake_instance_dir")
+        self.assertEqual(constants.LOCAL_INS_NAME + "-2", local_instance.name)
         self.assertEqual(True, local_instance.islocal)
         self.assertEqual("1080x1920 (480)", local_instance.display)
         self.assertEqual("Sat Nov 10 21:55:10 2018", local_instance.createtime)
-        expected_full_name = "device serial: 127.0.0.1:%s (%s) elapsed time: %s" % (
-            constants.CF_ADB_PORT, constants.LOCAL_INS_NAME, "fake_time")
+        expected_full_name = ("device serial: 127.0.0.1:%s (%s) elapsed time: %s"
+                              % ("6521",
+                                 constants.LOCAL_INS_NAME + "-2",
+                                 "fake_time"))
         self.assertEqual(expected_full_name, local_instance.fullname)
-
-        # test return None if no launch_cvd process found
-        self.Patch(subprocess, "check_output", return_value="no launch_cvd "
-                                                            "found")
-        self.assertEqual(None, instance.LocalInstance())
+        self.assertEqual(6521, local_instance.forwarding_adb_port)
+        self.assertEqual(6445, local_instance.forwarding_vnc_port)
 
     def testGetElapsedTime(self):
         """Test _GetElapsedTime"""
diff --git a/list/list.py b/list/list.py
index 4034171..05596df 100644
--- a/list/list.py
+++ b/list/list.py
@@ -20,6 +20,8 @@
 from __future__ import print_function
 import getpass
 import logging
+import re
+import subprocess
 
 from acloud import errors
 from acloud.internal import constants
@@ -32,6 +34,31 @@
 
 logger = logging.getLogger(__name__)
 
+_COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"]
+_RE_LOCAL_INSTANCE_ID = re.compile(r".+instance_home_(?P<ins_id>\d+).+")
+_RE_LOCAL_CVD_PORT = re.compile(r"^127\.0\.0\.1:65(?P<cvd_port_suffix>\d{2})\s+")
+
+
+def GetActiveCVDIds():
+    """Get active local cvd ids from adb devices.
+
+    The adb port of local instance will be decided according to instance id.
+    The rule of adb port will be '6520 + [instance id] - 1'. So we grep last
+    two digits of port and calculate the instance id.
+
+    Return:
+        List of cvd id.
+    """
+    local_cvd_ids = []
+    adb_cmd = [constants.ADB_BIN, "devices"]
+    device_info = subprocess.check_output(adb_cmd)
+    for device in device_info.splitlines():
+        match = _RE_LOCAL_CVD_PORT.match(device)
+        if match:
+            cvd_serial = match.group("cvd_port_suffix")
+            local_cvd_ids.append(int(cvd_serial) - 19)
+    return local_cvd_ids
+
 
 def _ProcessInstances(instance_list):
     """Get more details of remote instances.
@@ -102,14 +129,72 @@
     credentials = auth.CreateCredentials(cfg)
     compute_client = gcompute_client.ComputeClient(cfg, credentials)
     filter_item = "labels.%s=%s" % (constants.LABEL_CREATE_BY, getpass.getuser())
-    all_instances = compute_client.ListInstances(cfg.zone,
-                                                 instance_filter=filter_item)
-    logger.debug("Instance list from: %s (filter: %s\n%s):",
-                 cfg.zone, filter_item, all_instances)
+    all_instances = compute_client.ListInstances(instance_filter=filter_item)
+
+    logger.debug("Instance list from: (filter: %s\n%s):",
+                 filter_item, all_instances)
 
     return _ProcessInstances(all_instances)
 
 
+def GetLocalInstances():
+    """Look for local instances.
+
+    Gather local instances information from cuttlefish runtime config.
+
+    Returns:
+        instance_list: List of local instances.
+    """
+    # Running instances on local is not supported on all OS.
+    if not utils.IsSupportedPlatform():
+        return None
+
+    local_cvd_ids = GetActiveCVDIds()
+    local_instance_list = []
+    for cvd_id in local_cvd_ids:
+        ins_dir = x_res = y_res = dpi = cf_runtime_config_dict = None
+        try:
+            cf_runtime_config_dict = instance.GetCuttlefishRuntimeConfig(cvd_id)
+        except errors.ConfigError:
+            logger.error("Instance[id:%d] dir not found!", cvd_id)
+
+        if cf_runtime_config_dict:
+            ins_dir = instance.GetLocalInstanceRuntimeDir(cvd_id)
+            x_res = cf_runtime_config_dict["x_res"]
+            y_res = cf_runtime_config_dict["y_res"]
+            dpi = cf_runtime_config_dict["dpi"]
+        # TODO(143063678), there's no createtime info in
+        # cuttlefish_config.json so far.
+        local_instance_list.append(instance.LocalInstance(cvd_id,
+                                                          x_res,
+                                                          y_res,
+                                                          dpi,
+                                                          None,
+                                                          ins_dir))
+    return local_instance_list
+
+
+def _GetIdFromInstanceDirStr(instance_dir):
+    """Look for instance id from the path of instance dir.
+
+    Args:
+        instance_dir: String, path of instance_dir.
+
+    Returns:
+        Integer of instance id.
+
+    Raises:
+        errors.InvalidInstanceDir: Invalid instance idr.
+    """
+    match = _RE_LOCAL_INSTANCE_ID.match(instance_dir)
+    if match:
+        return int(match.group("ins_id"))
+
+    raise errors.InvalidInstanceDir("Instance dir is invalid:%s. local AVD "
+                                    "launched outside acloud is not supported"
+                                    % instance_dir)
+
+
 def GetInstances(cfg):
     """Look for remote/local instances.
 
@@ -120,9 +205,9 @@
         instance_list: List of instances.
     """
     instances_list = GetRemoteInstances(cfg)
-    local_instance = instance.LocalInstance()
-    if local_instance:
-        instances_list.append(local_instance)
+    local_instances = GetLocalInstances()
+    if local_instances:
+        instances_list.extend(local_instances)
 
     return instances_list
 
diff --git a/public/acloud_main.py b/public/acloud_main.py
index fb69e5e..65e649b 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -56,6 +56,13 @@
    To show more detail info on the list.
    $ acloud list -vv
 
+-  Pull:
+   Pull will download log files or show the log file in screen from one remote
+   cuttlefish instance:
+   $ acloud pull
+   Pull from a specified instance:
+   $ acloud pull --instance-name "your_instance_name"
+
 Try $acloud [cmd] --help for further details.
 
 """
diff --git a/public/actions/common_operations_test.py b/public/actions/common_operations_test.py
index 18275c1..eba74b8 100644
--- a/public/actions/common_operations_test.py
+++ b/public/actions/common_operations_test.py
@@ -26,14 +26,14 @@
 from acloud.internal.lib import android_compute_client
 from acloud.internal.lib import auth
 from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import gcompute_client
+from acloud.internal.lib import ssh
 from acloud.public import report
 from acloud.public.actions import common_operations
 
 
 class CommonOperationsTest(driver_test_lib.BaseDriverTest):
     """Test Common Operations."""
-    IP = gcompute_client.IP(external="127.0.0.1", internal="10.0.0.1")
+    IP = ssh.IP(external="127.0.0.1", internal="10.0.0.1")
     INSTANCE = "fake-instance"
     CMD = "test-cmd"
     AVD_TYPE = "fake-type"
diff --git a/public/actions/create_cuttlefish_action_test.py b/public/actions/create_cuttlefish_action_test.py
index 728d6e0..f4788c5 100644
--- a/public/actions/create_cuttlefish_action_test.py
+++ b/public/actions/create_cuttlefish_action_test.py
@@ -29,14 +29,14 @@
 from acloud.internal.lib import cvd_compute_client
 from acloud.internal.lib import cvd_compute_client_multi_stage
 from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import gcompute_client
+from acloud.internal.lib import ssh
 from acloud.public.actions import create_cuttlefish_action
 
 
 class CreateCuttlefishActionTest(driver_test_lib.BaseDriverTest):
     """Test create_cuttlefish_action."""
 
-    IP = gcompute_client.IP(external="127.0.0.1", internal="10.0.0.1")
+    IP = ssh.IP(external="127.0.0.1", internal="10.0.0.1")
     INSTANCE = "fake-instance"
     IMAGE = "fake-image"
     BUILD_TARGET = "fake-build-target"
diff --git a/public/actions/create_goldfish_action_test.py b/public/actions/create_goldfish_action_test.py
index 35f96ab..fa04ee2 100644
--- a/public/actions/create_goldfish_action_test.py
+++ b/public/actions/create_goldfish_action_test.py
@@ -23,15 +23,15 @@
 from acloud.internal.lib import android_compute_client
 from acloud.internal.lib import auth
 from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import gcompute_client
 from acloud.internal.lib import goldfish_compute_client
+from acloud.internal.lib import ssh
 from acloud.public.actions import create_goldfish_action
 
 
 class CreateGoldfishActionTest(driver_test_lib.BaseDriverTest):
     """Tests create_goldfish_action."""
 
-    IP = gcompute_client.IP(external="127.0.0.1", internal="10.0.0.1")
+    IP = ssh.IP(external="127.0.0.1", internal="10.0.0.1")
     INSTANCE = "fake-instance"
     IMAGE = "fake-image"
     BUILD_TARGET = "fake-build-target"
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
index 57447cf..8a539c1 100644
--- a/public/actions/remote_instance_cf_device_factory.py
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -44,7 +44,7 @@
                             connecting from another GCE instance.
         credentials: An oauth2client.OAuth2Credentials instance.
         compute_client: An object of cvd_compute_client.CvdComputeClient.
-        ip: Namedtuple of (internal, external) IP of the instance.
+        ssh: An Ssh object.
     """
     def __init__(self, avd_spec, local_image_artifact=None,
                  cvd_host_package_artifact=None):
@@ -166,7 +166,7 @@
             blank_data_disk_size_gb=self._cfg.extra_data_disk_size_gb,
             avd_spec=self._avd_spec)
         ip = self._compute_client.GetInstanceIP(instance)
-        self._ssh = ssh.Ssh(ip=ssh.IP(internal=ip.internal, external=ip.external),
+        self._ssh = ssh.Ssh(ip=ip,
                             gce_user=constants.GCE_USER,
                             ssh_private_key_path=self._cfg.ssh_private_key_path,
                             extra_args_ssh_tunnel=self._cfg.extra_args_ssh_tunnel,
diff --git a/public/device_driver.py b/public/device_driver.py
index a18dcc7..8e37a59 100755
--- a/public/device_driver.py
+++ b/public/device_driver.py
@@ -449,13 +449,24 @@
     Returns:
         A Report instance.
     """
+    # delete, failed, error_msgs are used to record result.
+    deleted = []
+    failed = []
+    error_msgs = []
+
     r = default_report if default_report else report.Report(command="delete")
     credentials = auth.CreateCredentials(cfg)
     compute_client = android_compute_client.AndroidComputeClient(cfg,
                                                                  credentials)
+    zone_instances = compute_client.GetZonesByInstances(instance_names)
+
     try:
-        deleted, failed, error_msgs = compute_client.DeleteInstances(
-            instance_names, cfg.zone)
+        for zone, instances in zone_instances.items():
+            deleted_ins, failed_ins, error_ins = compute_client.DeleteInstances(
+                instances, zone)
+            deleted.extend(deleted_ins)
+            failed.extend(failed_ins)
+            error_msgs.extend(error_ins)
         AddDeletionResultToReport(
             r, deleted,
             failed, error_msgs,
@@ -513,7 +524,7 @@
         storage_client = gstorage_client.StorageClient(credentials)
 
         # Cleanup expired instances
-        items = compute_client.ListInstances(zone=cfg.zone)
+        items = compute_client.ListInstances()
         cleanup_list = [
             item["name"]
             for item in _FindOldItems(items, cut_time, "creationTimestamp")
diff --git a/public/device_driver_test.py b/public/device_driver_test.py
index 42bb5c9..560c26b 100644
--- a/public/device_driver_test.py
+++ b/public/device_driver_test.py
@@ -29,8 +29,8 @@
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import android_compute_client
 from acloud.internal.lib import driver_test_lib
-from acloud.internal.lib import gcompute_client
 from acloud.internal.lib import gstorage_client
+from acloud.internal.lib import ssh
 from acloud.public import device_driver
 
 
@@ -81,7 +81,7 @@
         """Test CreateGCETypeAVD."""
         cfg = _CreateCfg()
         fake_gs_url = "fake_gs_url"
-        fake_ip = gcompute_client.IP(external="140.1.1.1", internal="10.1.1.1")
+        fake_ip = ssh.IP(external="140.1.1.1", internal="10.1.1.1")
         fake_instance = "fake-instance"
         fake_image = "fake-image"
         fake_build_target = "fake_target"
@@ -136,7 +136,7 @@
     def testCreateGCETypeAVDInternalIP(self):
         """Test CreateGCETypeAVD with internal IP."""
         cfg = _CreateCfg()
-        fake_ip = gcompute_client.IP(external="140.1.1.1", internal="10.1.1.1")
+        fake_ip = ssh.IP(external="140.1.1.1", internal="10.1.1.1")
         fake_instance = "fake-instance"
         fake_build_target = "fake_target"
         fake_build_id = "12345"
@@ -162,10 +162,12 @@
 
     def testDeleteAndroidVirtualDevices(self):
         """Test DeleteAndroidVirtualDevices."""
+        cfg = _CreateCfg()
         instance_names = ["fake-instance-1", "fake-instance-2"]
+        self.compute_client.GetZonesByInstances.return_value = (
+            {cfg.zone: instance_names})
         self.compute_client.DeleteInstances.return_value = (instance_names, [],
                                                             [])
-        cfg = _CreateCfg()
         report = device_driver.DeleteAndroidVirtualDevices(cfg, instance_names)
         self.compute_client.DeleteInstances.assert_called_once_with(
             instance_names, cfg.zone)
@@ -267,8 +269,7 @@
         }
         self.assertEqual(report.data, expected_report_data)
 
-        self.compute_client.ListInstances.assert_called_once_with(
-            zone=cfg.zone)
+        self.compute_client.ListInstances.assert_called_once_with()
         self.compute_client.DeleteInstances.assert_called_once_with(
             instances=["fake_instance_1"], zone=cfg.zone)
 
diff --git a/pull/pull.py b/pull/pull.py
index 60c2074..1f99c5c 100644
--- a/pull/pull.py
+++ b/pull/pull.py
@@ -41,7 +41,7 @@
 _IMG_FILE_EXTENSION = ".img"
 
 
-def PullFileFromInstance(cfg, instance):
+def PullFileFromInstance(cfg, instance, file_name=None, no_prompts=False):
     """Pull file from remote CF instance.
 
     1. Download log files to temp folder.
@@ -51,6 +51,8 @@
     Args:
         cfg: AcloudConfig object.
         instance: list.Instance() object.
+        file_name: String of file name.
+        no_prompts: Boolean, True to skip the prompt about file streaming.
 
     Returns:
         A Report instance.
@@ -59,11 +61,11 @@
               gce_user=constants.GCE_USER,
               ssh_private_key_path=cfg.ssh_private_key_path,
               extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
-    log_files = SelectLogFileToPull(ssh)
+    log_files = SelectLogFileToPull(ssh, file_name)
     download_folder = GetDownloadLogFolder(instance.name)
     PullLogs(ssh, log_files, download_folder)
     if len(log_files) == 1:
-        DisplayLog(ssh, log_files[0])
+        DisplayLog(ssh, log_files[0], no_prompts)
     return report.Report(command="pull")
 
 
@@ -81,17 +83,18 @@
     _DisplayPullResult(download_folder)
 
 
-def DisplayLog(ssh, log_file):
+def DisplayLog(ssh, log_file, no_prompts=False):
     """Display the content of log file in the screen.
 
     Args:
         ssh: Ssh object.
         log_file: String of the log file path.
+        no_prompts: Boolean, True to skip all prompts.
     """
     warning_msg = ("It will stream log to show on screen. If you want to stop "
                    "streaming, please press CTRL-C to exit.\nPress 'y' to show "
                    "log or read log by myself[y/N]:")
-    if utils.GetUserAnswerYes(warning_msg):
+    if no_prompts or utils.GetUserAnswerYes(warning_msg):
         ssh.Run("tail -f -n +1 %s" % log_file, show_output=True)
 
 
@@ -122,22 +125,29 @@
     return log_folder
 
 
-def SelectLogFileToPull(ssh):
+def SelectLogFileToPull(ssh, file_name=None):
     """Select one log file or all log files to downalod.
 
     1. Get all log file paths as selection list
-    2. Get user selected file path
+    2. Get user selected file path or user provided file name.
 
     Args:
         ssh: Ssh object.
-
-    Raises:
-        errors.CheckPathError: Can't find log files.
+        file_name: String of file name.
 
     Returns:
         List of selected file paths.
+
+    Raises:
+        errors.CheckPathError: Can't find log files.
     """
     log_files = GetAllLogFilePaths(ssh)
+    if file_name:
+        file_path = os.path.join(_REMOTE_LOG_FOLDER, file_name)
+        if file_path in log_files:
+            return [file_path]
+        raise errors.CheckPathError("Can't find this log file(%s) from remote "
+                                    "instance." % file_path)
 
     if len(log_files) == 1:
         return log_files
@@ -147,7 +157,7 @@
         return utils.GetAnswerFromList(log_files, enable_choose_all=True)
 
     raise errors.CheckPathError("Can't find any log file in folder(%s) from "
-                                "remote instance" % _REMOTE_LOG_FOLDER)
+                                "remote instance." % _REMOTE_LOG_FOLDER)
 
 
 def GetAllLogFilePaths(ssh):
@@ -207,5 +217,8 @@
     if args.instance_name:
         instance = list_instances.GetInstancesFromInstanceNames(
             cfg, [args.instance_name])
-        return PullFileFromInstance(cfg, instance[0])
-    return PullFileFromInstance(cfg, list_instances.ChooseOneRemoteInstance(cfg))
+        return PullFileFromInstance(cfg, instance[0], args.file_name, args.no_prompt)
+    return PullFileFromInstance(cfg,
+                                list_instances.ChooseOneRemoteInstance(cfg),
+                                args.file_name,
+                                args.no_prompt)
diff --git a/pull/pull_args.py b/pull/pull_args.py
index 62a28d4..7ab6172 100644
--- a/pull/pull_args.py
+++ b/pull/pull_args.py
@@ -39,5 +39,19 @@
         type=str,
         required=False,
         help="The name of the remote instance that need to pull log files.")
+    pull_parser.add_argument(
+        "--file-name",
+        dest="file_name",
+        type=str,
+        required=False,
+        help="The log file name to pull from the remote instance, "
+             "e.g. launcher.log, kernel.log.")
+    pull_parser.add_argument(
+        "--yes", "-y",
+        action="store_true",
+        dest="no_prompt",
+        required=False,
+        help="Assume 'yes' as an answer to the prompt when asking users about "
+             "file streaming.")
 
     return pull_parser
diff --git a/pull/pull_test.py b/pull/pull_test.py
index 458bb87..c82f12f 100644
--- a/pull/pull_test.py
+++ b/pull/pull_test.py
@@ -48,6 +48,22 @@
         expected_result = ["file2.log"]
         self.assertEqual(pull.SelectLogFileToPull(ssh), expected_result)
 
+        # Test user provided file name exist.
+        log_files = ["/home/vsoc-01/cuttlefish_runtime/file1.log",
+                     "/home/vsoc-01/cuttlefish_runtime/file2.log"]
+        input_file = "file1.log"
+        self.Patch(pull, "GetAllLogFilePaths", return_value=log_files)
+        expected_result = ["/home/vsoc-01/cuttlefish_runtime/file1.log"]
+        self.assertEqual(pull.SelectLogFileToPull(ssh, input_file), expected_result)
+
+        # Test user provided file name not exist.
+        log_files = ["/home/vsoc-01/cuttlefish_runtime/file1.log",
+                     "/home/vsoc-01/cuttlefish_runtime/file2.log"]
+        input_file = "not_exist.log"
+        self.Patch(pull, "GetAllLogFilePaths", return_value=log_files)
+        with self.assertRaises(errors.CheckPathError):
+            pull.SelectLogFileToPull(ssh, input_file)
+
     def testFilterLogfiles(self):
         """test filer log file from black list."""
         # Filter out file name is "kernel".
diff --git a/reconnect/reconnect.py b/reconnect/reconnect.py
index 3420081..2c653ec 100644
--- a/reconnect/reconnect.py
+++ b/reconnect/reconnect.py
@@ -76,7 +76,6 @@
     compute_client = android_compute_client.AndroidComputeClient(
         cfg, credentials)
     compute_client.AddSshRsaInstanceMetadata(
-        cfg.zone,
         user,
         cfg.ssh_public_key_path,
         instance_name)