xref: /aosp_15_r20/tools/acloud/create/create.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
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    # Trusty types
92    (constants.TYPE_TRUSTY, constants.IMAGE_SRC_LOCAL, constants.INSTANCE_TYPE_REMOTE):
93        local_image_remote_instance.LocalImageRemoteInstance,
94    (constants.TYPE_TRUSTY, constants.IMAGE_SRC_REMOTE, constants.INSTANCE_TYPE_REMOTE):
95        remote_image_remote_instance.RemoteImageRemoteInstance,
96}
97
98
99def GetAvdCreatorClass(avd_type, instance_type, image_source):
100    """Return the creator class for the specified spec.
101
102    Based on the image source and the instance type, return the proper
103    creator class.
104
105    Args:
106        avd_type: String, the AVD type(cuttlefish, gce).
107        instance_type: String, the AVD instance type (local or remote).
108        image_source: String, the source of the image (local or remote).
109
110    Returns:
111        An AVD creator class (e.g. LocalImageRemoteInstance).
112
113    Raises:
114        UnsupportedInstanceImageType if argments didn't match _CREATOR_CLASS_DICT.
115    """
116    creator_class = _CREATOR_CLASS_DICT.get(
117        (avd_type, image_source, instance_type))
118
119    if not creator_class:
120        raise errors.UnsupportedInstanceImageType(
121            "unsupported creation of avd type: %s, instance type: %s, "
122            "image source: %s" % (avd_type, instance_type, image_source))
123    return creator_class
124
125def _CheckForAutoconnect(args):
126    """Check that we have all prerequisites for autoconnect.
127
128    Autoconnect requires adb and ssh, we'll just check for adb for now and
129    assume ssh is everywhere. If adb isn't around, ask the user if they want us
130    to build it, if not we'll disable autoconnect.
131
132    Args:
133        args: Namespace object from argparse.parse_args.
134    """
135    if not args.autoconnect or utils.FindExecutable(constants.ADB_BIN):
136        return
137
138    disable_autoconnect = False
139    answer = _YES if args.no_prompt else utils.InteractWithQuestion(
140        "adb is required for autoconnect, without it autoconnect will be "
141        "disabled, would you like acloud to build it[y/N]? ")
142    if answer in constants.USER_ANSWER_YES:
143        utils.PrintColorString("Building adb ... ", end="")
144        android_build_top = os.environ.get(
145            constants.ENV_ANDROID_BUILD_TOP)
146        if not android_build_top:
147            utils.PrintColorString("Fail! (Not in a lunch'd env)",
148                                   utils.TextColors.FAIL)
149            disable_autoconnect = True
150        else:
151            make_cmd = os.path.join(android_build_top, _MAKE_CMD)
152            build_adb_cmd = [make_cmd, _MAKE_ARG, "adb"]
153            try:
154                with open(os.devnull, "w") as dev_null:
155                    subprocess.check_call(build_adb_cmd, stderr=dev_null,
156                                          stdout=dev_null)
157                    utils.PrintColorString("OK!", utils.TextColors.OKGREEN)
158            except subprocess.CalledProcessError:
159                utils.PrintColorString("Fail! (build failed)",
160                                       utils.TextColors.FAIL)
161                disable_autoconnect = True
162    else:
163        disable_autoconnect = True
164
165    if disable_autoconnect:
166        utils.PrintColorString("Disabling autoconnect",
167                               utils.TextColors.WARNING)
168        args.autoconnect = False
169
170
171def _CheckForSetup(args):
172    """Check that host is setup to run the create commands.
173
174    We'll check we have the necessary bits setup to do what the user wants, and
175    if not, tell them what they need to do before running create again.
176
177    Args:
178        args: Namespace object from argparse.parse_args.
179    """
180    # Need to set all these so if we need to run setup, it won't barf on us
181    # because of some missing fields.
182    args.gcp_init = False
183    args.host = False
184    args.host_base = False
185    args.force = False
186    args.update_config = None
187    args.host_local_ca = False
188    # Remote image/instance requires the GCP config setup.
189    if args.local_instance is None or args.local_image is None:
190        gcp_setup = gcp_setup_runner.GcpTaskRunner(args.config_file)
191        if gcp_setup.ShouldRun():
192            args.gcp_init = True
193            logger.debug("Auto-detect to setup GCP config.")
194
195    # Local instance requires host to be setup. We'll assume that if the
196    # packages were installed, then the user was added into the groups. This
197    # avoids the scenario where a user runs setup and creates a local instance.
198    # The following local instance create will trigger this if statment and go
199    # through the whole setup again even though it's already done because the
200    # user groups aren't set until the user logs out and back in.
201    if args.local_instance is not None:
202        host_pkg_setup = host_setup_runner.AvdPkgInstaller()
203        if host_pkg_setup.ShouldRun():
204            args.host = True
205            logger.debug("Auto-detect to install host packages.")
206
207        user_groups_setup = host_setup_runner.CuttlefishHostSetup()
208        if user_groups_setup.ShouldRun():
209            args.host = True
210            logger.debug("Auto-detect to setup user groups.")
211
212    if args.mkcert and args.autoconnect == constants.INS_KEY_WEBRTC:
213        local_ca_setup = host_setup_runner.LocalCAHostSetup()
214        if local_ca_setup.ShouldRun():
215            args.host_local_ca = True
216            logger.debug("Auto-detect to setup local CA.")
217
218    # Install base packages if we haven't already.
219    host_base_setup = host_setup_runner.HostBasePkgInstaller()
220    if host_base_setup.ShouldRun():
221        args.host_base = True
222        logger.debug("Auto-detect to install host_base packages.")
223
224    run_setup = any([
225        args.force, args.gcp_init, args.host, args.host_base, args.host_local_ca])
226
227    if run_setup:
228        answer = utils.InteractWithQuestion("Missing necessary acloud setup, "
229                                            "would you like to run setup[y/N]?")
230        if answer in constants.USER_ANSWER_YES:
231            setup.Run(args)
232        else:
233            print("Please run '#acloud setup' so we can get your host setup")
234            sys.exit(constants.EXIT_BY_USER)
235
236
237def PreRunCheck(args):
238    """Do some pre-run checks to ensure a smooth create experience.
239
240    Args:
241        args: Namespace object from argparse.parse_args.
242    """
243    _CheckForSetup(args)
244    _CheckForAutoconnect(args)
245
246
247def Run(args):
248    """Run create.
249
250    Args:
251        args: Namespace object from argparse.parse_args.
252
253    Returns:
254        A Report instance.
255    """
256    if not args.skip_pre_run_check:
257        PreRunCheck(args)
258    spec = avd_spec.AVDSpec(args)
259    avd_creator_class = GetAvdCreatorClass(spec.avd_type,
260                                           spec.instance_type,
261                                           spec.image_source)
262    avd_creator = avd_creator_class()
263    report = avd_creator.Create(spec, args.no_prompt)
264    return report
265