Merge "acloud: [WebRTC] fix reconnect local-instance failed." am: ed75ab0f03

Original change: https://android-review.googlesource.com/c/platform/tools/acloud/+/1262155

Change-Id: I408edc47f0439139d7cd5d8e1adea258c2d29175
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 088edeb..70bd1df 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -116,7 +116,7 @@
         try:
             self.CheckLaunchCVD(
                 cmd, host_bins_path, avd_spec.local_instance_id,
-                local_image_path, no_prompts,
+                local_image_path, avd_spec.connect_webrtc, no_prompts,
                 avd_spec.boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT)
         except errors.LaunchCVDFail as launch_error:
             result_report.SetStatus(report.Status.BOOT_FAIL)
@@ -223,7 +223,7 @@
         return launch_cmd
 
     def CheckLaunchCVD(self, cmd, host_bins_path, local_instance_id,
-                       local_image_path, no_prompts=False,
+                       local_image_path, connect_webrtc=False, no_prompts=False,
                        timeout_secs=constants.DEFAULT_CF_BOOT_TIMEOUT):
         """Execute launch_cvd command and wait for boot up completed.
 
@@ -236,6 +236,7 @@
             host_bins_path: String of host package directory.
             local_instance_id: Integer of instance id.
             local_image_path: String of local image directory.
+            connect_webrtc: Boolean, whether to auto connect webrtc to device.
             no_prompts: Boolean, True to skip all prompts.
             timeout_secs: Integer, the number of seconds to wait for the AVD to boot up.
         """
@@ -262,7 +263,8 @@
                     "instance." % (local_image_path, occupied_ins_id),
                     utils.TextColors.FAIL)
                 sys.exit(constants.EXIT_BY_USER)
-
+        if connect_webrtc:
+            utils.ReleasePort(constants.WEBRTC_LOCAL_PORT)
         self._LaunchCvd(cmd, local_instance_id, timeout=timeout_secs)
 
     @staticmethod
diff --git a/reconnect/reconnect.py b/reconnect/reconnect.py
index fa43af8..6ced430 100644
--- a/reconnect/reconnect.py
+++ b/reconnect/reconnect.py
@@ -40,14 +40,18 @@
 
 _RE_DISPLAY = re.compile(r"([\d]+)x([\d]+)\s.*")
 _VNC_STARTED_PATTERN = "ssvnc vnc://127.0.0.1:%(vnc_port)d"
+_WEBRTC_PORTS_SEARCH = "".join(
+    [utils.PORT_MAPPING % {"local_port":port["local"],
+                           "target_port":port["target"]}
+     for port in utils.WEBRTC_PORTS_MAPPING])
 
 
-def IsWebrtcEnable(ip_addr, host_user, host_ssh_private_key_path,
-                   extra_args_ssh_tunnel):
-    """Check remote instance webRTC is enable.
+def _IsWebrtcEnable(instance, host_user, host_ssh_private_key_path,
+                    extra_args_ssh_tunnel):
+    """Check local/remote instance webRTC is enable.
 
     Args:
-        ip_addr: String, use to connect to webrtc AVD on the instance.
+        instance: Local/Remote Instance object.
         host_user: String of user login into the instance.
         host_ssh_private_key_path: String of host key for logging in to the
                                    host.
@@ -56,7 +60,9 @@
     Returns:
         Boolean: True if cf_runtime_cfg.enable_webrtc is True.
     """
-    ssh = ssh_object.Ssh(ip=ssh_object.IP(ip=ip_addr), user=host_user,
+    if instance.islocal:
+        return instance.cf_runtime_cfg.enable_webrtc
+    ssh = ssh_object.Ssh(ip=ssh_object.IP(ip=instance.ip), user=host_user,
                          ssh_private_key_path=host_ssh_private_key_path,
                          extra_args_ssh_tunnel=extra_args_ssh_tunnel)
     remote_cuttlefish_config = os.path.join(constants.REMOTE_LOG_FOLDER,
@@ -72,6 +78,24 @@
     return False
 
 
+def _WebrtcPortOccupied():
+    """To decide whether need to release port.
+
+    Remote webrtc instance will create a ssh tunnel which may conflict with
+    local webrtc instance default port. Searching process cmd in the pattern
+    of _WEBRTC_PORTS_SEARCH to decide whether to release port.
+
+    Return:
+        True if need to release port.
+    """
+    process_output = utils.CheckOutput(constants.COMMAND_PS)
+    for line in process_output.splitlines():
+        match = re.search(_WEBRTC_PORTS_SEARCH, line)
+        if match:
+            return True
+    return False
+
+
 def StartVnc(vnc_port, display):
     """Start vnc connect to AVD.
 
