• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14r"""Reconnect entry point.
15
16Reconnect will:
17 - re-establish ssh tunnels for adb/vnc port forwarding for a remote instance
18 - adb connect to forwarded ssh port for remote instance
19 - restart vnc for remote/local instances
20"""
21
22import logging
23import os
24import re
25
26from acloud import errors
27from acloud.internal import constants
28from acloud.internal.lib import auth
29from acloud.internal.lib import android_compute_client
30from acloud.internal.lib import cvd_runtime_config
31from acloud.internal.lib import utils
32from acloud.internal.lib import ssh as ssh_object
33from acloud.internal.lib.adb_tools import AdbTools
34from acloud.list import list as list_instance
35from acloud.public import config
36from acloud.public import report
37
38
39logger = logging.getLogger(__name__)
40
41_RE_DISPLAY = re.compile(r"([\d]+)x([\d]+)\s.*")
42_VNC_STARTED_PATTERN = "ssvnc vnc://127.0.0.1:%(vnc_port)d"
43_WEBRTC_PORTS_SEARCH = "".join(
44    [utils.PORT_MAPPING % {"local_port":port["local"],
45                           "target_port":port["target"]}
46     for port in utils.WEBRTC_PORTS_MAPPING])
47
48
49def _IsWebrtcEnable(instance, host_user, host_ssh_private_key_path,
50                    extra_args_ssh_tunnel):
51    """Check local/remote instance webRTC is enable.
52
53    Args:
54        instance: Local/Remote Instance object.
55        host_user: String of user login into the instance.
56        host_ssh_private_key_path: String of host key for logging in to the
57                                   host.
58        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
59
60    Returns:
61        Boolean: True if cf_runtime_cfg.enable_webrtc is True.
62    """
63    if instance.islocal:
64        return instance.cf_runtime_cfg.enable_webrtc
65    ssh = ssh_object.Ssh(ip=ssh_object.IP(ip=instance.ip), user=host_user,
66                         ssh_private_key_path=host_ssh_private_key_path,
67                         extra_args_ssh_tunnel=extra_args_ssh_tunnel)
68    remote_cuttlefish_config = os.path.join(constants.REMOTE_LOG_FOLDER,
69                                            constants.CUTTLEFISH_CONFIG_FILE)
70    raw_data = ssh.GetCmdOutput("cat " + remote_cuttlefish_config)
71    try:
72        cf_runtime_cfg = cvd_runtime_config.CvdRuntimeConfig(
73            raw_data=raw_data.strip())
74        return cf_runtime_cfg.enable_webrtc
75    except errors.ConfigError:
76        logger.debug("No cuttlefish config[%s] found!",
77                     remote_cuttlefish_config)
78    return False
79
80
81def _WebrtcPortOccupied():
82    """To decide whether need to release port.
83
84    Remote webrtc instance will create a ssh tunnel which may conflict with
85    local webrtc instance default port. Searching process cmd in the pattern
86    of _WEBRTC_PORTS_SEARCH to decide whether to release port.
87
88    Return:
89        True if need to release port.
90    """
91    process_output = utils.CheckOutput(constants.COMMAND_PS)
92    for line in process_output.splitlines():
93        match = re.search(_WEBRTC_PORTS_SEARCH, line)
94        if match:
95            return True
96    return False
97
98
99def StartVnc(vnc_port, display):
100    """Start vnc connect to AVD.
101
102    Confirm whether there is already a connection before VNC connection.
103    If there is a connection, it will not be connected. If not, connect it.
104    Before reconnecting, clear old disconnect ssvnc viewer.
105
106    Args:
107        vnc_port: Integer of vnc port number.
108        display: String, vnc connection resolution. e.g., 1080x720 (240)
109    """
110    vnc_started_pattern = _VNC_STARTED_PATTERN % {"vnc_port": vnc_port}
111    if not utils.IsCommandRunning(vnc_started_pattern):
112        #clean old disconnect ssvnc viewer.
113        utils.CleanupSSVncviewer(vnc_port)
114
115        match = _RE_DISPLAY.match(display)
116        if match:
117            utils.LaunchVncClient(vnc_port, match.group(1), match.group(2))
118        else:
119            utils.LaunchVncClient(vnc_port)
120
121
122def AddPublicSshRsaToInstance(cfg, user, instance_name):
123    """Add the public rsa key to the instance's metadata.
124
125    When the public key doesn't exist in the metadata, it will add it.
126
127    Args:
128        cfg: An AcloudConfig instance.
129        user: String, the ssh username to access instance.
130        instance_name: String, instance name.
131    """
132    credentials = auth.CreateCredentials(cfg)
133    compute_client = android_compute_client.AndroidComputeClient(
134        cfg, credentials)
135    compute_client.AddSshRsaInstanceMetadata(
136        user,
137        cfg.ssh_public_key_path,
138        instance_name)
139
140
141@utils.TimeExecute(function_description="Reconnect instances")
142def ReconnectInstance(ssh_private_key_path,
143                      instance,
144                      reconnect_report,
145                      extra_args_ssh_tunnel=None,
146                      connect_vnc=True):
147    """Reconnect to the specified instance.
148
149    It will:
150     - re-establish ssh tunnels for adb/vnc port forwarding
151     - re-establish adb connection
152     - restart vnc client
153     - update device information in reconnect_report
154
155    Args:
156        ssh_private_key_path: Path to the private key file.
157                              e.g. ~/.ssh/acloud_rsa
158        instance: list.Instance() object.
159        reconnect_report: Report object.
160        extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
161        connect_vnc: Boolean, True will launch vnc.
162
163    Raises:
164        errors.UnknownAvdType: Unable to reconnect to instance of unknown avd
165                               type.
166    """
167    if instance.avd_type not in utils.AVD_PORT_DICT:
168        raise errors.UnknownAvdType("Unable to reconnect to instance (%s) of "
169                                    "unknown avd type: %s" %
170                                    (instance.name, instance.avd_type))
171
172    adb_cmd = AdbTools(instance.adb_port)
173    vnc_port = instance.vnc_port
174    adb_port = instance.adb_port
175    webrtc_port = instance.webrtc_port
176    # ssh tunnel is up but device is disconnected on adb
177    if instance.ssh_tunnel_is_connected and not adb_cmd.IsAdbConnectionAlive():
178        adb_cmd.DisconnectAdb()
179        adb_cmd.ConnectAdb()
180    # ssh tunnel is down and it's a remote instance
181    elif not instance.ssh_tunnel_is_connected and not instance.islocal:
182        adb_cmd.DisconnectAdb()
183        forwarded_ports = utils.AutoConnect(
184            ip_addr=instance.ip,
185            rsa_key_file=ssh_private_key_path,
186            target_vnc_port=utils.AVD_PORT_DICT[instance.avd_type].vnc_port,
187            target_adb_port=utils.AVD_PORT_DICT[instance.avd_type].adb_port,
188            ssh_user=constants.GCE_USER,
189            extra_args_ssh_tunnel=extra_args_ssh_tunnel)
190        vnc_port = forwarded_ports.vnc_port
191        adb_port = forwarded_ports.adb_port
192    if _IsWebrtcEnable(instance,
193                       constants.GCE_USER,
194                       ssh_private_key_path,
195                       extra_args_ssh_tunnel):
196        if instance.islocal:
197            if _WebrtcPortOccupied():
198                raise errors.PortOccupied("\nReconnect to a local webrtc instance "
199                                          "is not work because remote webrtc "
200                                          "instance has established ssh tunnel "
201                                          "which occupied local webrtc instance "
202                                          "port. If you want to connect to a "
203                                          "local-instance of webrtc. please run "
204                                          "'acloud create --local-instance "
205                                          "--autoconnect webrtc' directly.")
206        else:
207            utils.EstablishWebRTCSshTunnel(
208                ip_addr=instance.ip,
209                rsa_key_file=ssh_private_key_path,
210                ssh_user=constants.GCE_USER,
211                extra_args_ssh_tunnel=extra_args_ssh_tunnel)
212        utils.LaunchBrowser(constants.WEBRTC_LOCAL_HOST,
213                            webrtc_port)
214    elif(vnc_port and connect_vnc):
215        StartVnc(vnc_port, instance.display)
216
217    device_dict = {
218        constants.IP: instance.ip,
219        constants.INSTANCE_NAME: instance.name,
220        constants.VNC_PORT: vnc_port,
221        constants.ADB_PORT: adb_port
222    }
223    if adb_port and not instance.islocal:
224        device_dict[constants.DEVICE_SERIAL] = (
225            constants.REMOTE_INSTANCE_ADB_SERIAL % adb_port)
226
227    if vnc_port and adb_port:
228        reconnect_report.AddData(key="devices", value=device_dict)
229    else:
230        # We use 'ps aux' to grep adb/vnc fowarding port from ssh tunnel
231        # command. Therefore we report failure here if no vnc_port and
232        # adb_port found.
233        reconnect_report.AddData(key="device_failing_reconnect", value=device_dict)
234        reconnect_report.AddError(instance.name)
235
236
237def Run(args):
238    """Run reconnect.
239
240    Args:
241        args: Namespace object from argparse.parse_args.
242    """
243    cfg = config.GetAcloudConfig(args)
244    instances_to_reconnect = []
245    if args.instance_names is not None:
246        # user input instance name to get instance object.
247        instances_to_reconnect = list_instance.GetInstancesFromInstanceNames(
248            cfg, args.instance_names)
249    if not instances_to_reconnect:
250        instances_to_reconnect = list_instance.ChooseInstances(cfg, args.all)
251
252    reconnect_report = report.Report(command="reconnect")
253    for instance in instances_to_reconnect:
254        if instance.avd_type not in utils.AVD_PORT_DICT:
255            utils.PrintColorString("Skipping reconnect of instance %s due to "
256                                   "unknown avd type (%s)." %
257                                   (instance.name, instance.avd_type),
258                                   utils.TextColors.WARNING)
259            continue
260        if not instance.islocal:
261            AddPublicSshRsaToInstance(cfg, constants.GCE_USER, instance.name)
262        ReconnectInstance(cfg.ssh_private_key_path,
263                          instance,
264                          reconnect_report,
265                          cfg.extra_args_ssh_tunnel,
266                          connect_vnc=(args.autoconnect is True))
267
268    utils.PrintDeviceSummary(reconnect_report)
269