#!/usr/bin/env python
|
#
|
# Copyright 2016 - The Android Open Source Project
|
#
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
# you may not use this file except in compliance with the License.
|
# You may obtain a copy of the License at
|
#
|
# http://www.apache.org/licenses/LICENSE-2.0
|
#
|
# Unless required by applicable law or agreed to in writing, software
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# See the License for the specific language governing permissions and
|
# limitations under the License.
|
r"""
|
Welcome to
|
___ _______ ____ __ _____
|
/ _ |/ ___/ / / __ \/ / / / _ \
|
/ __ / /__/ /__/ /_/ / /_/ / // /
|
/_/ |_\___/____/\____/\____/____/
|
|
|
This a tool to create Android Virtual Devices locally/remotely.
|
|
- Prerequisites:
|
The manual will be available at
|
https://android.googlesource.com/platform/tools/acloud/+/master/README.md
|
|
- To get started:
|
- Create instances:
|
1) To create a remote cuttlefish instance with the local built image.
|
Example:
|
$ acloud create --local-image
|
Or specify built image dir:
|
$ acloud create --local-image /tmp/image_dir
|
2) To create a local cuttlefish instance using the image which has been
|
built out in your workspace.
|
Example:
|
$ acloud create --local-instance --local-image
|
|
- Delete instances:
|
$ acloud delete
|
|
Try $acloud [cmd] --help for further details.
|
|
"""
|
|
from __future__ import print_function
|
import argparse
|
import logging
|
import platform
|
import sys
|
import traceback
|
|
# TODO: Remove this once we switch over to embedded launcher.
|
# Exit out if python version is < 2.7.13 due to b/120883119.
|
if (sys.version_info.major == 2
|
and sys.version_info.minor == 7
|
and sys.version_info.micro < 13):
|
print("Acloud requires python version 2.7.13+ (currently @ %d.%d.%d)" %
|
(sys.version_info.major, sys.version_info.minor,
|
sys.version_info.micro))
|
print("Update your 2.7 python with:")
|
# pylint: disable=invalid-name
|
os_type = platform.system().lower()
|
if os_type == "linux":
|
print(" apt-get install python2.7")
|
elif os_type == "darwin":
|
print(" brew install python@2 (and then follow instructions at "
|
"https://docs.python-guide.org/starting/install/osx/)")
|
print(" - or -")
|
print(" POSIXLY_CORRECT=1 port -N install python27")
|
sys.exit(1)
|
|
# By Default silence root logger's stream handler since 3p lib may initial
|
# root logger no matter what level we're using. The acloud logger behavior will
|
# be defined in _SetupLogging(). This also could workaround to get rid of below
|
# oauth2client warning:
|
# 'No handlers could be found for logger "oauth2client.contrib.multistore_file'
|
DEFAULT_STREAM_HANDLER = logging.StreamHandler()
|
DEFAULT_STREAM_HANDLER.setLevel(logging.CRITICAL)
|
logging.getLogger().addHandler(DEFAULT_STREAM_HANDLER)
|
|
# pylint: disable=wrong-import-position
|
from acloud import errors
|
from acloud.create import create
|
from acloud.create import create_args
|
from acloud.delete import delete
|
from acloud.delete import delete_args
|
from acloud.internal import constants
|
from acloud.reconnect import reconnect
|
from acloud.reconnect import reconnect_args
|
from acloud.list import list as list_instances
|
from acloud.list import list_args
|
from acloud.metrics import metrics
|
from acloud.public import acloud_common
|
from acloud.public import config
|
from acloud.public import device_driver
|
from acloud.public.actions import create_cuttlefish_action
|
from acloud.public.actions import create_goldfish_action
|
from acloud.setup import setup
|
from acloud.setup import setup_args
|
|
LOGGING_FMT = "%(asctime)s |%(levelname)s| %(module)s:%(lineno)s| %(message)s"
|
ACLOUD_LOGGER = "acloud"
|
|
# Commands
|
CMD_CREATE_CUTTLEFISH = "create_cf"
|
CMD_CREATE_GOLDFISH = "create_gf"
|
CMD_CLEANUP = "cleanup"
|
|
|
# pylint: disable=too-many-statements
|
def _ParseArgs(args):
|
"""Parse args.
|
|
Args:
|
args: Argument list passed from main.
|
|
Returns:
|
Parsed args.
|
"""
|
usage = ",".join([
|
setup_args.CMD_SETUP,
|
create_args.CMD_CREATE,
|
list_args.CMD_LIST,
|
delete_args.CMD_DELETE,
|
reconnect_args.CMD_RECONNECT,
|
])
|
parser = argparse.ArgumentParser(
|
description=__doc__,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
usage="acloud {" + usage + "} ...")
|
subparsers = parser.add_subparsers(metavar="{" + usage + "}")
|
subparser_list = []
|
|
# Command "create_cf", create cuttlefish instances
|
create_cf_parser = subparsers.add_parser(CMD_CREATE_CUTTLEFISH)
|
create_cf_parser.required = False
|
create_cf_parser.set_defaults(which=CMD_CREATE_CUTTLEFISH)
|
create_cf_parser.add_argument(
|
"--branch",
|
type=str,
|
dest="branch",
|
help="Android branch, e.g. git_master")
|
create_cf_parser.add_argument(
|
"--kernel_build_id",
|
"--kernel-build-id",
|
type=str,
|
dest="kernel_build_id",
|
required=False,
|
help="Android kernel build id, e.g. 4586590. This is to test a new"
|
" kernel build with a particular Android build (--build_id). If neither"
|
" kernel_branch nor kernel_build_id are specified, the kernel that's"
|
" bundled with the Android build would be used.")
|
create_cf_parser.add_argument(
|
"--kernel_branch",
|
"--kernel-branch",
|
type=str,
|
dest="kernel_branch",
|
required=False,
|
help="Android kernel build branch name, e.g."
|
" kernel-common-android-4.14. This is to test a new kernel build with a"
|
" particular Android build (--build-id). If specified without"
|
" specifying kernel_build_id, the last green build in the branch will"
|
" be used. If neither kernel_branch nor kernel_build_id are specified,"
|
" the kernel that's bundled with the Android build would be used.")
|
|
create_args.AddCommonCreateArgs(create_cf_parser)
|
subparser_list.append(create_cf_parser)
|
|
# Command "create_gf", create goldfish instances
|
# In order to create a goldfish device we need the following parameters:
|
# 1. The emulator build we wish to use, this is the binary that emulates
|
# an android device. See go/emu-dev for more
|
# 2. A system-image. This is the android release we wish to run on the
|
# emulated hardware.
|
create_gf_parser = subparsers.add_parser(CMD_CREATE_GOLDFISH)
|
create_gf_parser.required = False
|
create_gf_parser.set_defaults(which=CMD_CREATE_GOLDFISH)
|
create_gf_parser.add_argument(
|
"--branch",
|
type=str,
|
dest="branch",
|
help="Android branch, e.g. git_master")
|
create_gf_parser.add_argument(
|
"--emulator_build_id",
|
type=str,
|
dest="emulator_build_id",
|
required=False,
|
help="Emulator build used to run the images. e.g. 4669466.")
|
create_gf_parser.add_argument(
|
"--gpu",
|
type=str,
|
dest="gpu",
|
required=False,
|
default=None,
|
help="GPU accelerator to use if any."
|
" e.g. nvidia-tesla-k80, omit to use swiftshader")
|
create_gf_parser.add_argument(
|
"--base_image",
|
type=str,
|
dest="base_image",
|
required=False,
|
help="Name of the goldfish base image to be used to create the instance. "
|
"This will override stable_goldfish_host_image_name from config. "
|
"e.g. emu-dev-cts-061118")
|
|
create_args.AddCommonCreateArgs(create_gf_parser)
|
subparser_list.append(create_gf_parser)
|
|
# Command "cleanup"
|
cleanup_parser = subparsers.add_parser(CMD_CLEANUP)
|
cleanup_parser.required = False
|
cleanup_parser.set_defaults(which=CMD_CLEANUP)
|
cleanup_parser.add_argument(
|
"--expiration_mins",
|
type=int,
|
dest="expiration_mins",
|
required=True,
|
help="Garbage collect all gce instances, gce images, cached disk "
|
"images that are older than |expiration_mins|.")
|
subparser_list.append(cleanup_parser)
|
|
# Command "create"
|
subparser_list.append(create_args.GetCreateArgParser(subparsers))
|
|
# Command "setup"
|
subparser_list.append(setup_args.GetSetupArgParser(subparsers))
|
|
# Command "delete"
|
subparser_list.append(delete_args.GetDeleteArgParser(subparsers))
|
|
# Command "list"
|
subparser_list.append(list_args.GetListArgParser(subparsers))
|
|
# Command "Reconnect"
|
subparser_list.append(reconnect_args.GetReconnectArgParser(subparsers))
|
|
# Add common arguments.
|
for subparser in subparser_list:
|
acloud_common.AddCommonArguments(subparser)
|
|
return parser.parse_args(args)
|
|
|
# pylint: disable=too-many-branches
|
def _VerifyArgs(parsed_args):
|
"""Verify args.
|
|
Args:
|
parsed_args: Parsed args.
|
|
Raises:
|
errors.CommandArgError: If args are invalid.
|
"""
|
if parsed_args.which == create_args.CMD_CREATE:
|
create_args.VerifyArgs(parsed_args)
|
if parsed_args.which == CMD_CREATE_CUTTLEFISH:
|
if not parsed_args.build_id or not parsed_args.build_target:
|
raise errors.CommandArgError(
|
"Must specify --build_id and --build_target")
|
if parsed_args.which == CMD_CREATE_GOLDFISH:
|
if not parsed_args.emulator_build_id and not parsed_args.build_id:
|
raise errors.CommandArgError("Must specify either "
|
"--emulator_build_id or --build_id")
|
if not parsed_args.build_target:
|
raise errors.CommandArgError("Must specify --build_target")
|
|
if parsed_args.which in [
|
create_args.CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH
|
]:
|
if (parsed_args.serial_log_file
|
and not parsed_args.serial_log_file.endswith(".tar.gz")):
|
raise errors.CommandArgError(
|
"--serial_log_file must ends with .tar.gz")
|
if (parsed_args.logcat_file
|
and not parsed_args.logcat_file.endswith(".tar.gz")):
|
raise errors.CommandArgError(
|
"--logcat_file must ends with .tar.gz")
|
|
|
def _SetupLogging(log_file, verbose):
|
"""Setup logging.
|
|
This function define the logging policy in below manners.
|
- without -v , -vv ,--log_file:
|
Only display critical log and print() message on screen.
|
|
- with -v:
|
Display INFO log and set StreamHandler to acloud parent logger to turn on
|
ONLY acloud modules logging.(silence all 3p libraries)
|
|
- with -vv:
|
Display INFO/DEBUG log and set StreamHandler to root logger to turn on all
|
acloud modules and 3p libraries logging.
|
|
- with --log_file.
|
Dump logs to FileHandler with DEBUG level.
|
|
Args:
|
log_file: String, if not None, dump the log to log file.
|
verbose: Int, if verbose = 1(-v), log at INFO level and turn on
|
logging on libraries to a StreamHandler.
|
If verbose = 2(-vv), log at DEBUG level and turn on logging on
|
all libraries and 3rd party libraries to a StreamHandler.
|
"""
|
# Define logging level and hierarchy by verbosity.
|
shandler_level = None
|
logger = None
|
if verbose == 0:
|
shandler_level = logging.CRITICAL
|
logger = logging.getLogger(ACLOUD_LOGGER)
|
elif verbose == 1:
|
shandler_level = logging.INFO
|
logger = logging.getLogger(ACLOUD_LOGGER)
|
elif verbose > 1:
|
shandler_level = logging.DEBUG
|
logger = logging.getLogger()
|
|
# Add StreamHandler by default.
|
shandler = logging.StreamHandler()
|
shandler.setFormatter(logging.Formatter(LOGGING_FMT))
|
shandler.setLevel(shandler_level)
|
logger.addHandler(shandler)
|
# Set the default level to DEBUG, the other handlers will handle
|
# their own levels via the args supplied (-v and --log_file).
|
logger.setLevel(logging.DEBUG)
|
|
# Add FileHandler if log_file is provided.
|
if log_file:
|
fhandler = logging.FileHandler(filename=log_file)
|
fhandler.setFormatter(logging.Formatter(LOGGING_FMT))
|
fhandler.setLevel(logging.DEBUG)
|
logger.addHandler(fhandler)
|
|
|
def main(argv=None):
|
"""Main entry.
|
|
Args:
|
argv: A list of system arguments.
|
|
Returns:
|
0 if success. None-zero if fails.
|
"""
|
if argv is None:
|
argv = sys.argv[1:]
|
|
args = _ParseArgs(argv)
|
_SetupLogging(args.log_file, args.verbose)
|
_VerifyArgs(args)
|
|
cfg = config.GetAcloudConfig(args)
|
# TODO: Move this check into the functions it is actually needed.
|
# Check access.
|
# device_driver.CheckAccess(cfg)
|
|
report = None
|
if args.which == create_args.CMD_CREATE:
|
create.Run(args)
|
elif args.which == CMD_CREATE_CUTTLEFISH:
|
report = create_cuttlefish_action.CreateDevices(
|
cfg=cfg,
|
build_target=args.build_target,
|
build_id=args.build_id,
|
kernel_build_id=args.kernel_build_id,
|
kernel_branch=args.kernel_branch,
|
num=args.num,
|
serial_log_file=args.serial_log_file,
|
logcat_file=args.logcat_file,
|
autoconnect=args.autoconnect,
|
report_internal_ip=args.report_internal_ip)
|
elif args.which == CMD_CREATE_GOLDFISH:
|
report = create_goldfish_action.CreateDevices(
|
cfg=cfg,
|
build_target=args.build_target,
|
build_id=args.build_id,
|
emulator_build_id=args.emulator_build_id,
|
gpu=args.gpu,
|
num=args.num,
|
serial_log_file=args.serial_log_file,
|
logcat_file=args.logcat_file,
|
autoconnect=args.autoconnect,
|
branch=args.branch,
|
report_internal_ip=args.report_internal_ip)
|
elif args.which == delete_args.CMD_DELETE:
|
report = delete.Run(args)
|
elif args.which == CMD_CLEANUP:
|
report = device_driver.Cleanup(cfg, args.expiration_mins)
|
elif args.which == list_args.CMD_LIST:
|
list_instances.Run(args)
|
elif args.which == reconnect_args.CMD_RECONNECT:
|
reconnect.Run(args)
|
elif args.which == setup_args.CMD_SETUP:
|
setup.Run(args)
|
else:
|
sys.stderr.write("Invalid command %s" % args.which)
|
return 2
|
|
if report and args.report_file:
|
report.Dump(args.report_file)
|
if report.errors:
|
msg = "\n".join(report.errors)
|
sys.stderr.write("Encountered the following errors:\n%s\n" % msg)
|
return 1
|
return 0
|
|
|
if __name__ == "__main__":
|
EXIT_CODE = None
|
EXCEPTION_STACKTRACE = None
|
EXCEPTION_LOG = None
|
metrics.LogUsage(sys.argv[1:])
|
try:
|
EXIT_CODE = main(sys.argv[1:])
|
except Exception as e:
|
EXIT_CODE = constants.EXIT_BY_ERROR
|
EXCEPTION_STACKTRACE = traceback.format_exc()
|
EXCEPTION_LOG = str(e)
|
raise
|
finally:
|
# Log Exit event here to calculate the consuming time.
|
metrics.LogExitEvent(EXIT_CODE,
|
stacktrace=EXCEPTION_STACKTRACE,
|
logs=EXCEPTION_LOG)
|