@@ -164,16 +188,26 @@
             extra_args_ssh_tunnel=extra_args_ssh_tunnel)
         vnc_port = forwarded_ports.vnc_port
         adb_port = forwarded_ports.adb_port
-
-    if IsWebrtcEnable(instance.ip,
-                      constants.GCE_USER,
-                      ssh_private_key_path,
-                      extra_args_ssh_tunnel):
-        utils.EstablishWebRTCSshTunnel(
-            ip_addr=instance.ip,
-            rsa_key_file=ssh_private_key_path,
-            ssh_user=constants.GCE_USER,
-            extra_args_ssh_tunnel=extra_args_ssh_tunnel)
+    if _IsWebrtcEnable(instance,
+                       constants.GCE_USER,
+                       ssh_private_key_path,
+                       extra_args_ssh_tunnel):
+        if instance.islocal:
+            if _WebrtcPortOccupied():
+                raise errors.PortOccupied("\nReconnect to a local webrtc instance "
+                                          "is not work because remote webrtc "
+                                          "instance has established ssh tunnel "
+                                          "which occupied local webrtc instance "
+                                          "port. If you want to connect to a "
+                                          "local-instance of webrtc. please run "
+                                          "'acloud create --local-instance "
+                                          "--autoconnect webrtc' directly.")
+        else:
+            utils.EstablishWebRTCSshTunnel(
+                ip_addr=instance.ip,
+                rsa_key_file=ssh_private_key_path,
+                ssh_user=constants.GCE_USER,
+                extra_args_ssh_tunnel=extra_args_ssh_tunnel)
         utils.LaunchBrowser(constants.WEBRTC_LOCAL_HOST,
                             constants.WEBRTC_LOCAL_PORT)
     elif(vnc_port and connect_vnc):
diff --git a/reconnect/reconnect_test.py b/reconnect/reconnect_test.py
index b33ca9b..ccce53e 100644
--- a/reconnect/reconnect_test.py
+++ b/reconnect/reconnect_test.py
@@ -51,7 +51,7 @@
         self.Patch(AdbTools, "IsAdbConnected", return_value=False)
         self.Patch(AdbTools, "IsAdbConnectionAlive", return_value=False)
         self.Patch(utils, "IsCommandRunning", return_value=False)
-        self.Patch(reconnect, "IsWebrtcEnable", return_value=False)
+        self.Patch(reconnect, "_IsWebrtcEnable", return_value=False)
         fake_device_dict = {
             constants.IP: "1.1.1.1",
             constants.INSTANCE_NAME: "fake_name",
@@ -165,7 +165,7 @@
         self.Patch(AdbTools, "IsAdbConnected", return_value=False)
         self.Patch(AdbTools, "IsAdbConnectionAlive", return_value=False)
         self.Patch(utils, "IsCommandRunning", return_value=False)
-        self.Patch(reconnect, "IsWebrtcEnable", return_value=True)
+        self.Patch(reconnect, "_IsWebrtcEnable", return_value=True)
 
         # test ssh tunnel not reconnect to the remote instance.
         instance_object.vnc_port = 6666
@@ -192,7 +192,7 @@
         instance_object.ssh_tunnel_is_connected = False
         self.Patch(utils, "AutoConnect")
         self.Patch(reconnect, "StartVnc")
-        self.Patch(reconnect, "IsWebrtcEnable", return_value=False)
+        self.Patch(reconnect, "_IsWebrtcEnable", return_value=False)
         #test reconnect remote instance when avd_type as gce.
         instance_object.avd_type = "gce"
         reconnect.ReconnectInstance(ssh_private_key_path, instance_object, fake_report)