# 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
"""
from __future__ import print_function
import getpass
import re
from acloud.delete import delete
from acloud.internal.lib import auth
from acloud.internal.lib import android_compute_client
from acloud.internal.lib import utils
from acloud.internal.lib.adb_tools import AdbTools
from acloud.list import list as list_instance
from acloud.public import config
_RE_DISPLAY = re.compile(r"([\d]+)x([\d]+)\s.*")
_VNC_STARTED_PATTERN = "ssvnc vnc://127.0.0.1:%(vnc_port)d"
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.
delete.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(
cfg.zone,
user,
cfg.ssh_public_key_path,
instance_name)
def ReconnectInstance(ssh_private_key_path, instance):
"""Reconnect adb/vnc/ssh to the specified instance.
Args:
ssh_private_key_path: Path to the private key file.
e.g. ~/.ssh/acloud_rsa
instance: list.Instance() object.
"""
adb_cmd = AdbTools(instance.forwarding_adb_port)
vnc_port = instance.forwarding_vnc_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(
instance.ip,
ssh_private_key_path,
utils.AVD_PORT_DICT[instance.avd_type].vnc_port,
utils.AVD_PORT_DICT[instance.avd_type].adb_port,
getpass.getuser())
vnc_port = forwarded_ports.vnc_port
if vnc_port:
StartVnc(vnc_port, instance.display)
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)
for instance in instances_to_reconnect:
AddPublicSshRsaToInstance(cfg, getpass.getuser(), instance.name)
ReconnectInstance(cfg.ssh_private_key_path, instance)