Merge "To support goldfish type AVD in acloud create command."
diff --git a/Android.bp b/Android.bp
index 41c2390..65b7b79 100644
--- a/Android.bp
+++ b/Android.bp
@@ -29,6 +29,8 @@
 
 python_binary_host {
     name: "acloud",
+    // Make acloud's built name to acloud-dev
+    stem: "acloud-dev",
     defaults: ["acloud_default"],
     main: "public/acloud_main.py",
     srcs: [
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..135fa2d
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,47 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# Discard the original naming "acloud", which is going to be replaced with "acloud-dev".
+$(call add-clean-step, rm -f $(HOST_OUT_EXECUTABLES)/acloud)
+$(call add-clean-step, rm -f $(SOONG_HOST_OUT_EXECUTABLES)/acloud)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 136a761..e5c5e28 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -2,4 +2,4 @@
 pylint = true
 
 [Hook Scripts]
-acloud_unittests = ${REPO_ROOT}/prebuilts/asuite/atest/${BUILD_OS}/atest acloud_test
+acloud_unittests = ${REPO_ROOT}/prebuilts/asuite/atest/${BUILD_OS}/atest acloud_test --host
diff --git a/internal/constants.py b/internal/constants.py
index aaec56a..8a896cc 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -91,7 +91,7 @@
 USER_ANSWER_YES = {"y", "yes", "Y"}
 
 # Cuttlefish groups
-LIST_CF_USER_GROUPS = ["kvm", "libvirt", "cvdnetwork"]
+LIST_CF_USER_GROUPS = ["kvm", "cvdnetwork"]
 #For the cuttlefish remote instances: adb port is 6520 and vnc is 6444.
 CF_TARGET_ADB_PORT = 6520
 CF_TARGET_VNC_PORT = 6444
diff --git a/internal/lib/cheeps_compute_client.py b/internal/lib/cheeps_compute_client.py
index ffa62a3..5ef344a 100644
--- a/internal/lib/cheeps_compute_client.py
+++ b/internal/lib/cheeps_compute_client.py
@@ -37,6 +37,7 @@
 import getpass
 import logging
 
+from acloud import errors
 from acloud.internal.lib import android_compute_client
 from acloud.internal.lib import gcompute_client
 
@@ -49,7 +50,15 @@
     """
     # This is the timeout for betty to start.
     BOOT_TIMEOUT_SECS = 10*60
+    # This is printed by betty.sh.
     BOOT_COMPLETED_MSG = "VM successfully started"
+    # systemd prints this if betty.sh returns nonzero status code.
+    BOOT_FAILED_MSG = "betty.service: Failed with result 'exit-code'"
+
+    def CheckBootFailure(self, serial_out, instance):
+        """Overrides superclass. Determines if there's a boot failure."""
+        if self.BOOT_FAILED_MSG in serial_out:
+            raise errors.DeviceBootError("Betty failed to start")
 
     # pylint: disable=too-many-locals,arguments-differ
     def CreateInstance(self, instance, image_name, image_project,
diff --git a/internal/lib/gcompute_client.py b/internal/lib/gcompute_client.py
index 1f44de0..f70a5ea 100755
--- a/internal/lib/gcompute_client.py
+++ b/internal/lib/gcompute_client.py
@@ -39,6 +39,11 @@
 logger = logging.getLogger(__name__)
 
 _MAX_RETRIES_ON_FINGERPRINT_CONFLICT = 10
+_METADATA_KEY = "key"
+_METADATA_KEY_VALUE = "value"
+_SSH_KEYS_NAME = "sshKeys"
+_ITEMS = "items"
+_METADATA = "metadata"
 
 BASE_DISK_ARGS = {
     "type": "PERSISTENT",
@@ -1127,10 +1132,10 @@
             body["scheduling"] = {"onHostMaintenance": "terminate"}
         if metadata:
             metadata_list = [{
-                "key": key,
-                "value": val
+                _METADATA_KEY: key,
+                _METADATA_KEY_VALUE: val
             } for key, val in metadata.iteritems()]
-            body["metadata"] = {"items": metadata_list}
+            body[_METADATA] = {_ITEMS: metadata_list}
         logger.info("Creating instance: project %s, zone %s, body:%s",
                     self._project, zone, body)
         api = self.service.instances().insert(
@@ -1364,63 +1369,53 @@
         external_ip = instance["networkInterfaces"][0]["accessConfigs"][0]["natIP"]
         return IP(internal=internal_ip, external=external_ip)
 
-    def SetCommonInstanceMetadata(self, body):
-        """Set project-wide metadata.
+    @utils.TimeExecute(function_description="Updating instance metadata: ")
+    def SetInstanceMetadata(self, zone, instance, body):
+        """Set instance metadata.
 
         Args:
-            body: Metadata body.
+            zone: String, name of zone.
+            instance: String, representing instance name.
+            body: Dict, Metadata body.
                   metdata is in the following format.
                   {
                     "kind": "compute#metadata",
                     "fingerprint": "a-23icsyx4E=",
                     "items": [
                       {
-                        "key": "google-compute-default-region",
-                        "value": "us-central1"
+                        "key": "sshKeys",
+                        "value": "key"
                       }, ...
                     ]
                   }
         """
-        api = self.service.projects().setCommonInstanceMetadata(
-            project=self._project, body=body)
+        api = self.service.instances().setMetadata(
+            project=self._project, zone=zone, instance=instance, body=body)
         operation = self.Execute(api)
-        self.WaitOnOperation(operation, operation_scope=OperationScope.GLOBAL)
+        self.WaitOnOperation(
+            operation, operation_scope=OperationScope.ZONE, scope_name=zone)
 
-    def AddSshRsa(self, user, ssh_rsa_path):
-        """Add the public rsa key to the project's metadata.
+    def AddSshRsaInstanceMetadata(self, zone, user, ssh_rsa_path, instance):
+        """Add the public rsa key to the instance's metadata.
 
-        Compute engine instances that are created after will
-        by default contain the key.
+        Confirm that the instance has this public key in the instance's
+        metadata, if not we will add this public key.
 
         Args:
-            user: the name of the user which the key belongs to.
-            ssh_rsa_path: The absolute path to public rsa key.
+            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.
         """
-        if not os.path.exists(ssh_rsa_path):
-            raise errors.DriverError(
-                "RSA file %s does not exist." % ssh_rsa_path)
-
-        logger.info("Adding ssh rsa key from %s to project %s for user: %s",
-                    ssh_rsa_path, self._project, user)
-        project = self.GetProject()
-        with open(ssh_rsa_path) as f:
-            rsa = f.read()
-            rsa = rsa.strip() if rsa else rsa
-            utils.VerifyRsaPubKey(rsa)
-        metadata = project["commonInstanceMetadata"]
-        for item in metadata.setdefault("items", []):
-            if item["key"] == "sshKeys":
-                sshkey_item = item
-                break
-        else:
-            sshkey_item = {"key": "sshKeys", "value": ""}
-            metadata["items"].append(sshkey_item)
-
+        ssh_rsa_path = os.path.expanduser(ssh_rsa_path)
+        rsa = GetRsaKey(ssh_rsa_path)
         entry = "%s:%s" % (user, rsa)
         logger.debug("New RSA entry: %s", entry)
-        sshkey_item["value"] = "\n".join([sshkey_item["value"].strip(),
-                                          entry]).strip()
-        self.SetCommonInstanceMetadata(metadata)
+
+        gce_instance = self.GetInstance(instance, zone)
+        metadata = gce_instance.get(_METADATA)
+        if RsaNotInMetadata(metadata, entry):
+            self.UpdateRsaInMetadata(zone, instance, metadata, entry)
 
     def CheckAccess(self):
         """Check if the user has read access to the cloud project.
@@ -1443,3 +1438,88 @@
                 return False
             raise
         return True
+
+    def UpdateRsaInMetadata(self, zone, instance, metadata, entry):
+        """Update ssh public key to sshKeys's value in this metadata.
+
+        Args:
+            zone: String, name of zone.
+            instance: String, representing instance name.
+            metadata: Dict, maps a metadata name to its value.
+            entry: String, ssh public key.
+        """
+        ssh_key_item = GetSshKeyFromMetadata(metadata)
+        if ssh_key_item:
+            # The ssh key exists in the metadata so update the reference to it
+            # 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]))
+        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,
+                            _METADATA_KEY_VALUE: entry}
+            metadata[_ITEMS].append(ssh_key_item)
+        utils.PrintColorString(
+            "Ssh public key doesn't exist in the instance(%s), adding it."
+            % instance, utils.TextColors.WARNING)
+        self.SetInstanceMetadata(zone, instance, metadata)
+
+
+def RsaNotInMetadata(metadata, entry):
+    """Check ssh public key exist in sshKeys's value.
+
+    Args:
+        metadata: Dict, maps a metadata name to its value.
+        entry: String, ssh public key.
+
+    Returns:
+        Boolean. True if ssh public key doesn't exist in metadata.
+    """
+    for item in metadata.setdefault(_ITEMS, []):
+        if item[_METADATA_KEY] == _SSH_KEYS_NAME:
+            if entry in item[_METADATA_KEY_VALUE]:
+                return False
+    return True
+
+
+def GetSshKeyFromMetadata(metadata):
+    """Get ssh key item from metadata.
+
+    Args:
+        metadata: Dict, maps a metadata name to its value.
+
+    Returns:
+        Dict of ssk_key_item in metadata, None if can't find the ssh key item
+        in metadata.
+    """
+    for item in metadata.setdefault(_ITEMS, []):
+        if item.get(_METADATA_KEY, '') == _SSH_KEYS_NAME:
+            return item
+    return None
+
+
+def GetRsaKey(ssh_rsa_path):
+    """Get rsa key from rsa path.
+
+    Args:
+        ssh_rsa_path: String, The absolute path to public rsa key.
+
+    Returns:
+        String, rsa key.
+
+    Raises:
+        errors.DriverError: RSA file does not exist.
+    """
+    ssh_rsa_path = os.path.expanduser(ssh_rsa_path)
+    if not os.path.exists(ssh_rsa_path):
+        raise errors.DriverError(
+            "RSA file %s does not exist." % ssh_rsa_path)
+
+    with open(ssh_rsa_path) as f:
+        rsa = f.read()
+        # The space must be removed here for string processing,
+        # if it is not string, it doesn't have a strip function.
+        rsa = rsa.strip() if rsa else rsa
+        utils.VerifyRsaPubKey(rsa)
+    return rsa
diff --git a/internal/lib/gcompute_client_test.py b/internal/lib/gcompute_client_test.py
index a34bfa2..f9ed0c7 100644
--- a/internal/lib/gcompute_client_test.py
+++ b/internal/lib/gcompute_client_test.py
@@ -57,6 +57,14 @@
     OPERATION_NAME = "fake-op"
     IMAGE_FINGERPRINT = "L_NWHuz7wTY="
     GPU = "fancy-graphics"
+    SSHKEY = (
+        "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBkTOTRze9v2VOqkkf7RG"
+        "jSkg6Z2kb9Q9UHsDGatvend3fmjIw1Tugg0O7nnjlPkskmlgyd4a/j99WOeLL"
+        "CPk6xPyoVjrPUVBU/pAk09ORTC4Zqk6YjlW7LOfzvqmXhmIZfYu6Q4Yt50pZzhl"
+        "lllfu26nYjY7Tg12D019nJi/kqPX5+NKgt0LGXTu8T1r2Gav/q4V7QRWQrB8Eiu"
+        "pxXR7I2YhynqovkEt/OXG4qWgvLEXGsWtSQs0CtCzqEVxz0Y9ECr7er4VdjSQxV"
+        "AaeLAsQsK9ROae8hMBFZ3//8zLVapBwpuffCu+fUoql9qeV9xagZcc9zj8XOUOW"
+        "ApiihqNL1111 test@test1.org")
 
     def setUp(self):
         """Set up test."""
@@ -1026,25 +1034,192 @@
         self.assertEqual(ip_name_map, {"172.22.22.22": "instance_1",
                                        "172.22.22.23": None})
 
-    def testAddSshRsa(self):
-        """Test AddSshRsa.."""
+    def testRsaNotInMetadata(self):
+        """Test rsa not in metadata."""
         fake_user = "fake_user"
-        sshkey = (
-            "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBkTOTRze9v2VOqkkf7RG"
-            "jSkg6Z2kb9Q9UHsDGatvend3fmjIw1Tugg0O7nnjlPkskmlgyd4a/j99WOeLL"
-            "CPk6xPyoVjrPUVBU/pAk09ORTC4Zqk6YjlW7LOfzvqmXhmIZfYu6Q4Yt50pZzhl"
-            "lllfu26nYjY7Tg12D019nJi/kqPX5+NKgt0LGXTu8T1r2Gav/q4V7QRWQrB8Eiu"
-            "pxXR7I2YhynqovkEt/OXG4qWgvLEXGsWtSQs0CtCzqEVxz0Y9ECr7er4VdjSQxV"
-            "AaeLAsQsK9ROae8hMBFZ3//8zLVapBwpuffCu+fUoql9qeV9xagZcc9zj8XOUOW"
-            "ApiihqNL1111 test@test1.org")
-        project = {
-            "commonInstanceMetadata": {
+        fake_ssh_key = "fake_ssh"
+        metadata = {
+            "kind": "compute#metadata",
+            "fingerprint": "a-23icsyx4E=",
+            "items": [
+                {
+                    "key": "sshKeys",
+                    "value": "%s:%s" % (fake_user, self.SSHKEY)
+                }
+            ]
+        }
+        # Test rsa doesn't exist in metadata.
+        new_entry = "%s:%s" % (fake_user, fake_ssh_key)
+        self.assertEqual(True, gcompute_client.RsaNotInMetadata(metadata, new_entry))
+
+        # Test rsa exists in metadata.
+        exist_entry = "%s:%s" %(fake_user, self.SSHKEY)
+        self.assertEqual(False, gcompute_client.RsaNotInMetadata(metadata, exist_entry))
+
+    def testGetSshKeyFromMetadata(self):
+        """Test get ssh key from metadata."""
+        fake_user = "fake_user"
+        metadata_key_exist_value_is_empty = {
+            "kind": "compute#metadata",
+            "fingerprint": "a-23icsyx4E=",
+            "items": [
+                {
+                    "key": "sshKeys",
+                    "value": ""
+                }
+            ]
+        }
+        metadata_key_exist = {
+            "kind": "compute#metadata",
+            "fingerprint": "a-23icsyx4E=",
+            "items": [
+                {
+                    "key": "sshKeys",
+                    "value": "%s:%s" % (fake_user, self.SSHKEY)
+                }
+            ]
+        }
+        metadata_key_not_exist = {
+            "kind": "compute#metadata",
+            "fingerprint": "a-23icsyx4E=",
+            "items": [
+                {
+                }
+            ]
+        }
+        expected_key_exist_value_is_empty = {
+            "key": "sshKeys",
+            "value": ""
+        }
+        expected_key_exist = {
+            "key": "sshKeys",
+            "value": "%s:%s" % (fake_user, self.SSHKEY)
+        }
+        self.assertEqual(expected_key_exist_value_is_empty,
+                         gcompute_client.GetSshKeyFromMetadata(metadata_key_exist_value_is_empty))
+        self.assertEqual(expected_key_exist,
+                         gcompute_client.GetSshKeyFromMetadata(metadata_key_exist))
+        self.assertEqual(None,
+                         gcompute_client.GetSshKeyFromMetadata(metadata_key_not_exist))
+
+
+    def testGetRsaKeyPathExistsFalse(self):
+        """Test the rsa key path not exists."""
+        fake_ssh_rsa_path = "/path/to/test_rsa.pub"
+        self.Patch(os.path, "exists", return_value=False)
+        self.assertRaisesRegexp(errors.DriverError,
+                                "RSA file %s does not exist." % fake_ssh_rsa_path,
+                                gcompute_client.GetRsaKey,
+                                ssh_rsa_path=fake_ssh_rsa_path)
+
+    def testGetRsaKey(self):
+        """Test get the rsa key."""
+        fake_ssh_rsa_path = "/path/to/test_rsa.pub"
+        self.Patch(os.path, "exists", return_value=True)
+        m = mock.mock_open(read_data=self.SSHKEY)
+        with mock.patch("__builtin__.open", m):
+            result = gcompute_client.GetRsaKey(fake_ssh_rsa_path)
+            self.assertEqual(self.SSHKEY, result)
+
+    def testUpdateRsaInMetadata(self):
+        """Test update rsa in metadata."""
+        fake_ssh_key = "fake_ssh"
+        fake_metadata_sshkeys_not_exist = {
+            "kind": "compute#metadata",
+            "fingerprint": "a-23icsyx4E=",
+            "items": [
+                {
+                    "key": "not_sshKeys",
+                    "value": ""
+                }
+            ]
+        }
+        new_entry = "new_user:%s" % fake_ssh_key
+        expected = {
+            "kind": "compute#metadata",
+            "fingerprint": "a-23icsyx4E=",
+            "items": [
+                {
+                    "key": "not_sshKeys",
+                    "value": ""
+                },
+                {
+                    "key": "sshKeys",
+                    "value": new_entry
+                }
+            ]
+        }
+        self.Patch(os.path, "exists", return_value=True)
+        self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
+        resource_mock = mock.MagicMock()
+        self.compute_client.SetInstanceMetadata = mock.MagicMock(
+            return_value=resource_mock)
+        # Test the key item not exists in the metadata.
+        self.compute_client.UpdateRsaInMetadata(
+            "fake_zone",
+            "fake_instance",
+            fake_metadata_sshkeys_not_exist,
+            new_entry)
+        self.compute_client.SetInstanceMetadata.assert_called_with(
+            "fake_zone",
+            "fake_instance",
+            expected)
+
+        # Test the key item exists in the metadata.
+        fake_metadata_ssh_keys_exists = {
+            "kind": "compute#metadata",
+            "fingerprint": "a-23icsyx4E=",
+            "items": [
+                {
+                    "key": "sshKeys",
+                    "value": "old_user:%s" % self.SSHKEY
+                }
+            ]
+        }
+        expected_ssh_keys_exists = {
+            "kind": "compute#metadata",
+            "fingerprint": "a-23icsyx4E=",
+            "items": [
+                {
+                    "key": "sshKeys",
+                    "value": "old_user:%s\n%s" % (self.SSHKEY, new_entry)
+                }
+            ]
+        }
+
+        self.compute_client.UpdateRsaInMetadata(
+            "fake_zone",
+            "fake_instance",
+            fake_metadata_ssh_keys_exists,
+            new_entry)
+        self.compute_client.SetInstanceMetadata.assert_called_with(
+            "fake_zone",
+            "fake_instance",
+            expected_ssh_keys_exists)
+
+    def testAddSshRsaToInstance(self):
+        """Test add ssh rsa key to instance."""
+        fake_user = "fake_user"
+        instance_metadata_key_not_exist = {
+            "metadata": {
                 "kind": "compute#metadata",
                 "fingerprint": "a-23icsyx4E=",
                 "items": [
                     {
                         "key": "sshKeys",
-                        "value": "user:key"
+                        "value": ""
+                    }
+                ]
+            }
+        }
+        instance_metadata_key_exist = {
+            "metadata": {
+                "kind": "compute#metadata",
+                "fingerprint": "a-23icsyx4E=",
+                "items": [
+                    {
+                        "key": "sshKeys",
+                        "value": "%s:%s" % (fake_user, self.SSHKEY)
                     }
                 ]
             }
@@ -1055,51 +1230,47 @@
             "items": [
                 {
                     "key": "sshKeys",
-                    "value": "user:key\n%s:%s" % (fake_user, sshkey)
+                    "value": "%s:%s" % (fake_user, self.SSHKEY)
                 }
             ]
         }
 
         self.Patch(os.path, "exists", return_value=True)
-        m = mock.mock_open(read_data=sshkey)
+        m = mock.mock_open(read_data=self.SSHKEY)
         self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
-        self.Patch(
-            gcompute_client.ComputeClient, "GetProject", return_value=project)
         resource_mock = mock.MagicMock()
-        self.compute_client._service.projects = mock.MagicMock(
+        self.compute_client._service.instances = mock.MagicMock(
             return_value=resource_mock)
-        resource_mock.setCommonInstanceMetadata = mock.MagicMock()
+        resource_mock.setMetadata = mock.MagicMock()
 
-        with mock.patch("__builtin__.open", m):
-            self.compute_client.AddSshRsa(fake_user, "/path/to/test_rsa.pub")
-            resource_mock.setCommonInstanceMetadata.assert_called_with(
-                project=PROJECT, body=expected)
-
-    def testAddSshRsaInvalidKey(self):
-        """Test AddSshRsa.."""
-        fake_user = "fake_user"
-        sshkey = "ssh-rsa v2VOqkkf7RGL1111 test@test1.org"
-        project = {
-            "commonInstanceMetadata": {
-                "kind": "compute#metadata",
-                "fingerprint": "a-23icsyx4E=",
-                "items": [
-                    {
-                        "key": "sshKeys",
-                        "value": "user:key"
-                    }
-                ]
-            }
-        }
-        self.Patch(os.path, "exists", return_value=True)
-        m = mock.mock_open(read_data=sshkey)
-        self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
+        # Test the key not exists in the metadata.
         self.Patch(
-            gcompute_client.ComputeClient, "GetProject", return_value=project)
+            gcompute_client.ComputeClient, "GetInstance",
+            return_value=instance_metadata_key_not_exist)
         with mock.patch("__builtin__.open", m):
-            self.assertRaisesRegexp(errors.DriverError, "rsa key is invalid:*",
-                                    self.compute_client.AddSshRsa, fake_user,
-                                    "/path/to/test_rsa.pub")
+            self.compute_client.AddSshRsaInstanceMetadata(
+                "fake_zone",
+                fake_user,
+                "/path/to/test_rsa.pub",
+                "fake_instance")
+            resource_mock.setMetadata.assert_called_with(
+                project=PROJECT,
+                zone="fake_zone",
+                instance="fake_instance",
+                body=expected)
+
+        # Test the key already exists in the metadata.
+        resource_mock.setMetadata.call_count = 0
+        self.Patch(
+            gcompute_client.ComputeClient, "GetInstance",
+            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")
+            resource_mock.setMetadata.assert_not_called()
 
     @mock.patch.object(gcompute_client.ComputeClient, "WaitOnOperation")
     def testDeleteDisks(self, mock_wait):
diff --git a/list/instance.py b/list/instance.py
index d726839..4cc64a7 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -50,7 +50,7 @@
                           r"(.+%s)")
 _RE_TIMEZONE = re.compile(r"^(?P<time>[0-9\-\.:T]*)(?P<timezone>[+-]\d+:\d+)$")
 
-_COMMAND_PS_LAUNCH_CVD = ["ps", "-eo", "lstart,cmd"]
+_COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"]
 _RE_LAUNCH_CVD = re.compile(r"(?P<date_str>^[^/]+)(.*launch_cvd --daemon )+"
                             r"((.*\s*-cpus\s)(?P<cpu>\d+))?"
                             r"((.*\s*-x_res\s)(?P<x_res>\d+))?"
diff --git a/public/acloud_main.py b/public/acloud_main.py
index bb4da6c..25a23e0 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -48,7 +48,6 @@
 
 from __future__ import print_function
 import argparse
-import getpass
 import logging
 import platform
 import sys
@@ -107,7 +106,6 @@
 CMD_CREATE_CUTTLEFISH = "create_cf"
 CMD_CREATE_GOLDFISH = "create_gf"
 CMD_CLEANUP = "cleanup"
-CMD_SSHKEY = "project_sshkey"
 
 
 # pylint: disable=too-many-statements
@@ -209,26 +207,6 @@
         "images that are older than |expiration_mins|.")
     subparser_list.append(cleanup_parser)
 
-    # Command "project_sshkey"
-    sshkey_parser = subparsers.add_parser(CMD_SSHKEY)
-    sshkey_parser.required = False
-    sshkey_parser.set_defaults(which=CMD_SSHKEY)
-    sshkey_parser.add_argument(
-        "--user",
-        type=str,
-        dest="user",
-        default=getpass.getuser(),
-        help="The user name which the sshkey belongs to, default to: %s." %
-        getpass.getuser())
-    sshkey_parser.add_argument(
-        "--ssh_rsa_path",
-        type=str,
-        dest="ssh_rsa_path",
-        required=True,
-        help="Absolute path to the file that contains the public rsa key "
-        "that will be added as project-wide ssh key.")
-    subparser_list.append(sshkey_parser)
-
     # Command "create"
     subparser_list.append(create_args.GetCreateArgParser(subparsers))
 
@@ -399,8 +377,6 @@
         list_instances.Run(args)
     elif args.which == reconnect_args.CMD_RECONNECT:
         reconnect.Run(args)
-    elif args.which == CMD_SSHKEY:
-        report = device_driver.AddSshRsa(cfg, args.user, args.ssh_rsa_path)
     elif args.which == setup_args.CMD_SETUP:
         setup.Run(args)
     else:
diff --git a/public/data/default.config b/public/data/default.config
index ecb0e01..53d114f 100644
--- a/public/data/default.config
+++ b/public/data/default.config
@@ -74,7 +74,7 @@
 
 common_hw_property_map {
   key: "tv"
-  value: "cpu:2,resolution:1080x1920,dpi:320,memory:4g,disk:4g"
+  value: "cpu:2,resolution:1280x720,dpi:213,memory:2g,disk:4g"
 }
 
 # Device resolution
diff --git a/public/device_driver.py b/public/device_driver.py
index 3fabc0c..4d54a98 100755
--- a/public/device_driver.py
+++ b/public/device_driver.py
@@ -575,30 +575,6 @@
     return r
 
 
-def AddSshRsa(cfg, user, ssh_rsa_path):
-    """Add public ssh rsa key to the project.
-
-    Args:
-        cfg: An AcloudConfig instance.
-        user: the name of the user which the key belongs to.
-        ssh_rsa_path: The absolute path to public rsa key.
-
-    Returns:
-        A Report instance.
-    """
-    r = report.Report(command="sshkey")
-    try:
-        credentials = auth.CreateCredentials(cfg)
-        compute_client = android_compute_client.AndroidComputeClient(
-            cfg, credentials)
-        compute_client.AddSshRsa(user, ssh_rsa_path)
-        r.SetStatus(report.Status.SUCCESS)
-    except errors.DriverError as e:
-        r.AddError(str(e))
-        r.SetStatus(report.Status.FAIL)
-    return r
-
-
 def CheckAccess(cfg):
     """Check if user has access.
 
diff --git a/reconnect/reconnect.py b/reconnect/reconnect.py
index 413b146..27f6280 100644
--- a/reconnect/reconnect.py
+++ b/reconnect/reconnect.py
@@ -27,6 +27,8 @@
 
 from acloud.delete import delete
 from acloud.internal import constants
+from acloud.internal.lib import auth
+from acloud.internal.lib import android_compute_client
 from acloud.internal.lib import utils
 from acloud.internal.lib.adb_tools import AdbTools
 from acloud.list import list as list_instance
@@ -68,6 +70,26 @@
             utils.LaunchVncClient(vnc_port)
 
 
+def AddPublicSshRsaToInstance(cfg, user, instance_name):
+    """Add the public rsa key to the instance's metadata.
+
+    When the public key doesn't exist in the metadata, it will add it.
+
+    Args:
+        cfg: An AcloudConfig instance.
+        user: String, the ssh username to access instance.
+        instance_name: String, instance name.
+    """
+    credentials = auth.CreateCredentials(cfg)
+    compute_client = android_compute_client.AndroidComputeClient(
+        cfg, credentials)
+    compute_client.AddSshRsaInstanceMetadata(
+        cfg.zone,
+        user,
+        cfg.ssh_public_key_path,
+        instance_name)
+
+
 def ReconnectInstance(ssh_private_key_path, instance):
     """Reconnect adb/vnc/ssh to the specified instance.
 
@@ -112,4 +134,5 @@
     if not instances_to_reconnect:
         instances_to_reconnect = list_instance.ChooseInstances(cfg, args.all)
     for instance in instances_to_reconnect:
+        AddPublicSshRsaToInstance(cfg, getpass.getuser(), instance.name)
         ReconnectInstance(cfg.ssh_private_key_path, instance)