Snap for 6001391 from 10b111c8044faf0acf0be504fc630c921c758134 to qt-aml-resolv-release

Change-Id: I6501f8ae18b374eeaf2a0eece0ca4176264b79df
diff --git a/README.md b/README.md
index 8fcbb7c..e9e9c06 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**
 
@@ -107,6 +105,10 @@
 disk in a key:value format like so:
 `cpu:2,resolution:1280x700,dpi:160,memory:2g,disk:2g`
 
+* `--reuse-gce`: 'cuttlefish only' This can help users use their own instance.
+Reusing specific gce instance if `--reuse-gce` [instance-name] is provided.
+Select one gce instance to reuse if `--reuse-gce` is provided.
+
 The full list of options are available via `--help`
 
 > $ acloud create --help
@@ -170,6 +172,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/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/create/local_image_remote_instance.py b/create/local_image_remote_instance.py
index d741a4f..20d03aa 100644
--- a/create/local_image_remote_instance.py
+++ b/create/local_image_remote_instance.py
@@ -54,8 +54,7 @@
         Return:
             A string, the path to the host package.
         """
-        dirs_to_check = filter(None,
-                               [os.environ.get(constants.ENV_ANDROID_HOST_OUT)])
+        dirs_to_check = list(filter(None, [os.environ.get(constants.ENV_ANDROID_HOST_OUT)]))
         dist_dir = utils.GetDistDir()
         if dist_dir:
             dirs_to_check.append(dist_dir)
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/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index 44c0e90..98783a4 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -175,7 +175,7 @@
         if avd_spec:
             if avd_spec.instance_name_to_reuse:
                 self.StopCvd()
-                self.CleanUpImages()
+                self.CleanUp()
             return instance
 
         # TODO: Remove following code after create_cf deprecated.
@@ -277,17 +277,18 @@
         except subprocess.CalledProcessError as e:
             logger.debug("Failed to stop_cvd (possibly no running device): %s", e)
 
-    def CleanUpImages(self):
-        """Clean up the images on the existing instance.
+    def CleanUp(self):
+        """Clean up the files/folders on the existing instance.
 
-        If previous AVD have these images, reusing the instance may have
-        side effects if didn't clean it.
+        If previous AVD have these files/folders, reusing the instance may have
+        side effects if not cleaned. The path in the instance is /home/vsoc-01/*
+        if the GCE user is vsoc-01.
         """
-        ssh_command = "/bin/rm ./*.img"
+        ssh_command = "'/bin/rm -rf /home/%s/*'" % constants.GCE_USER
         try:
             self._ssh.Run(ssh_command)
         except subprocess.CalledProcessError as e:
-            logger.debug("Failed to clean up the images failed: %s", e)
+            logger.debug("Failed to clean up the files/folders: %s", e)
 
     @utils.TimeExecute(function_description="Launching AVD(s) and waiting for boot up",
                        result_evaluator=utils.BootEvaluator)
diff --git a/internal/lib/gcompute_client.py b/internal/lib/gcompute_client.py
index 8c56ea7..bde5ac2 100755
--- a/internal/lib/gcompute_client.py
+++ b/internal/lib/gcompute_client.py
@@ -31,6 +31,7 @@
 import getpass
 import logging
 import os
+import re
 
 from acloud import errors
 from acloud.internal import constants
@@ -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",
@@ -801,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"
@@ -814,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,
@@ -1346,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
@@ -1355,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"]
@@ -1413,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.
@@ -1430,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.
 
