New command entry for acloud pull function

- Add new command for '$acloud pull'.
- Add args "--instance-name" to specify which instance to pull.

Bug: 120613398
Bug: 112274919
Test: acloud-dev pull
      acloud-dev pull --instance-name ins_name

Change-Id: I45e43a2a2a63dec3bae475fff57fde398e4cc915
diff --git a/Android.bp b/Android.bp
index 4d36801..3ff0421 100644
--- a/Android.bp
+++ b/Android.bp
@@ -46,6 +46,7 @@
         "acloud_reconnect",
         "acloud_internal",
         "acloud_list",
+        "acloud_pull",
         "acloud_metrics",
         "acloud_proto",
         "acloud_public",
@@ -79,9 +80,10 @@
     libs: [
         "acloud_create",
         "acloud_delete",
-	"acloud_reconnect",
+        "acloud_reconnect",
         "acloud_internal",
         "acloud_list",
+        "acloud_pull",
         "acloud_proto",
         "acloud_public",
         "acloud_setup",
@@ -177,6 +179,14 @@
 }
 
 python_library_host{
+    name: "acloud_pull",
+    defaults: ["acloud_default"],
+    srcs: [
+         "pull/*.py",
+    ],
+}
+
+python_library_host{
     name: "acloud_metrics",
     defaults: ["acloud_default"],
     srcs: [
diff --git a/errors.py b/errors.py
index 2b47cfa..79d0e90 100644
--- a/errors.py
+++ b/errors.py
@@ -220,7 +220,8 @@
 
 
 class UnknownAvdType(Exception):
-    """Unknow AVD type."""
+    """Unknown AVD type."""
+
 
 class UnknownType(Exception):
-    """Unknow type."""
+    """Unknown type."""
diff --git a/list/list.py b/list/list.py
index 9367efb..4034171 100644
--- a/list/list.py
+++ b/list/list.py
@@ -151,6 +151,34 @@
     return instances_list
 
 
+def ChooseOneRemoteInstance(cfg):
+    """Get one remote cuttlefish instance.
+
+    Retrieve all remote cuttlefish instances and if there is more than 1 instance
+    found, ask user which instance they'd like.
+
+    Args:
+        cfg: AcloudConfig object.
+
+    Raises:
+        errors.NoInstancesFound: No cuttlefish remote instance found.
+
+    Returns:
+        list.Instance() object.
+    """
+    instances_list = GetCFRemoteInstances(cfg)
+    if not instances_list:
+        raise errors.NoInstancesFound(
+            "Can't find any cuttlefish remote instances, please try "
+            "'$acloud create' to create instances")
+    if len(instances_list) > 1:
+        print("Multiple instances detected, choose any one to proceed:")
+        instances = utils.GetAnswerFromList(instances_list,
+                                            enable_choose_all=False)
+        return instances[0]
+
+    return instances_list[0]
+
 def GetInstancesFromInstanceNames(cfg, instance_names):
     """Get instances from instance names.
 
@@ -213,6 +241,19 @@
     raise errors.NoInstancesFound(hint_message)
 
 
+def GetCFRemoteInstances(cfg):
+    """Look for cuttlefish remote instances.
+
+    Args:
+        cfg: AcloudConfig object.
+
+    Returns:
+        instance_list: List of instance names.
+    """
+    instances = GetRemoteInstances(cfg)
+    return [ins for ins in instances if ins.avd_type == constants.TYPE_CF]
+
+
 def Run(args):
     """Run list.
 
diff --git a/list/list_test.py b/list/list_test.py
index d42fa37..dc328ee 100644
--- a/list/list_test.py
+++ b/list/list_test.py
@@ -18,6 +18,7 @@
 
 from acloud import errors
 from acloud.internal.lib import driver_test_lib
+from acloud.internal.lib import utils
 from acloud.list import list as list_instance
 
 
@@ -61,6 +62,29 @@
             cfg=cfg,
             instance_names=instance_names)
 
+    def testChooseOneRemoteInstance(self):
+        """test choose one remote instance from instance names."""
+        cfg = mock.MagicMock()
+
+        # Test only one instance case
+        instance_names = ["cf_instance1"]
+        self.Patch(list_instance, "GetCFRemoteInstances", return_value=instance_names)
+        expected_instance = "cf_instance1"
+        self.assertEqual(list_instance.ChooseOneRemoteInstance(cfg), expected_instance)
+
+        # Test no instance case
+        self.Patch(list_instance, "GetCFRemoteInstances", return_value=[])
+        with self.assertRaises(errors.NoInstancesFound):
+            list_instance.ChooseOneRemoteInstance(cfg)
+
+        # Test two instances case.
+        instance_names = ["cf_instance1", "cf_instance2"]
+        choose_instance = ["cf_instance2"]
+        self.Patch(list_instance, "GetCFRemoteInstances", return_value=instance_names)
+        self.Patch(utils, "GetAnswerFromList", return_value=choose_instance)
+        expected_instance = "cf_instance2"
+        self.assertEqual(list_instance.ChooseOneRemoteInstance(cfg), expected_instance)
+
     # pylint: disable=attribute-defined-outside-init
     def testGetInstanceFromAdbPort(self):
         """test GetInstanceFromAdbPort."""
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 28bdf30..fb69e5e 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -113,6 +113,8 @@
 from acloud.public import device_driver
 from acloud.public.actions import create_cuttlefish_action
 from acloud.public.actions import create_goldfish_action
+from acloud.pull import pull
+from acloud.pull import pull_args
 from acloud.setup import setup
 from acloud.setup import setup_args
 
@@ -142,6 +144,7 @@
         list_args.CMD_LIST,
         delete_args.CMD_DELETE,
         reconnect_args.CMD_RECONNECT,
+        pull_args.CMD_PULL,
     ])
     parser = argparse.ArgumentParser(
         description=__doc__,
@@ -231,9 +234,12 @@
     # Command "list"
     subparser_list.append(list_args.GetListArgParser(subparsers))
 
-    # Command "Reconnect"
+    # Command "reconnect"
     subparser_list.append(reconnect_args.GetReconnectArgParser(subparsers))
 
+    # Command "pull"
+    subparser_list.append(pull_args.GetPullArgParser(subparsers))
+
     # Add common arguments.
     for subparser in subparser_list:
         acloud_common.AddCommonArguments(subparser)
@@ -404,6 +410,8 @@
         list_instances.Run(args)
     elif args.which == reconnect_args.CMD_RECONNECT:
         reconnect.Run(args)
+    elif args.which == pull_args.CMD_PULL:
+        report = pull.Run(args)
     elif args.which == setup_args.CMD_SETUP:
         setup.Run(args)
     else:
diff --git a/pull/__init__.py b/pull/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pull/__init__.py
diff --git a/pull/pull.py b/pull/pull.py
new file mode 100644
index 0000000..dc548ae
--- /dev/null
+++ b/pull/pull.py
@@ -0,0 +1,61 @@
+# Copyright 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.
+r"""Pull entry point.
+
+This command will pull the log files from a remote instance for AVD troubleshooting.
+"""
+
+from __future__ import print_function
+import logging
+
+from acloud.list import list as list_instances
+from acloud.public import config
+from acloud.public import report
+
+
+logger = logging.getLogger(__name__)
+
+
+def PullFileFromInstance(instance):
+    """Pull file from remote CF instance.
+
+    Args:
+        instance: list.Instance() object.
+
+    Returns:
+        A Report instance.
+    """
+    # TODO(120613398): rewrite this function to pull file from the remote instance.
+    print("We will pull file from the instance: %s." % instance.name)
+    return report.Report(command="pull")
+
+
+def Run(args):
+    """Run pull.
+
+    After pull command executed, tool will return one Report instance.
+    If there is no instance to pull, just return empty Report.
+
+    Args:
+        args: Namespace object from argparse.parse_args.
+
+    Returns:
+        A Report instance.
+    """
+    cfg = config.GetAcloudConfig(args)
+    if args.instance_name:
+        instance = list_instances.GetInstancesFromInstanceNames(
+            cfg, [args.instance_name])
+        return PullFileFromInstance(instance[0])
+    return PullFileFromInstance(list_instances.ChooseOneRemoteInstance(cfg))
diff --git a/pull/pull_args.py b/pull/pull_args.py
new file mode 100644
index 0000000..1772eb4
--- /dev/null
+++ b/pull/pull_args.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#
+# Copyright 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.
+r"""Pull args.
+
+Defines the pull arg parser that holds pull specific args.
+"""
+import argparse
+
+
+CMD_PULL = "pull"
+
+
+def GetPullArgParser(subparser):
+    """Return the pull arg parser.
+
+    Args:
+       subparser: argparse.ArgumentParser that is attached to main acloud cmd.
+
+    Returns:
+        argparse.ArgumentParser with pull options defined.
+    """
+    pull_parser = subparser.add_parser(CMD_PULL)
+    pull_parser.required = False
+    pull_parser.set_defaults(which=CMD_PULL)
+    pull_group = pull_parser.add_mutually_exclusive_group()
+    pull_group.add_argument(
+        "--instance-name",
+        dest="instance_name",
+        type=str,
+        required=False,
+        help="The name of the remote instance that need to pull log files.")
+
+    # TODO(b/118439885): Old arg formats to support transition, delete when
+    # transistion is done.
+    pull_group.add_argument(
+        "--instance_name",
+        dest="instance_name",
+        type=str,
+        required=False,
+        help=argparse.SUPPRESS)
+
+    return pull_parser