Merge "Update new keywords for GCE quota issue."
diff --git a/Android.bp b/Android.bp
index ec2c2c1..c6e7534 100644
--- a/Android.bp
+++ b/Android.bp
@@ -74,6 +74,7 @@
         "acloud_public",
         "acloud_restart",
         "acloud_setup",
+        "acloud_hostcleanup",
         "py-apitools",
         "py-dateutil",
         "py-google-api-python-client",
@@ -112,6 +113,7 @@
         "acloud_proto",
         "acloud_restart",
         "acloud_setup",
+        "acloud_hostcleanup",
         "asuite_cc_client",
         "py-apitools",
         "py-dateutil",
@@ -232,6 +234,14 @@
 }
 
 python_library_host{
+    name: "acloud_hostcleanup",
+    defaults: ["acloud_default"],
+    srcs: [
+         "hostcleanup/*.py",
+    ],
+}
+
+python_library_host{
     name: "acloud_metrics",
     defaults: ["acloud_default"],
     srcs: [
diff --git a/delete/delete.py b/delete/delete.py
index a60f16d..47be404 100644
--- a/delete/delete.py
+++ b/delete/delete.py
@@ -306,7 +306,7 @@
     This method can identify the following types of instance names:
     local cuttlefish instance: local-instance-<id>
     local goldfish instance: local-goldfish-instance-<id>
-    remote host goldfish instance: host-<ip_addr>-goldfish-<port>-<build_info>
+    remote host goldfish instance: host-goldfish-<ip_addr>-<port>-<build_info>
     remote instance: ins-<uuid>-<build_info>
 
     Args:
diff --git a/delete/delete_test.py b/delete/delete_test.py
index 0359a33..23cb973 100644
--- a/delete/delete_test.py
+++ b/delete/delete_test.py
@@ -163,7 +163,7 @@
         cfg_attrs = {"ssh_private_key_path": "cfg_key_path",
                      "extra_args_ssh_tunnel": "extra args"}
         mock_cfg = mock.Mock(spec_set=list(cfg_attrs.keys()), **cfg_attrs)
-        instance_name = "host-192.0.2.1-goldfish-5554-123456-sdk_x86_64-sdk"
+        instance_name = "host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk"
         delete_report = report.Report(command="delete")
 
         delete.DeleteHostGoldfishInstance(mock_cfg, instance_name,
@@ -212,7 +212,7 @@
         mock_reset_lock.assert_called_with("local-instance-2", mock.ANY)
 
         # Test delete remote host instances.
-        instances = ["host-192.0.2.1-goldfish-5554-123456-sdk_x86_64-sdk"]
+        instances = ["host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk"]
         delete.DeleteInstanceByNames(cfg, instances, "user", "key")
         mock_delete_remote_host_gf.assert_called_with(
             cfg, instances[0], "user", "key", mock.ANY)
diff --git a/hostcleanup/__init__.py b/hostcleanup/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/hostcleanup/__init__.py
diff --git a/hostcleanup/host_cleanup_runner.py b/hostcleanup/host_cleanup_runner.py
new file mode 100644
index 0000000..22b5c14
--- /dev/null
+++ b/hostcleanup/host_cleanup_runner.py
@@ -0,0 +1,125 @@
+# Copyright 2021 - 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.
+r"""host cleanup runner
+
+A host cleanup sub task runner will cleanup host to a pristine state.
+"""
+
+from __future__ import print_function
+
+import logging
+import os
+import subprocess
+import textwrap
+
+from acloud.internal import constants
+from acloud.internal.lib import utils
+from acloud.setup import base_task_runner
+from acloud.setup import setup_common
+
+logger = logging.getLogger(__name__)
+
+_PARAGRAPH_BREAK = "="
+_PURGE_PACKAGE_CMD = "sudo apt-get purge --assume-yes %s"
+_UNINSTALL_SUCCESS_MSG = "Package(s) [%s] have uninstalled."
+
+
+class BasePurger(base_task_runner.BaseTaskRunner):
+    """Subtask base runner class for hostcleanup."""
+
+    PURGE_MESSAGE_TITLE = ""
+    PURGE_MESSAGE = ""
+
+    cmds = []
+    purge_packages = []
+
+    def ShouldRun(self):
+        """Check if required packages are all uninstalled.
+
+        Returns:
+            Boolean, True if command list not null.
+        """
+        if not utils.IsSupportedPlatform():
+            return False
+
+        if self.cmds:
+            return True
+
+        utils.PrintColorString(
+            "[%s]: don't have to process." % self.PURGE_MESSAGE_TITLE,
+            utils.TextColors.WARNING)
+        return False
+
+    def _Run(self):
+        """Run purge commands."""
+        utils.PrintColorString("Below commands will be run: \n%s" %
+                               "\n".join(self.cmds))
+
+        answer_client = utils.InteractWithQuestion(
+            "\nPress 'y' to continue or anything else to do it myself[y/N]: ",
+            utils.TextColors.WARNING)
+        if answer_client not in constants.USER_ANSWER_YES:
+            return
+
+        for cmd in self.cmds:
+            try:
+                setup_common.CheckCmdOutput(cmd,
+                                            shell=True,
+                                            stderr=subprocess.STDOUT)
+            except subprocess.CalledProcessError as cpe:
+                logger.error("Run command [%s] failed: %s",
+                             cmd, cpe.output)
+
+        utils.PrintColorString((_UNINSTALL_SUCCESS_MSG %
+                                ",".join(self.purge_packages)),
+                               utils.TextColors.OKGREEN)
+
+    def PrintPurgeMessage(self):
+        """Print purge message"""
+        # define the layout of message.
+        console_width = int(os.popen('stty size', 'r').read().split()[1])
+        break_width = int(console_width / 2)
+
+        # start to print purge message.
+        print("\n" + _PARAGRAPH_BREAK * break_width)
+        print(" [%s] " % self.PURGE_MESSAGE_TITLE)
+        print(textwrap.fill(
+            self.PURGE_MESSAGE,
+            break_width - 2,
+            initial_indent=" ",
+            subsequent_indent=" "))
+        print(_PARAGRAPH_BREAK * break_width + "\n")
+
+
+class PackagesUninstaller(BasePurger):
+    """Subtask base runner class for uninstalling packages."""
+
+    PURGE_MESSAGE_TITLE = "Uninstalling packages"
+    PURGE_MESSAGE = ("This will uninstall packages installed previously "
+                     "through \"acloud setup --host-setup\"")
+
+    def __init__(self):
+        """Initialize."""
+        packages = []
+        packages.extend(constants.AVD_REQUIRED_PKGS)
+        packages.extend(constants.BASE_REQUIRED_PKGS)
+        packages.append(constants.CUTTLEFISH_COMMOM_PKG)
+
+        self.purge_packages = [pkg for pkg in packages
+                               if setup_common.PackageInstalled(pkg)]
+
+        self.cmds = [
+            _PURGE_PACKAGE_CMD % pkg for pkg in self.purge_packages]
+
+        self.PrintPurgeMessage()
diff --git a/hostcleanup/hostcleanup.py b/hostcleanup/hostcleanup.py
new file mode 100644
index 0000000..56b03c9
--- /dev/null
+++ b/hostcleanup/hostcleanup.py
@@ -0,0 +1,31 @@
+# Copyright 2021 - 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.
+r"""Hostcleanup entry point.
+
+Hostcleanup will rollback acloud host setup steps.
+"""
+from acloud.hostcleanup import host_cleanup_runner
+
+def Run(args):
+    """Run Hostcleanup.
+
+    Hostcleanup options:
+        -cleanup_pkgs: Uninstall packages.
+
+    Args:
+        args: Namespace object from argparse.parse_args.
+    """
+    # TODO(b/145763747): Need to implement cleanup configs and usergroup.
+    if args.cleanup_pkgs:
+        host_cleanup_runner.PackagesUninstaller().Run()
diff --git a/hostcleanup/hostcleanup_args.py b/hostcleanup/hostcleanup_args.py
new file mode 100644
index 0000000..15c4a92
--- /dev/null
+++ b/hostcleanup/hostcleanup_args.py
@@ -0,0 +1,42 @@
+# Copyright 2021 - 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.
+r"""hostcleanup args.
+
+Defines the hostcleanup arg parser.
+"""
+
+CMD_HOSTCLEANUP = "hostcleanup"
+
+
+def GetHostcleanupArgParser(subparser):
+    """Return the hostcleanup arg parser.
+
+    Args:
+        subparser: argparse.ArgumentParser that is attached to main acloud cmd.
+
+    Returns:
+        argparse.ArgumentParser with hostcleanup options defined.
+    """
+    hostcleanup_parser = subparser.add_parser(CMD_HOSTCLEANUP)
+    hostcleanup_parser.required = False
+    hostcleanup_parser.set_defaults(which=CMD_HOSTCLEANUP)
+    hostcleanup_parser.add_argument(
+        "--packages",
+        action="store_true",
+        dest="cleanup_pkgs",
+        required=False,
+        default=True,
+        help="This feature will purge all packages installed by the acloud.")
+
+    return hostcleanup_parser
diff --git a/hostcleanup/hostcleanup_test.py b/hostcleanup/hostcleanup_test.py
new file mode 100644
index 0000000..d24692d
--- /dev/null
+++ b/hostcleanup/hostcleanup_test.py
@@ -0,0 +1,37 @@
+# Copyright 2021 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests for hostcleanup."""
+import unittest
+
+from unittest import mock
+
+from acloud.internal.lib import driver_test_lib
+from acloud.hostcleanup import hostcleanup
+from acloud.hostcleanup import host_cleanup_runner
+
+
+class HostcleanupTest(driver_test_lib.BaseDriverTest):
+    """Test hostcleanup."""
+
+    # pylint: disable=no-self-use
+    @mock.patch.object(host_cleanup_runner, "PackagesUninstaller")
+    def testRun(self, mock_uninstallpkgs):
+        """test Run."""
+        args = mock.MagicMock()
+        hostcleanup.Run(args)
+        mock_uninstallpkgs.assert_called_once()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/internal/constants.py b/internal/constants.py
index cca3f94..7b24056 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -233,3 +233,10 @@
 
 # The name of download image tool.
 FETCH_CVD = "fetch_cvd"
+
+# For setup and cleanup
+# Packages "devscripts" and "equivs" are required for "mk-build-deps".
+AVD_REQUIRED_PKGS = [
+    "devscripts", "equivs", "libvirt-clients", "libvirt-daemon-system"]
+BASE_REQUIRED_PKGS = ["ssvnc", "lzop", "python3-tk"]
+CUTTLEFISH_COMMOM_PKG = "cuttlefish-common"
diff --git a/internal/lib/goldfish_remote_host_client.py b/internal/lib/goldfish_remote_host_client.py
index dd41fb8..08e2f36 100644
--- a/internal/lib/goldfish_remote_host_client.py
+++ b/internal/lib/goldfish_remote_host_client.py
@@ -22,10 +22,10 @@
 from acloud.public import config
 
 
-_INSTANCE_NAME_FORMAT = ("host-%(ip_addr)s-goldfish-%(console_port)s-"
+_INSTANCE_NAME_FORMAT = ("host-goldfish-%(ip_addr)s-%(console_port)s-"
                          "%(build_id)s-%(build_target)s")
 _INSTANCE_NAME_PATTERN = re.compile(
-    r"host-(?P<ip_addr>[\d.]+)-goldfish-(?P<console_port>\d+)-.+")
+    r"host-goldfish-(?P<ip_addr>[\d.]+)-(?P<console_port>\d+)-.+")
 # Report keys
 _VERSION = "version"
 
diff --git a/internal/lib/goldfish_remote_host_client_test.py b/internal/lib/goldfish_remote_host_client_test.py
index aec2dbc..3b3c72a 100644
--- a/internal/lib/goldfish_remote_host_client_test.py
+++ b/internal/lib/goldfish_remote_host_client_test.py
@@ -29,7 +29,7 @@
     _CONSOLE_PORT = 5554
     _BUILD_INFO = {"build_id": "123456",
                    "build_target": "sdk_phone_x86_64-userdebug"}
-    _INSTANCE_NAME = ("host-192.0.2.1-goldfish-5554-"
+    _INSTANCE_NAME = ("host-goldfish-192.0.2.1-5554-"
                       "123456-sdk_phone_x86_64-userdebug")
     _INVALID_NAME = "host-192.0.2.1-123456-aosp_cf_x86_phone-userdebug"
 
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 8102136..fd094d9 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -136,6 +136,8 @@
 from acloud.restart import restart_args
 from acloud.setup import setup
 from acloud.setup import setup_args
+from acloud.hostcleanup import hostcleanup
+from acloud.hostcleanup import hostcleanup_args
 
 
 LOGGING_FMT = "%(asctime)s |%(levelname)s| %(module)s:%(lineno)s| %(message)s"
@@ -177,6 +179,7 @@
         reconnect_args.CMD_RECONNECT,
         pull_args.CMD_PULL,
         restart_args.CMD_RESTART,
+        hostcleanup_args.CMD_HOSTCLEANUP,
     ])
     parser = argparse.ArgumentParser(
         description=__doc__,
@@ -260,6 +263,9 @@
     # Command "pull"
     subparser_list.append(pull_args.GetPullArgParser(subparsers))
 
+    # Command "hostcleanup"
+    subparser_list.append(hostcleanup_args.GetHostcleanupArgParser(subparsers))
+
     # Add common arguments.
     for subparser in subparser_list:
         acloud_common.AddCommonArguments(subparser)
@@ -476,6 +482,8 @@
         reporter = pull.Run(args)
     elif args.which == setup_args.CMD_SETUP:
         setup.Run(args)
+    elif args.which == hostcleanup_args.CMD_HOSTCLEANUP:
+        hostcleanup.Run(args)
     else:
         error_msg = "Invalid command %s" % args.which
         sys.stderr.write(error_msg)
diff --git a/public/actions/remote_host_gf_device_factory_test.py b/public/actions/remote_host_gf_device_factory_test.py
index 1d848c1..95f3470 100644
--- a/public/actions/remote_host_gf_device_factory_test.py
+++ b/public/actions/remote_host_gf_device_factory_test.py
@@ -38,13 +38,13 @@
         constants.BUILD_TARGET: "sdk_x86_64-sdk",
     }
     _X86_64_INSTANCE_NAME = (
-        "host-192.0.2.1-goldfish-5554-123456-sdk_x86_64-sdk")
+        "host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk")
     _ARM64_BUILD_INFO = {
         constants.BUILD_ID: "123456",
         constants.BUILD_TARGET: "sdk_arm64-sdk",
     }
     _ARM64_INSTANCE_NAME = (
-        "host-192.0.2.1-goldfish-5554-123456-sdk_arm64-sdk")
+        "host-goldfish-192.0.2.1-5554-123456-sdk_arm64-sdk")
     _CFG_ATTRS = {
         "ssh_private_key_path": "cfg_key_path",
         "extra_args_ssh_tunnel": "extra args",
diff --git a/setup/host_setup_runner.py b/setup/host_setup_runner.py
index e272d88..c3cbad0 100644
--- a/setup/host_setup_runner.py
+++ b/setup/host_setup_runner.py
@@ -36,11 +36,6 @@
 
 logger = logging.getLogger(__name__)
 
-# Packages "devscripts" and "equivs" are required for "mk-build-deps".
-_AVD_REQUIRED_PKGS = [
-    "devscripts", "equivs", "libvirt-clients", "libvirt-daemon-system"]
-_BASE_REQUIRED_PKGS = ["ssvnc", "lzop", "python3-tk"]
-_CUTTLEFISH_COMMOM_PKG = "cuttlefish-common"
 _CF_COMMOM_FOLDER = "cf-common"
 _LIST_OF_MODULES = ["kvm_intel", "kvm"]
 _UPDATE_APT_GET_CMD = "sudo apt-get update"
@@ -61,9 +56,6 @@
                         {"mkcert_install_path": _MKCERT_INSTALL_PATH,
                          "mkcert_url": _MKCERT_URL,
                          "mkcert_ver": _MKCERT_VERSION})
