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 vnc 30port 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 variable 35[CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish json. 36 37To run this program outside of a build environment, the following setup is 38required. 39- One of the local tool directories is a decompressed cvd host package, 40 i.e., cvd-host_package.tar.gz. 41- If the instance doesn't require mixed images, the local image directory 42 should be an unzipped update package, i.e., <target>-img-<build>.zip, 43 which contains a super image. 44- If the instance requires mixing system image, the local image directory 45 should be an unzipped target files package, i.e., 46 <target>-target_files-<build>.zip, 47 which contains misc info and images not packed into a super image. 48- If the instance requires mixing system image, one of the local tool 49 directories should be an unzipped OTA tools package, i.e., otatools.zip. 50""" 51 52import collections 53import glob 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_SYSTEM_IMAGE_NAME_PATTERN = r"system\.img" 78_MISC_INFO_FILE_NAME = "misc_info.txt" 79_TARGET_FILES_IMAGES_DIR_NAME = "IMAGES" 80_TARGET_FILES_META_DIR_NAME = "META" 81_MIXED_SUPER_IMAGE_NAME = "mixed_super.img" 82_CMD_CVD_START = " start" 83_CMD_LAUNCH_CVD_ARGS = ( 84 " -daemon -config=%s -system_image_dir %s -instance_dir %s " 85 "-undefok=report_anonymous_usage_stats,config " 86 "-report_anonymous_usage_stats=y") 87_CMD_LAUNCH_CVD_HW_ARGS = " -cpus %s -x_res %s -y_res %s -dpi %s -memory_mb %s" 88_CMD_LAUNCH_CVD_DISK_ARGS = ( 89 " -blank_data_image_mb %s -data_policy always_create") 90_CMD_LAUNCH_CVD_WEBRTC_ARGS = " -start_webrtc=true" 91_CMD_LAUNCH_CVD_VNC_ARG = " -start_vnc_server=true" 92_CMD_LAUNCH_CVD_SUPER_IMAGE_ARG = " -super_image=%s" 93_CMD_LAUNCH_CVD_BOOT_IMAGE_ARG = " -boot_image=%s" 94_CMD_LAUNCH_CVD_VENDOR_BOOT_IMAGE_ARG = " -vendor_boot_image=%s" 95_CMD_LAUNCH_CVD_NO_ADB_ARG = " -run_adb_connector=false" 96# Connect the OpenWrt device via console file. 97_CMD_LAUNCH_CVD_CONSOLE_ARG = " -console=true" 98_CONFIG_RE = re.compile(r"^config=(?P<config>.+)") 99_CONSOLE_NAME = "console" 100# Files to store the output when launching cvds. 101_STDOUT = "stdout" 102_STDERR = "stderr" 103_MAX_REPORTED_ERROR_LINES = 10 104 105# In accordance with the number of network interfaces in 106# /etc/init.d/cuttlefish-common 107_MAX_INSTANCE_ID = 10 108 109# TODO(b/213521240): To check why the delete function is not work and 110# has to manually delete temp folder. 111_INSTANCES_IN_USE_MSG = ("All instances are in use. Try resetting an instance " 112 "by specifying --local-instance and an id between 1 " 113 "and %d. Alternatively, to run 'acloud delete --all' " 114 % _MAX_INSTANCE_ID) 115_CONFIRM_RELAUNCH = ("\nCuttlefish AVD[id:%d] is already running. \n" 116 "Enter 'y' to terminate current instance and launch a new " 117 "instance, enter anything else to exit out[y/N]: ") 118 119# The first two fields of this named tuple are image folder and CVD host 120# package folder which are essential for local instances. The following fields 121# are optional. They are set when the AVD spec requires to mix images. 122ArtifactPaths = collections.namedtuple( 123 "ArtifactPaths", 124 ["image_dir", "host_bins", "host_artifacts", "misc_info", "ota_tools_dir", 125 "system_image", "boot_image", "vendor_boot_image"]) 126 127 128class LocalImageLocalInstance(base_avd_create.BaseAVDCreate): 129 """Create class for a local image local instance AVD.""" 130 131 @utils.TimeExecute(function_description="Total time: ", 132 print_before_call=False, print_status=False) 133 def _CreateAVD(self, avd_spec, no_prompts): 134 """Create the AVD. 135 136 Args: 137 avd_spec: AVDSpec object that tells us what we're going to create. 138 no_prompts: Boolean, True to skip all prompts. 139 140 Returns: 141 A Report instance. 142 """ 143 # Running instances on local is not supported on all OS. 144 result_report = report.Report(command="create") 145 if not utils.IsSupportedPlatform(print_warning=True): 146 result_report.UpdateFailure( 147 "The platform doesn't support to run acloud.") 148 return result_report 149 if not utils.IsSupportedKvm(): 150 result_report.UpdateFailure( 151 "The environment doesn't support virtualization.") 152 return result_report 153 154 artifact_paths = self.GetImageArtifactsPath(avd_spec) 155 156 try: 157 ins_id, ins_lock = self._SelectAndLockInstance(avd_spec) 158 except errors.CreateError as e: 159 result_report.UpdateFailure(str(e)) 160 return result_report 161 162 try: 163 if not self._CheckRunningCvd(ins_id, no_prompts): 164 # Mark as in-use so that it won't be auto-selected again. 165 ins_lock.SetInUse(True) 166 sys.exit(constants.EXIT_BY_USER) 167 168 result_report = self._CreateInstance(ins_id, artifact_paths, 169 avd_spec, no_prompts) 170 # The infrastructure is able to delete the instance only if the 171 # instance name is reported. This method changes the state to 172 # in-use after creating the report. 173 ins_lock.SetInUse(True) 174 return result_report 175 finally: 176 ins_lock.Unlock() 177 178 @staticmethod 179 def _SelectAndLockInstance(avd_spec): 180 """Select an id and lock the instance. 181 182 Args: 183 avd_spec: AVDSpec for the device. 184 185 Returns: 186 The instance id and the LocalInstanceLock that is locked by this 187 process. 188 189 Raises: 190 errors.CreateError if fails to select or lock the instance. 191 """ 192 if avd_spec.local_instance_id: 193 ins_id = avd_spec.local_instance_id 194 ins_lock = instance.GetLocalInstanceLock(ins_id) 195 if ins_lock.Lock(): 196 return ins_id, ins_lock 197 raise errors.CreateError("Instance %d is locked by another " 198 "process." % ins_id) 199 200 for ins_id in range(1, _MAX_INSTANCE_ID + 1): 201 ins_lock = instance.GetLocalInstanceLock(ins_id) 202 if ins_lock.LockIfNotInUse(timeout_secs=0): 203 logger.info("Selected instance id: %d", ins_id) 204 return ins_id, ins_lock 205 raise errors.CreateError(_INSTANCES_IN_USE_MSG) 206 207 #pylint: disable=too-many-locals,too-many-statements 208 def _CreateInstance(self, local_instance_id, artifact_paths, avd_spec, 209 no_prompts): 210 """Create a CVD instance. 211 212 Args: 213 local_instance_id: Integer of instance id. 214 artifact_paths: ArtifactPaths object. 215 avd_spec: AVDSpec for the instance. 216 no_prompts: Boolean, True to skip all prompts. 217 218 Returns: 219 A Report instance. 220 """ 221 webrtc_port = self.GetWebrtcSigServerPort(local_instance_id) 222 if avd_spec.connect_webrtc: 223 utils.ReleasePort(webrtc_port) 224 225 cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id) 226 create_common.PrepareLocalInstanceDir(cvd_home_dir, avd_spec) 227 super_image_path = None 228 if artifact_paths.system_image: 229 super_image_path = self._MixSuperImage(cvd_home_dir, 230 artifact_paths) 231 runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id) 232 # TODO(b/168171781): cvd_status of list/delete via the symbolic. 233 self.PrepareLocalCvdToolsLink(cvd_home_dir, artifact_paths.host_bins) 234 if avd_spec.mkcert and avd_spec.connect_webrtc: 235 self._TrustCertificatesForWebRTC(artifact_paths.host_artifacts) 236 237 hw_property = None 238 if avd_spec.hw_customize: 239 hw_property = avd_spec.hw_property 240 config = self._GetConfigFromAndroidInfo( 241 os.path.join(artifact_paths.image_dir, constants.ANDROID_INFO_FILE)) 242 cmd = self.PrepareLaunchCVDCmd(hw_property, 243 avd_spec.connect_adb, 244 artifact_paths, 245 runtime_dir, 246 avd_spec.connect_webrtc, 247 avd_spec.connect_vnc, 248 super_image_path, 249 avd_spec.launch_args, 250 config or avd_spec.flavor, 251 avd_spec.openwrt, 252 avd_spec.use_launch_cvd) 253 254 result_report = report.Report(command="create") 255 instance_name = instance.GetLocalInstanceName(local_instance_id) 256 try: 257 self._LaunchCvd(cmd, local_instance_id, artifact_paths.host_bins, 258 artifact_paths.host_artifacts, 259 cvd_home_dir, (avd_spec.boot_timeout_secs or 260 constants.DEFAULT_CF_BOOT_TIMEOUT)) 261 logs = self._FindLogs(local_instance_id) 262 except errors.LaunchCVDFail as launch_error: 263 logs = self._FindLogs(local_instance_id) 264 err_msg = ("Cannot create cuttlefish instance: %s\n" 265 "For more detail: %s/launcher.log" % 266 (launch_error, runtime_dir)) 267 if constants.ERROR_MSG_WEBRTC_NOT_SUPPORT in str(launch_error): 268 err_msg = ( 269 "WEBRTC is not supported in current build. Please try VNC such " 270 "as '$acloud create --autoconnect vnc'") 271 result_report.SetStatus(report.Status.BOOT_FAIL) 272 result_report.SetErrorType(constants.ACLOUD_BOOT_UP_ERROR) 273 result_report.AddDeviceBootFailure( 274 instance_name, constants.LOCALHOST, None, None, error=err_msg, 275 logs=logs) 276 return result_report 277 278 active_ins = list_instance.GetActiveCVD(local_instance_id) 279 if active_ins: 280 update_data = None 281 if avd_spec.openwrt: 282 console_dir = os.path.dirname( 283 instance.GetLocalInstanceConfig(local_instance_id)) 284 console_path = os.path.join(console_dir, _CONSOLE_NAME) 285 update_data = {"screen_command": f"screen {console_path}"} 286 result_report.SetStatus(report.Status.SUCCESS) 287 result_report.AddDevice(instance_name, constants.LOCALHOST, 288 active_ins.adb_port, active_ins.vnc_port, 289 webrtc_port, logs=logs, update_data=update_data) 290 # Launch vnc client if we're auto-connecting. 291 if avd_spec.connect_vnc: 292 utils.LaunchVNCFromReport(result_report, avd_spec, no_prompts) 293 if avd_spec.connect_webrtc: 294 utils.LaunchBrowserFromReport(result_report) 295 if avd_spec.unlock_screen: 296 AdbTools(active_ins.adb_port).AutoUnlockScreen() 297 else: 298 err_msg = "cvd_status return non-zero after launch_cvd" 299 logger.error(err_msg) 300 result_report.SetStatus(report.Status.BOOT_FAIL) 301 result_report.SetErrorType(constants.ACLOUD_BOOT_UP_ERROR) 302 result_report.AddDeviceBootFailure( 303 instance_name, constants.LOCALHOST, None, None, error=err_msg, 304 logs=logs) 305 return result_report 306 307 @staticmethod 308 def GetWebrtcSigServerPort(instance_id): 309 """Get the port of the signaling server. 310 311 Args: 312 instance_id: Integer of instance id. 313 314 Returns: 315 Integer of signaling server port. 316 """ 317 return constants.WEBRTC_LOCAL_PORT + instance_id - 1 318 319 @staticmethod 320 def _FindCvdHostBinaries(search_paths): 321 """Return the directory that contains CVD host binaries.""" 322 for search_path in search_paths: 323 if os.path.isfile(os.path.join(search_path, "bin", 324 constants.CMD_LAUNCH_CVD)): 325 return search_path 326 327 raise errors.GetCvdLocalHostPackageError( 328 "CVD host binaries are not found. Please run `make hosttar`, or " 329 "set --local-tool to an extracted CVD host package.") 330 331 @staticmethod 332 def _FindCvdHostArtifactsPath(search_paths): 333 """Return the directory that contains CVD host artifacts (in particular webrtc).""" 334 for search_path in search_paths: 335 if os.path.isfile(os.path.join(search_path, "usr/share/webrtc/certs", "server.crt")): 336 return search_path 337 338 raise errors.GetCvdLocalHostPackageError( 339 "CVD host webrtc artifacts are not found. Please run `make hosttar`, or " 340 "set --local-tool to an extracted CVD host package.") 341 342 @staticmethod 343 def FindMiscInfo(image_dir): 344 """Find misc info in build output dir or extracted target files. 345 346 Args: 347 image_dir: The directory to search for misc info. 348 349 Returns: 350 image_dir if the directory structure looks like an output directory 351 in build environment. 352 image_dir/META if it looks like extracted target files. 353 354 Raises: 355 errors.CheckPathError if this method cannot find misc info. 356 """ 357 misc_info_path = os.path.join(image_dir, _MISC_INFO_FILE_NAME) 358 if os.path.isfile(misc_info_path): 359 return misc_info_path 360 misc_info_path = os.path.join(image_dir, _TARGET_FILES_META_DIR_NAME, 361 _MISC_INFO_FILE_NAME) 362 if os.path.isfile(misc_info_path): 363 return misc_info_path 364 raise errors.CheckPathError( 365 "Cannot find %s in %s." % (_MISC_INFO_FILE_NAME, image_dir)) 366 367 @staticmethod 368 def FindImageDir(image_dir): 369 """Find images in build output dir or extracted target files. 370 371 Args: 372 image_dir: The directory to search for images. 373 374 Returns: 375 image_dir if the directory structure looks like an output directory 376 in build environment. 377 image_dir/IMAGES if it looks like extracted target files. 378 379 Raises: 380 errors.GetLocalImageError if this method cannot find images. 381 """ 382 if glob.glob(os.path.join(image_dir, "*.img")): 383 return image_dir 384 subdir = os.path.join(image_dir, _TARGET_FILES_IMAGES_DIR_NAME) 385 if glob.glob(os.path.join(subdir, "*.img")): 386 return subdir 387 raise errors.GetLocalImageError( 388 "Cannot find images in %s." % image_dir) 389 390 def GetImageArtifactsPath(self, avd_spec): 391 """Get image artifacts path. 392 393 This method will check if launch_cvd is exist and return the tuple path 394 (image path and host bins path) where they are located respectively. 395 For remote image, RemoteImageLocalInstance will override this method and 396 return the artifacts path which is extracted and downloaded from remote. 397 398 Args: 399 avd_spec: AVDSpec object that tells us what we're going to create. 400 401 Returns: 402 ArtifactPaths object consisting of image directory and host bins 403 package. 404 405 Raises: 406 errors.GetCvdLocalHostPackageError, errors.GetLocalImageError, or 407 errors.CheckPathError if any artifact is not found. 408 """ 409 image_dir = os.path.abspath(avd_spec.local_image_dir) 410 tool_dirs = (avd_spec.local_tool_dirs + 411 create_common.GetNonEmptyEnvVars( 412 constants.ENV_ANDROID_SOONG_HOST_OUT, 413 constants.ENV_ANDROID_HOST_OUT)) 414 host_bins_path = self._FindCvdHostBinaries(tool_dirs) 415 host_artifacts_path = self._FindCvdHostArtifactsPath(tool_dirs) 416 417 if avd_spec.local_system_image: 418 misc_info_path = self.FindMiscInfo(image_dir) 419 image_dir = self.FindImageDir(image_dir) 420 ota_tools_dir = os.path.abspath( 421 ota_tools.FindOtaToolsDir(tool_dirs)) 422 system_image_path = create_common.FindLocalImage( 423 avd_spec.local_system_image, _SYSTEM_IMAGE_NAME_PATTERN) 424 else: 425 misc_info_path = None 426 ota_tools_dir = None 427 system_image_path = None 428 429 if avd_spec.local_kernel_image: 430 boot_image_path, vendor_boot_image_path = cvd_utils.FindBootImages( 431 avd_spec.local_kernel_image) 432 else: 433 boot_image_path = None 434 vendor_boot_image_path = None 435 436 return ArtifactPaths(image_dir, host_bins_path, 437 host_artifacts=host_artifacts_path, 438 misc_info=misc_info_path, 439 ota_tools_dir=ota_tools_dir, 440 system_image=system_image_path, 441 boot_image=boot_image_path, 442 vendor_boot_image=vendor_boot_image_path) 443 444 @staticmethod 445 def _MixSuperImage(output_dir, artifact_paths): 446 """Mix cuttlefish images and a system image into a super image. 447 448 Args: 449 output_dir: The path to the output directory. 450 artifact_paths: ArtifactPaths object. 451 452 Returns: 453 The path to the super image in output_dir. 454 """ 455 ota = ota_tools.OtaTools(artifact_paths.ota_tools_dir) 456 super_image_path = os.path.join(output_dir, _MIXED_SUPER_IMAGE_NAME) 457 ota.BuildSuperImage( 458 super_image_path, artifact_paths.misc_info, 459 lambda partition: ota_tools.GetImageForPartition( 460 partition, artifact_paths.image_dir, 461 system=artifact_paths.system_image)) 462 return super_image_path 463 464 @staticmethod 465 def _GetConfigFromAndroidInfo(android_info_path): 466 """Get config value from android-info.txt. 467 468 The config in android-info.txt would like "config=phone". 469 470 Args: 471 android_info_path: String of android-info.txt pah. 472 473 Returns: 474 Strings of config value. 475 """ 476 if os.path.exists(android_info_path): 477 with open(android_info_path, "r") as android_info_file: 478 android_info = android_info_file.read() 479 logger.debug("Android info: %s", android_info) 480 config_match = _CONFIG_RE.match(android_info) 481 if config_match: 482 return config_match.group("config") 483 return None 484 485 @staticmethod 486 def PrepareLaunchCVDCmd(hw_property, connect_adb, artifact_paths, 487 runtime_dir, connect_webrtc, connect_vnc, 488 super_image_path, launch_args, config, 489 openwrt=False, use_launch_cvd=False): 490 """Prepare launch_cvd command. 491 492 Create the launch_cvd commands with all the required args and add 493 in the user groups to it if necessary. 494 495 Args: 496 hw_property: dict object of hw property. 497 artifact_paths: ArtifactPaths object. 498 connect_adb: Boolean flag that enables adb_connector. 499 runtime_dir: String of runtime directory path. 500 connect_webrtc: Boolean of connect_webrtc. 501 connect_vnc: Boolean of connect_vnc. 502 super_image_path: String of non-default super image path. 503 launch_args: String of launch args. 504 config: String of config name. 505 openwrt: Boolean of enable OpenWrt devices. 506 use_launch_cvd: Boolean of using launch_cvd for old build cases. 507 508 Returns: 509 String, cvd start cmd. 510 """ 511 bin_dir = os.path.join(artifact_paths.host_bins, "bin") 512 start_cvd_cmd = (os.path.join(bin_dir, constants.CMD_CVD) + 513 _CMD_CVD_START) 514 if use_launch_cvd: 515 start_cvd_cmd = os.path.join(bin_dir, constants.CMD_LAUNCH_CVD) 516 launch_cvd_w_args = start_cvd_cmd + _CMD_LAUNCH_CVD_ARGS % ( 517 config, artifact_paths.image_dir, runtime_dir) 518 if hw_property: 519 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_HW_ARGS % ( 520 hw_property["cpu"], hw_property["x_res"], hw_property["y_res"], 521 hw_property["dpi"], hw_property["memory"]) 522 if constants.HW_ALIAS_DISK in hw_property: 523 launch_cvd_w_args = (launch_cvd_w_args + _CMD_LAUNCH_CVD_DISK_ARGS % 524 hw_property[constants.HW_ALIAS_DISK]) 525 526 if not connect_adb: 527 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_NO_ADB_ARG 528 529 if connect_webrtc: 530 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_WEBRTC_ARGS 531 532 if connect_vnc: 533 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_VNC_ARG 534 535 if super_image_path: 536 launch_cvd_w_args = (launch_cvd_w_args + 537 _CMD_LAUNCH_CVD_SUPER_IMAGE_ARG % 538 super_image_path) 539 540 if artifact_paths.boot_image: 541 launch_cvd_w_args = (launch_cvd_w_args + 542 _CMD_LAUNCH_CVD_BOOT_IMAGE_ARG % 543 artifact_paths.boot_image) 544 545 if artifact_paths.vendor_boot_image: 546 launch_cvd_w_args = (launch_cvd_w_args + 547 _CMD_LAUNCH_CVD_VENDOR_BOOT_IMAGE_ARG % 548 artifact_paths.vendor_boot_image) 549 550 if openwrt: 551 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_CONSOLE_ARG 552 553 if launch_args: 554 launch_cvd_w_args = launch_cvd_w_args + " " + launch_args 555 556 launch_cmd = utils.AddUserGroupsToCmd(launch_cvd_w_args, 557 constants.LIST_CF_USER_GROUPS) 558 logger.debug("launch_cvd cmd:\n %s", launch_cmd) 559 return launch_cmd 560 561 @staticmethod 562 def PrepareLocalCvdToolsLink(cvd_home_dir, host_bins_path): 563 """Create symbolic link for the cvd tools directory. 564 565 local instance's cvd tools could be generated in /out after local build 566 or be generated in the download image folder. It creates a symbolic 567 link then only check cvd_status using known link for both cases. 568 569 Args: 570 cvd_home_dir: The parent directory of the link 571 host_bins_path: String of host package directory. 572 573 Returns: 574 String of cvd_tools link path 575 """ 576 cvd_tools_link_path = os.path.join(cvd_home_dir, constants.CVD_TOOLS_LINK_NAME) 577 if os.path.islink(cvd_tools_link_path): 578 os.unlink(cvd_tools_link_path) 579 os.symlink(host_bins_path, cvd_tools_link_path) 580 return cvd_tools_link_path 581 582 @staticmethod 583 def _TrustCertificatesForWebRTC(host_bins_path): 584 """Copy the trusted certificates generated by openssl tool to the 585 webrtc frontend certificate directory. 586 587 Args: 588 host_bins_path: String of host package directory. 589 """ 590 webrtc_certs_dir = os.path.join(host_bins_path, 591 constants.WEBRTC_CERTS_PATH) 592 if not os.path.isdir(webrtc_certs_dir): 593 logger.debug("WebRTC frontend certificate path doesn't exist: %s", 594 webrtc_certs_dir) 595 return 596 local_cert_dir = os.path.join(os.path.expanduser("~"), 597 constants.SSL_DIR) 598 if mkcert.AllocateLocalHostCert(): 599 for cert_file_name in constants.WEBRTC_CERTS_FILES: 600 shutil.copyfile( 601 os.path.join(local_cert_dir, cert_file_name), 602 os.path.join(webrtc_certs_dir, cert_file_name)) 603 604 @staticmethod 605 def _CheckRunningCvd(local_instance_id, no_prompts=False): 606 """Check if launch_cvd with the same instance id is running. 607 608 Args: 609 local_instance_id: Integer of instance id. 610 no_prompts: Boolean, True to skip all prompts. 611 612 Returns: 613 Whether the user wants to continue. 614 """ 615 # Check if the instance with same id is running. 616 existing_ins = list_instance.GetActiveCVD(local_instance_id) 617 if existing_ins: 618 if no_prompts or utils.GetUserAnswerYes(_CONFIRM_RELAUNCH % 619 local_instance_id): 620 existing_ins.Delete() 621 else: 622 return False 623 return True 624 625 @staticmethod 626 def _StopCvd(local_instance_id, proc): 627 """Call stop_cvd or kill a launch_cvd process. 628 629 Args: 630 local_instance_id: Integer of instance id. 631 proc: subprocess.Popen object, the launch_cvd process. 632 """ 633 existing_ins = list_instance.GetActiveCVD(local_instance_id) 634 if existing_ins: 635 try: 636 existing_ins.Delete() 637 return 638 except subprocess.CalledProcessError as e: 639 logger.error("Cannot stop instance %d: %s", 640 local_instance_id, str(e)) 641 else: 642 logger.error("Instance %d is not active.", local_instance_id) 643 logger.info("Terminate launch_cvd process.") 644 proc.terminate() 645 646 @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up") 647 def _LaunchCvd(self, cmd, local_instance_id, host_bins_path, host_artifacts_path, 648 cvd_home_dir, timeout): 649 """Execute Launch CVD. 650 651 Kick off the launch_cvd command and log the output. 652 653 Args: 654 cmd: String, launch_cvd command. 655 local_instance_id: Integer of instance id. 656 host_bins_path: String of host package directory containing binaries. 657 host_artifacts_path: String of host package directory containing 658 other artifacts. 659 cvd_home_dir: String, the home directory for the instance. 660 timeout: Integer, the number of seconds to wait for the AVD to boot up. 661 662 Raises: 663 errors.LaunchCVDFail if launch_cvd times out or returns non-zero. 664 """ 665 cvd_env = os.environ.copy() 666 cvd_env[constants.ENV_ANDROID_SOONG_HOST_OUT] = host_artifacts_path 667 # launch_cvd assumes host bins are in $ANDROID_HOST_OUT. 668 cvd_env[constants.ENV_ANDROID_HOST_OUT] = host_bins_path 669 cvd_env[constants.ENV_CVD_HOME] = cvd_home_dir 670 cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(local_instance_id) 671 cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = ( 672 instance.GetLocalInstanceConfigPath(local_instance_id)) 673 stdout_file = os.path.join(cvd_home_dir, _STDOUT) 674 stderr_file = os.path.join(cvd_home_dir, _STDERR) 675 # Check the result of launch_cvd command. 676 # An exit code of 0 is equivalent to VIRTUAL_DEVICE_BOOT_COMPLETED 677 with open(stdout_file, "w+") as f_stdout, open(stderr_file, 678 "w+") as f_stderr: 679 try: 680 proc = subprocess.Popen( 681 cmd, shell=True, env=cvd_env, stdout=f_stdout, 682 stderr=f_stderr, text=True, cwd=host_bins_path) 683 proc.communicate(timeout=timeout) 684 f_stdout.seek(0) 685 f_stderr.seek(0) 686 if proc.returncode == 0: 687 logger.info("launch_cvd stdout:\n%s", f_stdout.read()) 688 logger.info("launch_cvd stderr:\n%s", f_stderr.read()) 689 return 690 error_msg = "launch_cvd returned %d." % proc.returncode 691 except subprocess.TimeoutExpired: 692 self._StopCvd(local_instance_id, proc) 693 proc.communicate(timeout=5) 694 error_msg = "Device did not boot within %d secs." % timeout 695 696 f_stdout.seek(0) 697 f_stderr.seek(0) 698 stderr = f_stderr.read() 699 logger.error("launch_cvd stdout:\n%s", f_stdout.read()) 700 logger.error("launch_cvd stderr:\n%s", stderr) 701 split_stderr = stderr.splitlines()[-_MAX_REPORTED_ERROR_LINES:] 702 raise errors.LaunchCVDFail( 703 "%s Stderr:\n%s" % (error_msg, "\n".join(split_stderr))) 704 705 @staticmethod 706 def _FindLogs(local_instance_id): 707 """Find log paths that will be written to report. 708 709 Args: 710 local_instance_id: An integer, the instance id. 711 712 Returns: 713 A list of report.LogFile. 714 """ 715 log_dir = instance.GetLocalInstanceLogDir(local_instance_id) 716 return [report.LogFile(os.path.join(log_dir, name), log_type) 717 for name, log_type in [ 718 ("launcher.log", constants.LOG_TYPE_TEXT), 719 ("kernel.log", constants.LOG_TYPE_KERNEL_LOG), 720 ("logcat", constants.LOG_TYPE_LOGCAT)]] 721