| # 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"""Instance class. |
| |
| Define the instance class used to hold details about an AVD instance. |
| |
| The instance class will hold details about AVD instances (remote/local) used to |
| enable users to understand what instances they've created. This will be leveraged |
| for the list, delete, and reconnect commands. |
| |
| The details include: |
| - instance name (for remote instances) |
| - creation date/instance duration |
| - instance image details (branch/target/build id) |
| - and more! |
| """ |
| |
| import collections |
| import datetime |
| import logging |
| import os |
| import re |
| import subprocess |
| import tempfile |
| |
| # pylint: disable=import-error |
| import dateutil.parser |
| import dateutil.tz |
| |
| from acloud.internal import constants |
| from acloud.internal.lib import cvd_runtime_config |
| from acloud.internal.lib import utils |
| from acloud.internal.lib.adb_tools import AdbTools |
| |
| |
| logger = logging.getLogger(__name__) |
| |
| _ACLOUD_CVD_TEMP = os.path.join(tempfile.gettempdir(), "acloud_cvd_temp") |
| _CVD_RUNTIME_FOLDER_NAME = "cuttlefish_runtime" |
| _CVD_STATUS_BIN = "cvd_status" |
| _MSG_UNABLE_TO_CALCULATE = "Unable to calculate" |
| _NO_ANDROID_ENV = "android source not available" |
| _RE_GROUP_ADB = "local_adb_port" |
| _RE_GROUP_VNC = "local_vnc_port" |
| _RE_SSH_TUNNEL_PATTERN = (r"((.*\s*-L\s)(?P<%s>\d+):127.0.0.1:%s)" |
| r"((.*\s*-L\s)(?P<%s>\d+):127.0.0.1:%s)" |
| r"(.+%s)") |
| _RE_TIMEZONE = re.compile(r"^(?P<time>[0-9\-\.:T]*)(?P<timezone>[+-]\d+:\d+)$") |
| |
| _COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"] |
| _RE_RUN_CVD = re.compile(r"(?P<date_str>^[^/]+)(.*run_cvd)") |
| _DISPLAY_STRING = "%(x_res)sx%(y_res)s (%(dpi)s)" |
| _RE_ZONE = re.compile(r".+/zones/(?P<zone>.+)$") |
| _LOCAL_ZONE = "local" |
| _FULL_NAME_STRING = ("device serial: %(device_serial)s (%(instance_name)s) " |
| "elapsed time: %(elapsed_time)s") |
| _INDENT = " " * 3 |
| LocalPorts = collections.namedtuple("LocalPorts", [constants.VNC_PORT, |
| constants.ADB_PORT]) |
| |
| |
| def GetDefaultCuttlefishConfig(): |
| """Get the path of default cuttlefish instance config. |
| |
| Return: |
| String, path of cf runtime config. |
| """ |
| return os.path.join(os.path.expanduser("~"), _CVD_RUNTIME_FOLDER_NAME, |
| constants.CUTTLEFISH_CONFIG_FILE) |
| |
| |
| def GetLocalInstanceName(local_instance_id): |
| """Get local cuttlefish instance name by instance id. |
| |
| Args: |
| local_instance_id: Integer of instance id. |
| |
| Return: |
| String, the instance name. |
| """ |
| return "%s-%d" % (constants.LOCAL_INS_NAME, local_instance_id) |
| |
| |
| def GetLocalInstanceConfig(local_instance_id): |
| """Get the path of instance config. |
| |
| Args: |
| local_instance_id: Integer of instance id. |
| |
| Return: |
| String, path of cf runtime config. |
| """ |
| cfg_path = os.path.join(GetLocalInstanceRuntimeDir(local_instance_id), |
| constants.CUTTLEFISH_CONFIG_FILE) |
| if os.path.isfile(cfg_path): |
| return cfg_path |
| return None |
| |
| |
| def GetAllLocalInstanceConfigs(): |
| """Get the list of instance config. |
| |
| Return: |
| List of instance config path. |
| """ |
| cfg_list = [] |
| # Check if any instance config is under home folder. |
| cfg_path = GetDefaultCuttlefishConfig() |
| if os.path.isfile(cfg_path): |
| cfg_list.append(cfg_path) |
| |
| # Check if any instance config is under acloud cvd temp folder. |
| if os.path.exists(_ACLOUD_CVD_TEMP): |
| for ins_name in os.listdir(_ACLOUD_CVD_TEMP): |
| cfg_path = os.path.join(_ACLOUD_CVD_TEMP, |
| ins_name, |
| _CVD_RUNTIME_FOLDER_NAME, |
| constants.CUTTLEFISH_CONFIG_FILE) |
| if os.path.isfile(cfg_path): |
| cfg_list.append(cfg_path) |
| return cfg_list |
| |
| |
| def GetLocalInstanceHomeDir(local_instance_id): |
| """Get local instance home dir according to instance id. |
| |
| Args: |
| local_instance_id: Integer of instance id. |
| |
| Return: |
| String, path of instance home dir. |
| """ |
| return os.path.join(_ACLOUD_CVD_TEMP, |
| GetLocalInstanceName(local_instance_id)) |
| |
| |
| def GetLocalInstanceRuntimeDir(local_instance_id): |
| """Get instance runtime dir |
| |
| Args: |
| local_instance_id: Integer of instance id. |
| |
| Return: |
| String, path of instance runtime dir. |
| """ |
| return os.path.join(GetLocalInstanceHomeDir(local_instance_id), |
| _CVD_RUNTIME_FOLDER_NAME) |
| |
| |
| def _GetCurrentLocalTime(): |
| """Return a datetime object for current time in local time zone.""" |
| return datetime.datetime.now(dateutil.tz.tzlocal()) |
| |
| |
| def _GetElapsedTime(start_time): |
| """Calculate the elapsed time from start_time till now. |
| |
| Args: |
| start_time: String of instance created time. |
| |
| Returns: |
| datetime.timedelta of elapsed time, _MSG_UNABLE_TO_CALCULATE for |
| datetime can't parse cases. |
| """ |
| match = _RE_TIMEZONE.match(start_time) |
| try: |
| # Check start_time has timezone or not. If timezone can't be found, |
| # use local timezone to get elapsed time. |
| if match: |
| return _GetCurrentLocalTime() - dateutil.parser.parse(start_time) |
| |
| return _GetCurrentLocalTime() - dateutil.parser.parse( |
| start_time).replace(tzinfo=dateutil.tz.tzlocal()) |
| except ValueError: |
| logger.debug(("Can't parse datetime string(%s)."), start_time) |
| return _MSG_UNABLE_TO_CALCULATE |
| |
| |
| class Instance(object): |
| """Class to store data of instance.""" |
| |
| # pylint: disable=too-many-locals |
| def __init__(self, name, fullname, display, ip, status=None, adb_port=None, |
| vnc_port=None, ssh_tunnel_is_connected=None, createtime=None, |
| elapsed_time=None, avd_type=None, avd_flavor=None, |
| is_local=False, device_information=None, zone=None): |
| self._name = name |
| self._fullname = fullname |
| self._status = status |
| self._display = display # Resolution and dpi |
| self._ip = ip |
| self._adb_port = adb_port # adb port which is forwarding to remote |
| self._vnc_port = vnc_port # vnc port which is forwarding to remote |
| # True if ssh tunnel is still connected |
| self._ssh_tunnel_is_connected = ssh_tunnel_is_connected |
| self._createtime = createtime |
| self._elapsed_time = elapsed_time |
| self._avd_type = avd_type |
| self._avd_flavor = avd_flavor |
| self._is_local = is_local # True if this is a local instance |
| self._device_information = device_information |
| self._zone = zone |
| |
| def __repr__(self): |
| """Return full name property for print.""" |
| return self._fullname |
| |
| def Summary(self): |
| """Let's make it easy to see what this class is holding.""" |
| representation = [] |
| representation.append(" name: %s" % self._name) |
| representation.append("%s IP: %s" % (_INDENT, self._ip)) |
| representation.append("%s create time: %s" % (_INDENT, self._createtime)) |
| representation.append("%s elapse time: %s" % (_INDENT, self._elapsed_time)) |
| representation.append("%s status: %s" % (_INDENT, self._status)) |
| representation.append("%s avd type: %s" % (_INDENT, self._avd_type)) |
| representation.append("%s display: %s" % (_INDENT, self._display)) |
| representation.append("%s vnc: 127.0.0.1:%s" % (_INDENT, self._vnc_port)) |
| representation.append("%s zone: %s" % (_INDENT, self._zone)) |
| |
| if self._adb_port and self._device_information: |
| representation.append("%s adb serial: 127.0.0.1:%s" % |
| (_INDENT, self._adb_port)) |
| representation.append("%s product: %s" % ( |
| _INDENT, self._device_information["product"])) |
| representation.append("%s model: %s" % ( |
| _INDENT, self._device_information["model"])) |
| representation.append("%s device: %s" % ( |
| _INDENT, self._device_information["device"])) |
| representation.append("%s transport_id: %s" % ( |
| _INDENT, self._device_information["transport_id"])) |
| else: |
| representation.append("%s adb serial: disconnected" % _INDENT) |
| |
| return "\n".join(representation) |
| |
| def AdbConnected(self): |
| """Check AVD adb connected. |
| |
| Returns: |
| Boolean, True when adb status of AVD is connected. |
| """ |
| if self._adb_port and self._device_information: |
| return True |
| return False |
| |
| @property |
| def name(self): |
| """Return the instance name.""" |
| return self._name |
| |
| @property |
| def fullname(self): |
| """Return the instance full name.""" |
| return self._fullname |
| |
| @property |
| def ip(self): |
| """Return the ip.""" |
| return self._ip |
| |
| @property |
| def status(self): |
| """Return status.""" |
| return self._status |
| |
| @property |
| def display(self): |
| """Return display.""" |
| return self._display |
| |
| @property |
| def ssh_tunnel_is_connected(self): |
| """Return the connect status.""" |
| return self._ssh_tunnel_is_connected |
| |
| @property |
| def createtime(self): |
| """Return create time.""" |
| return self._createtime |
| |
| @property |
| def avd_type(self): |
| """Return avd_type.""" |
| return self._avd_type |
| |
| @property |
| def avd_flavor(self): |
| """Return avd_flavor.""" |
| return self._avd_flavor |
| |
| @property |
| def islocal(self): |
| """Return if it is a local instance.""" |
| return self._is_local |
| |
| @property |
| def adb_port(self): |
| """Return adb_port.""" |
| return self._adb_port |
| |
| @property |
| def vnc_port(self): |
| """Return vnc_port.""" |
| return self._vnc_port |
| |
| @property |
| def zone(self): |
| """Return zone.""" |
| return self._zone |
| |
| |
| class LocalInstance(Instance): |
| """Class to store data of local cuttlefish instance.""" |
| def __init__(self, cf_config_path): |
| """Initialize a localInstance object. |
| |
| Args: |
| cf_config_path: String, path to the cf runtime config. |
| """ |
| self._cf_runtime_cfg = cvd_runtime_config.CvdRuntimeConfig(cf_config_path) |
| self._instance_dir = self._cf_runtime_cfg.instance_dir |
| self._virtual_disk_paths = self._cf_runtime_cfg.virtual_disk_paths |
| self._local_instance_id = int(self._cf_runtime_cfg.instance_id) |
| |
| display = _DISPLAY_STRING % {"x_res": self._cf_runtime_cfg.x_res, |
| "y_res": self._cf_runtime_cfg.y_res, |
| "dpi": self._cf_runtime_cfg.dpi} |
| # TODO(143063678), there's no createtime info in |
| # cuttlefish_config.json so far. |
| name = GetLocalInstanceName(self._local_instance_id) |
| fullname = (_FULL_NAME_STRING % |
| {"device_serial": "127.0.0.1:%s" % self._cf_runtime_cfg.adb_port, |
| "instance_name": name, |
| "elapsed_time": None}) |
| adb_device = AdbTools(self._cf_runtime_cfg.adb_port) |
| device_information = None |
| if adb_device.IsAdbConnected(): |
| device_information = adb_device.device_information |
| |
| super(LocalInstance, self).__init__( |
| name=name, fullname=fullname, display=display, ip="127.0.0.1", |
| status=constants.INS_STATUS_RUNNING, |
| adb_port=self._cf_runtime_cfg.adb_port, |
| vnc_port=self._cf_runtime_cfg.vnc_port, |
| createtime=None, elapsed_time=None, avd_type=constants.TYPE_CF, |
| is_local=True, device_information=device_information, |
| zone=_LOCAL_ZONE) |
| |
| def Summary(self): |
| """Return the string that this class is holding.""" |
| instance_home = "%s instance home: %s" % (_INDENT, self._instance_dir) |
| return "%s\n%s" % (super(LocalInstance, self).Summary(), instance_home) |
| |
| def CvdStatus(self): |
| """check if local instance is active. |
| |
| Execute cvd_status cmd to check if it exit without error. |
| |
| Returns |
| True if instance is active. |
| """ |
| if not self._cf_runtime_cfg.cvd_tools_path: |
| logger.debug("No cvd tools path found from config:%s", |
| self._cf_runtime_cfg.config_path) |
| return False |
| cvd_env = os.environ.copy() |
| cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = self._cf_runtime_cfg.config_path |
| cvd_env[constants.ENV_CVD_HOME] = GetLocalInstanceHomeDir(self._local_instance_id) |
| cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(self._local_instance_id) |
| try: |
| cvd_status_cmd = os.path.join(self._cf_runtime_cfg.cvd_tools_path, |
| _CVD_STATUS_BIN) |
| # TODO(b/150575261): Change the cvd home and cvd artifact path to |
| # another place instead of /tmp to prevent from the file not |
| # found exception. |
| if not os.path.exists(cvd_status_cmd): |
| logger.warning("Cvd tools path doesn't exist:%s", cvd_status_cmd) |
| if os.environ.get(constants.ENV_ANDROID_HOST_OUT, |
| _NO_ANDROID_ENV) in cvd_status_cmd: |
| logger.warning( |
| "Can't find the cvd_status tool (Try lunching a " |
| "cuttlefish target like aosp_cf_x86_phone-userdebug " |
| "and running 'make hosttar' before list/delete local " |
| "instances)") |
| return False |
| logger.debug("Running cmd[%s] to check cvd status.", cvd_status_cmd) |
| process = subprocess.Popen(cvd_status_cmd, |
| stdin=None, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| env=cvd_env) |
| stdout, _ = process.communicate() |
| if process.returncode != 0: |
| if stdout: |
| logger.debug("Local instance[%s] is not active: %s", |
| self.name, stdout.strip()) |
| return False |
| return True |
| except subprocess.CalledProcessError as cpe: |
| logger.error("Failed to run cvd_status: %s", cpe.output) |
| return False |
| |
| def Delete(self): |
| """Execute stop_cvd to stop local cuttlefish instance. |
| |
| - We should get the same host tool used to launch cvd to delete instance |
| , So get stop_cvd bin from the cvd runtime config. |
| - Add CUTTLEFISH_CONFIG_FILE env variable to tell stop_cvd which cvd |
| need to be deleted. |
| - Stop adb since local instance use the fixed adb port and could be |
| reused again soon. |
| """ |
| stop_cvd_cmd = os.path.join(self.cf_runtime_cfg.cvd_tools_path, |
| constants.CMD_STOP_CVD) |
| logger.debug("Running cmd[%s] to delete local cvd", stop_cvd_cmd) |
| with open(os.devnull, "w") as dev_null: |
| cvd_env = os.environ.copy() |
| if self.instance_dir: |
| cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = self._cf_runtime_cfg.config_path |
| cvd_env[constants.ENV_CVD_HOME] = GetLocalInstanceHomeDir( |
| self._local_instance_id) |
| cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(self._local_instance_id) |
| else: |
| logger.error("instance_dir is null!! instance[%d] might not be" |
| " deleted", self._local_instance_id) |
| subprocess.check_call( |
| utils.AddUserGroupsToCmd(stop_cvd_cmd, |
| constants.LIST_CF_USER_GROUPS), |
| stderr=dev_null, stdout=dev_null, shell=True, env=cvd_env) |
| |
| adb_cmd = AdbTools(self.adb_port) |
| # When relaunch a local instance, we need to pass in retry=True to make |
| # sure adb device is completely gone since it will use the same adb port |
| adb_cmd.DisconnectAdb(retry=True) |
| |
| @property |
| def instance_dir(self): |
| """Return _instance_dir.""" |
| return self._instance_dir |
| |
| @property |
| def instance_id(self): |
| """Return _local_instance_id.""" |
| return self._local_instance_id |
| |
| @property |
| def virtual_disk_paths(self): |
| """Return virtual_disk_paths""" |
| return self._virtual_disk_paths |
| |
| @property |
| def cf_runtime_cfg(self): |
| """Return _cf_runtime_cfg""" |
| return self._cf_runtime_cfg |
| |
| |
| class LocalGoldfishInstance(Instance): |
| """Class to store data of local goldfish instance.""" |
| |
| _INSTANCE_NAME_PATTERN = re.compile( |
| r"^local-goldfish-instance-(?P<id>\d+)$") |
| _CREATION_TIMESTAMP_FILE_NAME = "creation_timestamp.txt" |
| _INSTANCE_NAME_FORMAT = "local-goldfish-instance-%(id)s" |
| _EMULATOR_DEFAULT_CONSOLE_PORT = 5554 |
| _GF_ADB_DEVICE_SERIAL = "emulator-%(console_port)s" |
| |
| def __init__(self, local_instance_id, avd_flavor=None, create_time=None, |
| x_res=None, y_res=None, dpi=None): |
| """Initialize a LocalGoldfishInstance object. |
| |
| Args: |
| local_instance_id: Integer of instance id. |
| avd_flavor: String, the flavor of the virtual device. |
| create_time: String, the creation date and time. |
| x_res: Integer of x dimension. |
| y_res: Integer of y dimension. |
| dpi: Integer of dpi. |
| """ |
| self._id = local_instance_id |
| # By convention, adb port is console port + 1. |
| adb_port = self.console_port + 1 |
| |
| name = self._INSTANCE_NAME_FORMAT % {"id": local_instance_id} |
| |
| elapsed_time = _GetElapsedTime(create_time) if create_time else None |
| |
| fullname = _FULL_NAME_STRING % {"device_serial": self.device_serial, |
| "instance_name": name, |
| "elapsed_time": elapsed_time} |
| |
| if x_res and y_res and dpi: |
| display = _DISPLAY_STRING % {"x_res": x_res, "y_res": y_res, |
| "dpi": dpi} |
| else: |
| display = "unknown" |
| |
| adb = AdbTools(adb_port) |
| device_information = (adb.device_information if |
| adb.device_information else None) |
| |
| super(LocalGoldfishInstance, self).__init__( |
| name=name, fullname=fullname, display=display, ip="127.0.0.1", |
| status=None, adb_port=adb_port, avd_type=constants.TYPE_GF, |
| createtime=create_time, elapsed_time=elapsed_time, |
| avd_flavor=avd_flavor, is_local=True, |
| device_information=device_information) |
| |
| @staticmethod |
| def _GetInstanceDirRoot(): |
| """Return the root directory of all instance directories.""" |
| return os.path.join(tempfile.gettempdir(), "acloud_gf_temp") |
| |
| @property |
| def console_port(self): |
| """Return the console port as an integer""" |
| # Emulator requires the console port to be an even number. |
| return self._EMULATOR_DEFAULT_CONSOLE_PORT + (self._id - 1) * 2 |
| |
| @property |
| def device_serial(self): |
| """Return the serial number that contains the console port.""" |
| return self._GF_ADB_DEVICE_SERIAL % {"console_port": self.console_port} |
| |
| @property |
| def instance_dir(self): |
| """Return the path to instance directory.""" |
| return os.path.join(self._GetInstanceDirRoot(), |
| self._INSTANCE_NAME_FORMAT % {"id": self._id}) |
| |
| @property |
| def creation_timestamp_path(self): |
| """Return the file path containing the creation timestamp.""" |
| return os.path.join(self.instance_dir, |
| self._CREATION_TIMESTAMP_FILE_NAME) |
| |
| def WriteCreationTimestamp(self): |
| """Write creation timestamp to file.""" |
| with open(self.creation_timestamp_path, "w") as timestamp_file: |
| timestamp_file.write(str(_GetCurrentLocalTime())) |
| |
| def DeleteCreationTimestamp(self, ignore_errors): |
| """Delete the creation timestamp file. |
| |
| Args: |
| ignore_errors: Boolean, whether to ignore the errors. |
| |
| Raises: |
| OSError if fails to delete the file. |
| """ |
| try: |
| os.remove(self.creation_timestamp_path) |
| except OSError as e: |
| if not ignore_errors: |
| raise |
| logger.warning("Can't delete creation timestamp: %s", e) |
| |
| @classmethod |
| def GetExistingInstances(cls): |
| """Get a list of instances that have creation timestamp files.""" |
| instance_root = cls._GetInstanceDirRoot() |
| if not os.path.isdir(instance_root): |
| return [] |
| |
| instances = [] |
| for name in os.listdir(instance_root): |
| match = cls._INSTANCE_NAME_PATTERN.match(name) |
| timestamp_path = os.path.join(instance_root, name, |
| cls._CREATION_TIMESTAMP_FILE_NAME) |
| if match and os.path.isfile(timestamp_path): |
| instance_id = int(match.group("id")) |
| with open(timestamp_path, "r") as timestamp_file: |
| timestamp = timestamp_file.read().strip() |
| instances.append(LocalGoldfishInstance(instance_id, |
| create_time=timestamp)) |
| return instances |
| |
| |
| class RemoteInstance(Instance): |
| """Class to store data of remote instance.""" |
| |
| # pylint: disable=too-many-locals |
| def __init__(self, gce_instance): |
| """Process the args into class vars. |
| |
| RemoteInstace initialized by gce dict object. We parse the required data |
| from gce_instance to local variables. |
| Reference: |
| https://cloud.google.com/compute/docs/reference/rest/v1/instances/get |
| |
| We also gather more details on client side including the forwarding adb |
| port and vnc port which will be used to determine the status of ssh |
| tunnel connection. |
| |
| The status of gce instance will be displayed in _fullname property: |
| - Connected: If gce instance and ssh tunnel and adb connection are all |
| active. |
| - No connected: If ssh tunnel or adb connection is not found. |
| - Terminated: If we can't retrieve the public ip from gce instance. |
| |
| Args: |
| gce_instance: dict object queried from gce. |
| """ |
| name = gce_instance.get(constants.INS_KEY_NAME) |
| |
| create_time = gce_instance.get(constants.INS_KEY_CREATETIME) |
| elapsed_time = _GetElapsedTime(create_time) |
| status = gce_instance.get(constants.INS_KEY_STATUS) |
| zone = self._GetZoneName(gce_instance.get(constants.INS_KEY_ZONE)) |
| |
| ip = None |
| for network_interface in gce_instance.get("networkInterfaces"): |
| for access_config in network_interface.get("accessConfigs"): |
| ip = access_config.get("natIP") |
| |
| # Get metadata |
| display = None |
| avd_type = None |
| avd_flavor = None |
| for metadata in gce_instance.get("metadata", {}).get("items", []): |
| key = metadata["key"] |
| value = metadata["value"] |
| if key == constants.INS_KEY_DISPLAY: |
| display = value |
| elif key == constants.INS_KEY_AVD_TYPE: |
| avd_type = value |
| elif key == constants.INS_KEY_AVD_FLAVOR: |
| avd_flavor = value |
| |
| # Find ssl tunnel info. |
| adb_port = None |
| vnc_port = None |
| device_information = None |
| if ip: |
| forwarded_ports = self.GetAdbVncPortFromSSHTunnel(ip, avd_type) |
| adb_port = forwarded_ports.adb_port |
| vnc_port = forwarded_ports.vnc_port |
| ssh_tunnel_is_connected = adb_port is not None |
| |
| adb_device = AdbTools(adb_port) |
| if adb_device.IsAdbConnected(): |
| device_information = adb_device.device_information |
| fullname = (_FULL_NAME_STRING % |
| {"device_serial": "127.0.0.1:%d" % adb_port, |
| "instance_name": name, |
| "elapsed_time": elapsed_time}) |
| else: |
| fullname = (_FULL_NAME_STRING % |
| {"device_serial": "not connected", |
| "instance_name": name, |
| "elapsed_time": elapsed_time}) |
| # If instance is terminated, its ip is None. |
| else: |
| ssh_tunnel_is_connected = False |
| fullname = (_FULL_NAME_STRING % |
| {"device_serial": "terminated", |
| "instance_name": name, |
| "elapsed_time": elapsed_time}) |
| |
| super(RemoteInstance, self).__init__( |
| name=name, fullname=fullname, display=display, ip=ip, status=status, |
| adb_port=adb_port, vnc_port=vnc_port, |
| ssh_tunnel_is_connected=ssh_tunnel_is_connected, |
| createtime=create_time, elapsed_time=elapsed_time, avd_type=avd_type, |
| avd_flavor=avd_flavor, is_local=False, |
| device_information=device_information, |
| zone=zone) |
| |
| @staticmethod |
| def _GetZoneName(zone_info): |
| """Get the zone name from the zone information of gce instance. |
| |
| Zone information is like: |
| "https://www.googleapis.com/compute/v1/projects/project/zones/us-central1-c" |
| We want to get "us-central1-c" as zone name. |
| |
| Args: |
| zone_info: String, zone information of gce instance. |
| |
| Returns: |
| Zone name of gce instance. None if zone name can't find. |
| """ |
| zone_match = _RE_ZONE.match(zone_info) |
| if zone_match: |
| return zone_match.group("zone") |
| |
| logger.debug("Can't get zone name from %s.", zone_info) |
| return None |
| |
| @staticmethod |
| def GetAdbVncPortFromSSHTunnel(ip, avd_type): |
| """Get forwarding adb and vnc port from ssh tunnel. |
| |
| Args: |
| ip: String, ip address. |
| avd_type: String, the AVD type. |
| |
| Returns: |
| NamedTuple ForwardedPorts(vnc_port, adb_port) holding the ports |
| used in the ssh forwarded call. Both fields are integers. |
| """ |
| if avd_type not in utils.AVD_PORT_DICT: |
| return utils.ForwardedPorts(vnc_port=None, adb_port=None) |
| |
| default_vnc_port = utils.AVD_PORT_DICT[avd_type].vnc_port |
| default_adb_port = utils.AVD_PORT_DICT[avd_type].adb_port |
| re_pattern = re.compile(_RE_SSH_TUNNEL_PATTERN % |
| (_RE_GROUP_VNC, default_vnc_port, |
| _RE_GROUP_ADB, default_adb_port, ip)) |
| adb_port = None |
| vnc_port = None |
| process_output = utils.CheckOutput(constants.COMMAND_PS) |
| for line in process_output.splitlines(): |
| match = re_pattern.match(line) |
| if match: |
| adb_port = int(match.group(_RE_GROUP_ADB)) |
| vnc_port = int(match.group(_RE_GROUP_VNC)) |
| break |
| |
| logger.debug(("grathering detail for ssh tunnel. " |
| "IP:%s, forwarding (adb:%d, vnc:%d)"), ip, adb_port, |
| vnc_port) |
| |
| return utils.ForwardedPorts(vnc_port=vnc_port, adb_port=adb_port) |