Secondary migration from google3 into AOSP.

The 2nd revival of AOSP acloud is upon us!

The cl revision the acloud g3 code was pulled from is 195930083.

Things I did:
- Added AOSP copyright for new files and updated g3 imports to be relative.
- Merged in changes into existing files (easier to see changes here and
  for future cls).
- Scrubbed default.config of project and build info.
- Merge acloud.py (from g3) into acloud_main.py (entry point for AOSP
  acloud).
- Regenerated internal_config_pb2.py and user_config_pb2.py.
- Removed add_mock from gcomputer_client_test and added TODO in file
  where to replace it and updated parameterized to import from
  absl.testing.
- Updated references to gce_x86 to aosp_cf_x86_phone and updated branch
  references to 'aosp-master'.

Thing to note:
- New files fail pylint (in order to make it easy to check history on new files,
  formatting will be done using yapf in another cl).
- pip install acloud.zip seg faults so investigation and fix for that
  will happen in another cl.
- User needs to 'pip install absl-py' for parameterized lib in unittests.

Bug: 79684654
Test: ./run_tests.sh
Change-Id: I060641227d7c9162a45557e732686f22b83895e9
diff --git a/public/device_driver.py b/public/device_driver.py
index ae7e3c8..810a3a8 100755
--- a/public/device_driver.py
+++ b/public/device_driver.py
@@ -19,7 +19,7 @@
 This module provides public device driver APIs that can be called
 as a Python library.
 
-TODO(fdeng): The following APIs have not been implemented
+TODO: The following APIs have not been implemented
   - RebootAVD(ip):
   - RegisterSshPubKey(username, key):
   - UnregisterSshPubKey(username, key):
@@ -30,6 +30,8 @@
 import datetime
 import logging
 import os
+import socket
+import subprocess
 
 import dateutil.parser
 import dateutil.tz
@@ -37,6 +39,7 @@
 from acloud.public import avd
 from acloud.public import errors
 from acloud.public import report
+from acloud.public.actions import common_operations
 from acloud.internal import constants
 from acloud.internal.lib import auth
 from acloud.internal.lib import android_build_client
@@ -52,6 +55,12 @@
 
 MAX_BATCH_CLEANUP_COUNT = 100
 
+SSH_TUNNEL_CMD = ("/usr/bin/ssh -i %(rsa_key_file)s -o "
+                  "UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -L "
+                  "%(vnc_port)d:127.0.0.1:6444 -L %(adb_port)d:127.0.0.1:5555 "
+                  "-N -f -l root %(ip_addr)s")
+ADB_CONNECT_CMD = "adb connect 127.0.0.1:%(adb_port)d"
+
 
 class AndroidVirtualDevicePool(object):
     """A class that manages a pool of devices."""
@@ -73,7 +82,7 @@
         using launch control api. And then create a Gce image.
 
         Args:
-            build_target: Target name, e.g. "gce_x86-userdebug"
+            build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
             build_id: Build id, a string, e.g. "2263051", "P2804227"
 
         Returns:
@@ -175,7 +184,7 @@
 
         Args:
             num: Number of devices to create.
-            build_target: Target name, e.g. "gce_x86-userdebug"
+            build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
             build_id: Build id, a string, e.g. "2263051", "P2804227"
             gce_image: string, if given, will use this image
                        instead of creating a new one.
@@ -217,8 +226,10 @@
                     self._compute_client.CreateDisk(extra_disk_name,
                                                     precreated_data_image,
                                                     extra_data_disk_size_gb)
-                self._compute_client.CreateInstance(instance, image_name,
-                                                    extra_disk_name)
+                self._compute_client.CreateInstance(
+                    instance=instance,
+                    image_name=image_name,
+                    extra_disk_name=extra_disk_name)
                 ip = self._compute_client.GetInstanceIP(instance)
                 self.devices.append(avd.AndroidVirtualDevice(
                     ip=ip, instance_name=instance))
@@ -245,13 +256,13 @@
         Returns:
             A dictionary that contains all the failures.
             The key is the name of the instance that fails to boot,
