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 subprocess 25import tempfile 26import time 27import zipfile 28 29from acloud import errors 30from acloud.create import create_common 31from acloud.internal import constants 32from acloud.internal.lib import android_build_client 33from acloud.internal.lib import auth 34from acloud.internal.lib import goldfish_utils 35from acloud.internal.lib import emulator_console 36from acloud.internal.lib import ota_tools 37from acloud.internal.lib import remote_host_client 38from acloud.internal.lib import utils 39from acloud.internal.lib import ssh 40from acloud.public import report 41from acloud.public.actions import base_device_factory 42 43 44logger = logging.getLogger(__name__) 45# Artifacts 46_SDK_REPO_IMAGE_ZIP_NAME_FORMAT = ("sdk-repo-linux-system-images-" 47 "%(build_id)s.zip") 48_EXTRA_IMAGE_ZIP_NAME_FORMAT = "emu-extra-linux-system-images-%(build_id)s.zip" 49_IMAGE_ZIP_NAME_FORMAT = "%(build_target)s-img-%(build_id)s.zip" 50_OTA_TOOLS_ZIP_NAME = "otatools.zip" 51_EMULATOR_INFO_NAME = "emulator-info.txt" 52_EMULATOR_VERSION_PATTERN = re.compile(r"require\s+version-emulator=" 53 r"(?P<build_id>\w+)") 54_EMULATOR_ZIP_NAME_FORMAT = "sdk-repo-%(os)s-emulator-%(build_id)s.zip" 55_EMULATOR_BIN_DIR_NAMES = ("bin64", "qemu") 56_EMULATOR_BIN_NAME = "emulator" 57_SDK_REPO_EMULATOR_DIR_NAME = "emulator" 58# Files in temporary artifact directory. 59_DOWNLOAD_DIR_NAME = "download" 60_OTA_TOOLS_DIR_NAME = "ota_tools" 61_SYSTEM_IMAGE_NAME = "system.img" 62# Base directory of an instance. 63_REMOTE_INSTANCE_DIR_FORMAT = "acloud_gf_%d" 64# Relative paths in a base directory. 65_REMOTE_IMAGE_ZIP_PATH = "image.zip" 66_REMOTE_EMULATOR_ZIP_PATH = "emulator.zip" 67_REMOTE_IMAGE_DIR = "image" 68_REMOTE_KERNEL_PATH = "kernel" 69_REMOTE_RAMDISK_PATH = "mixed_ramdisk" 70_REMOTE_EMULATOR_DIR = "emulator" 71_REMOTE_RUNTIME_DIR = "instance" 72_REMOTE_LOGCAT_PATH = remote_path.join(_REMOTE_RUNTIME_DIR, "logcat.txt") 73_REMOTE_STDOUT_PATH = remote_path.join(_REMOTE_RUNTIME_DIR, "kernel.log") 74_REMOTE_STDERR_PATH = remote_path.join(_REMOTE_RUNTIME_DIR, "emu_stderr.txt") 75# Runtime parameters 76_EMULATOR_DEFAULT_CONSOLE_PORT = 5554 77_DEFAULT_BOOT_TIMEOUT_SECS = 150 78# Error messages 79_MISSING_EMULATOR_MSG = ("No emulator zip. Specify " 80 "--emulator-build-id, or --emulator-zip.") 81 82ArtifactPaths = collections.namedtuple( 83 "ArtifactPaths", 84 ["image_zip", "emulator_zip", "ota_tools_dir", 85 "system_image", "boot_image"]) 86 87RemotePaths = collections.namedtuple( 88 "RemotePaths", 89 ["image_dir", "emulator_dir", "kernel", "ramdisk"]) 90 91 92class RemoteHostGoldfishDeviceFactory(base_device_factory.BaseDeviceFactory): 93 """A class that creates a goldfish device on a remote host. 94 95 Attributes: 96 avd_spec: AVDSpec object that tells us what we're going to create. 97 android_build_client: An AndroidBuildClient that is lazily initialized. 98 temp_artifact_dir: The temporary artifact directory that is lazily 99 initialized during PrepareArtifacts. 100 ssh: Ssh object that executes commands on the remote host. 101 failures: A dictionary the maps instance names to 102 error.DeviceBootError objects. 103 logs: A dictionary that maps instance names to lists of report.LogFile. 104 """ 105 def __init__(self, avd_spec): 106 """Initialize the attributes and the compute client.""" 107 self._avd_spec = avd_spec 108 self._android_build_client = None 109 self._temp_artifact_dir = None 110 self._ssh = ssh.Ssh( 111 ip=ssh.IP(ip=self._avd_spec.remote_host), 112 user=self._ssh_user, 113 ssh_private_key_path=self._ssh_private_key_path, 114 extra_args_ssh_tunnel=self._ssh_extra_args, 115 report_internal_ip=False) 116 self._failures = {} 117 self._logs = {} 118 super().__init__(compute_client=( 119 remote_host_client.RemoteHostClient(avd_spec.remote_host))) 120 121 @property 122 def _build_api(self): 123 """Initialize android_build_client.""" 124 if not self._android_build_client: 125 credentials = auth.CreateCredentials(self._avd_spec.cfg) 126 self._android_build_client = android_build_client.AndroidBuildClient( 127 credentials) 128 return self._android_build_client 129 130 @property 131 def _artifact_dir(self): 132 """Initialize temp_artifact_dir.""" 133 if not self._temp_artifact_dir: 134 self._temp_artifact_dir = tempfile.mkdtemp("host_gf") 135 logger.info("Create temporary artifact directory: %s", 136 self._temp_artifact_dir) 137 return self._temp_artifact_dir 138 139 @property 140 def _download_dir(self): 141 """Get the directory where the artifacts are downloaded.""" 142 if self._avd_spec.image_download_dir: 143 return self._avd_spec.image_download_dir 144 return os.path.join(self._artifact_dir, _DOWNLOAD_DIR_NAME) 145 146 @property 147 def _ssh_user(self): 148 return self._avd_spec.host_user or constants.GCE_USER 149 150 @property 151 def _ssh_private_key_path(self): 152 return (self._avd_spec.host_ssh_private_key_path or 153 self._avd_spec.cfg.ssh_private_key_path) 154 155 @property 156 def _ssh_extra_args(self): 157 return self._avd_spec.cfg.extra_args_ssh_tunnel 158 159 def _GetConsolePort(self): 160 """Calculate the console port from the instance number. 161 162 By convention, the console port is an even number, and the adb port is 163 the console port + 1. The first instance uses port 5554 and 5555. The 164 second instance uses 5556 and 5557, and so on. 165 """ 166 return (_EMULATOR_DEFAULT_CONSOLE_PORT + 167 ((self._avd_spec.base_instance_num or 1) - 1) * 2) 168 169 def _GetInstancePath(self, relative_path): 170 """Append a relative path to the instance directory.""" 171 return remote_path.join( 172 _REMOTE_INSTANCE_DIR_FORMAT % 173 (self._avd_spec.base_instance_num or 1), 174 relative_path) 175 176 def CreateInstance(self): 177 """Create a goldfish instance on the remote host. 178 179 Returns: 180 The instance name. 181 """ 182 instance_name = goldfish_utils.FormatRemoteHostInstanceName( 183 self._avd_spec.remote_host, 184 self._GetConsolePort(), 185 self._avd_spec.remote_image) 186 187 client = self.GetComputeClient() 188 timed_stage = constants.TIME_GCE 189 start_time = time.time() 190 try: 191 client.SetStage(constants.STAGE_SSH_CONNECT) 192 self._InitRemoteHost() 193 194 start_time = client.RecordTime(timed_stage, start_time) 195 timed_stage = constants.TIME_ARTIFACT 196 client.SetStage(constants.STAGE_ARTIFACT) 197 remote_paths = self._PrepareArtifacts() 198 199 start_time = client.RecordTime(timed_stage, start_time) 200 timed_stage = constants.TIME_LAUNCH 201 client.SetStage(constants.STAGE_BOOT_UP) 202 self._logs[instance_name] = self._GetEmulatorLogs() 203 self._StartEmulator(remote_paths) 204 self._WaitForEmulator() 205 except (errors.DriverError, subprocess.CalledProcessError) as e: 206 # Catch the generic runtime error and CalledProcessError which is 207 # raised by the ssh module. 208 self._failures[instance_name] = e 209 finally: 210 client.RecordTime(timed_stage, start_time) 211 212 return instance_name 213 214 def _InitRemoteHost(self): 215 """Remove the existing instance and the instance directory.""" 216 # Disable authentication for emulator console. 217 self._ssh.Run("""'echo -n "" > .emulator_console_auth_token'""") 218 try: 219 with emulator_console.RemoteEmulatorConsole( 220 self._avd_spec.remote_host, 221 self._GetConsolePort(), 222 self._ssh_user, 223 self._ssh_private_key_path, 224 self._ssh_extra_args) as console: 225 console.Kill() 226 logger.info("Killed existing emulator.") 227 except errors.DeviceConnectionError as e: 228 logger.info("Did not kill existing emulator: %s", str(e)) 229 # Delete instance files. 230 self._ssh.Run(f"rm -rf {self._GetInstancePath('')}") 231 232 def _PrepareArtifacts(self): 233 """Prepare artifacts on remote host. 234 235 This method retrieves artifacts from cache or Android Build API and 236 uploads them to the remote host. 237 238 Returns: 239 An object of RemotePaths. 240 """ 241 try: 242 artifact_paths = self._RetrieveArtifacts() 243 return self._UploadArtifacts(artifact_paths) 244 finally: 245 if self._temp_artifact_dir: 246 shutil.rmtree(self._temp_artifact_dir, ignore_errors=True) 247 self._temp_artifact_dir = None 248 249 @staticmethod 250 def _InferEmulatorZipName(build_target, build_id): 251 """Determine the emulator zip name in build artifacts. 252 253 The emulator zip name is composed of build variables that are not 254 revealed in the artifacts. This method infers the emulator zip name 255 from its build target name. 256 257 Args: 258 build_target: The emulator build target name, e.g., 259 "emulator-linux_x64_nolocationui", "aarch64_sdk_tools_mac". 260 build_id: A string, the emulator build ID. 261 262 Returns: 263 The name of the emulator zip. e.g., 264 "sdk-repo-linux-emulator-123456.zip", 265 "sdk-repo-darwin_aarch64-emulator-123456.zip". 266 """ 267 split_target = [x for product_variant in build_target.split("-") 268 for x in product_variant.split("_")] 269 if "darwin" in split_target or "mac" in split_target: 270 os_name = "darwin" 271 else: 272 os_name = "linux" 273 if "aarch64" in split_target: 274 os_name = os_name + "_aarch64" 275 return _EMULATOR_ZIP_NAME_FORMAT % {"os": os_name, 276 "build_id": build_id} 277 278 def _RetrieveArtifact(self, build_target, build_id, 279 resource_id): 280 """Retrieve an artifact from cache or Android Build API. 281 282 Args: 283 build_target: A string, the build target of the artifact. e.g., 284 "sdk_phone_x86_64-userdebug". 285 build_id: A string, the build ID of the artifact. 286 resource_id: A string, the name of the artifact. e.g., 287 "sdk-repo-linux-system-images-123456.zip". 288 289 Returns: 290 The path to the artifact in download_dir. 291 """ 292 local_path = os.path.join(self._download_dir, build_id, build_target, 293 resource_id) 294 if os.path.isfile(local_path): 295 logger.info("Skip downloading existing artifact: %s", local_path) 296 return local_path 297 298 complete = False 299 try: 300 os.makedirs(os.path.dirname(local_path), exist_ok=True) 301 self._build_api.DownloadArtifact( 302 build_target, build_id, resource_id, local_path, 303 self._build_api.LATEST) 304 complete = True 305 finally: 306 if not complete and os.path.isfile(local_path): 307 os.remove(local_path) 308 return local_path 309 310 @utils.TimeExecute(function_description="Download Android Build artifacts") 311 def _RetrieveArtifacts(self): 312 """Retrieve goldfish images and tools from cache or Android Build API. 313 314 Returns: 315 An object of ArtifactPaths. 316 317 Raises: 318 errors.GetRemoteImageError: Fails to download rom images. 319 errors.GetLocalImageError: Fails to validate local image zip. 320 errors.GetSdkRepoPackageError: Fails to retrieve emulator zip. 321 """ 322 # Device images. 323 if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE: 324 image_zip_path = self._RetrieveDeviceImageZip() 325 elif self._avd_spec.image_source == constants.IMAGE_SRC_LOCAL: 326 image_zip_path = self._avd_spec.local_image_artifact 327 if not image_zip_path or not zipfile.is_zipfile(image_zip_path): 328 raise errors.GetLocalImageError( 329 f"{image_zip_path or self._avd_spec.local_image_dir} is " 330 "not an SDK repository zip.") 331 else: 332 raise errors.CreateError( 333 f"Unknown image source: {self._avd_spec.image_source}") 334 335 # Emulator tools. 336 emu_zip_path = (self._avd_spec.emulator_zip or 337 self._RetrieveEmulatorZip()) 338 if not emu_zip_path: 339 raise errors.GetSdkRepoPackageError(_MISSING_EMULATOR_MSG) 340 341 # System image. 342 if self._avd_spec.local_system_image: 343 system_image_path = create_common.FindSystemImage( 344 self._avd_spec.local_system_image) 345 else: 346 system_image_path = self._RetrieveSystemImage() 347 348 # Boot image. 349 if self._avd_spec.local_kernel_image: 350 boot_image_path = create_common.FindBootImage( 351 self._avd_spec.local_kernel_image) 352 else: 353 boot_image_path = self._RetrieveBootImage() 354 355 # OTA tools. 356 ota_tools_dir = None 357 if system_image_path or boot_image_path: 358 if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE: 359 ota_tools_dir = self._RetrieveOtaTools() 360 else: 361 ota_tools_dir = ota_tools.FindOtaToolsDir( 362 self._avd_spec.local_tool_dirs + 363 create_common.GetNonEmptyEnvVars( 364 constants.ENV_ANDROID_SOONG_HOST_OUT, 365 constants.ENV_ANDROID_HOST_OUT)) 366 367 return ArtifactPaths(image_zip_path, emu_zip_path, ota_tools_dir, 368 system_image_path, boot_image_path) 369 370 def _RetrieveDeviceImageZip(self): 371 """Retrieve device image zip from cache or Android Build API. 372 373 Returns: 374 The path to the device image zip in download_dir. 375 """ 376 build_id = self._avd_spec.remote_image.get(constants.BUILD_ID) 377 build_target = self._avd_spec.remote_image.get(constants.BUILD_TARGET) 378 image_zip_name_format = (_EXTRA_IMAGE_ZIP_NAME_FORMAT if 379 self._ShouldMixDiskImage() else 380 _SDK_REPO_IMAGE_ZIP_NAME_FORMAT) 381 return self._RetrieveArtifact( 382 build_target, build_id, 383 image_zip_name_format % {"build_id": build_id}) 384 385 def _RetrieveEmulatorBuildID(self): 386 """Retrieve required emulator build from a goldfish image build. 387 388 Returns: 389 A string, the emulator build ID. 390 None if the build info is empty. 391 """ 392 build_id = self._avd_spec.remote_image.get(constants.BUILD_ID) 393 build_target = self._avd_spec.remote_image.get(constants.BUILD_TARGET) 394 if build_id and build_target: 395 emu_info_path = self._RetrieveArtifact(build_target, build_id, 396 _EMULATOR_INFO_NAME) 397 with open(emu_info_path, "r", encoding="utf-8") as emu_info: 398 for line in emu_info: 399 match = _EMULATOR_VERSION_PATTERN.fullmatch(line.strip()) 400 if match: 401 logger.info("Found emulator build ID: %s", line) 402 return match.group("build_id") 403 return None 404 405 def _RetrieveEmulatorZip(self): 406 """Retrieve emulator zip from cache or Android Build API. 407 408 Returns: 409 The path to the emulator zip in download_dir. 410 None if this method cannot determine the emulator build ID. 411 """ 412 emu_build_id = (self._avd_spec.emulator_build_id or 413 self._RetrieveEmulatorBuildID()) 414 if not emu_build_id: 415 return None 416 emu_build_target = (self._avd_spec.emulator_build_target or 417 self._avd_spec.cfg.emulator_build_target) 418 emu_zip_name = self._InferEmulatorZipName(emu_build_target, 419 emu_build_id) 420 return self._RetrieveArtifact(emu_build_target, emu_build_id, 421 emu_zip_name) 422 423 def _RetrieveSystemImage(self): 424 """Retrieve and unzip system image if system build info is not empty. 425 426 Returns: 427 The path to the temporary system image. 428 None if the system build info is empty. 429 """ 430 build_id = self._avd_spec.system_build_info.get(constants.BUILD_ID) 431 build_target = self._avd_spec.system_build_info.get( 432 constants.BUILD_TARGET) 433 if not build_id or not build_target: 434 return None 435 image_zip_name = _IMAGE_ZIP_NAME_FORMAT % { 436 "build_target": build_target.split("-", 1)[0], 437 "build_id": build_id} 438 image_zip_path = self._RetrieveArtifact(build_target, build_id, 439 image_zip_name) 440 logger.debug("Unzip %s from %s to %s.", 441 _SYSTEM_IMAGE_NAME, image_zip_path, self._artifact_dir) 442 with zipfile.ZipFile(image_zip_path, "r") as zip_file: 443 zip_file.extract(_SYSTEM_IMAGE_NAME, self._artifact_dir) 444 return os.path.join(self._artifact_dir, _SYSTEM_IMAGE_NAME) 445 446 def _RetrieveBootImage(self): 447 """Retrieve boot image if boot build info is not empty. 448 449 Returns: 450 The path to the boot image in download_dir. 451 None if the boot build info is empty. 452 """ 453 build_id = self._avd_spec.boot_build_info.get(constants.BUILD_ID) 454 build_target = self._avd_spec.boot_build_info.get( 455 constants.BUILD_TARGET) 456 image_name = self._avd_spec.boot_build_info.get( 457 constants.BUILD_ARTIFACT) 458 if build_id and build_target and image_name: 459 return self._RetrieveArtifact(build_target, build_id, image_name) 460 return None 461 462 def _RetrieveOtaTools(self): 463 """Retrieve and unzip OTA tools. 464 465 This method retrieves OTA tools from the goldfish build which contains 466 mk_combined_img. 467 468 Returns: 469 The path to the temporary OTA tools directory. 470 """ 471 build_id = self._avd_spec.remote_image.get(constants.BUILD_ID) 472 build_target = self._avd_spec.remote_image.get(constants.BUILD_TARGET) 473 zip_path = self._RetrieveArtifact(build_target, build_id, 474 _OTA_TOOLS_ZIP_NAME) 475 ota_tools_dir = os.path.join(self._artifact_dir, _OTA_TOOLS_DIR_NAME) 476 logger.debug("Unzip %s to %s.", zip_path, ota_tools_dir) 477 os.mkdir(ota_tools_dir) 478 with zipfile.ZipFile(zip_path, "r") as zip_file: 479 zip_file.extractall(ota_tools_dir) 480 return ota_tools_dir 481 482 @staticmethod 483 def _GetSubdirNameInZip(zip_path): 484 """Get the name of the only subdirectory in a zip. 485 486 In an SDK repository zip, the images and the binaries are located in a 487 subdirectory. This class needs to find out the subdirectory name in 488 order to construct the remote commands. 489 490 For example, in a sdk-repo-linux-system-images-*.zip for arm64, all 491 files are in "arm64-v8a/". The zip entries are: 492 493 arm64-v8a/NOTICE.txt 494 arm64-v8a/system.img 495 arm64-v8a/data/local.prop 496 ... 497 498 This method scans the entries and returns the common subdirectory name. 499 """ 500 sep = "/" 501 with zipfile.ZipFile(zip_path, 'r') as zip_obj: 502 entries = zip_obj.namelist() 503 if len(entries) > 0 and sep in entries[0]: 504 subdir = entries[0].split(sep, 1)[0] 505 if all(e.startswith(subdir + sep) for e in entries): 506 return subdir 507 logger.warning("Expect one subdirectory in %s. Actual entries: %s", 508 zip_path, " ".join(entries)) 509 return "" 510 511 def _UploadArtifacts(self, artifact_paths): 512 """Process and upload all images and tools to the remote host. 513 514 Args: 515 artifact_paths: An object of ArtifactPaths. 516 517 Returns: 518 An object of RemotePaths. 519 """ 520 remote_emulator_dir, remote_image_dir = self._UploadDeviceImages( 521 artifact_paths.emulator_zip, artifact_paths.image_zip) 522 523 remote_kernel_path = None 524 remote_ramdisk_path = None 525 526 if artifact_paths.boot_image or artifact_paths.system_image: 527 with tempfile.TemporaryDirectory("host_gf") as temp_dir: 528 ota = ota_tools.OtaTools(artifact_paths.ota_tools_dir) 529 530 image_dir = os.path.join(temp_dir, "images") 531 logger.debug("Unzip %s.", artifact_paths.image_zip) 532 with zipfile.ZipFile(artifact_paths.image_zip, 533 "r") as zip_file: 534 zip_file.extractall(image_dir) 535 image_dir = os.path.join( 536 image_dir, 537 self._GetSubdirNameInZip(artifact_paths.image_zip)) 538 539 if artifact_paths.system_image: 540 self._MixAndUploadDiskImage( 541 remote_image_dir, image_dir, 542 artifact_paths.system_image, ota) 543 544 if artifact_paths.boot_image: 545 remote_kernel_path, remote_ramdisk_path = ( 546 self._MixAndUploadKernelImages( 547 image_dir, artifact_paths.boot_image, ota)) 548 549 return RemotePaths(remote_image_dir, remote_emulator_dir, 550 remote_kernel_path, remote_ramdisk_path) 551 552 def _ShouldMixDiskImage(self): 553 """Determines whether a mixed disk image is required. 554 555 This method checks whether the user requires to replace an image that 556 is part of the disk image. Acloud supports replacing system and kernel 557 images. Only the system is installed on the disk. 558 559 Returns: 560 Boolean, whether a mixed disk image is required. 561 """ 562 return self._avd_spec.local_system_image or ( 563 self._avd_spec.system_build_info.get(constants.BUILD_ID) and 564 self._avd_spec.system_build_info.get(constants.BUILD_TARGET)) 565 566 @utils.TimeExecute( 567 function_description="Processing and uploading tools and images") 568 def _UploadDeviceImages(self, emulator_zip_path, image_zip_path): 569 """Upload artifacts to remote host and extract them. 570 571 Args: 572 emulator_zip_path: The local path to the emulator zip. 573 image_zip_path: The local path to the image zip. 574 575 Returns: 576 The remote paths to the extracted emulator tools and images. 577 """ 578 remote_emulator_dir = self._GetInstancePath(_REMOTE_EMULATOR_DIR) 579 remote_image_dir = self._GetInstancePath(_REMOTE_IMAGE_DIR) 580 remote_emulator_zip_path = self._GetInstancePath( 581 _REMOTE_EMULATOR_ZIP_PATH) 582 remote_image_zip_path = self._GetInstancePath(_REMOTE_IMAGE_ZIP_PATH) 583 self._ssh.Run(f"mkdir -p {remote_emulator_dir} {remote_image_dir}") 584 self._ssh.ScpPushFile(emulator_zip_path, remote_emulator_zip_path) 585 self._ssh.ScpPushFile(image_zip_path, remote_image_zip_path) 586 587 self._ssh.Run(f"unzip -d {remote_emulator_dir} " 588 f"{remote_emulator_zip_path}") 589 self._ssh.Run(f"unzip -d {remote_image_dir} {remote_image_zip_path}") 590 remote_emulator_subdir = remote_path.join( 591 remote_emulator_dir, _SDK_REPO_EMULATOR_DIR_NAME) 592 remote_image_subdir = remote_path.join( 593 remote_image_dir, self._GetSubdirNameInZip(image_zip_path)) 594 # TODO(b/141898893): In Android build environment, emulator gets build 595 # information from $ANDROID_PRODUCT_OUT/system/build.prop. 596 # If image_dir is an extacted SDK repository, the file is at 597 # image_dir/build.prop. Acloud copies it to 598 # image_dir/system/build.prop. 599 src_path = remote_path.join(remote_image_subdir, "build.prop") 600 dst_path = remote_path.join(remote_image_subdir, "system", 601 "build.prop") 602 self._ssh.Run("'test -f %(dst)s || " 603 "{ mkdir -p %(dst_dir)s && cp %(src)s %(dst)s ; }'" % 604 {"src": src_path, 605 "dst": dst_path, 606 "dst_dir": remote_path.dirname(dst_path)}) 607 return remote_emulator_subdir, remote_image_subdir 608 609 def _MixAndUploadDiskImage(self, remote_image_dir, image_dir, 610 system_image_path, ota): 611 """Mix emulator images with a system image and upload them. 612 613 Args: 614 remote_image_dir: The remote directory where the mixed disk image 615 is uploaded. 616 image_dir: The directory containing emulator images. 617 system_image_path: The path to the system image. 618 ota: An instance of ota_tools.OtaTools. 619 620 Returns: 621 The remote path to the mixed disk image. 622 """ 623 with tempfile.TemporaryDirectory("host_gf_disk") as temp_dir: 624 mixed_image = goldfish_utils.MixWithSystemImage( 625 temp_dir, image_dir, system_image_path, ota) 626 627 # TODO(b/142228085): Use -system instead of overwriting the file. 628 remote_disk_image_path = os.path.join( 629 remote_image_dir, goldfish_utils.SYSTEM_QEMU_IMAGE_NAME) 630 self._ssh.ScpPushFile(mixed_image, remote_disk_image_path) 631 632 return remote_disk_image_path 633 634 def _MixAndUploadKernelImages(self, image_dir, boot_image_path, ota): 635 """Mix emulator kernel images with a boot image and upload them. 636 637 Args: 638 image_dir: The directory containing emulator images. 639 boot_image_path: The path to the boot image. 640 ota: An instance of ota_tools.OtaTools. 641 642 Returns: 643 The remote paths to the kernel image and the ramdisk image. 644 """ 645 remote_kernel_path = self._GetInstancePath(_REMOTE_KERNEL_PATH) 646 remote_ramdisk_path = self._GetInstancePath(_REMOTE_RAMDISK_PATH) 647 with tempfile.TemporaryDirectory("host_gf_kernel") as temp_dir: 648 kernel_path, ramdisk_path = goldfish_utils.MixWithBootImage( 649 temp_dir, image_dir, boot_image_path, ota) 650 651 self._ssh.ScpPushFile(kernel_path, remote_kernel_path) 652 self._ssh.ScpPushFile(ramdisk_path, remote_ramdisk_path) 653 654 return remote_kernel_path, remote_ramdisk_path 655 656 def _GetEmulatorLogs(self): 657 """Return the logs created by the remote emulator command.""" 658 return [report.LogFile(self._GetInstancePath(_REMOTE_STDOUT_PATH), 659 constants.LOG_TYPE_KERNEL_LOG), 660 report.LogFile(self._GetInstancePath(_REMOTE_STDERR_PATH), 661 constants.LOG_TYPE_TEXT), 662 report.LogFile(self._GetInstancePath(_REMOTE_LOGCAT_PATH), 663 constants.LOG_TYPE_LOGCAT)] 664 665 @utils.TimeExecute(function_description="Start emulator") 666 def _StartEmulator(self, remote_paths): 667 """Start emulator command as a remote background process. 668 669 Args: 670 remote_emulator_dir: The emulator tool directory on remote host. 671 remote_image_dir: The image directory on remote host. 672 """ 673 remote_emulator_bin_path = remote_path.join( 674 remote_paths.emulator_dir, _EMULATOR_BIN_NAME) 675 remote_bin_paths = [ 676 remote_path.join(remote_paths.emulator_dir, name) for 677 name in _EMULATOR_BIN_DIR_NAMES] 678 remote_bin_paths.append(remote_emulator_bin_path) 679 self._ssh.Run("chmod -R +x %s" % " ".join(remote_bin_paths)) 680 681 remote_runtime_dir = self._GetInstancePath(_REMOTE_RUNTIME_DIR) 682 self._ssh.Run(f"mkdir -p {remote_runtime_dir}") 683 env = {constants.ENV_ANDROID_PRODUCT_OUT: remote_paths.image_dir, 684 constants.ENV_ANDROID_TMP: remote_runtime_dir, 685 constants.ENV_ANDROID_BUILD_TOP: remote_runtime_dir} 686 cmd = ["nohup", remote_emulator_bin_path, "-verbose", "-show-kernel", 687 "-read-only", "-ports", 688 str(self._GetConsolePort()) + "," + str(self.GetAdbPorts()[0]), 689 "-no-window", 690 "-logcat-output", self._GetInstancePath(_REMOTE_LOGCAT_PATH)] 691 692 if remote_paths.kernel: 693 cmd.extend(("-kernel", remote_paths.kernel)) 694 695 if remote_paths.ramdisk: 696 cmd.extend(("-ramdisk", remote_paths.ramdisk)) 697 698 cmd.extend(goldfish_utils.ConvertAvdSpecToArgs(self._avd_spec)) 699 700 # Unlock the device so that the disabled vbmeta takes effect. 701 # These arguments must be at the end of the command line. 702 if self._ShouldMixDiskImage(): 703 cmd.extend(("-qemu", "-append", 704 "androidboot.verifiedbootstate=orange")) 705 706 # Emulator does not support -stdouterr-file on macOS. 707 self._ssh.Run( 708 "'export {env} ; {cmd} 1> {stdout} 2> {stderr} &'".format( 709 env=" ".join(k + "=~/" + v for k, v in env.items()), 710 cmd=" ".join(cmd), 711 stdout=self._GetInstancePath(_REMOTE_STDOUT_PATH), 712 stderr=self._GetInstancePath(_REMOTE_STDERR_PATH))) 713 714 @utils.TimeExecute(function_description="Wait for emulator") 715 def _WaitForEmulator(self): 716 """Wait for remote emulator console to be active. 717 718 Raises: 719 errors.DeviceBootError if connection fails. 720 errors.DeviceBootTimeoutError if boot times out. 721 """ 722 ip_addr = self._avd_spec.remote_host 723 console_port = self._GetConsolePort() 724 poll_timeout_secs = (self._avd_spec.boot_timeout_secs or 725 _DEFAULT_BOOT_TIMEOUT_SECS) 726 try: 727 with emulator_console.RemoteEmulatorConsole( 728 ip_addr, 729 console_port, 730 self._ssh_user, 731 self._ssh_private_key_path, 732 self._ssh_extra_args) as console: 733 utils.PollAndWait( 734 func=lambda: (True if console.Ping() else 735 console.Reconnect()), 736 expected_return=True, 737 timeout_exception=errors.DeviceBootTimeoutError, 738 timeout_secs=poll_timeout_secs, 739 sleep_interval_secs=5) 740 except errors.DeviceConnectionError as e: 741 raise errors.DeviceBootError("Fail to connect to %s:%d." % 742 (ip_addr, console_port)) from e 743 744 def GetBuildInfoDict(self): 745 """Get build info dictionary. 746 747 Returns: 748 A build info dictionary. 749 """ 750 build_info_dict = {key: val for key, val in 751 self._avd_spec.remote_image.items() if val} 752 return build_info_dict 753 754 def GetAdbPorts(self): 755 """Get ADB ports of the created devices. 756 757 This class does not support --num-avds-per-instance. 758 759 Returns: 760 The port numbers as a list of integers. 761 """ 762 return [self._GetConsolePort() + 1] 763 764 def GetFailures(self): 765 """Get Failures from all devices. 766 767 Returns: 768 A dictionary the contains all the failures. 769 The key is the name of the instance that fails to boot, 770 and the value is an errors.DeviceBootError object. 771 """ 772 return self._failures 773 774 def GetLogs(self): 775 """Get log files of created instances. 776 777 Returns: 778 A dictionary that maps instance names to lists of report.LogFile. 779 """ 780 return self._logs 781