1 #!/usr/bin/env python 2 # 3 # Copyright 2018 - The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 r"""LocalImageLocalInstance class. 17 18 Create class that is responsible for creating a local instance AVD with a 19 local image. For launching multiple local instances under the same user, 20 The cuttlefish tool requires 3 variables: 21 - ANDROID_HOST_OUT: To locate the launch_cvd tool. 22 - HOME: To specify the temporary folder of launch_cvd. 23 - CUTTLEFISH_INSTANCE: To specify the instance id. 24 Acloud user must either set ANDROID_HOST_OUT or run acloud with --local-tool. 25 The user can optionally specify the folder by --local-instance-dir and the 26 instance id by --local-instance. 27 28 The adb port and vnc port of local instance will be decided according to 29 instance id. The rule of adb port will be '6520 + [instance id] - 1' and the 30 vnc port will be '6444 + [instance id] - 1'. 31 e.g: 32 If instance id = 3 the adb port will be 6522 and vnc port will be 6446. 33 34 To delete the local instance, we will call stop_cvd with the environment 35 variable [CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish 36 json. 37 38 To run this program outside of a build environment, the following setup is 39 required. 40 - One of the local tool directories is a decompressed cvd host package, 41 i.e., cvd-host_package.tar.gz. 42 - If the instance doesn't require mixed images, the local image directory 43 should be an unzipped update package, i.e., <target>-img-<build>.zip, 44 which contains a super image. 45 - If the instance requires mixing system image, the local image directory 46 should be an unzipped target files package, i.e., 47 <target>-target_files-<build>.zip, 48 which contains misc info and images not packed into a super image. 49 - If the instance requires mixing system image, one of the local tool 50 directories should be an unzipped OTA tools package, i.e., otatools.zip. 51 """ 52 53 import collections 54 import logging 55 import os 56 import re 57 import shutil 58 import subprocess 59 import sys 60 61 from acloud import errors 62 from acloud.create import base_avd_create 63 from acloud.create import create_common 64 from acloud.internal import constants 65 from acloud.internal.lib import cvd_utils 66 from acloud.internal.lib import ota_tools 67 from acloud.internal.lib import utils 68 from acloud.internal.lib.adb_tools import AdbTools 69 from acloud.list import list as list_instance 70 from acloud.list import instance 71 from acloud.public import report 72 from acloud.setup import mkcert 73 74 75 logger = logging.getLogger(__name__) 76 77 _SUPER_IMAGE_NAME = "super.img" 78 _MIXED_SUPER_IMAGE_NAME = "mixed_super.img" 79 _CMD_CVD_START = " start" 80 _CMD_CVD_VERSION = " version" 81 _CMD_LAUNCH_CVD_ARGS = ( 82 " -daemon -config=%s -system_image_dir %s -instance_dir %s " 83 "-undefok=report_anonymous_usage_stats,config,proxy_fastboot " 84 "-report_anonymous_usage_stats=y") 85 _CMD_LAUNCH_CVD_HW_ARGS = " -cpus %s -x_res %s -y_res %s -dpi %s -memory_mb %s" 86 _CMD_LAUNCH_CVD_DISK_ARGS = ( 87 " -blank_data_image_mb %s -data_policy always_create") 88 _CMD_LAUNCH_CVD_WEBRTC_ARGS = " -start_webrtc=true" 89 _CMD_LAUNCH_CVD_VNC_ARG = " -start_vnc_server=true" 90 _CMD_LAUNCH_CVD_SUPER_IMAGE_ARG = " -super_image=%s" 91 _CMD_LAUNCH_CVD_BOOT_IMAGE_ARG = " -boot_image=%s" 92 _CMD_LAUNCH_CVD_VENDOR_BOOT_IMAGE_ARG = " -vendor_boot_image=%s" 93 _CMD_LAUNCH_CVD_KERNEL_IMAGE_ARG = " -kernel_path=%s" 94 _CMD_LAUNCH_CVD_INITRAMFS_IMAGE_ARG = " -initramfs_path=%s" 95 _CMD_LAUNCH_CVD_VBMETA_IMAGE_ARG = " -vbmeta_image=%s" 96 _CMD_LAUNCH_CVD_NO_ADB_ARG = " -run_adb_connector=false" 97 # Supported since U. 98 _CMD_LAUNCH_CVD_NO_FASTBOOT_ARG = " -proxy_fastboot=false" 99 _CMD_LAUNCH_CVD_INSTANCE_NUMS_ARG = " -instance_nums=%s" 100 # Connect the OpenWrt device via console file. 101 _CMD_LAUNCH_CVD_CONSOLE_ARG = " -console=true" 102 _CMD_LAUNCH_CVD_WEBRTC_DEIVE_ID = " -webrtc_device_id=%s" 103 _CONFIG_RE = re.compile(r"^config=(?P<config>.+)") 104 _CONSOLE_NAME = "console" 105 # Files to store the output when launching cvds. 106 _STDOUT = "stdout" 107 _STDERR = "stderr" 108 _MAX_REPORTED_ERROR_LINES = 10 109 110 # In accordance with the number of network interfaces in 111 # /etc/init.d/cuttlefish-common 112 _MAX_INSTANCE_ID = 10 113 114 # TODO(b/213521240): To check why the delete function is not work and 115 # has to manually delete temp folder. 116 _INSTANCES_IN_USE_MSG = ("All instances are in use. Try resetting an instance " 117 "by specifying --local-instance and an id between 1 " 118 "and %d. Alternatively, to run 'acloud delete --all' " 119 % _MAX_INSTANCE_ID) 120 _CONFIRM_RELAUNCH = ("\nCuttlefish AVD[id:%d] is already running. \n" 121 "Enter 'y' to terminate current instance and launch a " 122 "new instance, enter anything else to exit out[y/N]: ") 123 124 # The first two fields of this named tuple are image folder and CVD host 125 # package folder which are essential for local instances. The following fields 126 # are optional. They are set when the AVD spec requires to mix images. 127 ArtifactPaths = collections.namedtuple( 128 "ArtifactPaths", 129 ["image_dir", "host_bins", "host_artifacts", "misc_info", "ota_tools_dir", 130 "system_image", "boot_image", "vendor_boot_image", "kernel_image", 131 "initramfs_image", "vendor_image", "vendor_dlkm_image", "odm_image", 132 "odm_dlkm_image"]) 133 134 135 class LocalImageLocalInstance(base_avd_create.BaseAVDCreate): 136 """Create class for a local image local instance AVD.""" 137 138 @utils.TimeExecute(function_description="Total time: ", 139 print_before_call=False, print_status=False) 140 def _CreateAVD(self, avd_spec, no_prompts): 141 """Create the AVD. 142 143 Args: 144 avd_spec: AVDSpec object that tells us what we're going to create. 145 no_prompts: Boolean, True to skip all prompts. 146 147 Returns: 148 A Report instance. 149 """ 150 # Running instances on local is not supported on all OS. 151 result_report = report.Report(command="create") 152 if not utils.IsSupportedPlatform(print_warning=True): 153 result_report.UpdateFailure( 154 "The platform doesn't support to run acloud.") 155 return result_report 156 if not utils.IsSupportedKvm(): 157 result_report.UpdateFailure( 158 "The environment doesn't support virtualization.") 159 return result_report 160 161 artifact_paths = self.GetImageArtifactsPath(avd_spec) 162 163 try: 164 ins_ids, ins_locks = self._SelectAndLockInstances(avd_spec) 165 except errors.CreateError as e: 166 result_report.UpdateFailure(str(e)) 167 return result_report 168 169 try: 170 for ins_id, ins_lock in zip(ins_ids, ins_locks): 171 if not self._CheckRunningCvd(ins_id, no_prompts): 172 # Mark as in-use so that it won't be auto-selected again. 173 ins_lock.SetInUse(True) 174 sys.exit(constants.EXIT_BY_USER) 175 176 result_report = self._CreateInstance(ins_ids, artifact_paths, 177 avd_spec, no_prompts) 178 # Set the state to in-use if the instances start successfully. 179 # Failing instances are not set to in-use so that the user can 180 # restart them with the same IDs. 181 if result_report.status == report.Status.SUCCESS: 182 for ins_lock in ins_locks: 183 ins_lock.SetInUse(True) 184 return result_report 185 finally: 186 for ins_lock in ins_locks: 187 ins_lock.Unlock() 188 189 def _SelectAndLockInstances(self, avd_spec): 190 """Select the ids and lock these instances. 191 192 Args: 193 avd_spec: AVCSpec for the device. 194 195 Returns: 196 The instance ids and the LocalInstanceLock that are locked. 197 """ 198 main_id, main_lock = self._SelectAndLockInstance(avd_spec) 199 ins_ids = [main_id] 200 ins_locks = [main_lock] 201 for _ in range(2, avd_spec.num_avds_per_instance + 1): 202 ins_id, ins_lock = self._SelectOneFreeInstance() 203 ins_ids.append(ins_id) 204 ins_locks.append(ins_lock) 205 logger.info("Selected instance ids: %s", ins_ids) 206 return ins_ids, ins_locks 207 208 def _SelectAndLockInstance(self, avd_spec): 209 """Select an id and lock the instance. 210 211 Args: 212 avd_spec: AVDSpec for the device. 213 214 Returns: 215 The instance id and the LocalInstanceLock that is locked by this 216 process. 217 218 Raises: 219 errors.CreateError if fails to select or lock the instance. 220 """ 221 if avd_spec.local_instance_id: 222 ins_id = avd_spec.local_instance_id 223 ins_lock = instance.GetLocalInstanceLock(ins_id) 224 if ins_lock.Lock(): 225 return ins_id, ins_lock 226 raise errors.CreateError("Instance %d is locked by another " 227 "process." % ins_id) 228 return self._SelectOneFreeInstance() 229 230 @staticmethod 231 def _SelectOneFreeInstance(): 232 """Select one free id and lock the instance. 233 234 Returns: 235 The instance id and the LocalInstanceLock that is locked by this 236 process. 237 238 Raises: 239 errors.CreateError if fails to select or lock the instance. 240 """ 241 for ins_id in range(1, _MAX_INSTANCE_ID + 1): 242 ins_lock = instance.GetLocalInstanceLock(ins_id) 243 if ins_lock.LockIfNotInUse(timeout_secs=0): 244 return ins_id, ins_lock 245 raise errors.CreateError(_INSTANCES_IN_USE_MSG) 246 247 # pylint: disable=too-many-locals,too-many-statements 248 def _CreateInstance(self, instance_ids, artifact_paths, avd_spec, 249 no_prompts): 250 """Create a CVD instance. 251 252 Args: 253 instance_ids: List of integer of instance ids. 254 artifact_paths: ArtifactPaths object. 255 avd_spec: AVDSpec for the instance. 256 no_prompts: Boolean, True to skip all prompts. 257 258 Returns: 259 A Report instance. 260 """ 261 local_instance_id = instance_ids[0] 262 webrtc_port = self.GetWebrtcSigServerPort(local_instance_id) 263 if avd_spec.connect_webrtc: 264 utils.ReleasePort(webrtc_port) 265 266 cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id) 267 create_common.PrepareLocalInstanceDir(cvd_home_dir, avd_spec) 268 super_image_path = None 269 vbmeta_image_path = None 270 if artifact_paths.system_image or artifact_paths.vendor_image: 271 super_image_path = os.path.join(cvd_home_dir, 272 _MIXED_SUPER_IMAGE_NAME) 273 ota = ota_tools.OtaTools(artifact_paths.ota_tools_dir) 274 ota.MixSuperImage( 275 super_image_path, artifact_paths.misc_info, 276 artifact_paths.image_dir, 277 system_image=artifact_paths.system_image, 278 vendor_image=artifact_paths.vendor_image, 279 vendor_dlkm_image=artifact_paths.vendor_dlkm_image, 280 odm_image=artifact_paths.odm_image, 281 odm_dlkm_image=artifact_paths.odm_dlkm_image) 282 if artifact_paths.vendor_image: 283 vbmeta_image_path = os.path.join(cvd_home_dir, 284 "disabled_vbmeta.img") 285 ota.MakeDisabledVbmetaImage(vbmeta_image_path) 286 runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id) 287 # TODO(b/168171781): cvd_status of list/delete via the symbolic. 288 self.PrepareLocalCvdToolsLink(cvd_home_dir, artifact_paths.host_bins) 289 if avd_spec.mkcert and avd_spec.connect_webrtc: 290 self._TrustCertificatesForWebRTC(artifact_paths.host_artifacts) 291 if not avd_spec.use_launch_cvd: 292 self._LogCvdVersion(artifact_paths.host_bins) 293 294 hw_property = None 295 if avd_spec.hw_customize: 296 hw_property = avd_spec.hw_property 297 config = self._GetConfigFromAndroidInfo( 298 os.path.join(artifact_paths.image_dir, 299 constants.ANDROID_INFO_FILE)) 300 cmd = self.PrepareLaunchCVDCmd(hw_property, 301 avd_spec.connect_adb, 302 avd_spec.connect_fastboot, 303 artifact_paths, 304 runtime_dir, 305 avd_spec.connect_webrtc, 306 avd_spec.connect_vnc, 307 super_image_path, 308 avd_spec.launch_args, 309 config or avd_spec.flavor, 310 avd_spec.openwrt, 311 avd_spec.use_launch_cvd, 312 instance_ids, 313 avd_spec.webrtc_device_id, 314 vbmeta_image_path) 315 316 result_report = report.Report(command="create") 317 instance_name = instance.GetLocalInstanceName(local_instance_id) 318 try: 319 self._LaunchCvd(cmd, local_instance_id, artifact_paths.host_bins, 320 artifact_paths.host_artifacts, 321 cvd_home_dir, (avd_spec.boot_timeout_secs or 322 constants.DEFAULT_CF_BOOT_TIMEOUT)) 323 logs = cvd_utils.FindLocalLogs(runtime_dir, local_instance_id) 324 except errors.LaunchCVDFail as launch_error: 325 logs = cvd_utils.FindLocalLogs(runtime_dir, local_instance_id) 326 err_msg = ("Cannot create cuttlefish instance: %s\n" 327 "For more detail: %s/launcher.log" % 328 (launch_error, runtime_dir)) 329 if constants.ERROR_MSG_WEBRTC_NOT_SUPPORT in str(launch_error): 330 err_msg = ( 331 "WEBRTC is not supported in current build. Please try VNC " 332 "such as '$acloud create --autoconnect vnc'") 333 result_report.SetStatus(report.Status.BOOT_FAIL) 334 result_report.SetErrorType(constants.ACLOUD_BOOT_UP_ERROR) 335 result_report.AddDeviceBootFailure( 336 instance_name, constants.LOCALHOST, None, None, error=err_msg, 337 logs=logs) 338 return result_report 339 340 active_ins = list_instance.GetActiveCVD(local_instance_id) 341 if active_ins: 342 update_data = None 343 if avd_spec.openwrt: 344 console_dir = os.path.dirname( 345 instance.GetLocalInstanceConfig(local_instance_id)) 346 console_path = os.path.join(console_dir, _CONSOLE_NAME) 347 update_data = {"screen_command": f"screen {console_path}"} 348 result_report.SetStatus(report.Status.SUCCESS) 349 result_report.AddDevice(instance_name, constants.LOCALHOST, 350 active_ins.adb_port, active_ins.vnc_port, 351 webrtc_port, logs=logs, 352 update_data=update_data) 353 # Launch vnc client if we're auto-connecting. 354 if avd_spec.connect_vnc: 355 utils.LaunchVNCFromReport(result_report, avd_spec, no_prompts) 356 if avd_spec.connect_webrtc: 357 utils.LaunchBrowserFromReport(result_report) 358 if avd_spec.unlock_screen: 359 AdbTools(active_ins.adb_port).AutoUnlockScreen() 360 else: 361 err_msg = "cvd_status return non-zero after launch_cvd" 362 logger.error(err_msg) 363 result_report.SetStatus(report.Status.BOOT_FAIL) 364 result_report.SetErrorType(constants.ACLOUD_BOOT_UP_ERROR) 365 result_report.AddDeviceBootFailure( 366 instance_name, constants.LOCALHOST, None, None, error=err_msg, 367 logs=logs) 368 return result_report 369 370 @staticmethod 371 def GetWebrtcSigServerPort(instance_id): 372 """Get the port of the signaling server. 373 374 Args: 375 instance_id: Integer of instance id. 376 377 Returns: 378 Integer of signaling server port. 379 """ 380 return constants.WEBRTC_LOCAL_PORT + instance_id - 1 381 382 @staticmethod 383 def _FindCvdHostBinaries(search_paths): 384 """Return the directory that contains CVD host binaries.""" 385 for search_path in search_paths: 386 if os.path.isfile(os.path.join(search_path, "bin", 387 constants.CMD_LAUNCH_CVD)): 388 return search_path 389 390 raise errors.GetCvdLocalHostPackageError( 391 "CVD host binaries are not found. Please run `make hosttar`, or " 392 "set --local-tool to an extracted CVD host package.") 393 394 @staticmethod 395 def _FindCvdHostArtifactsPath(search_paths): 396 """Return the directory that contains CVD host artifacts (in particular 397 webrtc). 398 """ 399 for search_path in search_paths: 400 if os.path.isfile(os.path.join(search_path, 401 "usr/share/webrtc/certs", 402 "server.crt")): 403 return search_path 404 405 raise errors.GetCvdLocalHostPackageError( 406 "CVD host webrtc artifacts are not found. Please run " 407 "`make hosttar`, or set --local-tool to an extracted CVD host " 408 "package.") 409 410 @staticmethod 411 def _VerifyExtractedImgZip(image_dir): 412 """Verify that a path is build output dir or extracted img zip. 413 414 This method checks existence of super image. The file is in img zip 415 but not in target files zip. A cuttlefish instance requires a super 416 image if no system image or OTA tools are given. 417 418 Args: 419 image_dir: The directory to be verified. 420 421 Raises: 422 errors.GetLocalImageError if the directory does not contain the 423 needed file. 424 """ 425 if not os.path.isfile(os.path.join(image_dir, _SUPER_IMAGE_NAME)): 426 raise errors.GetLocalImageError( 427 f"Cannot find {_SUPER_IMAGE_NAME} in {image_dir}. The " 428 f"directory is expected to be an extracted img zip or " 429 f"{constants.ENV_ANDROID_PRODUCT_OUT}.") 430 431 @staticmethod 432 def _FindBootOrKernelImages(image_path): 433 """Find boot, vendor_boot, kernel, and initramfs images in a path. 434 435 This method expects image_path to be: 436 - An output directory of a kernel build. It contains a kernel image and 437 initramfs.img. 438 - A generic boot image or its parent directory. The image name is 439 boot-*.img. The directory does not contain vendor_boot.img. 440 - An output directory of a cuttlefish build. It contains boot.img and 441 vendor_boot.img. 442 443 Args: 444 image_path: A path to an image file or an image directory. 445 446 Returns: 447 A tuple of strings, the paths to boot, vendor_boot, kernel, and 448 initramfs images. Each value can be None. 449 450 Raises: 451 errors.GetLocalImageError if image_path does not contain boot or 452 kernel images. 453 """ 454 kernel_image_path, initramfs_image_path = cvd_utils.FindKernelImages( 455 image_path) 456 if kernel_image_path and initramfs_image_path: 457 return None, None, kernel_image_path, initramfs_image_path 458 459 boot_image_path, vendor_boot_image_path = cvd_utils.FindBootImages( 460 image_path) 461 if boot_image_path: 462 return boot_image_path, vendor_boot_image_path, None, None 463 464 raise errors.GetLocalImageError(f"{image_path} is not a boot image or " 465 f"a directory containing images.") 466 467 def GetImageArtifactsPath(self, avd_spec): 468 """Get image artifacts path. 469 470 This method will check if launch_cvd is exist and return the tuple path 471 (image path and host bins path) where they are located respectively. 472 For remote image, RemoteImageLocalInstance will override this method 473 and return the artifacts path which is extracted and downloaded from 474 remote. 475 476 Args: 477 avd_spec: AVDSpec object that tells us what we're going to create. 478 479 Returns: 480 ArtifactPaths object consisting of image directory and host bins 481 package. 482 483 Raises: 484 errors.GetCvdLocalHostPackageError, errors.GetLocalImageError, or 485 errors.CheckPathError if any artifact is not found. 486 """ 487 image_dir = os.path.abspath(avd_spec.local_image_dir) 488 tool_dirs = (avd_spec.local_tool_dirs + 489 create_common.GetNonEmptyEnvVars( 490 constants.ENV_ANDROID_SOONG_HOST_OUT, 491 constants.ENV_ANDROID_HOST_OUT)) 492 host_bins_path = self._FindCvdHostBinaries(tool_dirs) 493 host_artifacts_path = self._FindCvdHostArtifactsPath(tool_dirs) 494 495 if avd_spec.local_system_image: 496 misc_info_path = cvd_utils.FindMiscInfo(image_dir) 497 image_dir = cvd_utils.FindImageDir(image_dir) 498 ota_tools_dir = os.path.abspath( 499 ota_tools.FindOtaToolsDir(tool_dirs)) 500 system_image_path = create_common.FindSystemImage( 501 avd_spec.local_system_image) 502 else: 503 self._VerifyExtractedImgZip(image_dir) 504 misc_info_path = None 505 ota_tools_dir = None 506 system_image_path = None 507 508 if avd_spec.local_kernel_image: 509 ( 510 boot_image_path, 511 vendor_boot_image_path, 512 kernel_image_path, 513 initramfs_image_path, 514 ) = self._FindBootOrKernelImages( 515 os.path.abspath(avd_spec.local_kernel_image)) 516 else: 517 boot_image_path = None 518 vendor_boot_image_path = None 519 kernel_image_path = None 520 initramfs_image_path = None 521 522 if avd_spec.local_vendor_image: 523 vendor_image_paths = cvd_utils.FindVendorImages( 524 avd_spec.local_vendor_image) 525 vendor_image_path = vendor_image_paths.vendor 526 vendor_dlkm_image_path = vendor_image_paths.vendor_dlkm 527 odm_image_path = vendor_image_paths.odm 528 odm_dlkm_image_path = vendor_image_paths.odm_dlkm 529 else: 530 vendor_image_path = None 531 vendor_dlkm_image_path = None 532 odm_image_path = None 533 odm_dlkm_image_path = None 534 535 return ArtifactPaths(image_dir, host_bins_path, 536 host_artifacts=host_artifacts_path, 537 misc_info=misc_info_path, 538 ota_tools_dir=ota_tools_dir, 539 system_image=system_image_path, 540 boot_image=boot_image_path, 541 vendor_boot_image=vendor_boot_image_path, 542 kernel_image=kernel_image_path, 543 initramfs_image=initramfs_image_path, 544 vendor_image=vendor_image_path, 545 vendor_dlkm_image=vendor_dlkm_image_path, 546 odm_image=odm_image_path, 547 odm_dlkm_image=odm_dlkm_image_path) 548 549 @staticmethod 550 def _GetConfigFromAndroidInfo(android_info_path): 551 """Get config value from android-info.txt. 552 553 The config in android-info.txt would like "config=phone". 554 555 Args: 556 android_info_path: String of android-info.txt pah. 557 558 Returns: 559 Strings of config value. 560 """ 561 if os.path.exists(android_info_path): 562 with open(android_info_path, "r") as android_info_file: 563 android_info = android_info_file.read() 564 logger.debug("Android info: %s", android_info) 565 config_match = _CONFIG_RE.match(android_info) 566 if config_match: 567 return config_match.group("config") 568 return None 569 570 # pylint: disable=too-many-branches 571 @staticmethod 572 def PrepareLaunchCVDCmd(hw_property, connect_adb, connect_fastboot, 573 artifact_paths, runtime_dir, connect_webrtc, 574 connect_vnc, super_image_path, launch_args, 575 config, openwrt=False, use_launch_cvd=False, 576 instance_ids=None, webrtc_device_id=None, 577 vbmeta_image_path=None): 578 """Prepare launch_cvd command. 579 580 Create the launch_cvd commands with all the required args and add 581 in the user groups to it if necessary. 582 583 Args: 584 hw_property: dict object of hw property. 585 artifact_paths: ArtifactPaths object. 586 connect_adb: Boolean flag that enables adb_connector. 587 connect_fastboot: Boolean flag that enables fastboot_proxy. 588 runtime_dir: String of runtime directory path. 589 connect_webrtc: Boolean of connect_webrtc. 590 connect_vnc: Boolean of connect_vnc. 591 super_image_path: String of non-default super image path. 592 launch_args: String of launch args. 593 config: String of config name. 594 openwrt: Boolean of enable OpenWrt devices. 595 use_launch_cvd: Boolean of using launch_cvd for old build cases. 596 instance_ids: List of integer of instance ids. 597 webrtc_device_id: String of webrtc device id. 598 vbmeta_image_path: String of vbmeta image path. 599 600 Returns: 601 String, cvd start cmd. 602 """ 603 bin_dir = os.path.join(artifact_paths.host_bins, "bin") 604 cvd_path = os.path.join(bin_dir, constants.CMD_CVD) 605 start_cvd_cmd = cvd_path + _CMD_CVD_START 606 if use_launch_cvd or not os.path.isfile(cvd_path): 607 start_cvd_cmd = os.path.join(bin_dir, constants.CMD_LAUNCH_CVD) 608 launch_cvd_w_args = start_cvd_cmd + _CMD_LAUNCH_CVD_ARGS % ( 609 config, artifact_paths.image_dir, runtime_dir) 610 if hw_property: 611 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_HW_ARGS % ( 612 hw_property["cpu"], hw_property["x_res"], hw_property["y_res"], 613 hw_property["dpi"], hw_property["memory"]) 614 if constants.HW_ALIAS_DISK in hw_property: 615 launch_cvd_w_args = (launch_cvd_w_args + 616 _CMD_LAUNCH_CVD_DISK_ARGS % 617 hw_property[constants.HW_ALIAS_DISK]) 618 619 if not connect_adb: 620 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_NO_ADB_ARG 621 622 if not connect_fastboot: 623 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_NO_FASTBOOT_ARG 624 625 if connect_webrtc: 626 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_WEBRTC_ARGS 627 628 if connect_vnc: 629 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_VNC_ARG 630 631 if super_image_path: 632 launch_cvd_w_args = (launch_cvd_w_args + 633 _CMD_LAUNCH_CVD_SUPER_IMAGE_ARG % 634 super_image_path) 635 636 if artifact_paths.boot_image: 637 launch_cvd_w_args = (launch_cvd_w_args + 638 _CMD_LAUNCH_CVD_BOOT_IMAGE_ARG % 639 artifact_paths.boot_image) 640 641 if artifact_paths.vendor_boot_image: 642 launch_cvd_w_args = (launch_cvd_w_args + 643 _CMD_LAUNCH_CVD_VENDOR_BOOT_IMAGE_ARG % 644 artifact_paths.vendor_boot_image) 645 646 if artifact_paths.kernel_image: 647 launch_cvd_w_args = (launch_cvd_w_args + 648 _CMD_LAUNCH_CVD_KERNEL_IMAGE_ARG % 649 artifact_paths.kernel_image) 650 651 if artifact_paths.initramfs_image: 652 launch_cvd_w_args = (launch_cvd_w_args + 653 _CMD_LAUNCH_CVD_INITRAMFS_IMAGE_ARG % 654 artifact_paths.initramfs_image) 655 656 if vbmeta_image_path: 657 launch_cvd_w_args = (launch_cvd_w_args + 658 _CMD_LAUNCH_CVD_VBMETA_IMAGE_ARG % 659 vbmeta_image_path) 660 661 if openwrt: 662 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_CONSOLE_ARG 663 664 if instance_ids and len(instance_ids) > 1: 665 launch_cvd_w_args = ( 666 launch_cvd_w_args + 667 _CMD_LAUNCH_CVD_INSTANCE_NUMS_ARG % 668 ",".join(map(str, instance_ids))) 669 670 if webrtc_device_id: 671 launch_cvd_w_args = (launch_cvd_w_args + 672 _CMD_LAUNCH_CVD_WEBRTC_DEIVE_ID % 673 webrtc_device_id) 674 675 if launch_args: 676 launch_cvd_w_args = launch_cvd_w_args + " " + launch_args 677 678 launch_cmd = utils.AddUserGroupsToCmd(launch_cvd_w_args, 679 constants.LIST_CF_USER_GROUPS) 680 logger.debug("launch_cvd cmd:\n %s", launch_cmd) 681 return launch_cmd 682 683 @staticmethod 684 def PrepareLocalCvdToolsLink(cvd_home_dir, host_bins_path): 685 """Create symbolic link for the cvd tools directory. 686 687 local instance's cvd tools could be generated in /out after local build 688 or be generated in the download image folder. It creates a symbolic 689 link then only check cvd_status using known link for both cases. 690 691 Args: 692 cvd_home_dir: The parent directory of the link 693 host_bins_path: String of host package directory. 694 695 Returns: 696 String of cvd_tools link path 697 """ 698 cvd_tools_link_path = os.path.join(cvd_home_dir, 699 constants.CVD_TOOLS_LINK_NAME) 700 if os.path.islink(cvd_tools_link_path): 701 os.unlink(cvd_tools_link_path) 702 os.symlink(host_bins_path, cvd_tools_link_path) 703 return cvd_tools_link_path 704 705 @staticmethod 706 def _TrustCertificatesForWebRTC(host_bins_path): 707 """Copy the trusted certificates generated by openssl tool to the 708 webrtc frontend certificate directory. 709 710 Args: 711 host_bins_path: String of host package directory. 712 """ 713 webrtc_certs_dir = os.path.join(host_bins_path, 714 constants.WEBRTC_CERTS_PATH) 715 if not os.path.isdir(webrtc_certs_dir): 716 logger.debug("WebRTC frontend certificate path doesn't exist: %s", 717 webrtc_certs_dir) 718 return 719 local_cert_dir = os.path.join(os.path.expanduser("~"), 720 constants.SSL_DIR) 721 if mkcert.AllocateLocalHostCert(): 722 for cert_file_name in constants.WEBRTC_CERTS_FILES: 723 shutil.copyfile( 724 os.path.join(local_cert_dir, cert_file_name), 725 os.path.join(webrtc_certs_dir, cert_file_name)) 726 727 @staticmethod 728 def _LogCvdVersion(host_bins_path): 729 """Log the version of the cvd server. 730 731 Args: 732 host_bins_path: String of host package directory. 733 """ 734 cvd_path = os.path.join(host_bins_path, "bin", constants.CMD_CVD) 735 if not os.path.isfile(cvd_path): 736 logger.info("Skip logging cvd version as %s is not a file", 737 cvd_path) 738 return 739 740 cmd = cvd_path + _CMD_CVD_VERSION 741 try: 742 proc = subprocess.run(cmd, shell=True, text=True, 743 capture_output=True, timeout=5, 744 check=False, cwd=host_bins_path) 745 logger.info("`%s` returned %d; stdout:\n%s", 746 cmd, proc.returncode, proc.stdout) 747 logger.info("`%s` stderr:\n%s", cmd, proc.stderr) 748 except subprocess.SubprocessError as e: 749 logger.error("`%s` failed: %s", cmd, e) 750 751 @staticmethod 752 def _CheckRunningCvd(local_instance_id, no_prompts=False): 753 """Check if launch_cvd with the same instance id is running. 754 755 Args: 756 local_instance_id: Integer of instance id. 757 no_prompts: Boolean, True to skip all prompts. 758 759 Returns: 760 Whether the user wants to continue. 761 """ 762 # Check if the instance with same id is running. 763 existing_ins = list_instance.GetActiveCVD(local_instance_id) 764 if existing_ins: 765 if no_prompts or utils.GetUserAnswerYes(_CONFIRM_RELAUNCH % 766 local_instance_id): 767 existing_ins.Delete() 768 else: 769 return False 770 return True 771 772 @staticmethod 773 def _StopCvd(local_instance_id, proc): 774 """Call stop_cvd or kill a launch_cvd process. 775 776 Args: 777 local_instance_id: Integer of instance id. 778 proc: subprocess.Popen object, the launch_cvd process. 779 """ 780 existing_ins = list_instance.GetActiveCVD(local_instance_id) 781 if existing_ins: 782 try: 783 existing_ins.Delete() 784 return 785 except subprocess.CalledProcessError as e: 786 logger.error("Cannot stop instance %d: %s", 787 local_instance_id, str(e)) 788 else: 789 logger.error("Instance %d is not active.", local_instance_id) 790 logger.info("Terminate launch_cvd process.") 791 proc.terminate() 792 793 @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up") 794 def _LaunchCvd(self, cmd, local_instance_id, host_bins_path, 795 host_artifacts_path, cvd_home_dir, timeout): 796 """Execute Launch CVD. 797 798 Kick off the launch_cvd command and log the output. 799 800 Args: 801 cmd: String, launch_cvd command. 802 local_instance_id: Integer of instance id. 803 host_bins_path: String of host package directory containing 804 binaries. 805 host_artifacts_path: String of host package directory containing 806 other artifacts. 807 cvd_home_dir: String, the home directory for the instance. 808 timeout: Integer, the number of seconds to wait for the AVD to 809 boot up. 810 811 Raises: 812 errors.LaunchCVDFail if launch_cvd times out or returns non-zero. 813 """ 814 cvd_env = os.environ.copy() 815 cvd_env[constants.ENV_ANDROID_SOONG_HOST_OUT] = host_artifacts_path 816 # launch_cvd assumes host bins are in $ANDROID_HOST_OUT. 817 cvd_env[constants.ENV_ANDROID_HOST_OUT] = host_bins_path 818 cvd_env[constants.ENV_CVD_HOME] = cvd_home_dir 819 cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(local_instance_id) 820 cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = ( 821 instance.GetLocalInstanceConfigPath(local_instance_id)) 822 cvd_env[constants.ENV_CVD_ACQUIRE_FILE_LOCK] = "false" 823 stdout_file = os.path.join(cvd_home_dir, _STDOUT) 824 stderr_file = os.path.join(cvd_home_dir, _STDERR) 825 # Check the result of launch_cvd command. 826 # An exit code of 0 is equivalent to VIRTUAL_DEVICE_BOOT_COMPLETED 827 with open(stdout_file, "w+") as f_stdout, open(stderr_file, 828 "w+") as f_stderr: 829 try: 830 proc = subprocess.Popen( 831 cmd, shell=True, env=cvd_env, stdout=f_stdout, 832 stderr=f_stderr, text=True, cwd=host_bins_path) 833 proc.communicate(timeout=timeout) 834 f_stdout.seek(0) 835 f_stderr.seek(0) 836 if proc.returncode == 0: 837 logger.info("launch_cvd stdout:\n%s", f_stdout.read()) 838 logger.info("launch_cvd stderr:\n%s", f_stderr.read()) 839 return 840 error_msg = "launch_cvd returned %d." % proc.returncode 841 except subprocess.TimeoutExpired: 842 self._StopCvd(local_instance_id, proc) 843 proc.communicate(timeout=5) 844 error_msg = "Device did not boot within %d secs." % timeout 845 846 f_stdout.seek(0) 847 f_stderr.seek(0) 848 stderr = f_stderr.read() 849 logger.error("launch_cvd stdout:\n%s", f_stdout.read()) 850 logger.error("launch_cvd stderr:\n%s", stderr) 851 split_stderr = stderr.splitlines()[-_MAX_REPORTED_ERROR_LINES:] 852 raise errors.LaunchCVDFail( 853 "%s Stderr:\n%s" % (error_msg, "\n".join(split_stderr))) 854