• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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