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