@@ -1472,7 +1584,7 @@
             # in the metadata. There may not be an actual ssh key value so
             # that's why we filter for None to avoid an empty line in front.
             ssh_key_item[_METADATA_KEY_VALUE] = "\n".join(
-                filter(None, [ssh_key_item[_METADATA_KEY_VALUE], entry]))
+                list(filter(None, [ssh_key_item[_METADATA_KEY_VALUE], entry])))
         else:
             # Since there is no ssh key item in the metadata, we need to add it in.
             ssh_key_item = {_METADATA_KEY: _SSH_KEYS_NAME,
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/goldfish_compute_client.py b/internal/lib/goldfish_compute_client.py
index 926cdac..d9d1d20 100644
--- a/internal/lib/goldfish_compute_client.py
+++ b/internal/lib/goldfish_compute_client.py
@@ -122,6 +122,17 @@
                 raise errors.DeviceBootError(
                     "Emulator timed out while booting.")
 
+    @staticmethod
+    def GetKernelBuildArtifact(target):
+        if target == "kernel":
+            return "bzImage"
+        if target == "kernel_x86_64":
+            return "bzImage"
+        if target == "kernel_aarch64":
+            return "Image.gz"
+        raise errors.DeviceBootError(
+            "Don't know the artifact name for '%s' target" % target)
+
     # pylint: disable=too-many-locals,arguments-differ
     # TODO: Refactor CreateInstance to pass in an object instead of all these args.
     def CreateInstance(self,
@@ -133,6 +144,7 @@
                        build_id,
                        kernel_branch=None,
                        kernel_build_id=None,
+                       kernel_build_target=None,
                        emulator_branch=None,
                        emulator_build_id=None,
                        blank_data_disk_size_gb=None,
@@ -152,6 +164,7 @@
             build_id: String, build id, a string, e.g. "2263051", "P2804227"
             kernel_branch: String, kernel branch name.
             kernel_build_id: String, kernel build id.
+            kernel_build_target: kernel target, e.g. "kernel_x86_64"
             emulator_branch: String, emulator branch name, e.g."aosp-emu-master-dev"
             emulator_build_id: String, emulator build id, a string, e.g. "2263051", "P2804227"
             blank_data_disk_size_gb: Integer, size of the blank data disk in GB.
@@ -181,9 +194,12 @@
         metadata["cvd_01_fetch_android_build_target"] = build_target
         metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format(
             branch=branch, build_id=build_id)
-        if kernel_branch and kernel_build_id:
+        if kernel_branch and kernel_build_id and kernel_build_target:
             metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format(
                 branch=kernel_branch, build_id=kernel_build_id)
+            metadata["cvd_01_fetch_kernel_build_target"] = kernel_build_target
+            metadata["cvd_01_fetch_kernel_build_artifact"] = (
+                self.GetKernelBuildArtifact(kernel_build_target))
             metadata["cvd_01_use_custom_kernel"] = "true"
         if emulator_branch and emulator_build_id:
             metadata[
diff --git a/internal/lib/goldfish_compute_client_test.py b/internal/lib/goldfish_compute_client_test.py
index 8b67088..cfee21f 100644
--- a/internal/lib/goldfish_compute_client_test.py
+++ b/internal/lib/goldfish_compute_client_test.py
@@ -37,6 +37,8 @@
     BUILD_ID = "2263051"
     KERNEL_BRANCH = "kernel-p-dev-android-goldfish-4.14-x86-64"
     KERNEL_BUILD_ID = "112233"
+    KERNEL_BUILD_TARGET = "kernel_x86_64"
+    KERNEL_BUILD_ARTIFACT = "bzImage"
     EMULATOR_BRANCH = "aosp-emu-master-dev"
     EMULATOR_BUILD_ID = "1234567"
     DPI = 160
@@ -105,6 +107,8 @@
             "cvd_01_fetch_kernel_bid":
                 "{branch}/{build_id}".format(
                     branch=self.KERNEL_BRANCH, build_id=self.KERNEL_BUILD_ID),
+            "cvd_01_fetch_kernel_build_target": self.KERNEL_BUILD_TARGET,
+            "cvd_01_fetch_kernel_build_artifact": self.KERNEL_BUILD_ARTIFACT,
             "cvd_01_use_custom_kernel": "true",
             "cvd_01_fetch_emulator_bid":
                 "{branch}/{build_id}".format(
@@ -120,8 +124,11 @@
 
         self.goldfish_compute_client.CreateInstance(
             self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT, self.TARGET,
-            self.BRANCH, self.BUILD_ID, self.KERNEL_BRANCH,
-            self.KERNEL_BUILD_ID, self.EMULATOR_BRANCH,
+            self.BRANCH, self.BUILD_ID,
+            self.KERNEL_BRANCH,
+            self.KERNEL_BUILD_ID,
+            self.KERNEL_BUILD_TARGET,
+            self.EMULATOR_BRANCH,
             self.EMULATOR_BUILD_ID, self.EXTRA_DATA_DISK_SIZE_GB, self.GPU,
             extra_scopes=self.EXTRA_SCOPES,
             tags=self.TAGS)
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..c80b995 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.
 
 """
@@ -396,6 +403,7 @@
             emulator_branch=args.emulator_branch,
             kernel_build_id=args.kernel_build_id,
             kernel_branch=args.kernel_branch,
+            kernel_build_target=args.kernel_build_target,
             gpu=args.gpu,
             num=args.num,
             serial_log_file=args.serial_log_file,
diff --git a/public/actions/create_goldfish_action.py b/public/actions/create_goldfish_action.py
index 36124f8..c5b942a 100644
--- a/public/actions/create_goldfish_action.py
+++ b/public/actions/create_goldfish_action.py
@@ -67,6 +67,7 @@
                  emulator_build_id,
                  kernel_build_id=None,
                  kernel_branch=None,
+                 kernel_build_target=None,
                  gpu=None,
                  avd_spec=None,
                  tags=None,
@@ -113,7 +114,8 @@
         self.emulator_build_info = self._build_client.GetBuildInfo(
             emulator_build_target, emulator_build_id, emulator_branch)
         self.kernel_build_info = self._build_client.GetBuildInfo(
-            cfg.kernel_build_target, kernel_build_id, kernel_branch)
+            kernel_build_target or cfg.kernel_build_target, kernel_build_id,
+            kernel_branch)
 
     def GetBuildInfoDict(self):
         """Get build info dictionary.
@@ -159,6 +161,7 @@
             emulator_build_id=self.emulator_build_info.build_id,
             kernel_branch=self.kernel_build_info.branch,
             kernel_build_id=self.kernel_build_info.build_id,
+            kernel_build_target=self.kernel_build_info.build_target,
             gpu=self._gpu,
             blank_data_disk_size_gb=self._blank_data_disk_size_gb,
             avd_spec=self._avd_spec,
@@ -232,6 +235,7 @@
                   emulator_branch=None,
                   kernel_build_id=None,
                   kernel_branch=None,
+                  kernel_build_target=None,
                   gpu=None,
                   num=1,
                   serial_log_file=None,
@@ -252,6 +256,7 @@
         gpu: String, GPU to attach to the device or None. e.g. "nvidia-k80"
         kernel_build_id: Kernel build id, a string.
         kernel_branch: Kernel branch name, a string.
+        kernel_build_target: Kernel build artifact, a string.
         num: Integer, Number of devices to create.
         serial_log_file: String, A path to a file where serial output should
                         be saved to.
@@ -313,20 +318,22 @@
     logger.info(
         "Creating a goldfish device in project %s, build_target: %s, "
         "build_id: %s, emulator_bid: %s, kernel_build_id: %s, "
-        "kernel_branh: %s, GPU: %s, num: %s, "
+        "kernel_branch: %s, kernel_build_target: %s, GPU: %s, num: %s, "
         "serial_log_file: %s, "
         "autoconnect: %s", cfg.project, build_target, build_id,
-        emulator_build_id, kernel_build_id, kernel_branch, gpu, num,
-        serial_log_file, autoconnect)
+        emulator_build_id, kernel_build_id, kernel_branch, kernel_build_target,
+        gpu, num, serial_log_file, autoconnect)
 
-    device_factory = GoldfishDeviceFactory(cfg, build_target, build_id,
-                                           cfg.emulator_build_target,
-                                           emulator_build_id, gpu=gpu,
-                                           avd_spec=avd_spec, tags=tags,
-                                           branch=branch,
-                                           emulator_branch=emulator_branch,
-                                           kernel_build_id=kernel_build_id,
-                                           kernel_branch=kernel_branch)
+    device_factory = GoldfishDeviceFactory(
+        cfg, build_target, build_id,
+        cfg.emulator_build_target,
+        emulator_build_id, gpu=gpu,
+        avd_spec=avd_spec, tags=tags,
+        branch=branch,
+        emulator_branch=emulator_branch,
+        kernel_build_id=kernel_build_id,
+        kernel_branch=kernel_branch,
+        kernel_build_target=kernel_build_target)
 
     return common_operations.CreateDevices("create_gf", cfg, device_factory,
                                            num, constants.TYPE_GF,
diff --git a/public/actions/create_goldfish_action_test.py b/public/actions/create_goldfish_action_test.py
index fa04ee2..d031167 100644
--- a/public/actions/create_goldfish_action_test.py
+++ b/public/actions/create_goldfish_action_test.py
@@ -123,7 +123,10 @@
         report = create_goldfish_action.CreateDevices(
             none_avd_spec, cfg, build_target=self.BUILD_TARGET,
             build_id=self.BUILD_ID, emulator_build_id=self.EMULATOR_BUILD_ID,
-            gpu=self.GPU, kernel_build_id=self.KERNEL_BUILD_ID)
+            gpu=self.GPU,
+            kernel_branch=self.KERNEL_BRANCH,
+            kernel_build_id=self.KERNEL_BUILD_ID,
+            kernel_build_target=self.KERNEL_BUILD_TARGET)
 
         # Verify
         self.compute_client.CreateInstance.assert_called_with(
@@ -138,6 +141,7 @@
             emulator_build_id=self.EMULATOR_BUILD_ID,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
+            kernel_build_target=self.KERNEL_BUILD_TARGET,
             gpu=self.GPU,
             avd_spec=none_avd_spec,
             extra_scopes=self.EXTRA_SCOPES,
@@ -193,6 +197,7 @@
             emulator_build_id=self.EMULATOR_BUILD_ID,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
+            kernel_build_target=self.KERNEL_BUILD_TARGET,
             gpu=self.GPU,
             avd_spec=self.avd_spec,
             extra_scopes=self.EXTRA_SCOPES,
@@ -239,7 +244,9 @@
             emulator_branch=None,
             gpu=self.GPU,
             branch=None,
-            kernel_build_id=self.KERNEL_BUILD_ID)
+            kernel_branch=self.KERNEL_BRANCH,
+            kernel_build_id=self.KERNEL_BUILD_ID,
+            kernel_build_target=self.KERNEL_BUILD_TARGET)
 
         # Verify
         self.compute_client.CreateInstance.assert_called_with(
@@ -254,6 +261,7 @@
             emulator_build_id=self.EMULATOR_BUILD_ID,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
+            kernel_build_target=self.KERNEL_BUILD_TARGET,
             gpu=self.GPU,
             avd_spec=none_avd_spec,
             extra_scopes=self.EXTRA_SCOPES,
@@ -307,6 +315,7 @@
             emulator_build_id=self.EMULATOR_BUILD_ID,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
+            kernel_build_target=self.KERNEL_BUILD_TARGET,
             gpu=self.GPU,
             avd_spec=self.avd_spec,
             extra_scopes=self.EXTRA_SCOPES,
@@ -363,6 +372,7 @@
             emulator_build_id=self.EMULATOR_BUILD_ID,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
+            kernel_build_target=self.KERNEL_BUILD_TARGET,
             gpu=self.GPU,
             avd_spec=none_avd_spec,
             extra_scopes=self.EXTRA_SCOPES,
@@ -416,6 +426,7 @@
             emulator_build_id=self.EMULATOR_BUILD_ID,
             kernel_branch=self.KERNEL_BRANCH,
             kernel_build_id=self.KERNEL_BUILD_ID,
+            kernel_build_target=self.KERNEL_BUILD_TARGET,
             gpu=self.GPU,
             avd_spec=self.avd_spec,
             extra_scopes=self.EXTRA_SCOPES,
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
index 8a539c1..74f5ca7 100644
--- a/public/actions/remote_instance_cf_device_factory.py
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -250,3 +250,26 @@
             and the value is an errors.DeviceBootError object.
         """
         return self._compute_client.all_failures
+
+    def GetBuildInfoDict(self):
+        """Get build info dictionary.
+
+        Returns:
+          A build info dictionary.
+        """
+        build_info_dict = {
+            key: val for key, val in self._avd_spec.remote_image.items() if val}
+
+        # kernel_target have default value "kernel". If user provide kernel_build_id
+        # or kernel_branch, then start to process kernel image.
+        if (self._avd_spec.kernel_build_info[constants.BUILD_ID]
+                or self._avd_spec.kernel_build_info[constants.BUILD_BRANCH]):
+            build_info_dict.update(
+                {"kernel_%s" % key: val
+                 for key, val in self._avd_spec.kernel_build_info.items() if val}
+            )
+        build_info_dict.update(
+            {"system_%s" % key: val
+             for key, val in self._avd_spec.system_build_info.items() if val}
+        )
+        return build_info_dict
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 237d3ea..560c26b 100644
--- a/public/device_driver_test.py
+++ b/public/device_driver_test.py
@@ -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/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)