-_INSTALL_MKCERT_CMD = [
-    "sudo apt-get install wget libnss3-tools",
-    _MKCERT_DOWNLOAD_CMD]
 
 
 class BasePkgInstaller(base_task_runner.BaseTaskRunner):
@@ -116,7 +108,7 @@
     WELCOME_MESSAGE = ("This step will walk you through the required packages "
                        "installation for running Android cuttlefish devices "
                        "on your host.")
-    PACKAGES = _AVD_REQUIRED_PKGS
+    PACKAGES = constants.AVD_REQUIRED_PKGS
 
 
 class HostBasePkgInstaller(BasePkgInstaller):
@@ -125,7 +117,7 @@
     WELCOME_MESSAGE_TITLE = "Install base packages on the host"
     WELCOME_MESSAGE = ("This step will walk you through the base packages "
                        "installation for your host.")
-    PACKAGES = _BASE_REQUIRED_PKGS
+    PACKAGES = constants.BASE_REQUIRED_PKGS
 
 
 class CuttlefishCommonPkgInstaller(base_task_runner.BaseTaskRunner):
@@ -146,7 +138,7 @@
 
         # Any required package is not installed or not up-to-date will need to
         # run installation task.
-        if not setup_common.PackageInstalled(_CUTTLEFISH_COMMOM_PKG):
+        if not setup_common.PackageInstalled(constants.CUTTLEFISH_COMMOM_PKG):
             return True
         return False
 
@@ -191,16 +183,14 @@
 
     def _Run(self):
         """Install mkcert packages."""
-        cmd = "\n".join(_INSTALL_MKCERT_CMD)
-
         if not utils.GetUserAnswerYes("\nStart to install mkcert :\n%s"
                                       "\nEnter 'y' to continue, otherwise N or "
-                                      "enter to exit: " % cmd):
+                                      "enter to exit: " % _MKCERT_DOWNLOAD_CMD):
             sys.exit(constants.EXIT_BY_USER)
 
         if not os.path.isdir(_MKCERT_INSTALL_PATH):
             os.mkdir(_MKCERT_INSTALL_PATH)
-        setup_common.CheckCmdOutput(cmd, shell=True)
+        setup_common.CheckCmdOutput(_MKCERT_DOWNLOAD_CMD, shell=True)
         utils.SetExecutable(os.path.join(_MKCERT_INSTALL_PATH, "mkcert"))
         utils.CheckOutput(_MKCERT_CAROOT_CMD, shell=True)
         logger.info("Mkcert package is installed at \"%s\" now.",