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. 14r"""GoldfishLocalImageLocalInstance class. 15 16Create class that is responsible for creating a local goldfish instance with 17local images. 18 19The emulator binary supports two types of environments, Android build system 20and SDK. This class runs the emulator in build environment. 21- This class uses the prebuilt emulator in ANDROID_EMULATOR_PREBUILTS. 22- If the instance requires mixing system or boot image, this class uses the 23 OTA tools in ANDROID_HOST_OUT. 24 25To run this program outside of a build environment, the following setup is 26required. 27- One of the local tool directories is an unzipped SDK emulator repository, 28 i.e., sdk-repo-<os>-emulator-<build>.zip. 29- If the instance doesn't require mixing system image, the local image 30 directory should be an unzipped SDK image repository, i.e., 31 sdk-repo-<os>-system-images-<build>.zip. 32- If the instance requires mixing system image, the local image directory 33 should be an unzipped extra image package, i.e., 34 emu-extra-<os>-system-images-<build>.zip. 35- If the instance requires mixing system or boot image, one of the local tool 36 directories should be an unzipped OTA tools package, i.e., otatools.zip. 37""" 38 39import logging 40import os 41import shutil 42import subprocess 43import sys 44 45from acloud import errors 46from acloud.create import base_avd_create 47from acloud.create import create_common 48from acloud.internal import constants 49from acloud.internal.lib import goldfish_utils 50from acloud.internal.lib import ota_tools 51from acloud.internal.lib import utils 52from acloud.list import instance 53from acloud.public import report 54 55 56logger = logging.getLogger(__name__) 57 58# Input and output file names 59_EMULATOR_BIN_NAME = "emulator" 60_EMULATOR_BIN_DIR_NAMES = ("bin64", "qemu") 61_SDK_REPO_EMULATOR_DIR_NAME = "emulator" 62_NON_MIXED_BACKUP_IMAGE_EXT = ".bak-non-mixed" 63_BUILD_PROP_FILE_NAME = "build.prop" 64# Additional data written to VerifiedBootParams.textproto. 65_UNLOCKING_PARAM = 'param: "androidboot.verifiedbootstate=orange"' 66# Timeout 67_DEFAULT_EMULATOR_TIMEOUT_SECS = 150 68_EMULATOR_TIMEOUT_ERROR = "Emulator did not boot within %(timeout)d secs." 69_EMU_KILL_TIMEOUT_SECS = 20 70_EMU_KILL_TIMEOUT_ERROR = "Emulator did not stop within %(timeout)d secs." 71 72_CONFIRM_RELAUNCH = ("\nGoldfish AVD is already running. \n" 73 "Enter 'y' to terminate current instance and launch a " 74 "new instance, enter anything else to exit out[y/N]: ") 75 76_MISSING_EMULATOR_MSG = ("Emulator binary is not found. Check " 77 "ANDROID_EMULATOR_PREBUILTS in build environment, " 78 "or set --local-tool to an unzipped SDK emulator " 79 "repository.") 80 81_INSTANCES_IN_USE_MSG = ("All instances are in use. Try resetting an instance " 82 "by specifying --local-instance and an id between 1 " 83 "and %(max_id)d.") 84 85 86class GoldfishLocalImageLocalInstance(base_avd_create.BaseAVDCreate): 87 """Create class for a local image local instance emulator.""" 88 89 def _CreateAVD(self, avd_spec, no_prompts): 90 """Create the AVD. 91 92 Args: 93 avd_spec: AVDSpec object that provides the local image directory. 94 no_prompts: Boolean, True to skip all prompts. 95 96 Returns: 97 A Report instance. 98 """ 99 if not utils.IsSupportedPlatform(print_warning=True): 100 result_report = report.Report(command="create") 101 result_report.SetStatus(report.Status.FAIL) 102 return result_report 103 104 try: 105 ins_id, ins_lock = self._LockInstance(avd_spec) 106 except errors.CreateError as e: 107 result_report = report.Report(command="create") 108 result_report.AddError(str(e)) 109 result_report.SetStatus(report.Status.FAIL) 110 return result_report 111 112 try: 113 ins = instance.LocalGoldfishInstance(ins_id, 114 avd_flavor=avd_spec.flavor) 115 if not self._CheckRunningEmulator(ins.adb, no_prompts): 116 # Mark as in-use so that it won't be auto-selected again. 117 ins_lock.SetInUse(True) 118 sys.exit(constants.EXIT_BY_USER) 119 120 result_report = self._CreateAVDForInstance(ins, avd_spec) 121 # The infrastructure is able to delete the instance only if the 122 # instance name is reported. This method changes the state to 123 # in-use after creating the report. 124 ins_lock.SetInUse(True) 125 return result_report 126 finally: 127 ins_lock.Unlock() 128 129 @staticmethod 130 def _LockInstance(avd_spec): 131 """Select an id and lock the instance. 132 133 Args: 134 avd_spec: AVDSpec for the device. 135 136 Returns: 137 The instance id and the LocalInstanceLock that is locked by this 138 process. 139 140 Raises: 141 errors.CreateError if fails to select or lock the instance. 142 """ 143 if avd_spec.local_instance_id: 144 ins_id = avd_spec.local_instance_id 145 ins_lock = instance.LocalGoldfishInstance.GetLockById(ins_id) 146 if ins_lock.Lock(): 147 return ins_id, ins_lock 148 raise errors.CreateError("Instance %d is locked by another " 149 "process." % ins_id) 150 151 max_id = instance.LocalGoldfishInstance.GetMaxNumberOfInstances() 152 for ins_id in range(1, max_id + 1): 153 ins_lock = instance.LocalGoldfishInstance.GetLockById(ins_id) 154 if ins_lock.LockIfNotInUse(timeout_secs=0): 155 logger.info("Selected instance id: %d", ins_id) 156 return ins_id, ins_lock 157 raise errors.CreateError(_INSTANCES_IN_USE_MSG % {"max_id": max_id}) 158 159 def _CreateAVDForInstance(self, ins, avd_spec): 160 """Create an emulator process for the goldfish instance. 161 162 Args: 163 ins: LocalGoldfishInstance to be initialized. 164 avd_spec: AVDSpec for the device. 165 166 Returns: 167 A Report instance. 168 169 Raises: 170 errors.GetSdkRepoPackageError if emulator binary is not found. 171 errors.GetLocalImageError if the local image directory does not 172 contain required files. 173 errors.CheckPathError if OTA tools are not found. 174 """ 175 emulator_path = self._FindEmulatorBinary( 176 avd_spec.local_tool_dirs + 177 create_common.GetNonEmptyEnvVars( 178 constants.ENV_ANDROID_EMULATOR_PREBUILTS)) 179 180 image_dir = self._FindImageDir(avd_spec.local_image_dir) 181 # Validate the input dir. 182 goldfish_utils.FindDiskImage(image_dir) 183 184 # TODO(b/141898893): In Android build environment, emulator gets build 185 # information from $ANDROID_PRODUCT_OUT/system/build.prop. 186 # If image_dir is an extacted SDK repository, the file is at 187 # image_dir/build.prop. Acloud copies it to 188 # image_dir/system/build.prop. 189 self._CopyBuildProp(image_dir) 190 191 instance_dir = ins.instance_dir 192 create_common.PrepareLocalInstanceDir(instance_dir, avd_spec) 193 194 logcat_path = os.path.join(instance_dir, "logcat.txt") 195 stdouterr_path = os.path.join(instance_dir, "kernel.log") 196 logs = [report.LogFile(logcat_path, constants.LOG_TYPE_LOGCAT), 197 report.LogFile(stdouterr_path, constants.LOG_TYPE_KERNEL_LOG)] 198 extra_args = self._ConvertAvdSpecToArgs(avd_spec, instance_dir) 199 200 logger.info("Instance directory: %s", instance_dir) 201 proc = self._StartEmulatorProcess(emulator_path, instance_dir, 202 image_dir, ins.console_port, 203 ins.adb_port, logcat_path, 204 stdouterr_path, extra_args) 205 206 boot_timeout_secs = (avd_spec.boot_timeout_secs or 207 _DEFAULT_EMULATOR_TIMEOUT_SECS) 208 result_report = report.Report(command="create") 209 try: 210 self._WaitForEmulatorToStart(ins.adb, proc, boot_timeout_secs) 211 except (errors.DeviceBootTimeoutError, errors.SubprocessFail) as e: 212 result_report.SetStatus(report.Status.BOOT_FAIL) 213 result_report.AddDeviceBootFailure(ins.name, ins.ip, 214 ins.adb_port, vnc_port=None, 215 error=str(e), 216 device_serial=ins.device_serial, 217 logs=logs) 218 else: 219 result_report.SetStatus(report.Status.SUCCESS) 220 result_report.AddDevice(ins.name, ins.ip, ins.adb_port, 221 vnc_port=None, 222 device_serial=ins.device_serial, 223 logs=logs) 224 225 return result_report 226 227 @staticmethod 228 def _FindEmulatorBinary(search_paths): 229 """Find emulator binary in the directories. 230 231 The directories may be extracted from zip archives without preserving 232 file permissions. When this method finds the emulator binary and its 233 dependencies, it sets the files to be executable. 234 235 Args: 236 search_paths: Collection of strings, the directories to search for 237 emulator binary. 238 239 Returns: 240 The path to the emulator binary. 241 242 Raises: 243 errors.GetSdkRepoPackageError if emulator binary is not found. 244 """ 245 emulator_dir = None 246 # Find in unzipped sdk-repo-*.zip. 247 for search_path in search_paths: 248 if os.path.isfile(os.path.join(search_path, _EMULATOR_BIN_NAME)): 249 emulator_dir = search_path 250 break 251 252 sdk_repo_dir = os.path.join(search_path, 253 _SDK_REPO_EMULATOR_DIR_NAME) 254 if os.path.isfile(os.path.join(sdk_repo_dir, _EMULATOR_BIN_NAME)): 255 emulator_dir = sdk_repo_dir 256 break 257 258 if not emulator_dir: 259 raise errors.GetSdkRepoPackageError(_MISSING_EMULATOR_MSG) 260 261 emulator_dir = os.path.abspath(emulator_dir) 262 # Set the binaries to be executable. 263 for subdir_name in _EMULATOR_BIN_DIR_NAMES: 264 subdir_path = os.path.join(emulator_dir, subdir_name) 265 if os.path.isdir(subdir_path): 266 utils.SetDirectoryTreeExecutable(subdir_path) 267 268 emulator_path = os.path.join(emulator_dir, _EMULATOR_BIN_NAME) 269 utils.SetExecutable(emulator_path) 270 return emulator_path 271 272 @staticmethod 273 def _FindImageDir(image_dir): 274 """Find emulator images in the directory. 275 276 In build environment, the images are in $ANDROID_PRODUCT_OUT. 277 In an extracted SDK repository, the images are in the subdirectory 278 named after the CPU architecture. 279 280 Args: 281 image_dir: The output directory in build environment or an 282 extracted SDK repository. 283 284 Returns: 285 The subdirectory if image_dir contains only one subdirectory; 286 image_dir otherwise. 287 """ 288 image_dir = os.path.abspath(image_dir) 289 entries = os.listdir(image_dir) 290 if len(entries) == 1: 291 first_entry = os.path.join(image_dir, entries[0]) 292 if os.path.isdir(first_entry): 293 return first_entry 294 return image_dir 295 296 @staticmethod 297 def _IsEmulatorRunning(adb): 298 """Check existence of an emulator by sending an empty command. 299 300 Args: 301 adb: adb_tools.AdbTools initialized with the emulator's serial. 302 303 Returns: 304 Boolean, whether the emulator is running. 305 """ 306 return adb.EmuCommand() == 0 307 308 def _CheckRunningEmulator(self, adb, no_prompts): 309 """Attempt to delete a running emulator. 310 311 Args: 312 adb: adb_tools.AdbTools initialized with the emulator's serial. 313 no_prompts: Boolean, True to skip all prompts. 314 315 Returns: 316 Whether the user wants to continue. 317 318 Raises: 319 errors.CreateError if the emulator isn't deleted. 320 """ 321 if not self._IsEmulatorRunning(adb): 322 return True 323 logger.info("Goldfish AVD is already running.") 324 if no_prompts or utils.GetUserAnswerYes(_CONFIRM_RELAUNCH): 325 if adb.EmuCommand("kill") != 0: 326 raise errors.CreateError("Cannot kill emulator.") 327 self._WaitForEmulatorToStop(adb) 328 return True 329 return False 330 331 @staticmethod 332 def _CopyBuildProp(image_dir): 333 """Copy build.prop to system/build.prop if it doesn't exist. 334 335 Args: 336 image_dir: The directory to find build.prop in. 337 338 Raises: 339 errors.GetLocalImageError if build.prop does not exist. 340 """ 341 build_prop_path = os.path.join(image_dir, "system", 342 _BUILD_PROP_FILE_NAME) 343 if os.path.exists(build_prop_path): 344 return 345 build_prop_src_path = os.path.join(image_dir, _BUILD_PROP_FILE_NAME) 346 if not os.path.isfile(build_prop_src_path): 347 raise errors.GetLocalImageError("No %s in %s." % 348 (_BUILD_PROP_FILE_NAME, image_dir)) 349 build_prop_dir = os.path.dirname(build_prop_path) 350 logger.info("Copy %s to %s", _BUILD_PROP_FILE_NAME, build_prop_path) 351 if not os.path.exists(build_prop_dir): 352 os.makedirs(build_prop_dir) 353 shutil.copyfile(build_prop_src_path, build_prop_path) 354 355 @staticmethod 356 def _ReplaceSystemQemuImg(new_image, image_dir): 357 """Replace system-qemu.img in the directory. 358 359 Args: 360 new_image: The path to the new image. 361 image_dir: The directory containing system-qemu.img. 362 """ 363 system_qemu_img = os.path.join(image_dir, 364 goldfish_utils.SYSTEM_QEMU_IMAGE_NAME) 365 if os.path.exists(system_qemu_img): 366 system_qemu_img_bak = system_qemu_img + _NON_MIXED_BACKUP_IMAGE_EXT 367 if not os.path.exists(system_qemu_img_bak): 368 # If system-qemu.img.bak-non-mixed does not exist, the 369 # system-qemu.img was not created by acloud and should be 370 # preserved. The user can restore it by renaming the backup to 371 # system-qemu.img. 372 logger.info("Rename %s to %s%s.", 373 system_qemu_img, 374 goldfish_utils.SYSTEM_QEMU_IMAGE_NAME, 375 _NON_MIXED_BACKUP_IMAGE_EXT) 376 os.rename(system_qemu_img, system_qemu_img_bak) 377 else: 378 # The existing system-qemu.img.bak-non-mixed was renamed by 379 # the previous invocation on acloud. The existing 380 # system-qemu.img is a mixed image. Acloud removes the mixed 381 # image because it is large and not reused. 382 os.remove(system_qemu_img) 383 try: 384 logger.info("Link %s to %s.", system_qemu_img, new_image) 385 os.link(new_image, system_qemu_img) 386 except OSError: 387 logger.info("Fail to link. Copy %s to %s", 388 system_qemu_img, new_image) 389 shutil.copyfile(new_image, system_qemu_img) 390 391 @staticmethod 392 def _RewriteVerifiedBootParams(image_dir): 393 """Rewrite VerifiedBootParams.textproto. 394 395 This method appends the parameter that unlocks the device so that the 396 disabled vbmeta takes effect. An alternative is to append to the kernel 397 command line by `emulator -qemu -append`, but that does not pass the 398 compliance test. 399 400 Args: 401 image_dir: The directory containing VerifiedBootParams.textproto. 402 """ 403 params_path = os.path.join( 404 image_dir, goldfish_utils.VERIFIED_BOOT_PARAMS_FILE_NAME) 405 with open(params_path, "r", encoding="utf-8") as params_file: 406 if _UNLOCKING_PARAM in params_file.read(): 407 # The file was possibly rewritten in the previous invocation. 408 logging.info("%s conatins the parameter that unlocks the " 409 "device already.", params_path) 410 return 411 412 bak_params_path = params_path + _NON_MIXED_BACKUP_IMAGE_EXT 413 # If the backup file exists, it was possibly created in the previous 414 # invocation and contains the original contents. The user can restore 415 # the original file by renaming it. 416 if not os.path.exists(bak_params_path): 417 logging.info("Copy %s to %s.", params_path, bak_params_path) 418 shutil.copyfile(params_path, bak_params_path) 419 420 logging.info("Write the unlocking parameter to %s.", params_path) 421 with open(params_path, "a", encoding="utf-8") as params_file: 422 params_file.writelines(["\n", _UNLOCKING_PARAM, "\n"]) 423 424 def _FindAndMixKernelImages(self, kernel_search_path, image_dir, tool_dirs, 425 instance_dir): 426 """Find kernel images and mix them with emulator images. 427 428 Args: 429 kernel_search_path: The path to the boot image or the directory 430 containing kernel and ramdisk. 431 image_dir: The directory containing the emulator images. 432 tool_dirs: A list of directories to look for OTA tools. 433 instance_dir: The instance directory for mixed images. 434 435 Returns: 436 A pair of strings, the paths to kernel image and ramdisk image. 437 """ 438 # Find generic boot image. 439 boot_image_path = create_common.FindBootImage(kernel_search_path, 440 raise_error=False) 441 if boot_image_path: 442 logger.info("Found boot image: %s", boot_image_path) 443 return goldfish_utils.MixWithBootImage( 444 os.path.join(instance_dir, "mix_kernel"), 445 self._FindImageDir(image_dir), 446 boot_image_path, ota_tools.FindOtaTools(tool_dirs)) 447 448 # Find kernel and ramdisk images built for emulator. 449 kernel_dir = self._FindImageDir(kernel_search_path) 450 kernel_path, ramdisk_path = goldfish_utils.FindKernelImages(kernel_dir) 451 logger.info("Found kernel and ramdisk: %s %s", 452 kernel_path, ramdisk_path) 453 return kernel_path, ramdisk_path 454 455 def _ConvertAvdSpecToArgs(self, avd_spec, instance_dir): 456 """Convert AVD spec to emulator arguments. 457 458 Args: 459 avd_spec: AVDSpec object. 460 instance_dir: The instance directory for mixed images. 461 462 Returns: 463 List of strings, the arguments for emulator command. 464 """ 465 args = goldfish_utils.ConvertAvdSpecToArgs(avd_spec) 466 467 if not avd_spec.autoconnect: 468 args.append("-no-window") 469 470 ota_tools_search_paths = ( 471 avd_spec.local_tool_dirs + 472 create_common.GetNonEmptyEnvVars( 473 constants.ENV_ANDROID_SOONG_HOST_OUT, 474 constants.ENV_ANDROID_HOST_OUT)) 475 476 if avd_spec.local_kernel_image: 477 kernel_path, ramdisk_path = self._FindAndMixKernelImages( 478 avd_spec.local_kernel_image, avd_spec.local_image_dir, 479 ota_tools_search_paths, instance_dir) 480 args.extend(("-kernel", kernel_path, "-ramdisk", ramdisk_path)) 481 482 if avd_spec.local_system_image: 483 image_dir = self._FindImageDir(avd_spec.local_image_dir) 484 # No known use case requires replacing system_ext and product. 485 system_image_path = create_common.FindSystemImages( 486 avd_spec.local_system_image).system 487 mixed_image = goldfish_utils.MixDiskImage( 488 os.path.join(instance_dir, "mix_disk"), image_dir, 489 system_image_path, None, # system_dlkm is not implemented. 490 ota_tools.FindOtaTools(ota_tools_search_paths)) 491 492 # TODO(b/142228085): Use -system instead of modifying image_dir. 493 self._ReplaceSystemQemuImg(mixed_image, image_dir) 494 self._RewriteVerifiedBootParams(image_dir) 495 496 return args 497 498 @staticmethod 499 def _StartEmulatorProcess(emulator_path, working_dir, image_dir, 500 console_port, adb_port, logcat_path, 501 stdouterr_path, extra_args): 502 """Start an emulator process. 503 504 Args: 505 emulator_path: The path to emulator binary. 506 working_dir: The working directory for the emulator process. 507 The emulator command creates files in the directory. 508 image_dir: The directory containing the required images. 509 e.g., composite system.img or system-qemu.img. 510 console_port: The console port of the emulator. 511 adb_port: The ADB port of the emulator. 512 logcat_path: The path where logcat is redirected. 513 stdouterr_path: The path where stdout and stderr are redirected. 514 extra_args: List of strings, the extra arguments. 515 516 Returns: 517 A Popen object, the emulator process. 518 """ 519 emulator_env = os.environ.copy() 520 emulator_env[constants.ENV_ANDROID_PRODUCT_OUT] = image_dir 521 # Set ANDROID_TMP for emulator to create AVD info files in. 522 emulator_env[constants.ENV_ANDROID_TMP] = working_dir 523 # Set ANDROID_BUILD_TOP so that the emulator considers itself to be in 524 # build environment. 525 if constants.ENV_ANDROID_BUILD_TOP not in emulator_env: 526 emulator_env[constants.ENV_ANDROID_BUILD_TOP] = image_dir 527 528 # The command doesn't create -stdouterr-file automatically. 529 with open(stdouterr_path, "w") as _: 530 pass 531 532 emulator_cmd = [ 533 os.path.abspath(emulator_path), 534 "-verbose", "-show-kernel", "-read-only", 535 "-ports", str(console_port) + "," + str(adb_port), 536 "-logcat-output", logcat_path, 537 "-stdouterr-file", stdouterr_path 538 ] 539 emulator_cmd.extend(extra_args) 540 logger.debug("Execute %s", emulator_cmd) 541 542 with open(os.devnull, "rb+") as devnull: 543 return subprocess.Popen( 544 emulator_cmd, shell=False, cwd=working_dir, env=emulator_env, 545 stdin=devnull, stdout=devnull, stderr=devnull) 546 547 def _WaitForEmulatorToStop(self, adb): 548 """Wait for an emulator to be unavailable on the console port. 549 550 Args: 551 adb: adb_tools.AdbTools initialized with the emulator's serial. 552 553 Raises: 554 errors.CreateError if the emulator does not stop within timeout. 555 """ 556 create_error = errors.CreateError(_EMU_KILL_TIMEOUT_ERROR % 557 {"timeout": _EMU_KILL_TIMEOUT_SECS}) 558 utils.PollAndWait(func=lambda: self._IsEmulatorRunning(adb), 559 expected_return=False, 560 timeout_exception=create_error, 561 timeout_secs=_EMU_KILL_TIMEOUT_SECS, 562 sleep_interval_secs=1) 563 564 def _WaitForEmulatorToStart(self, adb, proc, timeout): 565 """Wait for an emulator to be available on the console port. 566 567 Args: 568 adb: adb_tools.AdbTools initialized with the emulator's serial. 569 proc: Popen object, the running emulator process. 570 timeout: Integer, timeout in seconds. 571 572 Raises: 573 errors.DeviceBootTimeoutError if the emulator does not boot within 574 timeout. 575 errors.SubprocessFail if the process terminates. 576 """ 577 timeout_error = errors.DeviceBootTimeoutError(_EMULATOR_TIMEOUT_ERROR % 578 {"timeout": timeout}) 579 utils.PollAndWait(func=lambda: (proc.poll() is None and 580 self._IsEmulatorRunning(adb)), 581 expected_return=True, 582 timeout_exception=timeout_error, 583 timeout_secs=timeout, 584 sleep_interval_secs=5) 585 if proc.poll() is not None: 586 raise errors.SubprocessFail("Emulator process returned %d." % 587 proc.returncode) 588