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