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