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 re 41import subprocess 42import tempfile 43import time 44 45from acloud import errors 46from acloud.internal import constants 47from acloud.internal.lib import android_build_client 48from acloud.internal.lib import android_compute_client 49from acloud.internal.lib import cvd_utils 50from acloud.internal.lib import gcompute_client 51from acloud.internal.lib import utils 52from acloud.internal.lib.ssh import Ssh 53from acloud.setup import mkcert 54 55 56logger = logging.getLogger(__name__) 57 58_CONFIG_ARG = "-config" 59_DECOMPRESS_KERNEL_ARG = "-decompress_kernel=true" 60_AGREEMENT_PROMPT_ARG = "-report_anonymous_usage_stats=y" 61_UNDEFOK_ARG = "-undefok=report_anonymous_usage_stats,config" 62_NUM_AVDS_ARG = "-num_instances=%(num_AVD)s" 63# Connect the OpenWrt device via console file. 64_ENABLE_CONSOLE_ARG = "-console=true" 65_DEFAULT_BRANCH = "aosp-master" 66_FETCHER_BUILD_TARGET = "aosp_cf_x86_64_phone-userdebug" 67_FETCHER_NAME = "fetch_cvd" 68# Time info to write in report. 69_FETCH_ARTIFACT = "fetch_artifact_time" 70_GCE_CREATE = "gce_create_time" 71_LAUNCH_CVD = "launch_cvd_time" 72# WebRTC args for launching AVD 73_START_WEBRTC = "--start_webrtc" 74_WEBRTC_ID = "--webrtc_device_id=%(instance)s" 75_VM_MANAGER = "--vm_manager=crosvm" 76_WEBRTC_ARGS = [_START_WEBRTC, _VM_MANAGER] 77_VNC_ARGS = ["--start_vnc_server=true"] 78_NO_RETRY = 0 79# Launch cvd command for acloud report 80_LAUNCH_CVD_COMMAND = "launch_cvd_command" 81_CONFIG_RE = re.compile(r"^config=(?P<config>.+)") 82_TRUST_REMOTE_INSTANCE_COMMAND = ( 83 f"\"sudo cp -p ~/{constants.WEBRTC_CERTS_PATH}/{constants.SSL_CA_NAME}.pem " 84 f"{constants.SSL_TRUST_CA_DIR}/{constants.SSL_CA_NAME}.crt;" 85 "sudo update-ca-certificates;\"") 86# Remote host instance name 87_HOST_INSTANCE_NAME_FORMAT = (constants.INSTANCE_TYPE_HOST + 88 "-%(ip_addr)s-%(build_id)s-%(build_target)s") 89_HOST_INSTANCE_NAME_PATTERN = re.compile(constants.INSTANCE_TYPE_HOST + 90 r"-(?P<ip_addr>[\d.]+)-.+") 91 92 93class CvdComputeClient(android_compute_client.AndroidComputeClient): 94 """Client that manages Android Virtual Device.""" 95 96 DATA_POLICY_CREATE_IF_MISSING = "create_if_missing" 97 # Data policy to customize disk size. 98 DATA_POLICY_ALWAYS_CREATE = "always_create" 99 100 def __init__(self, 101 acloud_config, 102 oauth2_credentials, 103 boot_timeout_secs=None, 104 ins_timeout_secs=None, 105 report_internal_ip=None, 106 gpu=None): 107 """Initialize. 108 109 Args: 110 acloud_config: An AcloudConfig object. 111 oauth2_credentials: An oauth2client.OAuth2Credentials instance. 112 boot_timeout_secs: Integer, the maximum time to wait for the AVD 113 to boot up. 114 ins_timeout_secs: Integer, the maximum time to wait for the 115 instance ready. 116 report_internal_ip: Boolean to report the internal ip instead of 117 external ip. 118 gpu: String, GPU to attach to the device. 119 """ 120 super().__init__(acloud_config, oauth2_credentials) 121 122 self._fetch_cvd_version = acloud_config.fetch_cvd_version 123 self._build_api = ( 124 android_build_client.AndroidBuildClient(oauth2_credentials)) 125 self._ssh_private_key_path = acloud_config.ssh_private_key_path 126 self._boot_timeout_secs = boot_timeout_secs 127 self._ins_timeout_secs = ins_timeout_secs 128 self._report_internal_ip = report_internal_ip 129 self._gpu = gpu 130 # Store all failures result when creating one or multiple instances. 131 # This attribute is only used by the deprecated create_cf command. 132 self._all_failures = {} 133 self._extra_args_ssh_tunnel = acloud_config.extra_args_ssh_tunnel 134 self._ssh = None 135 self._ip = None 136 self._user = constants.GCE_USER 137 self._openwrt = None 138 self._stage = constants.STAGE_INIT 139 self._execution_time = {_FETCH_ARTIFACT: 0, _GCE_CREATE: 0, _LAUNCH_CVD: 0} 140 141 @staticmethod 142 def FormatRemoteHostInstanceName(ip_addr, build_id, build_target): 143 """Convert an IP address and build info to an instance name. 144 145 Args: 146 ip_addr: String, the IP address of the remote host. 147 build_id: String, the build id. 148 build_target: String, the build target, e.g., aosp_cf_x86_64_phone. 149 150 Return: 151 String, the instance name. 152 """ 153 return _HOST_INSTANCE_NAME_FORMAT % { 154 "ip_addr": ip_addr, 155 "build_id": build_id, 156 "build_target": build_target} 157 158 @staticmethod 159 def ParseRemoteHostAddress(instance_name): 160 """Parse IP address from a remote host instance name. 161 162 Args: 163 instance_name: String, the instance name. 164 165 Returns: 166 The IP address as a string. 167 None if the name does not represent a remote host instance. 168 """ 169 match = _HOST_INSTANCE_NAME_PATTERN.fullmatch(instance_name) 170 return match.group("ip_addr") if match else None 171 172 def InitRemoteHost(self, ssh, ip, user): 173 """Init remote host. 174 175 Check if we can ssh to the remote host, stop any cf instances running 176 on it, and remove existing files. 177 178 Args: 179 ssh: Ssh object. 180 ip: namedtuple (internal, external) that holds IP address of the 181 remote host, e.g. "external:140.110.20.1, internal:10.0.0.1" 182 user: String of user log in to the instance. 183 """ 184 self.SetStage(constants.STAGE_SSH_CONNECT) 185 self._ssh = ssh 186 self._ip = ip 187 self._user = user 188 self._ssh.WaitForSsh(timeout=self._ins_timeout_secs) 189 cvd_utils.CleanUpRemoteCvd(self._ssh, raise_error=False) 190 191 # TODO(171376263): Refactor CreateInstance() args with avd_spec. 192 # pylint: disable=arguments-differ,too-many-locals,broad-except 193 def CreateInstance(self, instance, image_name, image_project, 194 build_target=None, branch=None, build_id=None, 195 kernel_branch=None, kernel_build_id=None, 196 kernel_build_target=None, blank_data_disk_size_gb=None, 197 avd_spec=None, extra_scopes=None, 198 system_build_target=None, system_branch=None, 199 system_build_id=None, bootloader_build_target=None, 200 bootloader_branch=None, bootloader_build_id=None, 201 ota_build_target=None, ota_branch=None, 202 ota_build_id=None): 203 204 """Create/Reuse a single configured cuttlefish device. 205 1. Prepare GCE instance. 206 Create a new instnace or get IP address for reusing the specific instance. 207 2. Put fetch_cvd on the instance. 208 3. Invoke fetch_cvd to fetch and run the instance. 209 210 Args: 211 instance: instance name. 212 image_name: A string, the name of the GCE image. 213 image_project: A string, name of the project where the image lives. 214 Assume the default project if None. 215 build_target: Target name, e.g. "aosp_cf_x86_64_phone-userdebug" 216 branch: Branch name, e.g. "aosp-master" 217 build_id: Build id, a string, e.g. "2263051", "P2804227" 218 kernel_branch: Kernel branch name, e.g. "kernel-common-android-4.14" 219 kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427" 220 kernel_build_target: String, Kernel build target name. 221 blank_data_disk_size_gb: Size of the blank data disk in GB. 222 avd_spec: An AVDSpec instance. 223 extra_scopes: A list of extra scopes to be passed to the instance. 224 system_build_target: String of the system image target name, 225 e.g. "cf_x86_phone-userdebug" 226 system_branch: String of the system image branch name. 227 system_build_id: String of the system image build id. 228 bootloader_build_target: String of the bootloader target name. 229 bootloader_branch: String of the bootloader branch name. 230 bootloader_build_id: String of the bootloader build id. 231 ota_build_target: String of the otatools target name. 232 ota_branch: String of the otatools branch name. 233 ota_build_id: String of the otatools build id. 234 235 Returns: 236 A string, representing instance name. 237 """ 238 239 # A blank data disk would be created on the host. Make sure the size of 240 # the boot disk is large enough to hold it. 241 boot_disk_size_gb = ( 242 int(self.GetImage(image_name, image_project)["diskSizeGb"]) + 243 blank_data_disk_size_gb) 244 245 if avd_spec and avd_spec.instance_name_to_reuse: 246 self._ip = self._ReusingGceInstance(avd_spec) 247 else: 248 self._VerifyZoneByQuota() 249 self._ip = self._CreateGceInstance(instance, image_name, image_project, 250 extra_scopes, boot_disk_size_gb, 251 avd_spec) 252 self._ssh = Ssh(ip=self._ip, 253 user=constants.GCE_USER, 254 ssh_private_key_path=self._ssh_private_key_path, 255 extra_args_ssh_tunnel=self._extra_args_ssh_tunnel, 256 report_internal_ip=self._report_internal_ip) 257 try: 258 self.SetStage(constants.STAGE_SSH_CONNECT) 259 self._ssh.WaitForSsh(timeout=self._ins_timeout_secs) 260 if avd_spec: 261 if avd_spec.instance_name_to_reuse: 262 cvd_utils.CleanUpRemoteCvd(self._ssh, raise_error=False) 263 return instance 264 265 # TODO: Remove following code after create_cf deprecated. 266 self.UpdateFetchCvd() 267 268 self.FetchBuild(build_id, branch, build_target, system_build_id, 269 system_branch, system_build_target, kernel_build_id, 270 kernel_branch, kernel_build_target, bootloader_build_id, 271 bootloader_branch, bootloader_build_target, 272 ota_build_id, ota_branch, ota_build_target) 273 failures = self.LaunchCvd( 274 instance, 275 blank_data_disk_size_gb=blank_data_disk_size_gb, 276 boot_timeout_secs=self._boot_timeout_secs, 277 extra_args=[]) 278 self._all_failures.update(failures) 279 return instance 280 except Exception as e: 281 self._all_failures[instance] = e 282 return instance 283 284 def _GetConfigFromAndroidInfo(self): 285 """Get config value from android-info.txt. 286 287 The config in android-info.txt would like "config=phone". 288 289 Returns: 290 Strings of config value. 291 """ 292 android_info = self._ssh.GetCmdOutput( 293 "cat %s" % constants.ANDROID_INFO_FILE) 294 logger.debug("Android info: %s", android_info) 295 config_match = _CONFIG_RE.match(android_info) 296 if config_match: 297 return config_match.group("config") 298 return None 299 300 # pylint: disable=too-many-branches 301 def _GetLaunchCvdArgs(self, avd_spec=None, blank_data_disk_size_gb=None, 302 decompress_kernel=None, instance=None): 303 """Get launch_cvd args. 304 305 Args: 306 avd_spec: An AVDSpec instance. 307 blank_data_disk_size_gb: Size of the blank data disk in GB. 308 decompress_kernel: Boolean, if true decompress the kernel. 309 instance: String, instance name. 310 311 Returns: 312 String, args of launch_cvd. 313 """ 314 launch_cvd_args = [] 315 if blank_data_disk_size_gb and blank_data_disk_size_gb > 0: 316 # Policy 'create_if_missing' would create a blank userdata disk if 317 # missing. If already exist, reuse the disk. 318 launch_cvd_args.append( 319 "-data_policy=" + self.DATA_POLICY_CREATE_IF_MISSING) 320 launch_cvd_args.append( 321 "-blank_data_image_mb=%d" % (blank_data_disk_size_gb * 1024)) 322 if avd_spec: 323 config = self._GetConfigFromAndroidInfo() 324 if config: 325 launch_cvd_args.append("-config=%s" % config) 326 if avd_spec.hw_customize or not config: 327 launch_cvd_args.append( 328 "-x_res=" + avd_spec.hw_property[constants.HW_X_RES]) 329 launch_cvd_args.append( 330 "-y_res=" + avd_spec.hw_property[constants.HW_Y_RES]) 331 launch_cvd_args.append( 332 "-dpi=" + avd_spec.hw_property[constants.HW_ALIAS_DPI]) 333 if constants.HW_ALIAS_DISK in avd_spec.hw_property: 334 launch_cvd_args.append( 335 "-data_policy=" + self.DATA_POLICY_ALWAYS_CREATE) 336 launch_cvd_args.append( 337 "-blank_data_image_mb=" 338 + avd_spec.hw_property[constants.HW_ALIAS_DISK]) 339 if constants.HW_ALIAS_CPUS in avd_spec.hw_property: 340 launch_cvd_args.append( 341 "-cpus=%s" % avd_spec.hw_property[constants.HW_ALIAS_CPUS]) 342 if constants.HW_ALIAS_MEMORY in avd_spec.hw_property: 343 launch_cvd_args.append( 344 "-memory_mb=%s" % avd_spec.hw_property[constants.HW_ALIAS_MEMORY]) 345 if avd_spec.connect_webrtc: 346 launch_cvd_args.extend(_WEBRTC_ARGS) 347 launch_cvd_args.append(_WEBRTC_ID % {"instance": instance}) 348 if avd_spec.connect_vnc: 349 launch_cvd_args.extend(_VNC_ARGS) 350 if avd_spec.openwrt: 351 launch_cvd_args.append(_ENABLE_CONSOLE_ARG) 352 if avd_spec.num_avds_per_instance > 1: 353 launch_cvd_args.append( 354 _NUM_AVDS_ARG % {"num_AVD": avd_spec.num_avds_per_instance}) 355 if avd_spec.base_instance_num: 356 launch_cvd_args.append( 357 "--base-instance-num=%s" % avd_spec.base_instance_num) 358 if avd_spec.launch_args: 359 launch_cvd_args.append(avd_spec.launch_args) 360 else: 361 resolution = self._resolution.split("x") 362 launch_cvd_args.append("-x_res=" + resolution[0]) 363 launch_cvd_args.append("-y_res=" + resolution[1]) 364 launch_cvd_args.append("-dpi=" + resolution[3]) 365 366 if not avd_spec and self._launch_args: 367 launch_cvd_args.append(self._launch_args) 368 369 if decompress_kernel: 370 launch_cvd_args.append(_DECOMPRESS_KERNEL_ARG) 371 372 launch_cvd_args.append(_UNDEFOK_ARG) 373 launch_cvd_args.append(_AGREEMENT_PROMPT_ARG) 374 return launch_cvd_args 375 376 @utils.TimeExecute(function_description="Launching AVD(s) and waiting for boot up", 377 result_evaluator=utils.BootEvaluator) 378 def LaunchCvd(self, instance, avd_spec=None, 379 blank_data_disk_size_gb=None, 380 decompress_kernel=None, 381 boot_timeout_secs=None, 382 extra_args=()): 383 """Launch CVD. 384 385 Launch AVD with launch_cvd. If the process is failed, acloud would show 386 error messages and auto download log files from remote instance. 387 388 Args: 389 instance: String, instance name. 390 avd_spec: An AVDSpec instance. 391 blank_data_disk_size_gb: Size of the blank data disk in GB. 392 decompress_kernel: Boolean, if true decompress the kernel. 393 boot_timeout_secs: Integer, the maximum time to wait for the 394 command to respond. 395 extra_args: Collection of strings, the extra arguments generated by 396 acloud. e.g., remote image paths. 397 398 Returns: 399 dict of faliures, return this dict for BootEvaluator to handle 400 LaunchCvd success or fail messages. 401 """ 402 self.SetStage(constants.STAGE_BOOT_UP) 403 timestart = time.time() 404 error_msg = "" 405 launch_cvd_args = list(extra_args) 406 launch_cvd_args.extend( 407 self._GetLaunchCvdArgs(avd_spec, blank_data_disk_size_gb, 408 decompress_kernel, instance)) 409 boot_timeout_secs = self._GetBootTimeout( 410 boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT) 411 ssh_command = "./bin/launch_cvd -daemon " + " ".join(launch_cvd_args) 412 try: 413 if avd_spec and avd_spec.base_instance_num: 414 self.ExtendReportData(constants.BASE_INSTANCE_NUM, avd_spec.base_instance_num) 415 self.ExtendReportData(_LAUNCH_CVD_COMMAND, ssh_command) 416 self._ssh.Run(ssh_command, boot_timeout_secs, retry=_NO_RETRY) 417 self._UpdateOpenWrtStatus(avd_spec) 418 except (subprocess.CalledProcessError, errors.DeviceConnectionError, 419 errors.LaunchCVDFail) as e: 420 error_msg = ("Device %s did not finish on boot within timeout (%s secs)" 421 % (instance, boot_timeout_secs)) 422 if constants.ERROR_MSG_VNC_NOT_SUPPORT in str(e): 423 error_msg = ( 424 "VNC is not supported in the current build. Please try WebRTC such " 425 "as '$acloud create' or '$acloud create --autoconnect webrtc'") 426 if constants.ERROR_MSG_WEBRTC_NOT_SUPPORT in str(e): 427 error_msg = ( 428 "WEBRTC is not supported in the current build. Please try VNC such " 429 "as '$acloud create --autoconnect vnc'") 430 utils.PrintColorString(str(e), utils.TextColors.FAIL) 431 432 self._execution_time[_LAUNCH_CVD] = round(time.time() - timestart, 2) 433 return {instance: error_msg} if error_msg else {} 434 435 def _GetBootTimeout(self, timeout_secs): 436 """Get boot timeout. 437 438 Timeout settings includes download artifacts and boot up. 439 440 Args: 441 timeout_secs: integer of timeout value. 442 443 Returns: 444 The timeout values for device boots up. 445 """ 446 boot_timeout_secs = timeout_secs - self._execution_time[_FETCH_ARTIFACT] 447 logger.debug("Timeout for boot: %s secs", boot_timeout_secs) 448 return boot_timeout_secs 449 450 @utils.TimeExecute(function_description="Reusing GCE instance") 451 def _ReusingGceInstance(self, avd_spec): 452 """Reusing a cuttlefish existing instance. 453 454 Args: 455 avd_spec: An AVDSpec instance. 456 457 Returns: 458 ssh.IP object, that stores internal and external ip of the instance. 459 """ 460 gcompute_client.ComputeClient.AddSshRsaInstanceMetadata( 461 self, constants.GCE_USER, avd_spec.cfg.ssh_public_key_path, 462 avd_spec.instance_name_to_reuse) 463 ip = gcompute_client.ComputeClient.GetInstanceIP( 464 self, instance=avd_spec.instance_name_to_reuse, zone=self._zone) 465 466 return ip 467 468 @utils.TimeExecute(function_description="Creating GCE instance") 469 def _CreateGceInstance(self, instance, image_name, image_project, 470 extra_scopes, boot_disk_size_gb, avd_spec): 471 """Create a single configured cuttlefish device. 472 473 Override method from parent class. 474 Args: 475 instance: String, instance name. 476 image_name: String, the name of the GCE image. 477 image_project: String, the name of the project where the image. 478 extra_scopes: A list of extra scopes to be passed to the instance. 479 boot_disk_size_gb: Integer, size of the boot disk in GB. 480 avd_spec: An AVDSpec instance. 481 482 Returns: 483 ssh.IP object, that stores internal and external ip of the instance. 484 """ 485 self.SetStage(constants.STAGE_GCE) 486 timestart = time.time() 487 metadata = self._metadata.copy() 488 489 if avd_spec: 490 metadata[constants.INS_KEY_AVD_TYPE] = avd_spec.avd_type 491 metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor 492 metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % ( 493 avd_spec.hw_property[constants.HW_X_RES], 494 avd_spec.hw_property[constants.HW_Y_RES], 495 avd_spec.hw_property[constants.HW_ALIAS_DPI])) 496 if avd_spec.gce_metadata: 497 for key, value in avd_spec.gce_metadata.items(): 498 metadata[key] = value 499 # Record webrtc port, it will be removed if cvd support to show it. 500 if avd_spec.connect_webrtc: 501 metadata[constants.INS_KEY_WEBRTC_PORT] = constants.WEBRTC_LOCAL_PORT 502 503 disk_args = self._GetDiskArgs( 504 instance, image_name, image_project, boot_disk_size_gb) 505 disable_external_ip = avd_spec.disable_external_ip if avd_spec else False 506 gcompute_client.ComputeClient.CreateInstance( 507 self, 508 instance=instance, 509 image_name=image_name, 510 image_project=image_project, 511 disk_args=disk_args, 512 metadata=metadata, 513 machine_type=self._machine_type, 514 network=self._network, 515 zone=self._zone, 516 gpu=self._gpu, 517 disk_type=avd_spec.disk_type if avd_spec else None, 518 extra_scopes=extra_scopes, 519 disable_external_ip=disable_external_ip) 520 ip = gcompute_client.ComputeClient.GetInstanceIP( 521 self, instance=instance, zone=self._zone) 522 logger.debug("'instance_ip': %s", ip.internal 523 if self._report_internal_ip else ip.external) 524 525 self._execution_time[_GCE_CREATE] = round(time.time() - timestart, 2) 526 return ip 527 528 @utils.TimeExecute(function_description="Uploading build fetcher to instance") 529 def UpdateFetchCvd(self): 530 """Download fetch_cvd from the Build API, and upload it to a remote instance. 531 532 The version of fetch_cvd to use is retrieved from the configuration file. Once fetch_cvd 533 is on the instance, future commands can use it to download relevant Cuttlefish files from 534 the Build API on the instance itself. 535 """ 536 self.SetStage(constants.STAGE_ARTIFACT) 537 download_dir = tempfile.mkdtemp() 538 download_target = os.path.join(download_dir, _FETCHER_NAME) 539 self._build_api.DownloadFetchcvd(download_target, self._fetch_cvd_version) 540 self._ssh.ScpPushFile(src_file=download_target, dst_file=_FETCHER_NAME) 541 os.remove(download_target) 542 os.rmdir(download_dir) 543 544 @utils.TimeExecute(function_description="Downloading build on instance") 545 def FetchBuild(self, build_id, branch, build_target, system_build_id, 546 system_branch, system_build_target, kernel_build_id, 547 kernel_branch, kernel_build_target, bootloader_build_id, 548 bootloader_branch, bootloader_build_target, ota_build_id, 549 ota_branch, ota_build_target): 550 """Execute fetch_cvd on the remote instance to get Cuttlefish runtime files. 551 552 Args: 553 build_id: String of build id, e.g. "2263051", "P2804227" 554 branch: String of branch name, e.g. "aosp-master" 555 build_target: String of target name. 556 e.g. "aosp_cf_x86_64_phone-userdebug" 557 system_build_id: String of the system image build id. 558 system_branch: String of the system image branch name. 559 system_build_target: String of the system image target name, 560 e.g. "cf_x86_phone-userdebug" 561 kernel_build_id: String of the kernel image build id. 562 kernel_branch: String of the kernel image branch name. 563 kernel_build_target: String of the kernel image target name, 564 bootloader_build_id: String of the bootloader build id. 565 bootloader_branch: String of the bootloader branch name. 566 bootloader_build_target: String of the bootloader target name. 567 ota_build_id: String of the otatools build id. 568 ota_branch: String of the otatools branch name. 569 ota_build_target: String of the otatools target name. 570 571 Returns: 572 List of string args for fetch_cvd. 573 """ 574 timestart = time.time() 575 fetch_cvd_args = ["-credential_source=gce"] 576 fetch_cvd_build_args = self._build_api.GetFetchBuildArgs( 577 build_id, branch, build_target, system_build_id, system_branch, 578 system_build_target, kernel_build_id, kernel_branch, 579 kernel_build_target, bootloader_build_id, bootloader_branch, 580 bootloader_build_target, ota_build_id, ota_branch, ota_build_target) 581 fetch_cvd_args.extend(fetch_cvd_build_args) 582 583 self._ssh.Run("./fetch_cvd " + " ".join(fetch_cvd_args), 584 timeout=constants.DEFAULT_SSH_TIMEOUT) 585 self._execution_time[_FETCH_ARTIFACT] = round(time.time() - timestart, 2) 586 587 @utils.TimeExecute(function_description="Update instance's certificates") 588 def UpdateCertificate(self): 589 """Update webrtc default certificates of the remote instance. 590 591 For trusting both the localhost and remote instance, the process will 592 upload certificates(rootCA.pem, server.crt, server.key) and the mkcert 593 tool from the client workstation to remote instance where running the 594 mkcert with the uploaded rootCA file and replace the webrtc frontend 595 default certificates for connecting to a remote webrtc AVD without the 596 insecure warning. 597 """ 598 local_cert_dir = os.path.join(os.path.expanduser("~"), 599 constants.SSL_DIR) 600 if mkcert.AllocateLocalHostCert(): 601 upload_files = [] 602 for cert_file in (constants.WEBRTC_CERTS_FILES + 603 [f"{constants.SSL_CA_NAME}.pem"]): 604 upload_files.append(os.path.join(local_cert_dir, 605 cert_file)) 606 try: 607 self._ssh.ScpPushFiles(upload_files, constants.WEBRTC_CERTS_PATH) 608 self._ssh.Run(_TRUST_REMOTE_INSTANCE_COMMAND) 609 except subprocess.CalledProcessError: 610 logger.debug("Update WebRTC frontend certificate failed.") 611 612 @utils.TimeExecute(function_description="Upload extra files to instance") 613 def UploadExtraFiles(self, extra_files): 614 """Upload extra files into GCE instance. 615 616 Args: 617 extra_files: List of namedtuple ExtraFile. 618 619 Raises: 620 errors.CheckPathError: The provided path doesn't exist. 621 """ 622 for extra_file in extra_files: 623 if not os.path.exists(extra_file.source): 624 raise errors.CheckPathError( 625 "The path doesn't exist: %s" % extra_file.source) 626 self._ssh.ScpPushFile(extra_file.source, extra_file.target) 627 628 def GetSshConnectCmd(self): 629 """Get ssh connect command. 630 631 Returns: 632 String of ssh connect command. 633 """ 634 return self._ssh.GetBaseCmd(constants.SSH_BIN) 635 636 def GetInstanceIP(self, instance=None): 637 """Override method from parent class. 638 639 It need to get the IP address in the common_operation. If the class 640 already defind the ip address, return the ip address. 641 642 Args: 643 instance: String, representing instance name. 644 645 Returns: 646 ssh.IP object, that stores internal and external ip of the instance. 647 """ 648 if self._ip: 649 return self._ip 650 return gcompute_client.ComputeClient.GetInstanceIP( 651 self, instance=instance, zone=self._zone) 652 653 def GetHostImageName(self, stable_image_name, image_family, image_project): 654 """Get host image name. 655 656 Args: 657 stable_image_name: String of stable host image name. 658 image_family: String of image family. 659 image_project: String of image project. 660 661 Returns: 662 String of stable host image name. 663 664 Raises: 665 errors.ConfigError: There is no host image name in config file. 666 """ 667 if stable_image_name: 668 return stable_image_name 669 670 if image_family: 671 image_name = gcompute_client.ComputeClient.GetImageFromFamily( 672 self, image_family, image_project)["name"] 673 logger.debug("Get the host image name from image family: %s", image_name) 674 return image_name 675 676 raise errors.ConfigError( 677 "Please specify 'stable_host_image_name' or 'stable_host_image_family'" 678 " in config.") 679 680 def SetStage(self, stage): 681 """Set stage to know the create progress. 682 683 Args: 684 stage: Integer, the stage would like STAGE_INIT, STAGE_GCE. 685 """ 686 self._stage = stage 687 688 def _UpdateOpenWrtStatus(self, avd_spec): 689 """Update the OpenWrt device status. 690 691 Args: 692 avd_spec: An AVDSpec instance. 693 """ 694 self._openwrt = avd_spec.openwrt if avd_spec else False 695 696 @property 697 def all_failures(self): 698 """Return all_failures""" 699 return self._all_failures 700 701 @property 702 def execution_time(self): 703 """Return execution_time""" 704 return self._execution_time 705 706 @property 707 def stage(self): 708 """Return stage""" 709 return self._stage 710 711 @property 712 def openwrt(self): 713 """Return openwrt""" 714 return self._openwrt 715 716 @property 717 def build_api(self): 718 """Return build_api""" 719 return self._build_api 720