1# Copyright 2019 - 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. 14"""A client that manages Cuttlefish Virtual Device on compute engine. 15 16** CvdComputeClient ** 17 18CvdComputeClient derives from AndroidComputeClient. It manges a google 19compute engine project that is setup for running Cuttlefish Virtual Devices. 20It knows how to create a host instance from Cuttlefish Stable Host Image, fetch 21Android build, and start Android within the host instance. 22 23** Class hierarchy ** 24 25 base_cloud_client.BaseCloudApiClient 26 ^ 27 | 28 gcompute_client.ComputeClient 29 ^ 30 | 31 android_compute_client.AndroidComputeClient 32 ^ 33 | 34 cvd_compute_client_multi_stage.CvdComputeClient 35 36""" 37 38import logging 39import os 40import subprocess 41import tempfile 42import time 43 44from acloud import errors 45from acloud.internal import constants 46from acloud.internal.lib import android_build_client 47from acloud.internal.lib import android_compute_client 48from acloud.internal.lib import gcompute_client 49from acloud.internal.lib import utils 50from acloud.internal.lib.ssh import Ssh 51from acloud.pull import pull 52 53 54logger = logging.getLogger(__name__) 55 56_CONFIG_ARG = "-config" 57_DECOMPRESS_KERNEL_ARG = "-decompress_kernel=true" 58_AGREEMENT_PROMPT_ARG = "-report_anonymous_usage_stats=y" 59_UNDEFOK_ARG = "-undefok=report_anonymous_usage_stats,config" 60_NUM_AVDS_ARG = "-num_instances=%(num_AVD)s" 61_DEFAULT_BRANCH = "aosp-master" 62_FETCHER_BUILD_TARGET = "aosp_cf_x86_phone-userdebug" 63_FETCHER_NAME = "fetch_cvd" 64# Time info to write in report. 65_FETCH_ARTIFACT = "fetch_artifact_time" 66_GCE_CREATE = "gce_create_time" 67_LAUNCH_CVD = "launch_cvd_time" 68# WebRTC args for launching AVD 69_GUEST_ENFORCE_SECURITY_FALSE = "--guest_enforce_security=false" 70_START_WEBRTC = "--start_webrtc" 71_WEBRTC_ID = "--webrtc_device_id=%(instance)s" 72_VM_MANAGER = "--vm_manager=crosvm" 73_WEBRTC_ARGS = [_GUEST_ENFORCE_SECURITY_FALSE, _START_WEBRTC, _VM_MANAGER] 74_VNC_ARGS = ["--start_vnc_server=true"] 75_NO_RETRY = 0 76# Launch cvd command for acloud report 77_LAUNCH_CVD_COMMAND = "launch_cvd_command" 78 79 80class CvdComputeClient(android_compute_client.AndroidComputeClient): 81 """Client that manages Android Virtual Device.""" 82 83 DATA_POLICY_CREATE_IF_MISSING = "create_if_missing" 84 # Data policy to customize disk size. 85 DATA_POLICY_ALWAYS_CREATE = "always_create" 86 87 def __init__(self, 88 acloud_config, 89 oauth2_credentials, 90 boot_timeout_secs=None, 91 ins_timeout_secs=None, 92 report_internal_ip=None, 93 gpu=None): 94 """Initialize. 95 96 Args: 97 acloud_config: An AcloudConfig object. 98 oauth2_credentials: An oauth2client.OAuth2Credentials instance. 99 boot_timeout_secs: Integer, the maximum time to wait for the AVD 100 to boot up. 101 ins_timeout_secs: Integer, the maximum time to wait for the 102 instance ready. 103 report_internal_ip: Boolean to report the internal ip instead of 104 external ip. 105 gpu: String, GPU to attach to the device. 106 """ 107 super().__init__(acloud_config, oauth2_credentials) 108 109 self._fetch_cvd_version = acloud_config.fetch_cvd_version 110 self._build_api = ( 111 android_build_client.AndroidBuildClient(oauth2_credentials)) 112 self._ssh_private_key_path = acloud_config.ssh_private_key_path 113 self._boot_timeout_secs = boot_timeout_secs 114 self._ins_timeout_secs = ins_timeout_secs 115 self._report_internal_ip = report_internal_ip 116 self._gpu = gpu 117 # Store all failures result when creating one or multiple instances. 118 self._all_failures = dict() 119 self._extra_args_ssh_tunnel = acloud_config.extra_args_ssh_tunnel 120 self._ssh = None 121 self._ip = None 122 self._user = constants.GCE_USER 123 self._stage = constants.STAGE_INIT 124 self._execution_time = {_FETCH_ARTIFACT: 0, _GCE_CREATE: 0, _LAUNCH_CVD: 0} 125 126 def InitRemoteHost(self, ssh, ip, user): 127 """Init remote host. 128 129 Check if we can ssh to the remote host, stop any cf instances running 130 on it, and remove existing files. 131 132 Args: 133 ssh: Ssh object. 134 ip: namedtuple (internal, external) that holds IP address of the 135 remote host, e.g. "external:140.110.20.1, internal:10.0.0.1" 136 user: String of user log in to the instance. 137 """ 138 self.SetStage(constants.STAGE_SSH_CONNECT) 139 self._ssh = ssh 140 self._ip = ip 141 self._user = user 142 self._ssh.WaitForSsh(timeout=self._ins_timeout_secs) 143 self.StopCvd() 144 self.CleanUp() 145 146 # TODO(171376263): Refactor CreateInstance() args with avd_spec. 147 # pylint: disable=arguments-differ,too-many-locals,broad-except 148 def CreateInstance(self, instance, image_name, image_project, 149 build_target=None, branch=None, build_id=None, 150 kernel_branch=None, kernel_build_id=None, 151 kernel_build_target=None, blank_data_disk_size_gb=None, 152 avd_spec=None, extra_scopes=None, 153 system_build_target=None, system_branch=None, 154 system_build_id=None, bootloader_build_target=None, 155 bootloader_branch=None, bootloader_build_id=None): 156 157 """Create/Reuse a single configured cuttlefish device. 158 1. Prepare GCE instance. 159 Create a new instnace or get IP address for reusing the specific instance. 160 2. Put fetch_cvd on the instance. 161 3. Invoke fetch_cvd to fetch and run the instance. 162 163 Args: 164 instance: instance name. 165 image_name: A string, the name of the GCE image. 166 image_project: A string, name of the project where the image lives. 167 Assume the default project if None. 168 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug" 169 branch: Branch name, e.g. "aosp-master" 170 build_id: Build id, a string, e.g. "2263051", "P2804227" 171 kernel_branch: Kernel branch name, e.g. "kernel-common-android-4.14" 172 kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427" 173 kernel_build_target: String, Kernel build target name. 174 blank_data_disk_size_gb: Size of the blank data disk in GB. 175 avd_spec: An AVDSpec instance. 176 extra_scopes: A list of extra scopes to be passed to the instance. 177 system_build_target: String of the system image target name, 178 e.g. "cf_x86_phone-userdebug" 179 system_branch: String of the system image branch name. 180 system_build_id: String of the system image build id. 181 bootloader_build_target: String of the bootloader target name. 182 bootloader_branch: String of the bootloader branch name. 183 bootloader_build_id: String of the bootloader build id. 184 185 Returns: 186 A string, representing instance name. 187 """ 188 189 # A blank data disk would be created on the host. Make sure the size of 190 # the boot disk is large enough to hold it. 191 boot_disk_size_gb = ( 192 int(self.GetImage(image_name, image_project)["diskSizeGb"]) + 193 blank_data_disk_size_gb) 194 195 # Record the build info into metadata. 196 self._RecordBuildInfo(avd_spec, build_id, build_target, 197 system_build_id, system_build_target, 198 kernel_build_id, kernel_build_target) 199 200 if avd_spec and avd_spec.instance_name_to_reuse: 201 self._ip = self._ReusingGceInstance(avd_spec) 202 else: 203 self._VerifyZoneByQuota() 204 self._ip = self._CreateGceInstance(instance, image_name, image_project, 205 extra_scopes, boot_disk_size_gb, 206 avd_spec) 207 self._ssh = Ssh(ip=self._ip, 208 user=constants.GCE_USER, 209 ssh_private_key_path=self._ssh_private_key_path, 210 extra_args_ssh_tunnel=self._extra_args_ssh_tunnel, 211 report_internal_ip=self._report_internal_ip) 212 try: 213 self.SetStage(constants.STAGE_SSH_CONNECT) 214 self._ssh.WaitForSsh(timeout=self._ins_timeout_secs) 215 if avd_spec: 216 if avd_spec.instance_name_to_reuse: 217 self.StopCvd() 218 self.CleanUp() 219 return instance 220 221 # TODO: Remove following code after create_cf deprecated. 222 self.UpdateFetchCvd() 223 224 self.FetchBuild(build_id, branch, build_target, system_build_id, 225 system_branch, system_build_target, kernel_build_id, 226 kernel_branch, kernel_build_target, bootloader_build_id, 227 bootloader_branch, bootloader_build_target) 228 self.LaunchCvd(instance, 229 blank_data_disk_size_gb=blank_data_disk_size_gb, 230 boot_timeout_secs=self._boot_timeout_secs) 231 232 return instance 233 except Exception as e: 234 self._all_failures[instance] = e 235 return instance 236 237 def _RecordBuildInfo(self, avd_spec, build_id, build_target, 238 system_build_id, system_build_target, 239 kernel_build_id, kernel_build_target): 240 """Rocord the build information into metadata. 241 242 The build information includes build id and build target of base image, 243 system image, and kernel image. 244 245 Args: 246 avd_spec: An AVDSpec instance. 247 build_id: String, build id for the base image. 248 build_target: String, target name for the base image, 249 e.g. "cf_x86_phone-userdebug" 250 system_build_id: String, build id for the system image. 251 system_build_target: String, system build target name, 252 e.g. "cf_x86_phone-userdebug" 253 kernel_build_id: String, kernel build id, e.g. "223051", "P280427" 254 kernel_build_target: String, kernel build target name. 255 """ 256 if avd_spec and avd_spec.image_source == constants.IMAGE_SRC_REMOTE: 257 build_id = avd_spec.remote_image.get(constants.BUILD_ID) 258 build_target = avd_spec.remote_image.get(constants.BUILD_TARGET) 259 system_build_id = avd_spec.system_build_info.get(constants.BUILD_ID) 260 system_build_target = avd_spec.system_build_info.get(constants.BUILD_TARGET) 261 kernel_build_id = avd_spec.kernel_build_info.get(constants.BUILD_ID) 262 kernel_build_target = avd_spec.kernel_build_info.get(constants.BUILD_TARGET) 263 if build_id and build_target: 264 self._metadata.update({"build_id": build_id}) 265 self._metadata.update({"build_target": build_target}) 266 if system_build_id and system_build_target: 267 self._metadata.update({"system_build_id": system_build_id}) 268 self._metadata.update({"system_build_target": system_build_target}) 269 if kernel_build_id and kernel_build_target: 270 self._metadata.update({"kernel_build_id": kernel_build_id}) 271 self._metadata.update({"kernel_build_target": kernel_build_target}) 272 273 # pylint: disable=too-many-branches 274 def _GetLaunchCvdArgs(self, avd_spec=None, blank_data_disk_size_gb=None, 275 decompress_kernel=None, instance=None): 276 """Get launch_cvd args. 277 278 Args: 279 avd_spec: An AVDSpec instance. 280 blank_data_disk_size_gb: Size of the blank data disk in GB. 281 decompress_kernel: Boolean, if true decompress the kernel. 282 instance: String, instance name. 283 284 Returns: 285 String, args of launch_cvd. 286 """ 287 launch_cvd_args = [] 288 if blank_data_disk_size_gb and blank_data_disk_size_gb > 0: 289 # Policy 'create_if_missing' would create a blank userdata disk if 290 # missing. If already exist, reuse the disk. 291 launch_cvd_args.append( 292 "-data_policy=" + self.DATA_POLICY_CREATE_IF_MISSING) 293 launch_cvd_args.append( 294 "-blank_data_image_mb=%d" % (blank_data_disk_size_gb * 1024)) 295 if avd_spec: 296 launch_cvd_args.append("-config=%s" % avd_spec.flavor) 297 if avd_spec.hw_customize or not self._ArgSupportInLaunchCVD(_CONFIG_ARG): 298 launch_cvd_args.append( 299 "-x_res=" + avd_spec.hw_property[constants.HW_X_RES]) 300 launch_cvd_args.append( 301 "-y_res=" + avd_spec.hw_property[constants.HW_Y_RES]) 302 launch_cvd_args.append( 303 "-dpi=" + avd_spec.hw_property[constants.HW_ALIAS_DPI]) 304 if constants.HW_ALIAS_DISK in avd_spec.hw_property: 305 launch_cvd_args.append( 306 "-data_policy=" + self.DATA_POLICY_ALWAYS_CREATE) 307 launch_cvd_args.append( 308 "-blank_data_image_mb=" 309 + avd_spec.hw_property[constants.HW_ALIAS_DISK]) 310 if constants.HW_ALIAS_CPUS in avd_spec.hw_property: 311 launch_cvd_args.append( 312 "-cpus=%s" % avd_spec.hw_property[constants.HW_ALIAS_CPUS]) 313 if constants.HW_ALIAS_MEMORY in avd_spec.hw_property: 314 launch_cvd_args.append( 315 "-memory_mb=%s" % avd_spec.hw_property[constants.HW_ALIAS_MEMORY]) 316 if avd_spec.connect_webrtc: 317 launch_cvd_args.extend(_WEBRTC_ARGS) 318 launch_cvd_args.append(_WEBRTC_ID % {"instance": instance}) 319 if avd_spec.connect_vnc: 320 launch_cvd_args.extend(_VNC_ARGS) 321 if avd_spec.num_avds_per_instance > 1: 322 launch_cvd_args.append( 323 _NUM_AVDS_ARG % {"num_AVD": avd_spec.num_avds_per_instance}) 324 if avd_spec.launch_args: 325 launch_cvd_args.append(avd_spec.launch_args) 326 else: 327 resolution = self._resolution.split("x") 328 launch_cvd_args.append("-x_res=" + resolution[0]) 329 launch_cvd_args.append("-y_res=" + resolution[1]) 330 launch_cvd_args.append("-dpi=" + resolution[3]) 331 332 if not avd_spec and self._launch_args: 333 launch_cvd_args.append(self._launch_args) 334 335 if decompress_kernel: 336 launch_cvd_args.append(_DECOMPRESS_KERNEL_ARG) 337 338 launch_cvd_args.append(_UNDEFOK_ARG) 339 launch_cvd_args.append(_AGREEMENT_PROMPT_ARG) 340 return launch_cvd_args 341 342 def _ArgSupportInLaunchCVD(self, arg): 343 """Check if the arg is supported in launch_cvd. 344 345 Args: 346 arg: String of the arg. e.g. "-config". 347 348 Returns: 349 True if this arg is supported. Otherwise False. 350 """ 351 if arg in self._ssh.GetCmdOutput("./bin/launch_cvd --help"): 352 return True 353 return False 354 355 def StopCvd(self): 356 """Stop CVD. 357 358 If stop_cvd fails, assume that it's because there was no previously 359 running device. 360 """ 361 ssh_command = "./bin/stop_cvd" 362 try: 363 self._ssh.Run(ssh_command) 364 except subprocess.CalledProcessError as e: 365 logger.debug("Failed to stop_cvd (possibly no running device): %s", e) 366 367 def CleanUp(self): 368 """Clean up the files/folders on the existing instance. 369 370 If previous AVD have these files/folders, reusing the instance may have 371 side effects if not cleaned. The path in the instance is /home/vsoc-01/* 372 if the GCE user is vsoc-01. 373 """ 374 375 ssh_command = "'/bin/rm -rf /home/%s/*'" % self._user 376 try: 377 self._ssh.Run(ssh_command) 378 except subprocess.CalledProcessError as e: 379 logger.debug("Failed to clean up the files/folders: %s", e) 380 381 @utils.TimeExecute(function_description="Launching AVD(s) and waiting for boot up", 382 result_evaluator=utils.BootEvaluator) 383 def LaunchCvd(self, instance, avd_spec=None, 384 blank_data_disk_size_gb=None, 385 decompress_kernel=None, 386 boot_timeout_secs=None): 387 """Launch CVD. 388 389 Launch AVD with launch_cvd. If the process is failed, acloud would show 390 error messages and auto download log files from remote instance. 391 392 Args: 393 instance: String, instance name. 394 avd_spec: An AVDSpec instance. 395 blank_data_disk_size_gb: Size of the blank data disk in GB. 396 decompress_kernel: Boolean, if true decompress the kernel. 397 boot_timeout_secs: Integer, the maximum time to wait for the 398 command to respond. 399 400 Returns: 401 dict of faliures, return this dict for BootEvaluator to handle 402 LaunchCvd success or fail messages. 403 """ 404 self.SetStage(constants.STAGE_BOOT_UP) 405 timestart = time.time() 406 error_msg = "" 407 launch_cvd_args = self._GetLaunchCvdArgs(avd_spec, 408 blank_data_disk_size_gb, 409 decompress_kernel, 410 instance) 411 boot_timeout_secs = boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT 412 ssh_command = "./bin/launch_cvd -daemon " + " ".join(launch_cvd_args) 413 try: 414 self.ExtendReportData(_LAUNCH_CVD_COMMAND, ssh_command) 415 self._ssh.Run(ssh_command, boot_timeout_secs, retry=_NO_RETRY) 416 except (subprocess.CalledProcessError, errors.DeviceConnectionError) as e: 417 # TODO(b/140475060): Distinguish the error is command return error 418 # or timeout error. 419 error_msg = ("Device %s did not finish on boot within timeout (%s secs)" 420 % (instance, boot_timeout_secs)) 421 self._all_failures[instance] = error_msg 422 utils.PrintColorString(str(e), utils.TextColors.FAIL) 423 if avd_spec and not avd_spec.no_pull_log: 424 self._PullAllLogFiles(instance) 425 426 self._execution_time[_LAUNCH_CVD] = round(time.time() - timestart, 2) 427 return {instance: error_msg} if error_msg else {} 428 429 def _PullAllLogFiles(self, instance): 430 """Pull all log files from instance. 431 432 1. Download log files to temp folder. 433 2. Show messages about the download folder for users. 434 435 Args: 436 instance: String, instance name. 437 """ 438 log_files = pull.GetAllLogFilePaths(self._ssh) 439 error_log_folder = pull.GetDownloadLogFolder(instance) 440 pull.PullLogs(self._ssh, log_files, error_log_folder) 441 self.ExtendReportData(constants.ERROR_LOG_FOLDER, error_log_folder) 442 443 @utils.TimeExecute(function_description="Reusing GCE instance") 444 def _ReusingGceInstance(self, avd_spec): 445 """Reusing a cuttlefish existing instance. 446 447 Args: 448 avd_spec: An AVDSpec instance. 449 450 Returns: 451 ssh.IP object, that stores internal and external ip of the instance. 452 """ 453 gcompute_client.ComputeClient.AddSshRsaInstanceMetadata( 454 self, constants.GCE_USER, avd_spec.cfg.ssh_public_key_path, 455 avd_spec.instance_name_to_reuse) 456 ip = gcompute_client.ComputeClient.GetInstanceIP( 457 self, instance=avd_spec.instance_name_to_reuse, zone=self._zone) 458 459 return ip 460 461 @utils.TimeExecute(function_description="Creating GCE instance") 462 def _CreateGceInstance(self, instance, image_name, image_project, 463 extra_scopes, boot_disk_size_gb, avd_spec): 464 """Create a single configured cuttlefish device. 465 466 Override method from parent class. 467 Args: 468 instance: String, instance name. 469 image_name: String, the name of the GCE image. 470 image_project: String, the name of the project where the image. 471 extra_scopes: A list of extra scopes to be passed to the instance. 472 boot_disk_size_gb: Integer, size of the boot disk in GB. 473 avd_spec: An AVDSpec instance. 474 475 Returns: 476 ssh.IP object, that stores internal and external ip of the instance. 477 """ 478 self.SetStage(constants.STAGE_GCE) 479 timestart = time.time() 480 metadata = self._metadata.copy() 481 482 if avd_spec: 483 metadata[constants.INS_KEY_AVD_TYPE] = avd_spec.avd_type 484 metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor 485 metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % ( 486 avd_spec.hw_property[constants.HW_X_RES], 487 avd_spec.hw_property[constants.HW_Y_RES], 488 avd_spec.hw_property[constants.HW_ALIAS_DPI])) 489 if avd_spec.gce_metadata: 490 for key, value in avd_spec.gce_metadata.items(): 491 metadata[key] = value 492 493 disk_args = self._GetDiskArgs( 494 instance, image_name, image_project, boot_disk_size_gb) 495 gcompute_client.ComputeClient.CreateInstance( 496 self, 497 instance=instance, 498 image_name=image_name, 499 image_project=image_project, 500 disk_args=disk_args, 501 metadata=metadata, 502 machine_type=self._machine_type, 503 network=self._network, 504 zone=self._zone, 505 gpu=self._gpu, 506 extra_scopes=extra_scopes, 507 tags=["appstreaming"] if ( 508 avd_spec and avd_spec.connect_webrtc) else None) 509 ip = gcompute_client.ComputeClient.GetInstanceIP( 510 self, instance=instance, zone=self._zone) 511 logger.debug("'instance_ip': %s", ip.internal 512 if self._report_internal_ip else ip.external) 513 514 self._execution_time[_GCE_CREATE] = round(time.time() - timestart, 2) 515 return ip 516 517 @utils.TimeExecute(function_description="Uploading build fetcher to instance") 518 def UpdateFetchCvd(self): 519 """Download fetch_cvd from the Build API, and upload it to a remote instance. 520 521 The version of fetch_cvd to use is retrieved from the configuration file. Once fetch_cvd 522 is on the instance, future commands can use it to download relevant Cuttlefish files from 523 the Build API on the instance itself. 524 """ 525 self.SetStage(constants.STAGE_ARTIFACT) 526 download_dir = tempfile.mkdtemp() 527 download_target = os.path.join(download_dir, _FETCHER_NAME) 528 self._build_api.DownloadFetchcvd(download_target, self._fetch_cvd_version) 529 self._ssh.ScpPushFile(src_file=download_target, dst_file=_FETCHER_NAME) 530 os.remove(download_target) 531 os.rmdir(download_dir) 532 533 @utils.TimeExecute(function_description="Downloading build on instance") 534 def FetchBuild(self, build_id, branch, build_target, system_build_id, 535 system_branch, system_build_target, kernel_build_id, 536 kernel_branch, kernel_build_target, bootloader_build_id, 537 bootloader_branch, bootloader_build_target): 538 """Execute fetch_cvd on the remote instance to get Cuttlefish runtime files. 539 540 Args: 541 build_id: String of build id, e.g. "2263051", "P2804227" 542 branch: String of branch name, e.g. "aosp-master" 543 build_target: String of target name. 544 e.g. "aosp_cf_x86_phone-userdebug" 545 system_build_id: String of the system image build id. 546 system_branch: String of the system image branch name. 547 system_build_target: String of the system image target name, 548 e.g. "cf_x86_phone-userdebug" 549 kernel_build_id: String of the kernel image build id. 550 kernel_branch: String of the kernel image branch name. 551 kernel_build_target: String of the kernel image target name, 552 bootloader_build_id: String of the bootloader build id. 553 bootloader_branch: String of the bootloader branch name. 554 bootloader_build_target: String of the bootloader target name. 555 556 Returns: 557 List of string args for fetch_cvd. 558 """ 559 timestart = time.time() 560 fetch_cvd_args = ["-credential_source=gce"] 561 fetch_cvd_build_args = self._build_api.GetFetchBuildArgs( 562 build_id, branch, build_target, system_build_id, system_branch, 563 system_build_target, kernel_build_id, kernel_branch, 564 kernel_build_target, bootloader_build_id, bootloader_branch, 565 bootloader_build_target) 566 fetch_cvd_args.extend(fetch_cvd_build_args) 567 568 self._ssh.Run("./fetch_cvd " + " ".join(fetch_cvd_args), 569 timeout=constants.DEFAULT_SSH_TIMEOUT) 570 self._execution_time[_FETCH_ARTIFACT] = round(time.time() - timestart, 2) 571 572 def GetInstanceIP(self, instance=None): 573 """Override method from parent class. 574 575 It need to get the IP address in the common_operation. If the class 576 already defind the ip address, return the ip address. 577 578 Args: 579 instance: String, representing instance name. 580 581 Returns: 582 ssh.IP object, that stores internal and external ip of the instance. 583 """ 584 if self._ip: 585 return self._ip 586 return gcompute_client.ComputeClient.GetInstanceIP( 587 self, instance=instance, zone=self._zone) 588 589 def GetHostImageName(self, stable_image_name, image_family, image_project): 590 """Get host image name. 591 592 Args: 593 stable_image_name: String of stable host image name. 594 image_family: String of image family. 595 image_project: String of image project. 596 597 Returns: 598 String of stable host image name. 599 600 Raises: 601 errors.ConfigError: There is no host image name in config file. 602 """ 603 if stable_image_name: 604 return stable_image_name 605 606 if image_family: 607 image_name = gcompute_client.ComputeClient.GetImageFromFamily( 608 self, image_family, image_project)["name"] 609 logger.debug("Get the host image name from image family: %s", image_name) 610 return image_name 611 612 raise errors.ConfigError( 613 "Please specify 'stable_host_image_name' or 'stable_host_image_family'" 614 " in config.") 615 616 def SetStage(self, stage): 617 """Set stage to know the create progress. 618 619 Args: 620 stage: Integer, the stage would like STAGE_INIT, STAGE_GCE. 621 """ 622 self._stage = stage 623 624 @property 625 def all_failures(self): 626 """Return all_failures""" 627 return self._all_failures 628 629 @property 630 def execution_time(self): 631 """Return execution_time""" 632 return self._execution_time 633 634 @property 635 def stage(self): 636 """Return stage""" 637 return self._stage 638 639 @property 640 def build_api(self): 641 """Return build_api""" 642 return self._build_api 643