• 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.
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