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