1# Copyright 2021 - 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 15"""RemoteInstanceDeviceFactory provides basic interface to create a goldfish 16device factory.""" 17 18import collections 19import logging 20import os 21import posixpath as remote_path 22import re 23import shutil 24import tempfile 25import zipfile 26 27from acloud import errors 28from acloud.internal import constants 29from acloud.internal.lib import android_build_client 30from acloud.internal.lib import auth 31from acloud.internal.lib import goldfish_remote_host_client 32from acloud.internal.lib import goldfish_utils 33from acloud.internal.lib import emulator_console 34from acloud.internal.lib import ota_tools 35from acloud.internal.lib import utils 36from acloud.internal.lib import ssh 37from acloud.public import report 38from acloud.public.actions import base_device_factory 39 40 41logger = logging.getLogger(__name__) 42# Artifacts 43_SDK_REPO_IMAGE_ZIP_NAME_FORMAT = ("sdk-repo-linux-system-images-" 44 "%(build_id)s.zip") 45_EXTRA_IMAGE_ZIP_NAME_FORMAT = "emu-extra-linux-system-images-%(build_id)s.zip" 46_IMAGE_ZIP_NAME_FORMAT = "%(build_target)s-img-%(build_id)s.zip" 47_OTA_TOOLS_ZIP_NAME = "otatools.zip" 48_SYSTEM_IMAGE_NAME = "system.img" 49 50_EMULATOR_INFO_NAME = "emulator-info.txt" 51_EMULATOR_VERSION_PATTERN = re.compile(r"require\s+version-emulator=" 52 r"(?P<build_id>\w+)") 53_EMULATOR_ZIP_NAME_FORMAT = "sdk-repo-%(os)s-emulator-%(build_id)s.zip" 54_EMULATOR_BIN_DIR_NAMES = ("bin64", "qemu") 55_EMULATOR_BIN_NAME = "emulator" 56# Remote paths 57_REMOTE_WORKING_DIR = "acloud_gf" 58_REMOTE_ARTIFACT_DIR = remote_path.join(_REMOTE_WORKING_DIR, "artifact") 59_REMOTE_IMAGE_DIR = remote_path.join(_REMOTE_WORKING_DIR, "image") 60_REMOTE_KERNEL_PATH = remote_path.join(_REMOTE_WORKING_DIR, "kernel") 61_REMOTE_RAMDISK_PATH = remote_path.join(_REMOTE_WORKING_DIR, "mixed_ramdisk") 62_REMOTE_EMULATOR_DIR = remote_path.join(_REMOTE_WORKING_DIR, "emulator") 63_REMOTE_INSTANCE_DIR = remote_path.join(_REMOTE_WORKING_DIR, "instance") 64_REMOTE_LOGCAT_PATH = os.path.join(_REMOTE_INSTANCE_DIR, "logcat.txt") 65_REMOTE_STDOUTERR_PATH = os.path.join(_REMOTE_INSTANCE_DIR, "kernel.log") 66# Runtime parameters 67_EMULATOR_DEFAULT_CONSOLE_PORT = 5554 68_DEFAULT_BOOT_TIMEOUT_SECS = 150 69 70ArtifactPaths = collections.namedtuple( 71 "ArtifactPaths", 72 ["image_zip", "emulator_zip", "ota_tools_zip", 73 "system_image_zip", "boot_image"]) 74 75RemotePaths = collections.namedtuple( 76 "RemotePaths", 77 ["image_dir", "emulator_dir", "kernel", "ramdisk"]) 78 79 80class RemoteHostGoldfishDeviceFactory(base_device_factory.BaseDeviceFactory): 81 """A class that creates a goldfish device on a remote host. 82 83 Attributes: 84 avd_spec: AVDSpec object that tells us what we're going to create. 85 ssh: Ssh object that executes commands on the remote host. 86 failures: A dictionary the maps instance names to 87 error.DeviceBootError objects. 88 logs: A dictionary that maps instance names to lists of report.LogFile. 89 """ 90 def __init__(self, avd_spec): 91 """Initialize the attributes and the compute client.""" 92 self._avd_spec = avd_spec 93 self._ssh = ssh.Ssh( 94 ip=ssh.IP(ip=self._avd_spec.remote_host), 95 user=self._ssh_user, 96 ssh_private_key_path=self._ssh_private_key_path, 97 extra_args_ssh_tunnel=self._ssh_extra_args, 98 report_internal_ip=False) 99 self._failures = {} 100 self._logs = {} 101 super().__init__(compute_client=( 102 goldfish_remote_host_client.GoldfishRemoteHostClient())) 103 104 @property 105 def _ssh_user(self): 106 return self._avd_spec.host_user or constants.GCE_USER 107 108 @property 109 def _ssh_private_key_path(self): 110 return (self._avd_spec.host_ssh_private_key_path or 111 self._avd_spec.cfg.ssh_private_key_path) 112 113 @property 114 def _ssh_extra_args(self): 115 return self._avd_spec.cfg.extra_args_ssh_tunnel 116 117 def CreateInstance(self): 118 """Create a goldfish instance on the remote host. 119 120 Returns: 121 The instance name. 122 """ 123 self._InitRemoteHost() 124 remote_paths = self._PrepareArtifacts() 125 126 instance_name = goldfish_remote_host_client.FormatInstanceName( 127 self._avd_spec.remote_host, 128 _EMULATOR_DEFAULT_CONSOLE_PORT, 129 self._avd_spec.remote_image) 130 self._logs[instance_name] = [ 131 report.LogFile(_REMOTE_STDOUTERR_PATH, 132 constants.LOG_TYPE_KERNEL_LOG), 133 report.LogFile(_REMOTE_LOGCAT_PATH, constants.LOG_TYPE_LOGCAT)] 134 try: 135 self._StartEmulator(remote_paths) 136 self._WaitForEmulator() 137 except errors.DeviceBootError as e: 138 self._failures[instance_name] = e 139 return instance_name 140 141 def _InitRemoteHost(self): 142 """Remove existing instance and working directory.""" 143 # Disable authentication for emulator console. 144 self._ssh.Run("""'echo -n "" > .emulator_console_auth_token'""") 145 try: 146 with emulator_console.RemoteEmulatorConsole( 147 self._avd_spec.remote_host, 148 _EMULATOR_DEFAULT_CONSOLE_PORT, 149 self._ssh_user, 150 self._ssh_private_key_path, 151 self._ssh_extra_args) as console: 152 console.Kill() 153 logger.info("Killed existing emulator.") 154 except errors.DeviceConnectionError as e: 155 logger.info("Did not kill existing emulator: %s", str(e)) 156 # Delete instance files. 157 self._ssh.Run("rm -rf %s" % _REMOTE_WORKING_DIR) 158 159 def _PrepareArtifacts(self): 160 """Prepare artifacts on remote host. 161 162 This method retrieves artifacts from cache or Android Build API and 163 uploads them to the remote host. 164 165 Returns: 166 An object of RemotePaths. 167 """ 168 if self._avd_spec.image_download_dir: 169 temp_download_dir = None 170 download_dir = self._avd_spec.image_download_dir 171 else: 172 temp_download_dir = tempfile.mkdtemp() 173 download_dir = temp_download_dir 174 logger.info("--image-download-dir is not specified. Create " 175 "temporary download directory: %s", download_dir) 176 177 try: 178 artifact_paths = self._RetrieveArtifacts(download_dir) 179 return self._UploadArtifacts(artifact_paths) 180 finally: 181 if temp_download_dir: 182 shutil.rmtree(temp_download_dir, ignore_errors=True) 183 184 @staticmethod 185 def _InferEmulatorZipName(build_target, build_id): 186 """Determine the emulator zip name in build artifacts. 187 188 The emulator zip name is composed of build variables that are not 189 revealed in the artifacts. This method infers the emulator zip name 190 from its build target name. 191 192 Args: 193 build_target: The emulator build target name, e.g., 194 "sdk_tools_linux", "aarch64_sdk_tools_mac". 195 build_id: A string, the emulator build ID. 196 197 Returns: 198 The name of the emulator zip. e.g., 199 "sdk-repo-linux-emulator-123456.zip", 200 "sdk-repo-darwin_aarch64-emulator-123456.zip". 201 """ 202 split_target = [x for product_variant in build_target.split("-") 203 for x in product_variant.split("_")] 204 if "darwin" in split_target or "mac" in split_target: 205 os_name = "darwin" 206 else: 207 os_name = "linux" 208 if "aarch64" in split_target: 209 os_name = os_name + "_aarch64" 210 return _EMULATOR_ZIP_NAME_FORMAT % {"os": os_name, 211 "build_id": build_id} 212 213 @staticmethod 214 def _RetrieveArtifact(download_dir, build_api, build_target, build_id, 215 resource_id): 216 """Retrieve an artifact from cache or Android Build API. 217 218 Args: 219 download_dir: The cache directory. 220 build_api: An AndroidBuildClient object. 221 build_target: A string, the build target of the artifact. e.g., 222 "sdk_phone_x86_64-userdebug". 223 build_id: A string, the build ID of the artifact. 224 resource_id: A string, the name of the artifact. e.g., 225 "sdk-repo-linux-system-images-123456.zip". 226 227 Returns: 228 The path to the artifact in download_dir. 229 """ 230 local_path = os.path.join(download_dir, build_id, build_target, 231 resource_id) 232 if os.path.isfile(local_path): 233 logger.info("Skip downloading existing artifact: %s", local_path) 234 return local_path 235 236 complete = False 237 try: 238 os.makedirs(os.path.dirname(local_path), exist_ok=True) 239 build_api.DownloadArtifact(build_target, build_id, resource_id, 240 local_path, build_api.LATEST) 241 complete = True 242 finally: 243 if not complete and os.path.isfile(local_path): 244 os.remove(local_path) 245 return local_path 246 247 def _RetrieveEmulatorBuildID(self, download_dir, build_api, build_target, 248 build_id): 249 """Retrieve required emulator build from a goldfish image build.""" 250 emulator_info_path = self._RetrieveArtifact(download_dir, build_api, 251 build_target, build_id, 252 _EMULATOR_INFO_NAME) 253 with open(emulator_info_path, 'r') as emulator_info: 254 for line in emulator_info: 255 match = _EMULATOR_VERSION_PATTERN.fullmatch(line.strip()) 256 if match: 257 logger.info("Found emulator build ID: %s", line) 258 return match.group("build_id") 259 return None 260 261 @utils.TimeExecute(function_description="Download Android Build artifacts") 262 def _RetrieveArtifacts(self, download_dir): 263 """Retrieve goldfish images and tools from cache or Android Build API. 264 265 Args: 266 download_dir: The cache directory. 267 268 Returns: 269 An object of ArtifactPaths. 270 271 Raises: 272 errors.GetRemoteImageError: Fails to download rom images. 273 """ 274 credentials = auth.CreateCredentials(self._avd_spec.cfg) 275 build_api = android_build_client.AndroidBuildClient(credentials) 276 # Device images. 277 build_id = self._avd_spec.remote_image.get(constants.BUILD_ID) 278 build_target = self._avd_spec.remote_image.get(constants.BUILD_TARGET) 279 image_zip_name_format = (_EXTRA_IMAGE_ZIP_NAME_FORMAT if 280 self._ShouldMixDiskImage() else 281 _SDK_REPO_IMAGE_ZIP_NAME_FORMAT) 282 image_zip_path = self._RetrieveArtifact( 283 download_dir, build_api, build_target, build_id, 284 image_zip_name_format % {"build_id": build_id}) 285 286 # Emulator tools. 287 emu_build_id = self._avd_spec.emulator_build_id 288 if not emu_build_id: 289 emu_build_id = self._RetrieveEmulatorBuildID( 290 download_dir, build_api, build_target, build_id) 291 if not emu_build_id: 292 raise errors.GetRemoteImageError( 293 "No emulator build ID in command line or " 294 "emulator-info.txt.") 295 296 emu_build_target = (self._avd_spec.emulator_build_target or 297 self._avd_spec.cfg.emulator_build_target) 298 emu_zip_name = self._InferEmulatorZipName(emu_build_target, 299 emu_build_id) 300 emu_zip_path = self._RetrieveArtifact(download_dir, build_api, 301 emu_build_target, emu_build_id, 302 emu_zip_name) 303 304 system_image_zip_path = self._RetrieveSystemImageZip( 305 download_dir, build_api) 306 boot_image_path = self._RetrieveBootImage(download_dir, build_api) 307 # Retrieve OTA tools from the goldfish build which contains 308 # mk_combined_img. 309 ota_tools_zip_path = ( 310 self._RetrieveArtifact(download_dir, build_api, build_target, 311 build_id, _OTA_TOOLS_ZIP_NAME) 312 if system_image_zip_path or boot_image_path else None) 313 314 return ArtifactPaths(image_zip_path, emu_zip_path, 315 ota_tools_zip_path, system_image_zip_path, 316 boot_image_path) 317 318 def _RetrieveSystemImageZip(self, download_dir, build_api): 319 """Retrieve system image zip if system build info is not empty. 320 321 Args: 322 download_dir: The download cache directory. 323 build_api: An AndroidBuildClient object. 324 325 Returns: 326 The path to the system image zip in download_dir. 327 None if the system build info is empty. 328 """ 329 build_id = self._avd_spec.system_build_info.get(constants.BUILD_ID) 330 build_target = self._avd_spec.system_build_info.get( 331 constants.BUILD_TARGET) 332 if build_id and build_target: 333 image_zip_name = _IMAGE_ZIP_NAME_FORMAT % { 334 "build_target": build_target.split("-", 1)[0], 335 "build_id": build_id} 336 return self._RetrieveArtifact( 337 download_dir, build_api, build_target, build_id, 338 image_zip_name) 339 return None 340 341 def _RetrieveBootImage(self, download_dir, build_api): 342 """Retrieve boot image if kernel build info is not empty. 343 344 Args: 345 download_dir: The download cache directory. 346 build_api: An AndroidBuildClient object. 347 348 Returns: 349 The path to the boot image in download_dir. 350 None if the kernel build info is empty. 351 """ 352 build_id = self._avd_spec.kernel_build_info.get(constants.BUILD_ID) 353 build_target = self._avd_spec.kernel_build_info.get( 354 constants.BUILD_TARGET) 355 image_name = self._avd_spec.kernel_build_info.get( 356 constants.BUILD_ARTIFACT) 357 if build_id and build_target and image_name: 358 return self._RetrieveArtifact( 359 download_dir, build_api, build_target, build_id, image_name) 360 return None 361 362 @staticmethod 363 def _GetSubdirNameInZip(zip_path): 364 """Get the name of the only subdirectory in a zip. 365 366 In an SDK repository zip, the images and the binaries are located in a 367 subdirectory. This class needs to find out the subdirectory name in 368 order to construct the remote commands. 369 370 For example, in sdk-repo-*-emulator-*.zip, all files are in 371 "emulator/". The zip entries are: 372 373 emulator/NOTICE.txt 374 emulator/emulator 375 emulator/lib64/libc++.so 376 ... 377 378 This method scans the entries and returns the common subdirectory name. 379 """ 380 sep = "/" 381 with zipfile.ZipFile(zip_path, 'r') as zip_obj: 382 entries = zip_obj.namelist() 383 if len(entries) > 0 and sep in entries[0]: 384 subdir = entries[0].split(sep, 1)[0] 385 if all(e.startswith(subdir + sep) for e in entries): 386 return subdir 387 logger.warning("Expect one subdirectory in %s. Actual entries: %s", 388 zip_path, " ".join(entries)) 389 return "" 390 391 def _UploadArtifacts(self, artifacts_paths): 392 """Process and upload all images and tools to the remote host. 393 394 Args: 395 artifact_paths: An object of ArtifactPaths. 396 397 Returns: 398 An object of RemotePaths. 399 """ 400 remote_emulator_dir, remote_image_dir = self._UploadDeviceImages( 401 artifacts_paths.emulator_zip, artifacts_paths.image_zip) 402 403 remote_kernel_path = None 404 remote_ramdisk_path = None 405 406 if artifacts_paths.boot_image or artifacts_paths.system_image_zip: 407 with tempfile.TemporaryDirectory("host_gf") as temp_dir: 408 ota_tools_dir = os.path.join(temp_dir, "ota_tools") 409 logger.debug("Unzip %s.", artifacts_paths.ota_tools_zip) 410 with zipfile.ZipFile(artifacts_paths.ota_tools_zip, 411 "r") as zip_file: 412 zip_file.extractall(ota_tools_dir) 413 ota = ota_tools.OtaTools(ota_tools_dir) 414 415 image_dir = os.path.join(temp_dir, "images") 416 logger.debug("Unzip %s.", artifacts_paths.image_zip) 417 with zipfile.ZipFile(artifacts_paths.image_zip, 418 "r") as zip_file: 419 zip_file.extractall(image_dir) 420 image_dir = os.path.join( 421 image_dir, 422 self._GetSubdirNameInZip(artifacts_paths.image_zip)) 423 424 if artifacts_paths.system_image_zip: 425 self._MixAndUploadDiskImage( 426 remote_image_dir, image_dir, 427 artifacts_paths.system_image_zip, ota) 428 429 if artifacts_paths.boot_image: 430 remote_kernel_path, remote_ramdisk_path = ( 431 self._MixAndUploadKernelImages( 432 image_dir, artifacts_paths.boot_image, ota)) 433 434 return RemotePaths(remote_image_dir, remote_emulator_dir, 435 remote_kernel_path, remote_ramdisk_path) 436 437 def _ShouldMixDiskImage(self): 438 """Determines whether a mixed disk image is required. 439 440 This method checks whether the user requires to replace an image that 441 is part of the disk image. Acloud supports replacing system and kernel 442 images. Only the system is installed on the disk. 443 444 Returns: 445 Boolean, whether a mixed disk image is required. 446 """ 447 return (self._avd_spec.system_build_info.get(constants.BUILD_ID) and 448 self._avd_spec.system_build_info.get(constants.BUILD_TARGET)) 449 450 @utils.TimeExecute( 451 function_description="Processing and uploading tools and images") 452 def _UploadDeviceImages(self, emulator_zip_path, image_zip_path): 453 """Upload artifacts to remote host and extract them. 454 455 Args: 456 emulator_zip_path: The local path to the emulator zip. 457 image_zip_path: The local path to the image zip. 458 459 Returns: 460 The remote paths to the extracted emulator tools and images. 461 """ 462 self._ssh.Run("mkdir -p " + 463 " ".join([_REMOTE_INSTANCE_DIR, _REMOTE_ARTIFACT_DIR, 464 _REMOTE_EMULATOR_DIR, _REMOTE_IMAGE_DIR])) 465 self._ssh.ScpPushFile(emulator_zip_path, _REMOTE_ARTIFACT_DIR) 466 self._ssh.ScpPushFile(image_zip_path, _REMOTE_ARTIFACT_DIR) 467 468 self._ssh.Run("unzip -d %s %s" % ( 469 _REMOTE_EMULATOR_DIR, 470 remote_path.join(_REMOTE_ARTIFACT_DIR, 471 os.path.basename(emulator_zip_path)))) 472 self._ssh.Run("unzip -d %s %s" % ( 473 _REMOTE_IMAGE_DIR, 474 remote_path.join(_REMOTE_ARTIFACT_DIR, 475 os.path.basename(image_zip_path)))) 476 remote_emulator_subdir = remote_path.join( 477 _REMOTE_EMULATOR_DIR, self._GetSubdirNameInZip(emulator_zip_path)) 478 remote_image_subdir = remote_path.join( 479 _REMOTE_IMAGE_DIR, self._GetSubdirNameInZip(image_zip_path)) 480 # TODO(b/141898893): In Android build environment, emulator gets build 481 # information from $ANDROID_PRODUCT_OUT/system/build.prop. 482 # If image_dir is an extacted SDK repository, the file is at 483 # image_dir/build.prop. Acloud copies it to 484 # image_dir/system/build.prop. 485 src_path = remote_path.join(remote_image_subdir, "build.prop") 486 dst_path = remote_path.join(remote_image_subdir, "system", 487 "build.prop") 488 self._ssh.Run("'test -f %(dst)s || " 489 "{ mkdir -p %(dst_dir)s && cp %(src)s %(dst)s ; }'" % 490 {"src": src_path, 491 "dst": dst_path, 492 "dst_dir": remote_path.dirname(dst_path)}) 493 return remote_emulator_subdir, remote_image_subdir 494 495 def _MixAndUploadDiskImage(self, remote_image_dir, image_dir, 496 system_image_zip_path, ota): 497 """Mix emulator images with a system image and upload them. 498 499 Args: 500 remote_image_dir: The remote directory where the mixed disk image 501 is uploaded. 502 image_dir: The directory containing emulator images. 503 system_image_zip_path: The path to the zip containing the system 504 image. 505 ota: An instance of ota_tools.OtaTools. 506 507 Returns: 508 The remote path to the mixed disk image. 509 """ 510 with tempfile.TemporaryDirectory("host_gf_disk") as temp_dir: 511 logger.debug("Unzip %s.", system_image_zip_path) 512 with zipfile.ZipFile(system_image_zip_path, "r") as zip_file: 513 zip_file.extract(_SYSTEM_IMAGE_NAME, temp_dir) 514 515 mixed_image = goldfish_utils.MixWithSystemImage( 516 os.path.join(temp_dir, "mix_disk"), 517 image_dir, 518 os.path.join(temp_dir, _SYSTEM_IMAGE_NAME), 519 ota) 520 521 # TODO(b/142228085): Use -system instead of overwriting the file. 522 remote_disk_image_path = os.path.join( 523 remote_image_dir, goldfish_utils.SYSTEM_QEMU_IMAGE_NAME) 524 self._ssh.ScpPushFile(mixed_image, remote_disk_image_path) 525 526 return remote_disk_image_path 527 528 def _MixAndUploadKernelImages(self, image_dir, boot_image_path, ota): 529 """Mix emulator kernel images with a boot image and upload them. 530 531 Args: 532 image_dir: The directory containing emulator images. 533 boot_image_path: The path to the boot image. 534 ota: An instance of ota_tools.OtaTools. 535 536 Returns: 537 The remote paths to the kernel image and the ramdisk image. 538 """ 539 with tempfile.TemporaryDirectory("host_gf_kernel") as temp_dir: 540 kernel_path, ramdisk_path = goldfish_utils.MixWithBootImage( 541 temp_dir, image_dir, boot_image_path, ota) 542 543 self._ssh.ScpPushFile(kernel_path, _REMOTE_KERNEL_PATH) 544 self._ssh.ScpPushFile(ramdisk_path, _REMOTE_RAMDISK_PATH) 545 546 return _REMOTE_KERNEL_PATH, _REMOTE_RAMDISK_PATH 547 548 @utils.TimeExecute(function_description="Start emulator") 549 def _StartEmulator(self, remote_paths): 550 """Start emulator command as a remote background process. 551 552 Args: 553 remote_emulator_dir: The emulator tool directory on remote host. 554 remote_image_dir: The image directory on remote host. 555 """ 556 remote_emulator_bin_path = remote_path.join( 557 remote_paths.emulator_dir, _EMULATOR_BIN_NAME) 558 remote_bin_paths = [ 559 remote_path.join(remote_paths.emulator_dir, name) for 560 name in _EMULATOR_BIN_DIR_NAMES] 561 remote_bin_paths.append(remote_emulator_bin_path) 562 self._ssh.Run("chmod -R +x %s" % " ".join(remote_bin_paths)) 563 564 env = {constants.ENV_ANDROID_PRODUCT_OUT: remote_paths.image_dir, 565 constants.ENV_ANDROID_TMP: _REMOTE_INSTANCE_DIR, 566 constants.ENV_ANDROID_BUILD_TOP: _REMOTE_INSTANCE_DIR} 567 adb_port = _EMULATOR_DEFAULT_CONSOLE_PORT + 1 568 cmd = ["nohup", remote_emulator_bin_path, "-verbose", "-show-kernel", 569 "-read-only", "-ports", 570 str(_EMULATOR_DEFAULT_CONSOLE_PORT) + "," + str(adb_port), 571 "-no-window", 572 "-logcat-output", _REMOTE_LOGCAT_PATH, 573 "-stdouterr-file", _REMOTE_STDOUTERR_PATH] 574 575 if remote_paths.kernel: 576 cmd.extend(("-kernel", remote_paths.kernel)) 577 578 if remote_paths.ramdisk: 579 cmd.extend(("-ramdisk", remote_paths.ramdisk)) 580 581 cmd.extend(goldfish_utils.ConvertAvdSpecToArgs(self._avd_spec)) 582 583 # Unlock the device so that the disabled vbmeta takes effect. 584 # These arguments must be at the end of the command line. 585 if self._ShouldMixDiskImage(): 586 cmd.extend(("-qemu", "-append", 587 "androidboot.verifiedbootstate=orange")) 588 589 # Emulator doesn't create -stdouterr-file automatically. 590 self._ssh.Run( 591 "'export {env} ; touch {stdouterr} ; {cmd} &'".format( 592 env=" ".join(k + "=~/" + v for k, v in env.items()), 593 stdouterr=_REMOTE_STDOUTERR_PATH, 594 cmd=" ".join(cmd))) 595 596 @utils.TimeExecute(function_description="Wait for emulator") 597 def _WaitForEmulator(self): 598 """Wait for remote emulator console to be active. 599 600 Raises: 601 errors.DeviceBootError if connection fails. 602 errors.DeviceBootTimeoutError if boot times out. 603 """ 604 ip_addr = self._avd_spec.remote_host 605 console_port = _EMULATOR_DEFAULT_CONSOLE_PORT 606 poll_timeout_secs = (self._avd_spec.boot_timeout_secs or 607 _DEFAULT_BOOT_TIMEOUT_SECS) 608 try: 609 with emulator_console.RemoteEmulatorConsole( 610 ip_addr, 611 console_port, 612 self._ssh_user, 613 self._ssh_private_key_path, 614 self._ssh_extra_args) as console: 615 utils.PollAndWait( 616 func=lambda: (True if console.Ping() else 617 console.Reconnect()), 618 expected_return=True, 619 timeout_exception=errors.DeviceBootTimeoutError, 620 timeout_secs=poll_timeout_secs, 621 sleep_interval_secs=5) 622 except errors.DeviceConnectionError as e: 623 raise errors.DeviceBootError("Fail to connect to %s:%d." % 624 (ip_addr, console_port)) from e 625 626 def GetBuildInfoDict(self): 627 """Get build info dictionary. 628 629 Returns: 630 A build info dictionary. 631 """ 632 build_info_dict = {key: val for key, val in 633 self._avd_spec.remote_image.items() if val} 634 return build_info_dict 635 636 def GetFailures(self): 637 """Get Failures from all devices. 638 639 Returns: 640 A dictionary the contains all the failures. 641 The key is the name of the instance that fails to boot, 642 and the value is an errors.DeviceBootError object. 643 """ 644 return self._failures 645 646 def GetLogs(self): 647 """Get log files of created instances. 648 649 Returns: 650 A dictionary that maps instance names to lists of report.LogFile. 651 """ 652 return self._logs 653