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"""Instance class. 15 16Define the instance class used to hold details about an AVD instance. 17 18The instance class will hold details about AVD instances (remote/local) used to 19enable users to understand what instances they've created. This will be leveraged 20for the list, delete, and reconnect commands. 21 22The details include: 23- instance name (for remote instances) 24- creation date/instance duration 25- instance image details (branch/target/build id) 26- and more! 27""" 28 29import collections 30import datetime 31import logging 32import os 33import re 34import subprocess 35import tempfile 36 37# pylint: disable=import-error 38import dateutil.parser 39import dateutil.tz 40 41from acloud.internal import constants 42from acloud.internal.lib import cvd_runtime_config 43from acloud.internal.lib import utils 44from acloud.internal.lib.adb_tools import AdbTools 45 46 47logger = logging.getLogger(__name__) 48 49_ACLOUD_CVD_TEMP = os.path.join(tempfile.gettempdir(), "acloud_cvd_temp") 50_CVD_RUNTIME_FOLDER_NAME = "cuttlefish_runtime" 51_CVD_STATUS_BIN = "cvd_status" 52_MSG_UNABLE_TO_CALCULATE = "Unable to calculate" 53_NO_ANDROID_ENV = "android source not available" 54_RE_GROUP_ADB = "local_adb_port" 55_RE_GROUP_VNC = "local_vnc_port" 56_RE_SSH_TUNNEL_PATTERN = (r"((.*\s*-L\s)(?P<%s>\d+):127.0.0.1:%s)" 57 r"((.*\s*-L\s)(?P<%s>\d+):127.0.0.1:%s)" 58 r"(.+%s)") 59_RE_TIMEZONE = re.compile(r"^(?P<time>[0-9\-\.:T]*)(?P<timezone>[+-]\d+:\d+)$") 60 61_COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"] 62_RE_RUN_CVD = re.compile(r"(?P<date_str>^[^/]+)(.*run_cvd)") 63_DISPLAY_STRING = "%(x_res)sx%(y_res)s (%(dpi)s)" 64_RE_ZONE = re.compile(r".+/zones/(?P<zone>.+)$") 65_LOCAL_ZONE = "local" 66_FULL_NAME_STRING = ("device serial: %(device_serial)s (%(instance_name)s) " 67 "elapsed time: %(elapsed_time)s") 68_INDENT = " " * 3 69LocalPorts = collections.namedtuple("LocalPorts", [constants.VNC_PORT, 70 constants.ADB_PORT]) 71 72 73def GetDefaultCuttlefishConfig(): 74 """Get the path of default cuttlefish instance config. 75 76 Return: 77 String, path of cf runtime config. 78 """ 79 return os.path.join(os.path.expanduser("~"), _CVD_RUNTIME_FOLDER_NAME, 80 constants.CUTTLEFISH_CONFIG_FILE) 81 82 83def GetLocalInstanceName(local_instance_id): 84 """Get local cuttlefish instance name by instance id. 85 86 Args: 87 local_instance_id: Integer of instance id. 88 89 Return: 90 String, the instance name. 91 """ 92 return "%s-%d" % (constants.LOCAL_INS_NAME, local_instance_id) 93 94 95def GetLocalInstanceConfig(local_instance_id): 96 """Get the path of instance config. 97 98 Args: 99 local_instance_id: Integer of instance id. 100 101 Return: 102 String, path of cf runtime config. 103 """ 104 cfg_path = os.path.join(GetLocalInstanceRuntimeDir(local_instance_id), 105 constants.CUTTLEFISH_CONFIG_FILE) 106 if os.path.isfile(cfg_path): 107 return cfg_path 108 return None 109 110 111def GetAllLocalInstanceConfigs(): 112 """Get the list of instance config. 113 114 Return: 115 List of instance config path. 116 """ 117 cfg_list = [] 118 # Check if any instance config is under home folder. 119 cfg_path = GetDefaultCuttlefishConfig() 120 if os.path.isfile(cfg_path): 121 cfg_list.append(cfg_path) 122 123 # Check if any instance config is under acloud cvd temp folder. 124 if os.path.exists(_ACLOUD_CVD_TEMP): 125 for ins_name in os.listdir(_ACLOUD_CVD_TEMP): 126 cfg_path = os.path.join(_ACLOUD_CVD_TEMP, 127 ins_name, 128 _CVD_RUNTIME_FOLDER_NAME, 129 constants.CUTTLEFISH_CONFIG_FILE) 130 if os.path.isfile(cfg_path): 131 cfg_list.append(cfg_path) 132 return cfg_list 133 134 135def GetLocalInstanceHomeDir(local_instance_id): 136 """Get local instance home dir according to instance id. 137 138 Args: 139 local_instance_id: Integer of instance id. 140 141 Return: 142 String, path of instance home dir. 143 """ 144 return os.path.join(_ACLOUD_CVD_TEMP, 145 GetLocalInstanceName(local_instance_id)) 146 147 148def GetLocalInstanceRuntimeDir(local_instance_id): 149 """Get instance runtime dir 150 151 Args: 152 local_instance_id: Integer of instance id. 153 154 Return: 155 String, path of instance runtime dir. 156 """ 157 return os.path.join(GetLocalInstanceHomeDir(local_instance_id), 158 _CVD_RUNTIME_FOLDER_NAME) 159 160 161def _GetCurrentLocalTime(): 162 """Return a datetime object for current time in local time zone.""" 163 return datetime.datetime.now(dateutil.tz.tzlocal()) 164 165 166def _GetElapsedTime(start_time): 167 """Calculate the elapsed time from start_time till now. 168 169 Args: 170 start_time: String of instance created time. 171 172 Returns: 173 datetime.timedelta of elapsed time, _MSG_UNABLE_TO_CALCULATE for 174 datetime can't parse cases. 175 """ 176 match = _RE_TIMEZONE.match(start_time) 177 try: 178 # Check start_time has timezone or not. If timezone can't be found, 179 # use local timezone to get elapsed time. 180 if match: 181 return _GetCurrentLocalTime() - dateutil.parser.parse(start_time) 182 183 return _GetCurrentLocalTime() - dateutil.parser.parse( 184 start_time).replace(tzinfo=dateutil.tz.tzlocal()) 185 except ValueError: 186 logger.debug(("Can't parse datetime string(%s)."), start_time) 187 return _MSG_UNABLE_TO_CALCULATE 188 189 190class Instance(object): 191 """Class to store data of instance.""" 192 193 # pylint: disable=too-many-locals 194 def __init__(self, name, fullname, display, ip, status=None, adb_port=None, 195 vnc_port=None, ssh_tunnel_is_connected=None, createtime=None, 196 elapsed_time=None, avd_type=None, avd_flavor=None, 197 is_local=False, device_information=None, zone=None): 198 self._name = name 199 self._fullname = fullname 200 self._status = status 201 self._display = display # Resolution and dpi 202 self._ip = ip 203 self._adb_port = adb_port # adb port which is forwarding to remote 204 self._vnc_port = vnc_port # vnc port which is forwarding to remote 205 # True if ssh tunnel is still connected 206 self._ssh_tunnel_is_connected = ssh_tunnel_is_connected 207 self._createtime = createtime 208 self._elapsed_time = elapsed_time 209 self._avd_type = avd_type 210 self._avd_flavor = avd_flavor 211 self._is_local = is_local # True if this is a local instance 212 self._device_information = device_information 213 self._zone = zone 214 215 def __repr__(self): 216 """Return full name property for print.""" 217 return self._fullname 218 219 def Summary(self): 220 """Let's make it easy to see what this class is holding.""" 221 representation = [] 222 representation.append(" name: %s" % self._name) 223 representation.append("%s IP: %s" % (_INDENT, self._ip)) 224 representation.append("%s create time: %s" % (_INDENT, self._createtime)) 225 representation.append("%s elapse time: %s" % (_INDENT, self._elapsed_time)) 226 representation.append("%s status: %s" % (_INDENT, self._status)) 227 representation.append("%s avd type: %s" % (_INDENT, self._avd_type)) 228 representation.append("%s display: %s" % (_INDENT, self._display)) 229 representation.append("%s vnc: 127.0.0.1:%s" % (_INDENT, self._vnc_port)) 230 representation.append("%s zone: %s" % (_INDENT, self._zone)) 231 232 if self._adb_port and self._device_information: 233 representation.append("%s adb serial: 127.0.0.1:%s" % 234 (_INDENT, self._adb_port)) 235 representation.append("%s product: %s" % ( 236 _INDENT, self._device_information["product"])) 237 representation.append("%s model: %s" % ( 238 _INDENT, self._device_information["model"])) 239 representation.append("%s device: %s" % ( 240 _INDENT, self._device_information["device"])) 241 representation.append("%s transport_id: %s" % ( 242 _INDENT, self._device_information["transport_id"])) 243 else: 244 representation.append("%s adb serial: disconnected" % _INDENT) 245 246 return "\n".join(representation) 247 248 def AdbConnected(self): 249 """Check AVD adb connected. 250 251 Returns: 252 Boolean, True when adb status of AVD is connected. 253 """ 254 if self._adb_port and self._device_information: 255 return True 256 return False 257 258 @property 259 def name(self): 260 """Return the instance name.""" 261 return self._name 262 263 @property 264 def fullname(self): 265 """Return the instance full name.""" 266 return self._fullname 267 268 @property 269 def ip(self): 270 """Return the ip.""" 271 return self._ip 272 273 @property 274 def status(self): 275 """Return status.""" 276 return self._status 277 278 @property 279 def display(self): 280 """Return display.""" 281 return self._display 282 283 @property 284 def ssh_tunnel_is_connected(self): 285 """Return the connect status.""" 286 return self._ssh_tunnel_is_connected 287 288 @property 289 def createtime(self): 290 """Return create time.""" 291 return self._createtime 292 293 @property 294 def avd_type(self): 295 """Return avd_type.""" 296 return self._avd_type 297 298 @property 299 def avd_flavor(self): 300 """Return avd_flavor.""" 301 return self._avd_flavor 302 303 @property 304 def islocal(self): 305 """Return if it is a local instance.""" 306 return self._is_local 307 308 @property 309 def adb_port(self): 310 """Return adb_port.""" 311 return self._adb_port 312 313 @property 314 def vnc_port(self): 315 """Return vnc_port.""" 316 return self._vnc_port 317 318 @property 319 def zone(self): 320 """Return zone.""" 321 return self._zone 322 323 324class LocalInstance(Instance): 325 """Class to store data of local cuttlefish instance.""" 326 def __init__(self, cf_config_path): 327 """Initialize a localInstance object. 328 329 Args: 330 cf_config_path: String, path to the cf runtime config. 331 """ 332 self._cf_runtime_cfg = cvd_runtime_config.CvdRuntimeConfig(cf_config_path) 333 self._instance_dir = self._cf_runtime_cfg.instance_dir 334 self._virtual_disk_paths = self._cf_runtime_cfg.virtual_disk_paths 335 self._local_instance_id = int(self._cf_runtime_cfg.instance_id) 336 337 display = _DISPLAY_STRING % {"x_res": self._cf_runtime_cfg.x_res, 338 "y_res": self._cf_runtime_cfg.y_res, 339 "dpi": self._cf_runtime_cfg.dpi} 340 # TODO(143063678), there's no createtime info in 341 # cuttlefish_config.json so far. 342 name = GetLocalInstanceName(self._local_instance_id) 343 fullname = (_FULL_NAME_STRING % 344 {"device_serial": "127.0.0.1:%s" % self._cf_runtime_cfg.adb_port, 345 "instance_name": name, 346 "elapsed_time": None}) 347 adb_device = AdbTools(self._cf_runtime_cfg.adb_port) 348 device_information = None 349 if adb_device.IsAdbConnected(): 350 device_information = adb_device.device_information 351 352 super(LocalInstance, self).__init__( 353 name=name, fullname=fullname, display=display, ip="127.0.0.1", 354 status=constants.INS_STATUS_RUNNING, 355 adb_port=self._cf_runtime_cfg.adb_port, 356 vnc_port=self._cf_runtime_cfg.vnc_port, 357 createtime=None, elapsed_time=None, avd_type=constants.TYPE_CF, 358 is_local=True, device_information=device_information, 359 zone=_LOCAL_ZONE) 360 361 def Summary(self): 362 """Return the string that this class is holding.""" 363 instance_home = "%s instance home: %s" % (_INDENT, self._instance_dir) 364 return "%s\n%s" % (super(LocalInstance, self).Summary(), instance_home) 365 366 def CvdStatus(self): 367 """check if local instance is active. 368 369 Execute cvd_status cmd to check if it exit without error. 370 371 Returns 372 True if instance is active. 373 """ 374 if not self._cf_runtime_cfg.cvd_tools_path: 375 logger.debug("No cvd tools path found from config:%s", 376 self._cf_runtime_cfg.config_path) 377 return False 378 cvd_env = os.environ.copy() 379 cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = self._cf_runtime_cfg.config_path 380 cvd_env[constants.ENV_CVD_HOME] = GetLocalInstanceHomeDir(self._local_instance_id) 381 cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(self._local_instance_id) 382 try: 383 cvd_status_cmd = os.path.join(self._cf_runtime_cfg.cvd_tools_path, 384 _CVD_STATUS_BIN) 385 # TODO(b/150575261): Change the cvd home and cvd artifact path to 386 # another place instead of /tmp to prevent from the file not 387 # found exception. 388 if not os.path.exists(cvd_status_cmd): 389 logger.warning("Cvd tools path doesn't exist:%s", cvd_status_cmd) 390 if os.environ.get(constants.ENV_ANDROID_HOST_OUT, 391 _NO_ANDROID_ENV) in cvd_status_cmd: 392 logger.warning( 393 "Can't find the cvd_status tool (Try lunching a " 394 "cuttlefish target like aosp_cf_x86_phone-userdebug " 395 "and running 'make hosttar' before list/delete local " 396 "instances)") 397 return False 398 logger.debug("Running cmd[%s] to check cvd status.", cvd_status_cmd) 399 process = subprocess.Popen(cvd_status_cmd, 400 stdin=None, 401 stdout=subprocess.PIPE, 402 stderr=subprocess.STDOUT, 403 env=cvd_env) 404 stdout, _ = process.communicate() 405 if process.returncode != 0: 406 if stdout: 407 logger.debug("Local instance[%s] is not active: %s", 408 self.name, stdout.strip()) 409 return False 410 return True 411 except subprocess.CalledProcessError as cpe: 412 logger.error("Failed to run cvd_status: %s", cpe.output) 413 return False 414 415 def Delete(self): 416 """Execute stop_cvd to stop local cuttlefish instance. 417 418 - We should get the same host tool used to launch cvd to delete instance 419 , So get stop_cvd bin from the cvd runtime config. 420 - Add CUTTLEFISH_CONFIG_FILE env variable to tell stop_cvd which cvd 421 need to be deleted. 422 - Stop adb since local instance use the fixed adb port and could be 423 reused again soon. 424 """ 425 stop_cvd_cmd = os.path.join(self.cf_runtime_cfg.cvd_tools_path, 426 constants.CMD_STOP_CVD) 427 logger.debug("Running cmd[%s] to delete local cvd", stop_cvd_cmd) 428 with open(os.devnull, "w") as dev_null: 429 cvd_env = os.environ.copy() 430 if self.instance_dir: 431 cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = self._cf_runtime_cfg.config_path 432 cvd_env[constants.ENV_CVD_HOME] = GetLocalInstanceHomeDir( 433 self._local_instance_id) 434 cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(self._local_instance_id) 435 else: 436 logger.error("instance_dir is null!! instance[%d] might not be" 437 " deleted", self._local_instance_id) 438 subprocess.check_call( 439 utils.AddUserGroupsToCmd(stop_cvd_cmd, 440 constants.LIST_CF_USER_GROUPS), 441 stderr=dev_null, stdout=dev_null, shell=True, env=cvd_env) 442 443 adb_cmd = AdbTools(self.adb_port) 444 # When relaunch a local instance, we need to pass in retry=True to make 445 # sure adb device is completely gone since it will use the same adb port 446 adb_cmd.DisconnectAdb(retry=True) 447 448 @property 449 def instance_dir(self): 450 """Return _instance_dir.""" 451 return self._instance_dir 452 453 @property 454 def instance_id(self): 455 """Return _local_instance_id.""" 456 return self._local_instance_id 457 458 @property 459 def virtual_disk_paths(self): 460 """Return virtual_disk_paths""" 461 return self._virtual_disk_paths 462 463 @property 464 def cf_runtime_cfg(self): 465 """Return _cf_runtime_cfg""" 466 return self._cf_runtime_cfg 467 468 469class LocalGoldfishInstance(Instance): 470 """Class to store data of local goldfish instance.""" 471 472 _INSTANCE_NAME_PATTERN = re.compile( 473 r"^local-goldfish-instance-(?P<id>\d+)$") 474 _CREATION_TIMESTAMP_FILE_NAME = "creation_timestamp.txt" 475 _INSTANCE_NAME_FORMAT = "local-goldfish-instance-%(id)s" 476 _EMULATOR_DEFAULT_CONSOLE_PORT = 5554 477 _GF_ADB_DEVICE_SERIAL = "emulator-%(console_port)s" 478 479 def __init__(self, local_instance_id, avd_flavor=None, create_time=None, 480 x_res=None, y_res=None, dpi=None): 481 """Initialize a LocalGoldfishInstance object. 482 483 Args: 484 local_instance_id: Integer of instance id. 485 avd_flavor: String, the flavor of the virtual device. 486 create_time: String, the creation date and time. 487 x_res: Integer of x dimension. 488 y_res: Integer of y dimension. 489 dpi: Integer of dpi. 490 """ 491 self._id = local_instance_id 492 # By convention, adb port is console port + 1. 493 adb_port = self.console_port + 1 494 495 name = self._INSTANCE_NAME_FORMAT % {"id": local_instance_id} 496 497 elapsed_time = _GetElapsedTime(create_time) if create_time else None 498 499 fullname = _FULL_NAME_STRING % {"device_serial": self.device_serial, 500 "instance_name": name, 501 "elapsed_time": elapsed_time} 502 503 if x_res and y_res and dpi: 504 display = _DISPLAY_STRING % {"x_res": x_res, "y_res": y_res, 505 "dpi": dpi} 506 else: 507 display = "unknown" 508 509 adb = AdbTools(adb_port) 510 device_information = (adb.device_information if 511 adb.device_information else None) 512 513 super(LocalGoldfishInstance, self).__init__( 514 name=name, fullname=fullname, display=display, ip="127.0.0.1", 515 status=None, adb_port=adb_port, avd_type=constants.TYPE_GF, 516 createtime=create_time, elapsed_time=elapsed_time, 517 avd_flavor=avd_flavor, is_local=True, 518 device_information=device_information) 519 520 @staticmethod 521 def _GetInstanceDirRoot(): 522 """Return the root directory of all instance directories.""" 523 return os.path.join(tempfile.gettempdir(), "acloud_gf_temp") 524 525 @property 526 def console_port(self): 527 """Return the console port as an integer""" 528 # Emulator requires the console port to be an even number. 529 return self._EMULATOR_DEFAULT_CONSOLE_PORT + (self._id - 1) * 2 530 531 @property 532 def device_serial(self): 533 """Return the serial number that contains the console port.""" 534 return self._GF_ADB_DEVICE_SERIAL % {"console_port": self.console_port} 535 536 @property 537 def instance_dir(self): 538 """Return the path to instance directory.""" 539 return os.path.join(self._GetInstanceDirRoot(), 540 self._INSTANCE_NAME_FORMAT % {"id": self._id}) 541 542 @property 543 def creation_timestamp_path(self): 544 """Return the file path containing the creation timestamp.""" 545 return os.path.join(self.instance_dir, 546 self._CREATION_TIMESTAMP_FILE_NAME) 547 548 def WriteCreationTimestamp(self): 549 """Write creation timestamp to file.""" 550 with open(self.creation_timestamp_path, "w") as timestamp_file: 551 timestamp_file.write(str(_GetCurrentLocalTime())) 552 553 def DeleteCreationTimestamp(self, ignore_errors): 554 """Delete the creation timestamp file. 555 556 Args: 557 ignore_errors: Boolean, whether to ignore the errors. 558 559 Raises: 560 OSError if fails to delete the file. 561 """ 562 try: 563 os.remove(self.creation_timestamp_path) 564 except OSError as e: 565 if not ignore_errors: 566 raise 567 logger.warning("Can't delete creation timestamp: %s", e) 568 569 @classmethod 570 def GetExistingInstances(cls): 571 """Get a list of instances that have creation timestamp files.""" 572 instance_root = cls._GetInstanceDirRoot() 573 if not os.path.isdir(instance_root): 574 return [] 575 576 instances = [] 577 for name in os.listdir(instance_root): 578 match = cls._INSTANCE_NAME_PATTERN.match(name) 579 timestamp_path = os.path.join(instance_root, name, 580 cls._CREATION_TIMESTAMP_FILE_NAME) 581 if match and os.path.isfile(timestamp_path): 582 instance_id = int(match.group("id")) 583 with open(timestamp_path, "r") as timestamp_file: 584 timestamp = timestamp_file.read().strip() 585 instances.append(LocalGoldfishInstance(instance_id, 586 create_time=timestamp)) 587 return instances 588 589 590class RemoteInstance(Instance): 591 """Class to store data of remote instance.""" 592 593 # pylint: disable=too-many-locals 594 def __init__(self, gce_instance): 595 """Process the args into class vars. 596 597 RemoteInstace initialized by gce dict object. We parse the required data 598 from gce_instance to local variables. 599 Reference: 600 https://cloud.google.com/compute/docs/reference/rest/v1/instances/get 601 602 We also gather more details on client side including the forwarding adb 603 port and vnc port which will be used to determine the status of ssh 604 tunnel connection. 605 606 The status of gce instance will be displayed in _fullname property: 607 - Connected: If gce instance and ssh tunnel and adb connection are all 608 active. 609 - No connected: If ssh tunnel or adb connection is not found. 610 - Terminated: If we can't retrieve the public ip from gce instance. 611 612 Args: 613 gce_instance: dict object queried from gce. 614 """ 615 name = gce_instance.get(constants.INS_KEY_NAME) 616 617 create_time = gce_instance.get(constants.INS_KEY_CREATETIME) 618 elapsed_time = _GetElapsedTime(create_time) 619 status = gce_instance.get(constants.INS_KEY_STATUS) 620 zone = self._GetZoneName(gce_instance.get(constants.INS_KEY_ZONE)) 621 622 ip = None 623 for network_interface in gce_instance.get("networkInterfaces"): 624 for access_config in network_interface.get("accessConfigs"): 625 ip = access_config.get("natIP") 626 627 # Get metadata 628 display = None 629 avd_type = None 630 avd_flavor = None 631 for metadata in gce_instance.get("metadata", {}).get("items", []): 632 key = metadata["key"] 633 value = metadata["value"] 634 if key == constants.INS_KEY_DISPLAY: 635 display = value 636 elif key == constants.INS_KEY_AVD_TYPE: 637 avd_type = value 638 elif key == constants.INS_KEY_AVD_FLAVOR: 639 avd_flavor = value 640 641 # Find ssl tunnel info. 642 adb_port = None 643 vnc_port = None 644 device_information = None 645 if ip: 646 forwarded_ports = self.GetAdbVncPortFromSSHTunnel(ip, avd_type) 647 adb_port = forwarded_ports.adb_port 648 vnc_port = forwarded_ports.vnc_port 649 ssh_tunnel_is_connected = adb_port is not None 650 651 adb_device = AdbTools(adb_port) 652 if adb_device.IsAdbConnected(): 653 device_information = adb_device.device_information 654 fullname = (_FULL_NAME_STRING % 655 {"device_serial": "127.0.0.1:%d" % adb_port, 656 "instance_name": name, 657 "elapsed_time": elapsed_time}) 658 else: 659 fullname = (_FULL_NAME_STRING % 660 {"device_serial": "not connected", 661 "instance_name": name, 662 "elapsed_time": elapsed_time}) 663 # If instance is terminated, its ip is None. 664 else: 665 ssh_tunnel_is_connected = False 666 fullname = (_FULL_NAME_STRING % 667 {"device_serial": "terminated", 668 "instance_name": name, 669 "elapsed_time": elapsed_time}) 670 671 super(RemoteInstance, self).__init__( 672 name=name, fullname=fullname, display=display, ip=ip, status=status, 673 adb_port=adb_port, vnc_port=vnc_port, 674 ssh_tunnel_is_connected=ssh_tunnel_is_connected, 675 createtime=create_time, elapsed_time=elapsed_time, avd_type=avd_type, 676 avd_flavor=avd_flavor, is_local=False, 677 device_information=device_information, 678 zone=zone) 679 680 @staticmethod 681 def _GetZoneName(zone_info): 682 """Get the zone name from the zone information of gce instance. 683 684 Zone information is like: 685 "https://www.googleapis.com/compute/v1/projects/project/zones/us-central1-c" 686 We want to get "us-central1-c" as zone name. 687 688 Args: 689 zone_info: String, zone information of gce instance. 690 691 Returns: 692 Zone name of gce instance. None if zone name can't find. 693 """ 694 zone_match = _RE_ZONE.match(zone_info) 695 if zone_match: 696 return zone_match.group("zone") 697 698 logger.debug("Can't get zone name from %s.", zone_info) 699 return None 700 701 @staticmethod 702 def GetAdbVncPortFromSSHTunnel(ip, avd_type): 703 """Get forwarding adb and vnc port from ssh tunnel. 704 705 Args: 706 ip: String, ip address. 707 avd_type: String, the AVD type. 708 709 Returns: 710 NamedTuple ForwardedPorts(vnc_port, adb_port) holding the ports 711 used in the ssh forwarded call. Both fields are integers. 712 """ 713 if avd_type not in utils.AVD_PORT_DICT: 714 return utils.ForwardedPorts(vnc_port=None, adb_port=None) 715 716 default_vnc_port = utils.AVD_PORT_DICT[avd_type].vnc_port 717 default_adb_port = utils.AVD_PORT_DICT[avd_type].adb_port 718 re_pattern = re.compile(_RE_SSH_TUNNEL_PATTERN % 719 (_RE_GROUP_VNC, default_vnc_port, 720 _RE_GROUP_ADB, default_adb_port, ip)) 721 adb_port = None 722 vnc_port = None 723 process_output = utils.CheckOutput(constants.COMMAND_PS) 724 for line in process_output.splitlines(): 725 match = re_pattern.match(line) 726 if match: 727 adb_port = int(match.group(_RE_GROUP_ADB)) 728 vnc_port = int(match.group(_RE_GROUP_VNC)) 729 break 730 731 logger.debug(("grathering detail for ssh tunnel. " 732 "IP:%s, forwarding (adb:%d, vnc:%d)"), ip, adb_port, 733 vnc_port) 734 735 return utils.ForwardedPorts(vnc_port=vnc_port, adb_port=adb_port) 736