You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
10 KiB
269 lines
10 KiB
# Copyright 2018 - 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"""Reconnect entry point.
|
|
|
|
Reconnect will:
|
|
- re-establish ssh tunnels for adb/vnc port forwarding for a remote instance
|
|
- adb connect to forwarded ssh port for remote instance
|
|
- restart vnc for remote/local instances
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import re
|
|
|
|
from acloud import errors
|
|
from acloud.internal import constants
|
|
from acloud.internal.lib import auth
|
|
from acloud.internal.lib import android_compute_client
|
|
from acloud.internal.lib import cvd_runtime_config
|
|
from acloud.internal.lib import utils
|
|
from acloud.internal.lib import ssh as ssh_object
|
|
from acloud.internal.lib.adb_tools import AdbTools
|
|
from acloud.list import list as list_instance
|
|
from acloud.public import config
|
|
from acloud.public import report
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_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(instance, host_user, host_ssh_private_key_path,
|
|
extra_args_ssh_tunnel):
|
|
"""Check local/remote instance webRTC is enable.
|
|
|
|
Args:
|
|
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.
|
|
extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
|
|
|
|
Returns:
|
|
Boolean: True if cf_runtime_cfg.enable_webrtc is True.
|
|
"""
|
|
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,
|
|
constants.CUTTLEFISH_CONFIG_FILE)
|
|
raw_data = ssh.GetCmdOutput("cat " + remote_cuttlefish_config)
|
|
try:
|
|
cf_runtime_cfg = cvd_runtime_config.CvdRuntimeConfig(
|
|
raw_data=raw_data.strip())
|
|
return cf_runtime_cfg.enable_webrtc
|
|
except errors.ConfigError:
|
|
logger.debug("No cuttlefish config[%s] found!",
|
|
remote_cuttlefish_config)
|
|
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.
|
|
|
|
Confirm whether there is already a connection before VNC connection.
|
|
If there is a connection, it will not be connected. If not, connect it.
|
|
Before reconnecting, clear old disconnect ssvnc viewer.
|
|
|
|
Args:
|
|
vnc_port: Integer of vnc port number.
|
|
display: String, vnc connection resolution. e.g., 1080x720 (240)
|
|
"""
|
|
vnc_started_pattern = _VNC_STARTED_PATTERN % {"vnc_port": vnc_port}
|
|
if not utils.IsCommandRunning(vnc_started_pattern):
|
|
#clean old disconnect ssvnc viewer.
|
|
utils.CleanupSSVncviewer(vnc_port)
|
|
|
|
match = _RE_DISPLAY.match(display)
|
|
if match:
|
|
utils.LaunchVncClient(vnc_port, match.group(1), match.group(2))
|
|
else:
|
|
utils.LaunchVncClient(vnc_port)
|
|
|
|
|
|
def AddPublicSshRsaToInstance(cfg, user, instance_name):
|
|
"""Add the public rsa key to the instance's metadata.
|
|
|
|
When the public key doesn't exist in the metadata, it will add it.
|
|
|
|
Args:
|
|
cfg: An AcloudConfig instance.
|
|
user: String, the ssh username to access instance.
|
|
instance_name: String, instance name.
|
|
"""
|
|
credentials = auth.CreateCredentials(cfg)
|
|
compute_client = android_compute_client.AndroidComputeClient(
|
|
cfg, credentials)
|
|
compute_client.AddSshRsaInstanceMetadata(
|
|
user,
|
|
cfg.ssh_public_key_path,
|
|
instance_name)
|
|
|
|
|
|
@utils.TimeExecute(function_description="Reconnect instances")
|
|
def ReconnectInstance(ssh_private_key_path,
|
|
instance,
|
|
reconnect_report,
|
|
extra_args_ssh_tunnel=None,
|
|
connect_vnc=True):
|
|
"""Reconnect to the specified instance.
|
|
|
|
It will:
|
|
- re-establish ssh tunnels for adb/vnc port forwarding
|
|
- re-establish adb connection
|
|
- restart vnc client
|
|
- update device information in reconnect_report
|
|
|
|
Args:
|
|
ssh_private_key_path: Path to the private key file.
|
|
e.g. ~/.ssh/acloud_rsa
|
|
instance: list.Instance() object.
|
|
reconnect_report: Report object.
|
|
extra_args_ssh_tunnel: String, extra args for ssh tunnel connection.
|
|
connect_vnc: Boolean, True will launch vnc.
|
|
|
|
Raises:
|
|
errors.UnknownAvdType: Unable to reconnect to instance of unknown avd
|
|
type.
|
|
"""
|
|
if instance.avd_type not in utils.AVD_PORT_DICT:
|
|
raise errors.UnknownAvdType("Unable to reconnect to instance (%s) of "
|
|
"unknown avd type: %s" %
|
|
(instance.name, instance.avd_type))
|
|
|
|
adb_cmd = AdbTools(instance.adb_port)
|
|
vnc_port = instance.vnc_port
|
|
adb_port = instance.adb_port
|
|
webrtc_port = instance.webrtc_port
|
|
# ssh tunnel is up but device is disconnected on adb
|
|
if instance.ssh_tunnel_is_connected and not adb_cmd.IsAdbConnectionAlive():
|
|
adb_cmd.DisconnectAdb()
|
|
adb_cmd.ConnectAdb()
|
|
# ssh tunnel is down and it's a remote instance
|
|
elif not instance.ssh_tunnel_is_connected and not instance.islocal:
|
|
adb_cmd.DisconnectAdb()
|
|
forwarded_ports = utils.AutoConnect(
|
|
ip_addr=instance.ip,
|
|
rsa_key_file=ssh_private_key_path,
|
|
target_vnc_port=utils.AVD_PORT_DICT[instance.avd_type].vnc_port,
|
|
target_adb_port=utils.AVD_PORT_DICT[instance.avd_type].adb_port,
|
|
ssh_user=constants.GCE_USER,
|
|
extra_args_ssh_tunnel=extra_args_ssh_tunnel)
|
|
vnc_port = forwarded_ports.vnc_port
|
|
adb_port = forwarded_ports.adb_port
|
|
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,
|
|
webrtc_port)
|
|
elif(vnc_port and connect_vnc):
|
|
StartVnc(vnc_port, instance.display)
|
|
|
|
device_dict = {
|
|
constants.IP: instance.ip,
|
|
constants.INSTANCE_NAME: instance.name,
|
|
constants.VNC_PORT: vnc_port,
|
|
constants.ADB_PORT: adb_port
|
|
}
|
|
if adb_port and not instance.islocal:
|
|
device_dict[constants.DEVICE_SERIAL] = (
|
|
constants.REMOTE_INSTANCE_ADB_SERIAL % adb_port)
|
|
|
|
if vnc_port and adb_port:
|
|
reconnect_report.AddData(key="devices", value=device_dict)
|
|
else:
|
|
# We use 'ps aux' to grep adb/vnc fowarding port from ssh tunnel
|
|
# command. Therefore we report failure here if no vnc_port and
|
|
# adb_port found.
|
|
reconnect_report.AddData(key="device_failing_reconnect", value=device_dict)
|
|
reconnect_report.AddError(instance.name)
|
|
|
|
|
|
def Run(args):
|
|
"""Run reconnect.
|
|
|
|
Args:
|
|
args: Namespace object from argparse.parse_args.
|
|
"""
|
|
cfg = config.GetAcloudConfig(args)
|
|
instances_to_reconnect = []
|
|
if args.instance_names is not None:
|
|
# user input instance name to get instance object.
|
|
instances_to_reconnect = list_instance.GetInstancesFromInstanceNames(
|
|
cfg, args.instance_names)
|
|
if not instances_to_reconnect:
|
|
instances_to_reconnect = list_instance.ChooseInstances(cfg, args.all)
|
|
|
|
reconnect_report = report.Report(command="reconnect")
|
|
for instance in instances_to_reconnect:
|
|
if instance.avd_type not in utils.AVD_PORT_DICT:
|
|
utils.PrintColorString("Skipping reconnect of instance %s due to "
|
|
"unknown avd type (%s)." %
|
|
(instance.name, instance.avd_type),
|
|
utils.TextColors.WARNING)
|
|
continue
|
|
if not instance.islocal:
|
|
AddPublicSshRsaToInstance(cfg, constants.GCE_USER, instance.name)
|
|
ReconnectInstance(cfg.ssh_private_key_path,
|
|
instance,
|
|
reconnect_report,
|
|
cfg.extra_args_ssh_tunnel,
|
|
connect_vnc=(args.autoconnect is True))
|
|
|
|
utils.PrintDeviceSummary(reconnect_report)
|