• 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 sys
74import traceback
75
76if sys.version_info.major == 2:
77    print("Acloud only supports python3 (currently @ %d.%d.%d)."
78          " Please run Acloud with python3." % (sys.version_info.major,
79                                                sys.version_info.minor,
80                                                sys.version_info.micro))
81    sys.exit(1)
82
83# (b/219847353) Move googleapiclient to the last position of sys.path when
84#  existed.
85for lib in sys.path:
86    if 'googleapiclient' in lib:
87        sys.path.remove(lib)
88        sys.path.append(lib)
89        break
90
91# By Default silence root logger's stream handler since 3p lib may initial
92# root logger no matter what level we're using. The acloud logger behavior will
93# be defined in _SetupLogging(). This also could workaround to get rid of below
94# oauth2client warning:
95# 'No handlers could be found for logger "oauth2client.contrib.multistore_file'
96DEFAULT_STREAM_HANDLER = logging.StreamHandler()
97DEFAULT_STREAM_HANDLER.setLevel(logging.CRITICAL)
98logging.getLogger().addHandler(DEFAULT_STREAM_HANDLER)
99
100# pylint: disable=wrong-import-position
101from acloud import errors
102from acloud.create import create
103from acloud.create import create_args
104from acloud.delete import delete
105from acloud.delete import delete_args
106from acloud.internal import constants
107from acloud.reconnect import reconnect
108from acloud.reconnect import reconnect_args
109from acloud.list import list as list_instances
110from acloud.list import list_args
111from acloud.metrics import metrics
112from acloud.powerwash import powerwash
113from acloud.powerwash import powerwash_args
114from acloud.public import acloud_common
115from acloud.public import config
116from acloud.public import report
117from acloud.public.actions import create_goldfish_action
118from acloud.pull import pull
119from acloud.pull import pull_args
120from acloud.restart import restart
121from acloud.restart import restart_args
122from acloud.setup import setup
123from acloud.setup import setup_args
124from acloud.hostcleanup import hostcleanup
125from acloud.hostcleanup import hostcleanup_args
126
127
128LOGGING_FMT = "%(asctime)s |%(levelname)s| %(module)s:%(lineno)s| %(message)s"
129ACLOUD_LOGGER = "acloud"
130_LOGGER = logging.getLogger(ACLOUD_LOGGER)
131NO_ERROR_MESSAGE = ""
132PROG = "acloud"
133
134# Commands
135CMD_CREATE_GOLDFISH = "create_gf"
136
137# Config requires fields.
138_CREATE_REQUIRE_FIELDS = ["project", "zone", "machine_type"]
139# show contact info to user.
140_CONTACT_INFO = ("If you have any question or need acloud team support, "
141                 "please feel free to contact us by email at "
142                 "buganizer-system+419709@google.com")
143_LOG_INFO = " and attach those log files from %s"
144
145
146# pylint: disable=too-many-statements
147def _ParseArgs(args):
148    """Parse args.
149
150    Args:
151        args: Argument list passed from main.
152
153    Returns:
154        Parsed args and a list of unknown argument strings.
155    """
156    usage = ",".join([
157        setup_args.CMD_SETUP,
158        create_args.CMD_CREATE,
159        list_args.CMD_LIST,
160        delete_args.CMD_DELETE,
161        reconnect_args.CMD_RECONNECT,
162        pull_args.CMD_PULL,
163        restart_args.CMD_RESTART,
164        hostcleanup_args.CMD_HOSTCLEANUP,
165    ])
166    parser = argparse.ArgumentParser(
167        description=__doc__,
168        formatter_class=argparse.RawDescriptionHelpFormatter,
169        usage="acloud {" + usage + "} ...")
170    parser = argparse.ArgumentParser(prog=PROG)
171    parser.add_argument('--version', action='version', version=(
172        '%(prog)s ' + config.GetVersion()))
173    subparsers = parser.add_subparsers(metavar="{" + usage + "}")
174    subparser_list = []
175
176    # Command "create_gf", create goldfish instances
177    # In order to create a goldfish device we need the following parameters:
178    # 1. The emulator build we wish to use, this is the binary that emulates
179    #    an android device. See go/emu-dev for more
180    # 2. A system-image. This is the android release we wish to run on the
181    #    emulated hardware.
182    create_gf_parser = subparsers.add_parser(CMD_CREATE_GOLDFISH)
183    create_gf_parser.required = False
184    create_gf_parser.set_defaults(which=CMD_CREATE_GOLDFISH)
185    create_gf_parser.add_argument(
186        "--emulator_build_id",
187        type=str,
188        dest="emulator_build_id",
189        required=False,
190        help="Emulator build used to run the images. e.g. 4669466.")
191    create_gf_parser.add_argument(
192        "--emulator_branch",
193        type=str,
194        dest="emulator_branch",
195        required=False,
196        help="Emulator build branch name, e.g. aosp-emu-master-dev. If specified"
197        " without emulator_build_id, the last green build will be used.")
198    create_gf_parser.add_argument(
199        "--base_image",
200        type=str,
201        dest="base_image",
202        required=False,
203        help="Name of the goldfish base image to be used to create the instance. "
204        "This will override stable_goldfish_host_image_name from config. "
205        "e.g. emu-dev-cts-061118")
206    create_gf_parser.add_argument(
207        "--tags",
208        dest="tags",
209        nargs="*",
210        required=False,
211        default=None,
212        help="Tags to be set on to the created instance. e.g. https-server.")
213
214    create_args.AddCommonCreateArgs(create_gf_parser)
215    subparser_list.append(create_gf_parser)
216
217    # Command "create"
218    subparser_list.append(create_args.GetCreateArgParser(subparsers))
219
220    # Command "setup"
221    subparser_list.append(setup_args.GetSetupArgParser(subparsers))
222
223    # Command "delete"
224    subparser_list.append(delete_args.GetDeleteArgParser(subparsers))
225
226    # Command "list"
227    subparser_list.append(list_args.GetListArgParser(subparsers))
228
229    # Command "reconnect"
230    subparser_list.append(reconnect_args.GetReconnectArgParser(subparsers))
231
232    # Command "restart"
233    subparser_list.append(restart_args.GetRestartArgParser(subparsers))
234
235    # Command "powerwash"
236    subparser_list.append(powerwash_args.GetPowerwashArgParser(subparsers))
237
238    # Command "pull"
239    subparser_list.append(pull_args.GetPullArgParser(subparsers))
240
241    # Command "hostcleanup"
242    subparser_list.append(hostcleanup_args.GetHostcleanupArgParser(subparsers))
243
244    # Add common arguments.
245    for subparser in subparser_list:
246        acloud_common.AddCommonArguments(subparser)
247
248    if not args:
249        parser.print_help()
250        sys.exit(constants.EXIT_BY_WRONG_CMD)
251
252    return parser.parse_known_args(args)
253
254
255# pylint: disable=too-many-branches
256def _VerifyArgs(parsed_args):
257    """Verify args.
258
259    Args:
260        parsed_args: Parsed args.
261
262    Raises:
263        errors.CommandArgError: If args are invalid.
264        errors.UnsupportedCreateArgs: When a create arg is specified but
265                                      unsupported for a particular avd type.
266                                      (e.g. --system-build-id for gf)
267    """
268    if parsed_args.which == create_args.CMD_CREATE:
269        create_args.VerifyArgs(parsed_args)
270    if parsed_args.which == setup_args.CMD_SETUP:
271        setup_args.VerifyArgs(parsed_args)
272    if parsed_args.which == CMD_CREATE_GOLDFISH:
273        if not parsed_args.emulator_build_id and not parsed_args.build_id and (
274                not parsed_args.emulator_branch and not parsed_args.branch):
275            raise errors.CommandArgError(
276                "Must specify either --build_id or --branch or "
277                "--emulator_branch or --emulator_build_id")
278        if not parsed_args.build_target:
279            raise errors.CommandArgError("Must specify --build_target")
280        if (parsed_args.system_branch
281                or parsed_args.system_build_id
282                or parsed_args.system_build_target):
283            raise errors.UnsupportedCreateArgs(
284                "--system-* args are not supported for AVD type: %s"
285                % constants.TYPE_GF)
286
287    if parsed_args.which in [create_args.CMD_CREATE, CMD_CREATE_GOLDFISH]:
288        if (parsed_args.serial_log_file
289                and not parsed_args.serial_log_file.endswith(".tar.gz")):
290            raise errors.CommandArgError(
291                "--serial_log_file must ends with .tar.gz")
292
293
294def _ParsingConfig(args, cfg):
295    """Parse config to check if missing any field.
296
297    Args:
298        args: Namespace object from argparse.parse_args.
299        cfg: AcloudConfig object.
300
301    Returns:
302        error message about list of missing config fields.
303    """
304    missing_fields = []
305    if args.which == create_args.CMD_CREATE and args.local_instance is None:
306        missing_fields = cfg.GetMissingFields(_CREATE_REQUIRE_FIELDS)
307    if missing_fields:
308        return (
309            "Config file (%s) missing required fields: %s, please add these "
310            "fields or reset config file. For reset config information: "
311            "go/acloud-googler-setup#reset-configuration" %
312            (config.GetUserConfigPath(args.config_file), missing_fields))
313    return None
314
315
316def _SetupLogging(log_file, verbose):
317    """Setup logging.
318
319    This function define the logging policy in below manners.
320    - without -v , -vv ,--log_file:
321    Only display critical log and print() message on screen.
322
323    - with -v:
324    Display INFO log and set StreamHandler to acloud parent logger to turn on
325    ONLY acloud modules logging.(silence all 3p libraries)
326
327    - with -vv:
328    Display INFO/DEBUG log and set StreamHandler to root logger to turn on all
329    acloud modules and 3p libraries logging.
330
331    - with --log_file.
332    Dump logs to FileHandler with DEBUG level.
333
334    Args:
335        log_file: String, if not None, dump the log to log file.
336        verbose: Int, if verbose = 1(-v), log at INFO level and turn on
337                 logging on libraries to a StreamHandler.
338                 If verbose = 2(-vv), log at DEBUG level and turn on logging on
339                 all libraries and 3rd party libraries to a StreamHandler.
340    """
341    # Define logging level and hierarchy by verbosity.
342    shandler_level = None
343    logger = None
344    if verbose == 0:
345        shandler_level = logging.CRITICAL
346        logger = logging.getLogger(ACLOUD_LOGGER)
347    elif verbose == 1:
348        shandler_level = logging.INFO
349        logger = logging.getLogger(ACLOUD_LOGGER)
350    elif verbose > 1:
351        shandler_level = logging.DEBUG
352        logger = logging.getLogger()
353
354    # Add StreamHandler by default.
355    shandler = logging.StreamHandler()
356    shandler.setFormatter(logging.Formatter(LOGGING_FMT))
357    shandler.setLevel(shandler_level)
358    logger.addHandler(shandler)
359    # Set the default level to DEBUG, the other handlers will handle
360    # their own levels via the args supplied (-v and --log_file).
361    logger.setLevel(logging.DEBUG)
362
363    # Add FileHandler if log_file is provided.
364    if log_file:
365        fhandler = logging.FileHandler(filename=log_file)
366        fhandler.setFormatter(logging.Formatter(LOGGING_FMT))
367        fhandler.setLevel(logging.DEBUG)
368        logger.addHandler(fhandler)
369
370
371def main(argv=None):
372    """Main entry.
373
374    Args:
375        argv: A list of system arguments.
376
377    Returns:
378        Job status: Integer, 0 if success. None-zero if fails.
379        Stack trace: String of errors.
380    """
381    args, unknown_args = _ParseArgs(argv)
382    _SetupLogging(args.log_file, args.verbose)
383    _VerifyArgs(args)
384    _LOGGER.info("Acloud version: %s", config.GetVersion())
385
386    cfg = config.GetAcloudConfig(args)
387    parsing_config_error = _ParsingConfig(args, cfg)
388    # TODO: Move this check into the functions it is actually needed.
389    # Check access.
390    # device_driver.CheckAccess(cfg)
391
392    reporter = None
393    if parsing_config_error:
394        reporter = report.Report(command=args.which)
395        reporter.UpdateFailure(parsing_config_error,
396                               constants.ACLOUD_CONFIG_ERROR)
397    elif unknown_args:
398        reporter = report.Report(command=args.which)
399        reporter.UpdateFailure(
400            "unrecognized arguments: %s" % ",".join(unknown_args),
401            constants.ACLOUD_UNKNOWN_ARGS_ERROR)
402    elif args.which == create_args.CMD_CREATE:
403        reporter = create.Run(args)
404    elif args.which == CMD_CREATE_GOLDFISH:
405        reporter = create_goldfish_action.CreateDevices(
406            cfg=cfg,
407            build_target=args.build_target,
408            build_id=args.build_id,
409            emulator_build_id=args.emulator_build_id,
410            branch=args.branch,
411            emulator_branch=args.emulator_branch,
412            kernel_build_id=args.kernel_build_id,
413            kernel_branch=args.kernel_branch,
414            kernel_build_target=args.kernel_build_target,
415            gpu=args.gpu,
416            num=args.num,
417            serial_log_file=args.serial_log_file,
418            autoconnect=args.autoconnect,
419            tags=args.tags,
420            report_internal_ip=args.report_internal_ip,
421            boot_timeout_secs=args.boot_timeout_secs)
422    elif args.which == delete_args.CMD_DELETE:
423        reporter = delete.Run(args)
424    elif args.which == list_args.CMD_LIST:
425        list_instances.Run(args)
426    elif args.which == reconnect_args.CMD_RECONNECT:
427        reconnect.Run(args)
428    elif args.which == restart_args.CMD_RESTART:
429        reporter = restart.Run(args)
430    elif args.which == powerwash_args.CMD_POWERWASH:
431        reporter = powerwash.Run(args)
432    elif args.which == pull_args.CMD_PULL:
433        reporter = pull.Run(args)
434    elif args.which == setup_args.CMD_SETUP:
435        setup.Run(args)
436    elif args.which == hostcleanup_args.CMD_HOSTCLEANUP:
437        hostcleanup.Run(args)
438    else:
439        error_msg = "Invalid command %s" % args.which
440        sys.stderr.write(error_msg)
441        return constants.EXIT_BY_WRONG_CMD, error_msg
442
443    if reporter and args.report_file:
444        reporter.Dump(args.report_file)
445    if reporter and reporter.errors:
446        error_msg = "\n".join(reporter.errors)
447        help_msg = _CONTACT_INFO
448        if reporter.data.get(constants.ERROR_LOG_FOLDER):
449            help_msg += _LOG_INFO % reporter.data.get(constants.ERROR_LOG_FOLDER)
450        sys.stderr.write("Encountered the following errors:\n%s\n\n%s.\n" %
451                         (error_msg, help_msg))
452        return constants.EXIT_BY_FAIL_REPORT, error_msg
453    return constants.EXIT_SUCCESS, NO_ERROR_MESSAGE
454
455
456if __name__ == "__main__":
457    EXIT_CODE = None
458    EXCEPTION_STACKTRACE = None
459    EXCEPTION_LOG = None
460    LOG_METRICS = metrics.LogUsage(sys.argv[1:])
461    try:
462        EXIT_CODE, EXCEPTION_STACKTRACE = main(sys.argv[1:])
463    except Exception as e:
464        EXIT_CODE = constants.EXIT_BY_ERROR
465        EXCEPTION_STACKTRACE = traceback.format_exc()
466        EXCEPTION_LOG = str(e)
467        raise
468    finally:
469        # Log Exit event here to calculate the consuming time.
470        if LOG_METRICS:
471            metrics.LogExitEvent(EXIT_CODE,
472                                 stacktrace=EXCEPTION_STACKTRACE,
473                                 logs=EXCEPTION_LOG)
474