-            the value is an errors.DeviceBootTimeoutError object.
+            the value is an errors.DeviceBoottError object.
         """
         failures = {}
         for device in self._devices:
             try:
                 self._compute_client.WaitForBoot(device.instance_name)
-            except errors.DeviceBootTimeoutError as e:
+            except errors.DeviceBootError as e:
                 failures[device.instance_name] = e
         return failures
 
@@ -322,34 +333,46 @@
         utils.MakeTarFile(src_dict, output_file)
 
 
-def _CreateSshKeyPairIfNecessary(cfg):
-    """Create ssh key pair if necessary.
+def _PickFreePort():
+    """Helper to pick a free port.
+
+    Returns:
+        A free port number.
+    """
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.bind(("", 0))
+    port = s.getsockname()[1]
+    s.close()
+    return port
+
+
+def _AutoConnect(device_dict, rsa_key_file):
+    """Autoconnect to an AVD instance.
 
     Args:
-        cfg: An Acloudconfig instance.
-
-    Raises:
-        error.DriverError: If it falls into an unexpected condition.
+        device_dict: device_dict representing the device we are autoconnecting
+                     to. This dict will be updated with the adb & vnc tunnel
+                     ports.
+        rsa_key_file: Private key file to use when creating the ssh tunnels.
     """
-    if not cfg.ssh_public_key_path:
-        logger.warning("ssh_public_key_path is not specified in acloud config. "
-                       "Project-wide public key will "
-                       "be used when creating AVD instances. "
-                       "Please ensure you have the correct private half of "
-                       "a project-wide public key if you want to ssh into the "
-                       "instances after creation.")
-    elif cfg.ssh_public_key_path and not cfg.ssh_private_key_path:
-        logger.warning("Only ssh_public_key_path is specified in acloud config,"
-                       " but ssh_private_key_path is missing. "
-                       "Please ensure you have the correct private half "
-                       "if you want to ssh into the instances after creation.")
-    elif cfg.ssh_public_key_path and cfg.ssh_private_key_path:
-        utils.CreateSshKeyPairIfNotExist(
-                cfg.ssh_private_key_path, cfg.ssh_public_key_path)
-    else:
-        # Should never reach here.
-        raise errors.DriverError(
-                "Unexpected error in _CreateSshKeyPairIfNecessary")
+    try:
+        adb_port = _PickFreePort()
+        vnc_port = _PickFreePort()
+        tunnel_cmd = SSH_TUNNEL_CMD % {"rsa_key_file": rsa_key_file,
+                                       "vnc_port": vnc_port,
+                                       "adb_port": adb_port,
+                                       "ip_addr": device_dict["ip"]}
+        logging.debug("Running '%s'", tunnel_cmd)
+        subprocess.check_call([tunnel_cmd], shell=True)
+        adb_connect_cmd = ADB_CONNECT_CMD % {"adb_port": adb_port}
+        logging.debug("Running '%s'", adb_connect_cmd)
+        device_dict["adb_tunnel_port"] = adb_port
+        device_dict["vnc_tunnel_port"] = vnc_port
+        subprocess.check_call([adb_connect_cmd], shell=True)
+    except subprocess.CalledProcessError:
+        logging.error("Failed to autoconnect %s through local adb tunnel port"
+                      " %d and vnc tunnel port %d", device_dict["ip"], adb_port,
+                      vnc_port)
 
 
 def CreateAndroidVirtualDevices(cfg,
@@ -360,12 +383,13 @@
                                 local_disk_image=None,
                                 cleanup=True,
                                 serial_log_file=None,
-                                logcat_file=None):
+                                logcat_file=None,
+                                autoconnect=False):
     """Creates one or multiple android devices.
 
     Args:
         cfg: An AcloudConfig instance.
-        build_target: Target name, e.g. "gce_x86-userdebug"
+        build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
         build_id: Build id, a string, e.g. "2263051", "P2804227"
         num: Number of devices to create.
         gce_image: string, if given, will use this gce image
@@ -378,6 +402,7 @@
         serial_log_file: A path to a file where serial output should
                          be saved to.
         logcat_file: A path to a file where logcat logs should be saved.
+        autoconnect: Create ssh tunnel(s) and adb connect after device creation.
 
     Returns:
         A Report instance.
@@ -387,7 +412,7 @@
     compute_client = android_compute_client.AndroidComputeClient(cfg,
                                                                  credentials)
     try:
-        _CreateSshKeyPairIfNecessary(cfg)
+        common_operations.CreateSshKeyPairIfNecessary(cfg)
         device_pool = AndroidVirtualDevicePool(cfg)
         device_pool.CreateDevices(
             num,
@@ -404,6 +429,8 @@
         for device in device_pool.devices:
             device_dict = {"ip": device.ip,
                            "instance_name": device.instance_name}
+            if autoconnect:
+                _AutoConnect(device_dict, cfg.ssh_private_key_path)
             if device.instance_name in failures:
                 r.AddData(key="devices_failing_boot", value=device_dict)
                 r.AddError(str(failures[device.instance_name]))