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