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"""Create entry point.
17
18Create will handle all the logic related to creating a local/remote instance
19an Android Virtual Device and the logic related to prepping the local/remote
20image artifacts.
21"""
22
23from __future__ import print_function
24
25import logging
26import os
27import subprocess
28import sys
29
30from acloud import errors
31from acloud.create import avd_spec
32from acloud.create import cheeps_remote_image_remote_instance
33from acloud.create import gce_local_image_remote_instance
34from acloud.create import gce_remote_image_remote_instance
35from acloud.create import goldfish_local_image_local_instance
36from acloud.create import goldfish_remote_host
37from acloud.create import goldfish_remote_image_remote_instance
38from acloud.create import local_image_local_instance
39from acloud.create import local_image_remote_instance
40from acloud.create import local_image_remote_host
41from acloud.create import remote_image_remote_instance
42from acloud.create import remote_image_local_instance
43from acloud.create import remote_image_remote_host
44from acloud.internal import constants
45from acloud.internal.lib import utils
46from acloud.setup import setup
47from acloud.setup import gcp_setup_runner
48from acloud.setup import host_setup_runner
49
50
51logger = logging.getLogger(__name__)
52
53_MAKE_CMD = "build/soong/soong_ui.bash"
54_MAKE_ARG = "--make-mode"
55_YES = "y"
56
57_CREATOR_CLASS_DICT = {
58    # GCE types
59    (constants.TYPE_GCE, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_REMOTE):
60        gce_local_image_remote_instance.GceLocalImageRemoteInstance,
61    (constants.TYPE_GCE, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_REMOTE):
62        gce_remote_image_remote_instance.GceRemoteImageRemoteInstance,
63    # CF types
64    (constants.TYPE_CF, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_LOCAL):
65        local_image_local_instance.LocalImageLocalInstance,
66    (constants.TYPE_CF, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_REMOTE):
67        local_image_remote_instance.LocalImageRemoteInstance,
68    (constants.TYPE_CF, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_HOST):
69        local_image_remote_host.LocalImageRemoteHost,
70    (constants.TYPE_CF, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_REMOTE):
71        remote_image_remote_instance.RemoteImageRemoteInstance,
72    (constants.TYPE_CF, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_LOCAL):
73        remote_image_local_instance.RemoteImageLocalInstance,
74    (constants.TYPE_CF, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_HOST):
75        remote_image_remote_host.RemoteImageRemoteHost,
76    # Cheeps types
77    (constants.TYPE_CHEEPS, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_REMOTE):
78        cheeps_remote_image_remote_instance.CheepsRemoteImageRemoteInstance,
79    # GF types
80    (constants.TYPE_GF, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_REMOTE):
81        goldfish_remote_image_remote_instance.GoldfishRemoteImageRemoteInstance,
82    (constants.TYPE_GF, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_LOCAL):
83        goldfish_local_image_local_instance.GoldfishLocalImageLocalInstance,
84    (constants.TYPE_GF, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_HOST):
85        goldfish_remote_host.GoldfishRemoteHost,
86    (constants.TYPE_GF, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_HOST):
87        goldfish_remote_host.GoldfishRemoteHost,
88    # FVP types
89    (constants.TYPE_FVP, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_REMOTE):
90        local_image_remote_instance.LocalImageRemoteInstance,
91}
92
93
94def GetAvdCreatorClass(avd_type, instance_type, image_source):
95    """Return the creator class for the specified spec.
96
97    Based on the image source and the instance type, return the proper
98    creator class.
99
100    Args:
101        avd_type: String, the AVD type(cuttlefish, gce).
102        instance_type: String, the AVD instance type (local or remote).
103        image_source: String, the source of the image (local or remote).
104
105    Returns:
106        An AVD creator class (e.g. LocalImageRemoteInstance).
107
108    Raises:
109        UnsupportedInstanceImageType if argments didn't match _CREATOR_CLASS_DICT.
110    """
111    creator_class = _CREATOR_CLASS_DICT.get(
112        (avd_type, image_source, instance_type))
113
114    if not creator_class:
115        raise errors.UnsupportedInstanceImageType(
116            "unsupported creation of avd type: %s, instance type: %s, "
117            "image source: %s" % (avd_type, instance_type, image_source))
118    return creator_class
119
120def _CheckForAutoconnect(args):
121    """Check that we have all prerequisites for autoconnect.
122
123    Autoconnect requires adb and ssh, we'll just check for adb for now and
124    assume ssh is everywhere. If adb isn't around, ask the user if they want us
125    to build it, if not we'll disable autoconnect.
126
127    Args:
128        args: Namespace object from argparse.parse_args.
129    """
130    if not args.autoconnect or utils.FindExecutable(constants.ADB_BIN):
131        return
132
133    disable_autoconnect = False
134    answer = _YES if args.no_prompt else utils.InteractWithQuestion(
135        "adb is required for autoconnect, without it autoconnect will be "
136        "disabled, would you like acloud to build it[y/N]? ")
137    if answer in constants.USER_ANSWER_YES:
138        utils.PrintColorString("Building adb ... ", end="")
139        android_build_top = os.environ.get(
140            constants.ENV_ANDROID_BUILD_TOP)
141        if not android_build_top:
142            utils.PrintColorString("Fail! (Not in a lunch'd env)",
143                                   utils.TextColors.FAIL)
144            disable_autoconnect = True
145        else:
146            make_cmd = os.path.join(android_build_top, _MAKE_CMD)
147            build_adb_cmd = [make_cmd, _MAKE_ARG, "adb"]
148            try:
149                with open(os.devnull, "w") as dev_null:
150                    subprocess.check_call(build_adb_cmd, stderr=dev_null,
151                                          stdout=dev_null)
152                    utils.PrintColorString("OK!", utils.TextColors.OKGREEN)
153            except subprocess.CalledProcessError:
154                utils.PrintColorString("Fail! (build failed)",
155                                       utils.TextColors.FAIL)
156                disable_autoconnect = True
157    else:
158        disable_autoconnect = True
159
160    if disable_autoconnect:
161        utils.PrintColorString("Disabling autoconnect",
162                               utils.TextColors.WARNING)
163        args.autoconnect = False
164
165
166def _CheckForSetup(args):
167    """Check that host is setup to run the create commands.
168
169    We'll check we have the necessary bits setup to do what the user wants, and
170    if not, tell them what they need to do before running create again.
171
172    Args:
173        args: Namespace object from argparse.parse_args.
174    """
175    # Need to set all these so if we need to run setup, it won't barf on us
176    # because of some missing fields.
177    args.gcp_init = False
178    args.host = False
179    args.host_base = False
180    args.force = False
181    args.update_config = None
182    args.host_local_ca = False
183    # Remote image/instance requires the GCP config setup.
184    if args.local_instance is None or args.local_image is None:
185        gcp_setup = gcp_setup_runner.GcpTaskRunner(args.config_file)
186        if gcp_setup.ShouldRun():
187            args.gcp_init = True
188            logger.debug("Auto-detect to setup GCP config.")
189
190    # Local instance requires host to be setup. We'll assume that if the
191    # packages were installed, then the user was added into the groups. This
192    # avoids the scenario where a user runs setup and creates a local instance.
193    # The following local instance create will trigger this if statment and go
194    # through the whole setup again even though it's already done because the
195    # user groups aren't set until the user logs out and back in.
196    if args.local_instance is not None:
197        host_pkg_setup = host_setup_runner.AvdPkgInstaller()
198        if host_pkg_setup.ShouldRun():
199            args.host = True
200            logger.debug("Auto-detect to install host packages.")
201
202        user_groups_setup = host_setup_runner.CuttlefishHostSetup()
203        if user_groups_setup.ShouldRun():
204            args.host = True
205            logger.debug("Auto-detect to setup user groups.")
206
207    if args.mkcert and args.autoconnect == constants.INS_KEY_WEBRTC:
208        local_ca_setup = host_setup_runner.LocalCAHostSetup()
209        if local_ca_setup.ShouldRun():
210            args.host_local_ca = True
211            logger.debug("Auto-detect to setup local CA.")
212
213    # Install base packages if we haven't already.
214    host_base_setup = host_setup_runner.HostBasePkgInstaller()
215    if host_base_setup.ShouldRun():
216        args.host_base = True
217        logger.debug("Auto-detect to install host_base packages.")
218
219    run_setup = any([
220        args.force, args.gcp_init, args.host, args.host_base, args.host_local_ca])
221
222    if run_setup:
223        answer = utils.InteractWithQuestion("Missing necessary acloud setup, "
224                                            "would you like to run setup[y/N]?")
225        if answer in constants.USER_ANSWER_YES:
226            setup.Run(args)
227        else:
228            print("Please run '#acloud setup' so we can get your host setup")
229            sys.exit(constants.EXIT_BY_USER)
230
231
232def PreRunCheck(args):
233    """Do some pre-run checks to ensure a smooth create experience.
234
235    Args:
236        args: Namespace object from argparse.parse_args.
237    """
238    _CheckForSetup(args)
239    _CheckForAutoconnect(args)
240
241
242def Run(args):
243    """Run create.
244
245    Args:
246        args: Namespace object from argparse.parse_args.
247
248    Returns:
249        A Report instance.
250    """
251    if not args.skip_pre_run_check:
252        PreRunCheck(args)
253    spec = avd_spec.AVDSpec(args)
254    avd_creator_class = GetAvdCreatorClass(spec.avd_type,
255                                           spec.instance_type,
256                                           spec.image_source)
257    avd_creator = avd_creator_class()
258    report = avd_creator.Create(spec, args.no_prompt)
259    return report
260