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"""AVDSpec class.
17
18AVDSpec will take in args from the user and be the main data type that will
19get passed into the create classes. The inferring magic will happen within
20initialization of AVDSpec (like LKGB build id, image branch, etc).
21"""
22
23import glob
24import logging
25import os
26import re
27import subprocess
28import tempfile
29import threading
30
31from acloud import errors
32from acloud.create import create_common
33from acloud.internal import constants
34from acloud.internal.lib import android_build_client
35from acloud.internal.lib import auth
36from acloud.internal.lib import utils
37from acloud.list import list as list_instance
38from acloud.public import config
39
40
41logger = logging.getLogger(__name__)
42
43# Default values for build target.
44_BRANCH_RE = re.compile(r"^Manifest branch: (?P<branch>.+)")
45_COMMAND_REPO_INFO = "repo info platform/tools/acloud"
46_REPO_TIMEOUT = 3
47_CF_ZIP_PATTERN = "*img*.zip"
48_DEFAULT_BUILD_BITNESS = "x86"
49_DEFAULT_BUILD_TYPE = "userdebug"
50_ENV_ANDROID_PRODUCT_OUT = "ANDROID_PRODUCT_OUT"
51_ENV_ANDROID_BUILD_TOP = "ANDROID_BUILD_TOP"
52_GCE_LOCAL_IMAGE_CANDIDATES = ["avd-system.tar.gz",
53                               "android_system_disk_syslinux.img"]
54_LOCAL_ZIP_WARNING_MSG = "'adb sync' will take a long time if using images " \
55                         "built with `m dist`. Building with just `m` will " \
56                         "enable a faster 'adb sync' process."
57_RE_ANSI_ESCAPE = re.compile(r"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]")
58_RE_FLAVOR = re.compile(r"^.+_(?P<flavor>.+)-img.+")
59_RE_MEMORY = re.compile(r"(?P<gb_size>\d+)g$|(?P<mb_size>\d+)m$",
60                        re.IGNORECASE)
61_RE_INT = re.compile(r"^\d+$")
62_RE_RES = re.compile(r"^(?P<x_res>\d+)x(?P<y_res>\d+)$")
63_X_RES = "x_res"
64_Y_RES = "y_res"
65_COMMAND_GIT_REMOTE = ["git", "remote"]
66
67# The branch prefix is necessary for the Android Build system to know what we're
68# talking about. For instance, on an aosp remote repo in the master branch,
69# Android Build will recognize it as aosp-master.
70_BRANCH_PREFIX = {"aosp": "aosp-"}
71_DEFAULT_BRANCH_PREFIX = "git_"
72_DEFAULT_BRANCH = "aosp-master"
73
74# The target prefix is needed to help concoct the lunch target name given a
75# the branch, avd type and device flavor:
76# aosp, cf and phone -> aosp_cf_x86_phone.
77_BRANCH_TARGET_PREFIX = {"aosp": "aosp_"}
78
79
80def EscapeAnsi(line):
81    """Remove ANSI control sequences (e.g. temrinal color codes...)
82
83    Args:
84        line: String, one line of command output.
85
86    Returns:
87        String without ANSI code.
88    """
89    return _RE_ANSI_ESCAPE.sub('', line)
90
91
92# pylint: disable=too-many-public-methods
93class AVDSpec:
94    """Class to store data on the type of AVD to create."""
95
96    def __init__(self, args):
97        """Process the args into class vars.
98
99        Args:
100            args: Namespace object from argparse.parse_args.
101        """
102        # Let's define the private class vars here and then process the user
103        # args afterwards.
104        self._client_adb_port = args.adb_port
105        self._autoconnect = None
106        self._instance_name_to_reuse = None
107        self._unlock_screen = None
108        self._report_internal_ip = None
109        self._avd_type = None
110        self._flavor = None
111        self._image_source = None
112        self._instance_type = None
113        self._local_image_dir = None
114        self._local_image_artifact = None
115        self._local_system_image_dir = None
116        self._local_tool_dirs = None
117        self._image_download_dir = None
118        self._num_of_instances = None
119        self._no_pull_log = None
120        self._remote_image = None
121        self._system_build_info = None
122        self._kernel_build_info = None
123        self._hw_property = None
124        self._remote_host = None
125        self._host_user = None
126        self._host_ssh_private_key_path = None
127        # Create config instance for android_build_client to query build api.
128        self._cfg = config.GetAcloudConfig(args)
129        # Reporting args.
130        self._serial_log_file = None
131        # gpu and emulator_build_id is only used for goldfish avd_type.
132        self._gpu = None
133        self._emulator_build_id = None
134
135        # Fields only used for cheeps type.
136        self._stable_cheeps_host_image_name = None
137        self._stable_cheeps_host_image_project = None
138        self._username = None
139        self._password = None
140
141        # The maximum time in seconds used to wait for the AVD to boot.
142        self._boot_timeout_secs = None
143        # The maximum time in seconds used to wait for the instance ready.
144        self._ins_timeout_secs = None
145
146        # The local instance id
147        self._local_instance_id = None
148
149        self._ProcessArgs(args)
150
151    def __repr__(self):
152        """Let's make it easy to see what this class is holding."""
153        # TODO: I'm pretty sure there's a better way to do this, but I'm not
154        # quite sure what that would be.
155        representation = []
156        representation.append("")
157        representation.append(" - instance_type: %s" % self._instance_type)
158        representation.append(" - avd type: %s" % self._avd_type)
159        representation.append(" - flavor: %s" % self._flavor)
160        representation.append(" - autoconnect: %s" % self._autoconnect)
161        representation.append(" - num of instances requested: %s" %
162                              self._num_of_instances)
163        representation.append(" - image source type: %s" %
164                              self._image_source)
165        image_summary = None
166        image_details = None
167        if self._image_source == constants.IMAGE_SRC_LOCAL:
168            image_summary = "local image dir"
169            image_details = self._local_image_dir
170            representation.append(" - instance id: %s" % self._local_instance_id)
171        elif self._image_source == constants.IMAGE_SRC_REMOTE:
172            image_summary = "remote image details"
173            image_details = self._remote_image
174        representation.append(" - %s: %s" % (image_summary, image_details))
175        representation.append(" - hw properties: %s" %
176                              self._hw_property)
177        return "\n".join(representation)
178
179    def _ProcessArgs(self, args):
180        """Main entry point to process args for the different type of args.
181
182        Split up the arg processing into related areas (image, instance type,
183        etc) so that we don't have one huge monolilthic method that does
184        everything. It makes it easier to review, write tests, and maintain.
185
186        Args:
187            args: Namespace object from argparse.parse_args.
188        """
189        self._ProcessMiscArgs(args)
190        self._ProcessImageArgs(args)
191        self._ProcessHWPropertyArgs(args)
192
193    def _ProcessImageArgs(self, args):
194        """ Process Image Args.
195
196        Args:
197            args: Namespace object from argparse.parse_args.
198        """
199        # If user didn't specify --local-image, infer remote image args
200        if args.local_image == "":
201            self._image_source = constants.IMAGE_SRC_REMOTE
202            if (self._avd_type == constants.TYPE_GF and
203                    self._instance_type != constants.INSTANCE_TYPE_REMOTE):
204                raise errors.UnsupportedInstanceImageType(
205                    "unsupported creation of avd type: %s, "
206                    "instance type: %s, image source: %s" %
207                    (self._avd_type, self._instance_type, self._image_source))
208            self._ProcessRemoteBuildArgs(args)
209        else:
210            self._image_source = constants.IMAGE_SRC_LOCAL
211            self._ProcessLocalImageArgs(args)
212
213        self.image_download_dir = (
214            args.image_download_dir if args.image_download_dir
215            else tempfile.gettempdir())
216
217    @staticmethod
218    def _ParseHWPropertyStr(hw_property_str):
219        """Parse string to dict.
220
221        Args:
222            hw_property_str: A hw properties string.
223
224        Returns:
225            Dict converted from a string.
226
227        Raises:
228            error.MalformedHWPropertyError: If hw_property_str is malformed.
229        """
230        hw_dict = create_common.ParseHWPropertyArgs(hw_property_str)
231        arg_hw_properties = {}
232        for key, value in hw_dict.items():
233            # Parsing HW properties int to avdspec.
234            if key == constants.HW_ALIAS_RESOLUTION:
235                match = _RE_RES.match(value)
236                if match:
237                    arg_hw_properties[_X_RES] = match.group("x_res")
238                    arg_hw_properties[_Y_RES] = match.group("y_res")
239                else:
240                    raise errors.InvalidHWPropertyError(
241                        "[%s] is an invalid resolution. Example:1280x800" % value)
242            elif key in [constants.HW_ALIAS_MEMORY, constants.HW_ALIAS_DISK]:
243                match = _RE_MEMORY.match(value)
244                if match and match.group("gb_size"):
245                    arg_hw_properties[key] = str(
246                        int(match.group("gb_size")) * 1024)
247                elif match and match.group("mb_size"):
248                    arg_hw_properties[key] = match.group("mb_size")
249                else:
250                    raise errors.InvalidHWPropertyError(
251                        "Expected gb size.[%s] is not allowed. Example:4g" % value)
252            elif key in [constants.HW_ALIAS_CPUS, constants.HW_ALIAS_DPI]:
253                if not _RE_INT.match(value):
254                    raise errors.InvalidHWPropertyError(
255                        "%s value [%s] is not an integer." % (key, value))
256                arg_hw_properties[key] = value
257
258        return arg_hw_properties
259
260    def _ProcessHWPropertyArgs(self, args):
261        """Get the HW properties from argparse.parse_args.
262
263        This method will initialize _hw_property in the following
264        manner:
265        1. Get default hw properties from config.
266        2. Override by hw_property args.
267
268        Args:
269            args: Namespace object from argparse.parse_args.
270        """
271        self._cfg.OverrideHwPropertyWithFlavor(self._flavor)
272        self._hw_property = {}
273        self._hw_property = self._ParseHWPropertyStr(self._cfg.hw_property)
274        logger.debug("Default hw property for [%s] flavor: %s", self._flavor,
275                     self._hw_property)
276
277        if args.hw_property:
278            arg_hw_property = self._ParseHWPropertyStr(args.hw_property)
279            logger.debug("Use custom hw property: %s", arg_hw_property)
280            self._hw_property.update(arg_hw_property)
281
282    def _ProcessMiscArgs(self, args):
283        """These args we can take as and don't belong to a group of args.
284
285        Args:
286            args: Namespace object from argparse.parse_args.
287        """
288        self._autoconnect = args.autoconnect
289        self._unlock_screen = args.unlock_screen
290        self._report_internal_ip = args.report_internal_ip
291        self._avd_type = args.avd_type
292        self._flavor = args.flavor or constants.FLAVOR_PHONE
293        if args.remote_host:
294            self._instance_type = constants.INSTANCE_TYPE_HOST
295        else:
296            self._instance_type = (constants.INSTANCE_TYPE_LOCAL
297                                   if args.local_instance else
298                                   constants.INSTANCE_TYPE_REMOTE)
299        self._remote_host = args.remote_host
300        self._host_user = args.host_user
301        self._host_ssh_private_key_path = args.host_ssh_private_key_path
302        self._local_instance_id = args.local_instance
303        self._local_tool_dirs = args.local_tool
304        self._num_of_instances = args.num
305        self._no_pull_log = args.no_pull_log
306        self._serial_log_file = args.serial_log_file
307        self._emulator_build_id = args.emulator_build_id
308        self._gpu = args.gpu
309
310        self._stable_cheeps_host_image_name = args.stable_cheeps_host_image_name
311        self._stable_cheeps_host_image_project = args.stable_cheeps_host_image_project
312        self._username = args.username
313        self._password = args.password
314
315        self._boot_timeout_secs = args.boot_timeout_secs
316        self._ins_timeout_secs = args.ins_timeout_secs
317
318        if args.reuse_gce:
319            if args.reuse_gce != constants.SELECT_ONE_GCE_INSTANCE:
320                if list_instance.GetInstancesFromInstanceNames(
321                        self._cfg, [args.reuse_gce]):
322                    self._instance_name_to_reuse = args.reuse_gce
323            if self._instance_name_to_reuse is None:
324                instance = list_instance.ChooseOneRemoteInstance(self._cfg)
325                self._instance_name_to_reuse = instance.name
326
327    @staticmethod
328    def _GetFlavorFromString(flavor_string):
329        """Get flavor name from flavor string.
330
331        Flavor string can come from the zipped image name or the lunch target.
332        e.g.
333        If flavor_string come from zipped name:aosp_cf_x86_phone-img-5455843.zip
334        , then "phone" is the flavor.
335        If flavor_string come from a lunch'd target:aosp_cf_x86_auto-userdebug,
336        then "auto" is the flavor.
337
338        Args:
339            flavor_string: String which contains flavor.It can be a
340                           build target or filename.
341
342        Returns:
343            String of flavor name. None if flavor can't be determined.
344        """
345        for flavor in constants.ALL_FLAVORS:
346            if re.match(r"(.*_)?%s" % flavor, flavor_string):
347                return flavor
348
349        logger.debug("Unable to determine flavor from build target: %s",
350                     flavor_string)
351        return None
352
353    def _ProcessLocalImageArgs(self, args):
354        """Get local image path.
355
356        Args:
357            args: Namespace object from argparse.parse_args.
358        """
359        if self._avd_type == constants.TYPE_CF:
360            self._ProcessCFLocalImageArgs(args.local_image, args.flavor)
361        elif self._avd_type == constants.TYPE_GF:
362            self._local_image_dir = self._ProcessGFLocalImageArgs(
363                args.local_image)
364            if args.local_system_image != "":
365                self._local_system_image_dir = self._ProcessGFLocalImageArgs(
366                    args.local_system_image)
367        elif self._avd_type == constants.TYPE_GCE:
368            self._local_image_artifact = self._GetGceLocalImagePath(
369                args.local_image)
370        else:
371            raise errors.CreateError(
372                "Local image doesn't support the AVD type: %s" % self._avd_type
373            )
374
375    @staticmethod
376    def _GetGceLocalImagePath(local_image_dir):
377        """Get gce local image path.
378
379        Choose image file in local_image_dir over $ANDROID_PRODUCT_OUT.
380        There are various img files so we prioritize returning the one we find
381        first based in the specified order in _GCE_LOCAL_IMAGE_CANDIDATES.
382
383        Args:
384            local_image_dir: A string to specify local image dir.
385
386        Returns:
387            String, image file path if exists.
388
389        Raises:
390            errors.ImgDoesNotExist if image doesn't exist.
391        """
392        # IF the user specified a file, return it
393        if local_image_dir and os.path.isfile(local_image_dir):
394            return local_image_dir
395
396        # If the user didn't specify a dir, assume $ANDROID_PRODUCT_OUT
397        if not local_image_dir:
398            local_image_dir = utils.GetBuildEnvironmentVariable(
399                _ENV_ANDROID_PRODUCT_OUT)
400
401        for img_name in _GCE_LOCAL_IMAGE_CANDIDATES:
402            full_file_path = os.path.join(local_image_dir, img_name)
403            if os.path.exists(full_file_path):
404                return full_file_path
405
406        raise errors.ImgDoesNotExist("Could not find any GCE images (%s), you "
407                                     "can build them via \"m dist\"" %
408                                     ", ".join(_GCE_LOCAL_IMAGE_CANDIDATES))
409
410    @staticmethod
411    def _ProcessGFLocalImageArgs(local_image_arg):
412        """Get local built image path for goldfish.
413
414        Args:
415            local_image_arg: The path to the unzipped update package or SDK
416                             repository, i.e., <target>-img-<build>.zip or
417                             sdk-repo-<os>-system-images-<build>.zip.
418                             If the value is empty, this method returns
419                             ANDROID_PRODUCT_OUT in build environment.
420
421        Returns:
422            String, the path to the image directory.
423
424        Raises:
425            errors.GetLocalImageError if the directory is not found.
426        """
427        image_dir = (local_image_arg if local_image_arg else
428                     utils.GetBuildEnvironmentVariable(
429                         constants.ENV_ANDROID_PRODUCT_OUT))
430
431        if not os.path.isdir(image_dir):
432            raise errors.GetLocalImageError(
433                "%s is not a directory." % image_dir)
434
435        return image_dir
436
437    def _ProcessCFLocalImageArgs(self, local_image_arg, flavor_arg):
438        """Get local built image path for cuttlefish-type AVD.
439
440        Two scenarios of using --local-image:
441        - Without a following argument
442          Set flavor string if the required images are in $ANDROID_PRODUCT_OUT,
443        - With a following filename/dirname
444          Set flavor string from the specified image/dir name.
445
446        Args:
447            local_image_arg: String of local image args.
448            flavor_arg: String of flavor arg
449
450        """
451        flavor_from_build_string = None
452        local_image_path = local_image_arg or utils.GetBuildEnvironmentVariable(
453            _ENV_ANDROID_PRODUCT_OUT)
454
455        if os.path.isfile(local_image_path):
456            self._local_image_artifact = local_image_arg
457            flavor_from_build_string = self._GetFlavorFromString(
458                self._local_image_artifact)
459            # Since file is provided and I assume it's a zip, so print the
460            # warning message.
461            utils.PrintColorString(_LOCAL_ZIP_WARNING_MSG,
462                                   utils.TextColors.WARNING)
463        else:
464            self._local_image_dir = local_image_path
465            # Since dir is provided, so checking that any images exist to ensure
466            # user didn't forget to 'make' before launch AVD.
467            image_list = glob.glob(os.path.join(self.local_image_dir, "*.img"))
468            if not image_list:
469                raise errors.GetLocalImageError(
470                    "No image found(Did you choose a lunch target and run `m`?)"
471                    ": %s.\n " % self.local_image_dir)
472
473            try:
474                flavor_from_build_string = self._GetFlavorFromString(
475                    utils.GetBuildEnvironmentVariable(constants.ENV_BUILD_TARGET))
476            except errors.GetAndroidBuildEnvVarError:
477                logger.debug("Unable to determine flavor from env variable: %s",
478                             constants.ENV_BUILD_TARGET)
479
480        if flavor_from_build_string and not flavor_arg:
481            self._flavor = flavor_from_build_string
482
483    def _ProcessRemoteBuildArgs(self, args):
484        """Get the remote build args.
485
486        Some of the acloud magic happens here, we will infer some of these
487        values if the user hasn't specified them.
488
489        Args:
490            args: Namespace object from argparse.parse_args.
491        """
492        self._remote_image = {}
493        self._remote_image[constants.BUILD_BRANCH] = args.branch
494        if not self._remote_image[constants.BUILD_BRANCH]:
495            self._remote_image[constants.BUILD_BRANCH] = self._GetBuildBranch(
496                args.build_id, args.build_target)
497
498        self._remote_image[constants.BUILD_TARGET] = args.build_target
499        if not self._remote_image[constants.BUILD_TARGET]:
500            self._remote_image[constants.BUILD_TARGET] = self._GetBuildTarget(args)
501        else:
502            # If flavor isn't specified, try to infer it from build target,
503            # if we can't, just default to phone flavor.
504            self._flavor = args.flavor or self._GetFlavorFromString(
505                self._remote_image[constants.BUILD_TARGET]) or constants.FLAVOR_PHONE
506            # infer avd_type from build_target.
507            for avd_type, avd_type_abbr in constants.AVD_TYPES_MAPPING.items():
508                if re.match(r"(.*_)?%s_" % avd_type_abbr,
509                            self._remote_image[constants.BUILD_TARGET]):
510                    self._avd_type = avd_type
511                    break
512
513        self._remote_image[constants.BUILD_ID] = args.build_id
514        if not self._remote_image[constants.BUILD_ID]:
515            build_client = android_build_client.AndroidBuildClient(
516                auth.CreateCredentials(self._cfg))
517
518            self._remote_image[constants.BUILD_ID] = build_client.GetLKGB(
519                self._remote_image[constants.BUILD_TARGET],
520                self._remote_image[constants.BUILD_BRANCH])
521
522        # Process system image and kernel image.
523        self._system_build_info = {constants.BUILD_ID: args.system_build_id,
524                                   constants.BUILD_BRANCH: args.system_branch,
525                                   constants.BUILD_TARGET: args.system_build_target}
526        self._kernel_build_info = {constants.BUILD_ID: args.kernel_build_id,
527                                   constants.BUILD_BRANCH: args.kernel_branch,
528                                   constants.BUILD_TARGET: args.kernel_build_target}
529
530    @staticmethod
531    def _GetGitRemote():
532        """Get the remote repo.
533
534        We'll go to a project we know exists (tools/acloud) and grab the git
535        remote output from there.
536
537        Returns:
538            remote: String, git remote (e.g. "aosp").
539        """
540        try:
541            android_build_top = os.environ[constants.ENV_ANDROID_BUILD_TOP]
542        except KeyError:
543            raise errors.GetAndroidBuildEnvVarError(
544                "Could not get environment var: %s\n"
545                "Try to run '#source build/envsetup.sh && lunch <target>'"
546                % _ENV_ANDROID_BUILD_TOP
547            )
548
549        acloud_project = os.path.join(android_build_top, "tools", "acloud")
550        return EscapeAnsi(subprocess.check_output(_COMMAND_GIT_REMOTE,
551                                                  cwd=acloud_project).strip())
552
553    def _GetBuildBranch(self, build_id, build_target):
554        """Infer build branch if user didn't specify branch name.
555
556        Args:
557            build_id: String, Build id, e.g. "2263051", "P2804227"
558            build_target: String, the build target, e.g. cf_x86_phone-userdebug
559
560        Returns:
561            String, name of build branch.
562        """
563        # Infer branch from build_target and build_id
564        if build_id and build_target:
565            build_client = android_build_client.AndroidBuildClient(
566                auth.CreateCredentials(self._cfg))
567            return build_client.GetBranch(build_target, build_id)
568
569        return self._GetBranchFromRepo()
570
571    def _GetBranchFromRepo(self):
572        """Get branch information from command "repo info".
573
574        If branch can't get from "repo info", it will be set as default branch
575        "aosp-master".
576
577        Returns:
578            branch: String, git branch name. e.g. "aosp-master"
579        """
580        branch = None
581        # TODO(149460014): Migrate acloud to py3, then remove this
582        # workaround.
583        env = os.environ.copy()
584        env.pop("PYTHONPATH", None)
585        logger.info("Running command \"%s\"", _COMMAND_REPO_INFO)
586        process = subprocess.Popen(_COMMAND_REPO_INFO, shell=True, stdin=None,
587                                   stdout=subprocess.PIPE,
588                                   stderr=subprocess.STDOUT, env=env)
589        timer = threading.Timer(_REPO_TIMEOUT, process.kill)
590        timer.start()
591        stdout, _ = process.communicate()
592        if stdout:
593            for line in stdout.splitlines():
594                match = _BRANCH_RE.match(EscapeAnsi(line))
595                if match:
596                    branch_prefix = _BRANCH_PREFIX.get(self._GetGitRemote(),
597                                                       _DEFAULT_BRANCH_PREFIX)
598                    branch = branch_prefix + match.group("branch")
599        timer.cancel()
600        if branch:
601            return branch
602        utils.PrintColorString(
603            "Unable to determine your repo branch, defaulting to %s"
604            % _DEFAULT_BRANCH, utils.TextColors.WARNING)
605        return _DEFAULT_BRANCH
606
607    def _GetBuildTarget(self, args):
608        """Infer build target if user doesn't specified target name.
609
610        Target = {REPO_PREFIX}{avd_type}_{bitness}_{flavor}-
611            {DEFAULT_BUILD_TARGET_TYPE}.
612        Example target: aosp_cf_x86_phone-userdebug
613
614        Args:
615            args: Namespace object from argparse.parse_args.
616
617        Returns:
618            build_target: String, name of build target.
619        """
620        branch = re.split("-|_", self._remote_image[constants.BUILD_BRANCH])[0]
621        return "%s%s_%s_%s-%s" % (
622            _BRANCH_TARGET_PREFIX.get(branch, ""),
623            constants.AVD_TYPES_MAPPING[args.avd_type],
624            _DEFAULT_BUILD_BITNESS, self._flavor,
625            _DEFAULT_BUILD_TYPE)
626
627    @property
628    def instance_type(self):
629        """Return the instance type."""
630        return self._instance_type
631
632    @property
633    def image_source(self):
634        """Return the image type."""
635        return self._image_source
636
637    @property
638    def hw_property(self):
639        """Return the hw_property."""
640        return self._hw_property
641
642    @property
643    def local_image_dir(self):
644        """Return local image dir."""
645        return self._local_image_dir
646
647    @property
648    def local_image_artifact(self):
649        """Return local image artifact."""
650        return self._local_image_artifact
651
652    @property
653    def local_system_image_dir(self):
654        """Return local system image dir."""
655        return self._local_system_image_dir
656
657    @property
658    def local_tool_dirs(self):
659        """Return a list of local tool directories."""
660        return self._local_tool_dirs
661
662    @property
663    def avd_type(self):
664        """Return the avd type."""
665        return self._avd_type
666
667    @property
668    def autoconnect(self):
669        """autoconnect.
670
671        args.autoconnect could pass as Boolean or String.
672
673        Return: Boolean, True only if self._autoconnect is not False.
674        """
675        return self._autoconnect is not False
676
677    @property
678    def connect_adb(self):
679        """Auto-connect to adb.
680
681        Return: Boolean, whether autoconnect is enabled.
682        """
683        return self._autoconnect is not False
684
685    @property
686    def connect_vnc(self):
687        """Launch vnc.
688
689        Return: Boolean, True if self._autoconnect is 'vnc'.
690        """
691        return self._autoconnect == constants.INS_KEY_VNC
692
693    @property
694    def connect_webrtc(self):
695        """Auto-launch webRTC AVD on the browser.
696
697        Return: Boolean, True if args.autoconnect is "webrtc".
698        """
699        return self._autoconnect == constants.INS_KEY_WEBRTC
700
701    @property
702    def unlock_screen(self):
703        """Return unlock_screen."""
704        return self._unlock_screen
705
706    @property
707    def remote_image(self):
708        """Return the remote image."""
709        return self._remote_image
710
711    @property
712    def num(self):
713        """Return num of instances."""
714        return self._num_of_instances
715
716    @property
717    def report_internal_ip(self):
718        """Return report internal ip."""
719        return self._report_internal_ip
720
721    @property
722    def kernel_build_info(self):
723        """Return kernel build info."""
724        return self._kernel_build_info
725
726    @property
727    def flavor(self):
728        """Return flavor."""
729        return self._flavor
730
731    @property
732    def cfg(self):
733        """Return cfg instance."""
734        return self._cfg
735
736    @property
737    def image_download_dir(self):
738        """Return image download dir."""
739        return self._image_download_dir
740
741    @image_download_dir.setter
742    def image_download_dir(self, value):
743        """Set image download dir."""
744        self._image_download_dir = value
745
746    @property
747    def serial_log_file(self):
748        """Return serial log file path."""
749        return self._serial_log_file
750
751    @property
752    def gpu(self):
753        """Return gpu."""
754        return self._gpu
755
756    @property
757    def emulator_build_id(self):
758        """Return emulator_build_id."""
759        return self._emulator_build_id
760
761    @property
762    def client_adb_port(self):
763        """Return the client adb port."""
764        return self._client_adb_port
765
766    @property
767    def stable_cheeps_host_image_name(self):
768        """Return the Cheeps host image name."""
769        return self._stable_cheeps_host_image_name
770
771    # pylint: disable=invalid-name
772    @property
773    def stable_cheeps_host_image_project(self):
774        """Return the project hosting the Cheeps host image."""
775        return self._stable_cheeps_host_image_project
776
777    @property
778    def username(self):
779        """Return username."""
780        return self._username
781
782    @property
783    def password(self):
784        """Return password."""
785        return self._password
786
787    @property
788    def boot_timeout_secs(self):
789        """Return boot_timeout_secs."""
790        return self._boot_timeout_secs
791
792    @property
793    def ins_timeout_secs(self):
794        """Return ins_timeout_secs."""
795        return self._ins_timeout_secs
796
797    @property
798    def system_build_info(self):
799        """Return system_build_info."""
800        return self._system_build_info
801
802    @property
803    def local_instance_id(self):
804        """Return local_instance_id."""
805        return self._local_instance_id
806
807    @property
808    def instance_name_to_reuse(self):
809        """Return instance_name_to_reuse."""
810        return self._instance_name_to_reuse
811
812    @property
813    def remote_host(self):
814        """Return host."""
815        return self._remote_host
816
817    @property
818    def host_user(self):
819        """Return host_user."""
820        return self._host_user
821
822    @property
823    def host_ssh_private_key_path(self):
824        """Return host_ssh_private_key_path."""
825        return self._host_ssh_private_key_path
826
827    @property
828    def no_pull_log(self):
829        """Return no_pull_log."""
830        return self._no_pull_log
831