• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2016 - 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"""
17Welcome to
18   ___  _______   ____  __  _____
19  / _ |/ ___/ /  / __ \/ / / / _ \
20 / __ / /__/ /__/ /_/ / /_/ / // /
21/_/ |_\___/____/\____/\____/____/
22
23
24This a tool to create Android Virtual Devices locally/remotely.
25
26- Prerequisites:
27 The manual will be available at
28 https://android.googlesource.com/platform/tools/acloud/+/master/README.md
29
30- To get started:
31 - Create instances:
32    1) To create a remote cuttlefish instance with the local built image.
33       Example:
34       $ acloud create --local-image
35       Or specify built image dir:
36       $ acloud create --local-image /tmp/image_dir
37    2) To create a local cuttlefish instance using the image which has been
38       built out in your workspace.
39       Example:
40       $ acloud create --local-instance --local-image
41
42 - Delete instances:
43   $ acloud delete
44
45 - Reconnect:
46   To reconnect adb/vnc to an existing instance that's been disconnected:
47   $ acloud reconnect
48   Or to specify a specific instance:
49   $ acloud reconnect --instance-names <instance_name like ins-123-cf-x86-phone>
50
51 - List:
52   List will retrieve all the remote instances you've created in addition to any
53   local instances created as well.
54   To show device IP address, adb port and instance name:
55   $ acloud list
56   To show more detail info on the list.
57   $ acloud list -vv
58
59-  Pull:
60   Pull will download log files or show the log file in screen from one remote
61   cuttlefish instance:
62   $ acloud pull
63   Pull from a specified instance:
64   $ acloud pull --instance-name "your_instance_name"
65
66Try $acloud [cmd] --help for further details.
67
68"""
69
70from __future__ import print_function
71import argparse
72import logging
73import platform
74import sys
75import traceback
76
77# TODO: Remove this once we switch over to embedded launcher.
78# Exit out if python version is < 2.7.13 due to b/120883119.
79if (sys.version_info.major == 2
80        and sys.version_info.minor == 7
81        and sys.version_info.micro < 13):
82    print("Acloud requires python version 2.7.13+ (currently @ %d.%d.%d)" %
83          (sys.version_info.major, sys.version_info.minor,
84           sys.version_info.micro))
85    print("Update your 2.7 python with:")
86    # pylint: disable=invalid-name
87    os_type = platform.system().lower()
88    if os_type == "linux":
89        print("  apt-get install python2.7")
90    elif os_type == "darwin":
91        print("  brew install python@2 (and then follow instructions at "
92              "https://docs.python-guide.org/starting/install/osx/)")
93        print("  - or -")
94        print("  POSIXLY_CORRECT=1 port -N install python27")
95    sys.exit(1)
96
97# By Default silence root logger's stream handler since 3p lib may initial
98# root logger no matter what level we're using. The acloud logger behavior will
99# be defined in _SetupLogging(). This also could workaround to get rid of below
100# oauth2client warning:
101# 'No handlers could be found for logger "oauth2client.contrib.multistore_file'
102DEFAULT_STREAM_HANDLER = logging.StreamHandler()
103DEFAULT_STREAM_HANDLER.setLevel(logging.CRITICAL)
104logging.getLogger().addHandler(DEFAULT_STREAM_HANDLER)
105
106# pylint: disable=wrong-import-position
107from acloud import errors
108from acloud.create import create
109from acloud.create import create_args
110from acloud.delete import delete
111from acloud.delete import delete_args
112from acloud.internal import constants
113from acloud.reconnect import reconnect
114from acloud.reconnect import reconnect_args
115from acloud.list import list as list_instances
116from acloud.list import list_args
117from acloud.metrics import metrics
118from acloud.public import acloud_common
119from acloud.public import config
120from acloud.public.actions import create_cuttlefish_action
121from acloud.public.actions import create_goldfish_action
122from acloud.pull import pull
123from acloud.pull import pull_args
124from acloud.setup import setup
125from acloud.setup import setup_args
126
127
128LOGGING_FMT = "%(asctime)s |%(levelname)s| %(module)s:%(lineno)s| %(message)s"
129ACLOUD_LOGGER = "acloud"
130NO_ERROR_MESSAGE = ""
131
132# Commands
133CMD_CREATE_CUTTLEFISH = "create_cf"
134CMD_CREATE_GOLDFISH = "create_gf"
135
136
137# pylint: disable=too-many-statements
138def _ParseArgs(args):
139    """Parse args.
140
141    Args:
142        args: Argument list passed from main.
143
144    Returns:
145        Parsed args.
146    """
147    usage = ",".join([
148        setup_args.CMD_SETUP,
149        create_args.CMD_CREATE,
150        list_args.CMD_LIST,
151        delete_args.CMD_DELETE,
152        reconnect_args.CMD_RECONNECT,
153        pull_args.CMD_PULL,
154    ])
155    parser = argparse.ArgumentParser(
156        description=__doc__,
157        formatter_class=argparse.RawDescriptionHelpFormatter,
158        usage="acloud {" + usage + "} ...")
159    subparsers = parser.add_subparsers(metavar="{" + usage + "}")
160    subparser_list = []
161
162    # Command "create_cf", create cuttlefish instances
163    create_cf_parser = subparsers.add_parser(CMD_CREATE_CUTTLEFISH)
164    create_cf_parser.required = False
165    create_cf_parser.set_defaults(which=CMD_CREATE_CUTTLEFISH)
166    create_args.AddCommonCreateArgs(create_cf_parser)
167    subparser_list.append(create_cf_parser)
168
169    # Command "create_gf", create goldfish instances
170    # In order to create a goldfish device we need the following parameters:
171    # 1. The emulator build we wish to use, this is the binary that emulates
172    #    an android device. See go/emu-dev for more
173    # 2. A system-image. This is the android release we wish to run on the
174    #    emulated hardware.
175    create_gf_parser = subparsers.add_parser(CMD_CREATE_GOLDFISH)
176    create_gf_parser.required = False
177    create_gf_parser.set_defaults(which=CMD_CREATE_GOLDFISH)
178    create_gf_parser.add_argument(
179        "--emulator_build_id",
180        type=str,
181        dest="emulator_build_id",
182        required=False,
183        help="Emulator build used to run the images. e.g. 4669466.")
184    create_gf_parser.add_argument(
185        "--emulator_branch",
186        type=str,
187        dest="emulator_branch",
188        required=False,
189        help="Emulator build branch name, e.g. aosp-emu-master-dev. If specified"
190        " without emulator_build_id, the last green build will be used.")
191    create_gf_parser.add_argument(
192        "--base_image",
193        type=str,
194        dest="base_image",
195        required=False,
196        help="Name of the goldfish base image to be used to create the instance. "
197        "This will override stable_goldfish_host_image_name from config. "
198        "e.g. emu-dev-cts-061118")
199    create_gf_parser.add_argument(
200        "--tags",
201        dest="tags",
202        nargs="*",
203        required=False,
204        default=None,
205        help="Tags to be set on to the created instance. e.g. https-server.")
206
207    create_args.AddCommonCreateArgs(create_gf_parser)
208    subparser_list.append(create_gf_parser)
209
210    # Command "create"
211    subparser_list.append(create_args.GetCreateArgParser(subparsers))
212
213    # Command "setup"
214    subparser_list.append(setup_args.GetSetupArgParser(subparsers))
215
216    # Command "delete"
217    subparser_list.append(delete_args.GetDeleteArgParser(subparsers))
218
219    # Command "list"
220    subparser_list.append(list_args.GetListArgParser(subparsers))
221
222    # Command "reconnect"
223    subparser_list.append(reconnect_args.GetReconnectArgParser(subparsers))
224
225    # Command "pull"
226    subparser_list.append(pull_args.GetPullArgParser(subparsers))
227
228    # Add common arguments.
229    for subparser in subparser_list:
230        acloud_common.AddCommonArguments(subparser)
231
232    return parser.parse_args(args)
233
234
235# pylint: disable=too-many-branches
236def _VerifyArgs(parsed_args):
237    """Verify args.
238
239    Args:
240        parsed_args: Parsed args.
241
242    Raises:
243        errors.CommandArgError: If args are invalid.
244        errors.UnsupportedCreateArgs: When a create arg is specified but
245                                      unsupported for a particular avd type.
246                                      (e.g. --system-build-id for gf)
247    """
248    if parsed_args.which == create_args.CMD_CREATE:
249        create_args.VerifyArgs(parsed_args)
250    if parsed_args.which == CMD_CREATE_CUTTLEFISH:
251        if not parsed_args.build_id and not parsed_args.branch:
252            raise errors.CommandArgError(
253                "Must specify --build_id or --branch")
254    if parsed_args.which == CMD_CREATE_GOLDFISH:
255        if not parsed_args.emulator_build_id and not parsed_args.build_id and (
256                not parsed_args.emulator_branch and not parsed_args.branch):
257            raise errors.CommandArgError(
258                "Must specify either --build_id or --branch or "
259                "--emulator_branch or --emulator_build_id")
260        if not parsed_args.build_target:
261            raise errors.CommandArgError("Must specify --build_target")
262        if (parsed_args.system_branch
263                or parsed_args.system_build_id
264                or parsed_args.system_build_target):
265            raise errors.UnsupportedCreateArgs(
266                "--system-* args are not supported for AVD type: %s"
267                % constants.TYPE_GF)
268
269    if parsed_args.which in [
270            create_args.CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH
271    ]:
272        if (parsed_args.serial_log_file
273                and not parsed_args.serial_log_file.endswith(".tar.gz")):
274            raise errors.CommandArgError(
275                "--serial_log_file must ends with .tar.gz")
276
277
278def _SetupLogging(log_file, verbose):
279    """Setup logging.
280
281    This function define the logging policy in below manners.
282    - without -v , -vv ,--log_file:
283    Only display critical log and print() message on screen.
284
285    - with -v:
286    Display INFO log and set StreamHandler to acloud parent logger to turn on
287    ONLY acloud modules logging.(silence all 3p libraries)
288
289    - with -vv:
290    Display INFO/DEBUG log and set StreamHandler to root logger to turn on all
291    acloud modules and 3p libraries logging.
292
293    - with --log_file.
294    Dump logs to FileHandler with DEBUG level.
295
296    Args:
297        log_file: String, if not None, dump the log to log file.
298        verbose: Int, if verbose = 1(-v), log at INFO level and turn on
299                 logging on libraries to a StreamHandler.
300                 If verbose = 2(-vv), log at DEBUG level and turn on logging on
301                 all libraries and 3rd party libraries to a StreamHandler.
302    """
303    # Define logging level and hierarchy by verbosity.
304    shandler_level = None
305    logger = None
306    if verbose == 0:
307        shandler_level = logging.CRITICAL
308        logger = logging.getLogger(ACLOUD_LOGGER)
309    elif verbose == 1:
310        shandler_level = logging.INFO
311        logger = logging.getLogger(ACLOUD_LOGGER)
312    elif verbose > 1:
313        shandler_level = logging.DEBUG
314        logger = logging.getLogger()
315
316    # Add StreamHandler by default.
317    shandler = logging.StreamHandler()
318    shandler.setFormatter(logging.Formatter(LOGGING_FMT))
319    shandler.setLevel(shandler_level)
320    logger.addHandler(shandler)
321    # Set the default level to DEBUG, the other handlers will handle
322    # their own levels via the args supplied (-v and --log_file).
323    logger.setLevel(logging.DEBUG)
324
325    # Add FileHandler if log_file is provided.
326    if log_file:
327        fhandler = logging.FileHandler(filename=log_file)
328        fhandler.setFormatter(logging.Formatter(LOGGING_FMT))
329        fhandler.setLevel(logging.DEBUG)
330        logger.addHandler(fhandler)
331
332
333def main(argv=None):
334    """Main entry.
335
336    Args:
337        argv: A list of system arguments.
338
339    Returns:
340        Job status: Integer, 0 if success. None-zero if fails.
341        Stack trace: String of errors.
342    """
343    if argv is None:
344        argv = sys.argv[1:]
345
346    args = _ParseArgs(argv)
347    _SetupLogging(args.log_file, args.verbose)
348    _VerifyArgs(args)
349
350    cfg = config.GetAcloudConfig(args)
351    # TODO: Move this check into the functions it is actually needed.
352    # Check access.
353    # device_driver.CheckAccess(cfg)
354
355    report = None
356    if args.which == create_args.CMD_CREATE:
357        report = create.Run(args)
358    elif args.which == CMD_CREATE_CUTTLEFISH:
359        report = create_cuttlefish_action.CreateDevices(
360            cfg=cfg,
361            build_target=args.build_target,
362            build_id=args.build_id,
363            branch=args.branch,
364            kernel_build_id=args.kernel_build_id,
365            kernel_branch=args.kernel_branch,
366            kernel_build_target=args.kernel_build_target,
367            system_branch=args.system_branch,
368            system_build_id=args.system_build_id,
369            system_build_target=args.system_build_target,
370            gpu=args.gpu,
371            num=args.num,
372            serial_log_file=args.serial_log_file,
373            autoconnect=args.autoconnect,
374            report_internal_ip=args.report_internal_ip,
375            boot_timeout_secs=args.boot_timeout_secs,
376            ins_timeout_secs=args.ins_timeout_secs)
377    elif args.which == CMD_CREATE_GOLDFISH:
378        report = create_goldfish_action.CreateDevices(
379            cfg=cfg,
380            build_target=args.build_target,
381            build_id=args.build_id,
382            emulator_build_id=args.emulator_build_id,
383            branch=args.branch,
384            emulator_branch=args.emulator_branch,
385            kernel_build_id=args.kernel_build_id,
386            kernel_branch=args.kernel_branch,
387            kernel_build_target=args.kernel_build_target,
388            gpu=args.gpu,
389            num=args.num,
390            serial_log_file=args.serial_log_file,
391            autoconnect=args.autoconnect,
392            tags=args.tags,
393            report_internal_ip=args.report_internal_ip)
394    elif args.which == delete_args.CMD_DELETE:
395        report = delete.Run(args)
396    elif args.which == list_args.CMD_LIST:
397        list_instances.Run(args)
398    elif args.which == reconnect_args.CMD_RECONNECT:
399        reconnect.Run(args)
400    elif args.which == pull_args.CMD_PULL:
401        report = pull.Run(args)
402    elif args.which == setup_args.CMD_SETUP:
403        setup.Run(args)
404    else:
405        error_msg = "Invalid command %s" % args.which
406        sys.stderr.write(error_msg)
407        return constants.EXIT_BY_WRONG_CMD, error_msg
408
409    if report and args.report_file:
410        report.Dump(args.report_file)
411    if report and report.errors:
412        error_msg = "\n".join(report.errors)
413        sys.stderr.write("Encountered the following errors:\n%s\n" % error_msg)
414        return constants.EXIT_BY_FAIL_REPORT, error_msg
415    return constants.EXIT_SUCCESS, NO_ERROR_MESSAGE
416
417
418if __name__ == "__main__":
419    EXIT_CODE = None
420    EXCEPTION_STACKTRACE = None
421    EXCEPTION_LOG = None
422    LOG_METRICS = metrics.LogUsage(sys.argv[1:])
423    try:
424        EXIT_CODE, EXCEPTION_STACKTRACE = main(sys.argv[1:])
425    except Exception as e:
426        EXIT_CODE = constants.EXIT_BY_ERROR
427        EXCEPTION_STACKTRACE = traceback.format_exc()
428        EXCEPTION_LOG = str(e)
429        raise
430    finally:
431        # Log Exit event here to calculate the consuming time.
432        if LOG_METRICS:
433            metrics.LogExitEvent(EXIT_CODE,
434                                 stacktrace=EXCEPTION_STACKTRACE,
435                                 logs=EXCEPTION_LOG)
436