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