blob: 4802bac1f5b64da0076ba03703385fec12a11996 [file] [log] [blame]
Keun Soo Yimb293fdb2016-09-21 16:03:44 -07001#!/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.
Fang Dengfed6a6f2017-03-01 18:27:28 -080016r"""Cloud Android Driver.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070017
18This CLI manages google compute engine project for android devices.
19
20- Prerequisites:
21 See: go/acloud-manual
22
23- Configuration:
24 The script takes a required configuration file, which should look like
25 <Start of the file>
26 # If using service account
27 service_account_name: "your_account@developer.gserviceaccount.com"
28 service_account_private_key_path: "/path/to/your-project.p12"
29
30 # If using OAuth2 authentication flow
31 client_id: <client id created in the project>
32 client_secret: <client secret for the client id>
33
34 # Optional
Fang Deng69498c32017-03-02 14:29:30 -080035 ssh_private_key_path: "~/.ssh/acloud_rsa"
36 ssh_public_key_path: "~/.ssh/acloud_rsa.pub"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070037 orientation: "portrait"
38 resolution: "800x1280x32x213"
39 network: "default"
40 machine_type: "n1-standard-1"
41 extra_data_disk_size_gb: 10 # 4G or 10G
42
43 # Required
44 project: "your-project"
45 zone: "us-central1-f"
46 storage_bucket_name: "your_google_storage_bucket_name"
47 <End of the file>
48
49 Save it at /path/to/acloud.config
50
51- Example calls:
52 - Create two instances:
Kevin Chengb5963882018-05-09 00:06:27 -070053 $ acloud.par create_cf
54 --build_target aosp_cf_x86_phone-userdebug \
Fang Dengfed6a6f2017-03-01 18:27:28 -080055 --build_id 3744001 --num 2 --config_file /path/to/acloud.config \
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070056 --report_file /tmp/acloud_report.json --log_file /tmp/acloud.log
57
58 - Delete two instances:
59 $ acloud.par delete --instance_names
Fang Dengfed6a6f2017-03-01 18:27:28 -080060 ins-b638cdba-3744001-gce-x86-phone-userdebug-fastbuild3c-linux
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070061 --config_file /path/to/acloud.config
62 --report_file /tmp/acloud_report.json --log_file /tmp/acloud.log
63"""
64import argparse
65import getpass
66import logging
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070067import sys
68
Kevin Chengf4137c62018-05-22 16:06:58 -070069# Needed to silence oauth2client.
70logging.basicConfig(level=logging.CRITICAL)
Kevin Chengb5963882018-05-09 00:06:27 -070071
Kevin Chengf4137c62018-05-22 16:06:58 -070072# pylint: disable=wrong-import-position
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070073from acloud.internal import constants
74from acloud.public import acloud_common
75from acloud.public import config
76from acloud.public import device_driver
77from acloud.public import errors
Kevin Chengb5963882018-05-09 00:06:27 -070078from acloud.public.actions import create_cuttlefish_action
79from acloud.public.actions import create_goldfish_action
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070080
81LOGGING_FMT = "%(asctime)s |%(levelname)s| %(module)s:%(lineno)s| %(message)s"
Tri Vo8e292532016-10-01 16:55:51 -070082LOGGER_NAME = "acloud_main"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070083
84# Commands
85CMD_CREATE = "create"
Kevin Chengb5963882018-05-09 00:06:27 -070086CMD_CREATE_CUTTLEFISH = "create_cf"
87CMD_CREATE_GOLDFISH = "create_gf"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070088CMD_DELETE = "delete"
89CMD_CLEANUP = "cleanup"
Fang Deng69498c32017-03-02 14:29:30 -080090CMD_SSHKEY = "project_sshkey"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070091
92
Kevin Cheng3031f8a2018-05-16 13:21:51 -070093# pylint: disable=too-many-statements
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070094def _ParseArgs(args):
95 """Parse args.
96
97 Args:
98 args: Argument list passed from main.
99
100 Returns:
101 Parsed args.
102 """
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700103 usage = ",".join([
104 CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_DELETE, CMD_CLEANUP, CMD_SSHKEY
105 ])
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700106 parser = argparse.ArgumentParser(
107 description=__doc__,
108 formatter_class=argparse.RawDescriptionHelpFormatter,
Kevin Cheng7ff74be2018-05-23 14:18:55 -0700109 usage="acloud {" + usage + "} ...")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700110 subparsers = parser.add_subparsers()
111 subparser_list = []
112
113 # Command "create"
114 create_parser = subparsers.add_parser(CMD_CREATE)
115 create_parser.required = False
116 create_parser.set_defaults(which=CMD_CREATE)
117 create_parser.add_argument(
118 "--build_target",
119 type=str,
120 dest="build_target",
Kevin Chengb5963882018-05-09 00:06:27 -0700121 help="Android build target, e.g. aosp_cf_x86_phone-userdebug, "
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700122 "or short names: phone, tablet, or tablet_mobile.")
123 create_parser.add_argument(
124 "--branch",
125 type=str,
126 dest="branch",
127 help="Android branch, e.g. mnc-dev or git_mnc-dev")
Kevin Chengb5963882018-05-09 00:06:27 -0700128 # TODO: Support HEAD (the latest build)
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700129 create_parser.add_argument(
130 "--build_id",
131 type=str,
132 dest="build_id",
133 help="Android build id, e.g. 2145099, P2804227")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700134 create_parser.add_argument(
135 "--spec",
136 type=str,
137 dest="spec",
138 required=False,
139 help="The name of a pre-configured device spec that we are "
140 "going to use. Choose from: %s" % ", ".join(constants.SPEC_NAMES))
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700141 create_parser.add_argument(
142 "--num",
143 type=int,
144 dest="num",
145 required=False,
146 default=1,
147 help="Number of instances to create.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700148 create_parser.add_argument(
149 "--gce_image",
150 type=str,
151 dest="gce_image",
152 required=False,
153 help="Name of an existing compute engine image to reuse.")
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700154 create_parser.add_argument(
155 "--local_disk_image",
156 type=str,
157 dest="local_disk_image",
158 required=False,
159 help="Path to a local disk image to use, "
160 "e.g /tmp/avd-system.tar.gz")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700161 create_parser.add_argument(
162 "--no_cleanup",
163 dest="no_cleanup",
164 default=False,
165 action="store_true",
166 help="Do not clean up temporary disk image and compute engine image. "
167 "For debugging purposes.")
168 create_parser.add_argument(
169 "--serial_log_file",
170 type=str,
171 dest="serial_log_file",
172 required=False,
173 help="Path to a *tar.gz file where serial logs will be saved "
174 "when a device fails on boot.")
175 create_parser.add_argument(
176 "--logcat_file",
177 type=str,
178 dest="logcat_file",
179 required=False,
180 help="Path to a *tar.gz file where logcat logs will be saved "
181 "when a device fails on boot.")
Kevin Chengb5963882018-05-09 00:06:27 -0700182 create_parser.add_argument(
183 "--autoconnect",
184 action="store_true",
185 dest="autoconnect",
186 required=False,
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700187 help=
188 "For each instance created, we will automatically creates both 2 ssh"
Kevin Chengb5963882018-05-09 00:06:27 -0700189 " tunnels forwarding both adb & vnc. Then add the device to adb.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700190
191 subparser_list.append(create_parser)
192
Kevin Chengb5963882018-05-09 00:06:27 -0700193 # Command "create_cf", create cuttlefish instances
194 create_cf_parser = subparsers.add_parser(CMD_CREATE_CUTTLEFISH)
195 create_cf_parser.required = False
196 create_cf_parser.set_defaults(which=CMD_CREATE_CUTTLEFISH)
197 create_cf_parser.add_argument(
198 "--build_target",
199 type=str,
200 dest="build_target",
201 help="Android build target, should be a cuttlefish target name.")
202 create_cf_parser.add_argument(
203 "--branch",
204 type=str,
205 dest="branch",
206 help="Android branch, e.g. git_master")
207 create_cf_parser.add_argument(
208 "--build_id",
209 type=str,
210 dest="build_id",
211 help="Android build id, e.g. 2145099, P2804227")
212 create_cf_parser.add_argument(
213 "--kernel_build_id",
214 type=str,
215 dest="kernel_build_id",
216 required=False,
217 help="Android kernel build id, e.g. 4586590. This is to test a new"
218 " kernel build with a particular Android build (--build_id). If not"
219 " specified, the kernel that's bundled with the Android build would"
220 " be used.")
221 create_cf_parser.add_argument(
222 "--num",
223 type=int,
224 dest="num",
225 required=False,
226 default=1,
227 help="Number of instances to create.")
228 create_cf_parser.add_argument(
229 "--serial_log_file",
230 type=str,
231 dest="serial_log_file",
232 required=False,
233 help="Path to a *tar.gz file where serial logs will be saved "
234 "when a device fails on boot.")
235 create_cf_parser.add_argument(
236 "--logcat_file",
237 type=str,
238 dest="logcat_file",
239 required=False,
240 help="Path to a *tar.gz file where logcat logs will be saved "
241 "when a device fails on boot.")
242 create_cf_parser.add_argument(
243 "--autoconnect",
244 action="store_true",
245 dest="autoconnect",
246 required=False,
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700247 help=
248 "For each instance created, we will automatically creates both 2 ssh"
Kevin Chengb5963882018-05-09 00:06:27 -0700249 " tunnels forwarding both adb & vnc. Then add the device to adb.")
250
251 subparser_list.append(create_cf_parser)
252
253 # Command "create_gf", create goldfish instances
254 # In order to create a goldfish device we need the following parameters:
255 # 1. The emulator build we wish to use, this is the binary that emulates
256 # an android device. See go/emu-dev for more
257 # 2. A system-image. This is the android release we wish to run on the
258 # emulated hardware.
259 create_gf_parser = subparsers.add_parser(CMD_CREATE_GOLDFISH)
260 create_gf_parser.required = False
261 create_gf_parser.set_defaults(which=CMD_CREATE_GOLDFISH)
262 create_gf_parser.add_argument(
263 "--build_target",
264 type=str,
265 dest="build_target",
266 help="Android build target, should be a goldfish target name.")
267 create_gf_parser.add_argument(
268 "--branch",
269 type=str,
270 dest="branch",
271 help="Android branch, e.g. git_master")
272 create_gf_parser.add_argument(
273 "--build_id",
274 type=str,
275 dest="build_id",
276 help="Android build id, e.g. 4669424, P2804227")
277 create_gf_parser.add_argument(
278 "--emulator_build_id",
279 type=str,
280 dest="emulator_build_id",
281 required=False,
282 help="Emulator build used to run the images. e.g. 4669466.")
283 create_gf_parser.add_argument(
284 "--gpu",
285 type=str,
286 dest="gpu",
287 required=False,
288 default=None,
289 help="GPU accelerator to use if any."
290 " e.g. nvidia-tesla-k80, omit to use swiftshader")
291 create_gf_parser.add_argument(
292 "--num",
293 type=int,
294 dest="num",
295 required=False,
296 default=1,
297 help="Number of instances to create.")
298 create_gf_parser.add_argument(
299 "--serial_log_file",
300 type=str,
301 dest="serial_log_file",
302 required=False,
303 help="Path to a *tar.gz file where serial logs will be saved "
304 "when a device fails on boot.")
305 create_gf_parser.add_argument(
306 "--logcat_file",
307 type=str,
308 dest="logcat_file",
309 required=False,
310 help="Path to a *tar.gz file where logcat logs will be saved "
311 "when a device fails on boot.")
312 create_gf_parser.add_argument(
313 "--autoconnect",
314 action="store_true",
315 dest="autoconnect",
316 required=False,
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700317 help=
318 "For each instance created, we will automatically creates both 2 ssh"
Kevin Chengb5963882018-05-09 00:06:27 -0700319 " tunnels forwarding both adb & vnc. Then add the device to adb.")
Kevin Chengbced4af2018-06-26 10:35:01 -0700320 create_gf_parser.add_argument(
321 "--base_image",
322 type=str,
323 dest="base_image",
324 required=False,
325 help="Name of the goldfish base image to be used to create the instance. "
326 "This will override stable_goldfish_host_image_name from config. "
327 "e.g. emu-dev-cts-061118")
Kevin Chengb5963882018-05-09 00:06:27 -0700328
329 subparser_list.append(create_gf_parser)
330
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700331 # Command "Delete"
332 delete_parser = subparsers.add_parser(CMD_DELETE)
333 delete_parser.required = False
334 delete_parser.set_defaults(which=CMD_DELETE)
335 delete_parser.add_argument(
336 "--instance_names",
337 dest="instance_names",
338 nargs="+",
339 required=True,
340 help="The names of the instances that need to delete, "
341 "separated by spaces, e.g. --instance_names instance-1 instance-2")
342 subparser_list.append(delete_parser)
343
344 # Command "cleanup"
345 cleanup_parser = subparsers.add_parser(CMD_CLEANUP)
346 cleanup_parser.required = False
347 cleanup_parser.set_defaults(which=CMD_CLEANUP)
348 cleanup_parser.add_argument(
349 "--expiration_mins",
350 type=int,
351 dest="expiration_mins",
352 required=True,
353 help="Garbage collect all gce instances, gce images, cached disk "
354 "images that are older than |expiration_mins|.")
355 subparser_list.append(cleanup_parser)
356
Fang Deng69498c32017-03-02 14:29:30 -0800357 # Command "project_sshkey"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700358 sshkey_parser = subparsers.add_parser(CMD_SSHKEY)
359 sshkey_parser.required = False
360 sshkey_parser.set_defaults(which=CMD_SSHKEY)
361 sshkey_parser.add_argument(
362 "--user",
363 type=str,
364 dest="user",
365 default=getpass.getuser(),
366 help="The user name which the sshkey belongs to, default to: %s." %
367 getpass.getuser())
368 sshkey_parser.add_argument(
369 "--ssh_rsa_path",
370 type=str,
371 dest="ssh_rsa_path",
372 required=True,
Fang Deng69498c32017-03-02 14:29:30 -0800373 help="Absolute path to the file that contains the public rsa key "
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700374 "that will be added as project-wide ssh key.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700375 subparser_list.append(sshkey_parser)
376
377 # Add common arguments.
Kevin Chengb21d7712018-05-24 14:54:55 -0700378 for subparser in subparser_list:
379 acloud_common.AddCommonArguments(subparser)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700380
381 return parser.parse_args(args)
382
383
384def _TranslateAlias(parsed_args):
385 """Translate alias to Launch Control compatible values.
386
387 This method translates alias to Launch Control compatible values.
388 - branch: "git_" prefix will be added if branch name doesn't have it.
389 - build_target: For example, "phone" will be translated to full target
390 name "git_x86_phone-userdebug",
391
392 Args:
393 parsed_args: Parsed args.
394
395 Returns:
396 Parsed args with its values being translated.
397 """
398 if parsed_args.which == CMD_CREATE:
399 if (parsed_args.branch and
400 not parsed_args.branch.startswith(constants.BRANCH_PREFIX)):
401 parsed_args.branch = constants.BRANCH_PREFIX + parsed_args.branch
402 parsed_args.build_target = constants.BUILD_TARGET_MAPPING.get(
403 parsed_args.build_target, parsed_args.build_target)
404 return parsed_args
405
406
407def _VerifyArgs(parsed_args):
408 """Verify args.
409
410 Args:
411 parsed_args: Parsed args.
412
413 Raises:
414 errors.CommandArgError: If args are invalid.
415 """
416 if parsed_args.which == CMD_CREATE:
417 if (parsed_args.spec and parsed_args.spec not in constants.SPEC_NAMES):
418 raise errors.CommandArgError(
419 "%s is not valid. Choose from: %s" %
420 (parsed_args.spec, ", ".join(constants.SPEC_NAMES)))
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700421 if not ((parsed_args.build_id and parsed_args.build_target)
422 or parsed_args.gce_image or parsed_args.local_disk_image):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700423 raise errors.CommandArgError(
424 "At least one of the following should be specified: "
425 "--build_id and --build_target, or --gce_image, or "
426 "--local_disk_image.")
427 if bool(parsed_args.build_id) != bool(parsed_args.build_target):
428 raise errors.CommandArgError(
429 "Must specify --build_id and --build_target at the same time.")
Kevin Chengb5963882018-05-09 00:06:27 -0700430
431 if parsed_args.which in [CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH]:
432 if not parsed_args.build_id or not parsed_args.build_target:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700433 raise errors.CommandArgError(
434 "Must specify --build_id and --build_target")
Kevin Chengb5963882018-05-09 00:06:27 -0700435
436 if parsed_args.which == CMD_CREATE_GOLDFISH:
437 if not parsed_args.emulator_build_id:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700438 raise errors.CommandArgError("Must specify --emulator_build_id")
Kevin Chengb5963882018-05-09 00:06:27 -0700439
440 if parsed_args.which in [
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700441 CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH
Kevin Chengb5963882018-05-09 00:06:27 -0700442 ]:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700443 if (parsed_args.serial_log_file
444 and not parsed_args.serial_log_file.endswith(".tar.gz")):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700445 raise errors.CommandArgError(
446 "--serial_log_file must ends with .tar.gz")
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700447 if (parsed_args.logcat_file
448 and not parsed_args.logcat_file.endswith(".tar.gz")):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700449 raise errors.CommandArgError(
450 "--logcat_file must ends with .tar.gz")
451
452
453def _SetupLogging(log_file, verbose, very_verbose):
454 """Setup logging.
455
456 Args:
457 log_file: path to log file.
458 verbose: If True, log at DEBUG level, otherwise log at INFO level.
459 very_verbose: If True, log at DEBUG level and turn on logging on
460 all libraries. Take take precedence over |verbose|.
461 """
462 if very_verbose:
463 logger = logging.getLogger()
464 else:
465 logger = logging.getLogger(LOGGER_NAME)
466
467 logging_level = logging.DEBUG if verbose or very_verbose else logging.INFO
468 logger.setLevel(logging_level)
469
470 if not log_file:
471 handler = logging.StreamHandler()
472 else:
473 handler = logging.FileHandler(filename=log_file)
474 log_formatter = logging.Formatter(LOGGING_FMT)
475 handler.setFormatter(log_formatter)
476 logger.addHandler(handler)
477
478
479def main(argv):
480 """Main entry.
481
482 Args:
483 argv: A list of system arguments.
484
485 Returns:
486 0 if success. None-zero if fails.
487 """
488 args = _ParseArgs(argv)
489 _SetupLogging(args.log_file, args.verbose, args.very_verbose)
490 args = _TranslateAlias(args)
491 _VerifyArgs(args)
492
493 config_mgr = config.AcloudConfigManager(args.config_file)
494 cfg = config_mgr.Load()
495 cfg.OverrideWithArgs(args)
496
Fang Dengcef4b112017-03-02 11:20:17 -0800497 # Check access.
498 device_driver.CheckAccess(cfg)
499
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700500 if args.which == CMD_CREATE:
501 report = device_driver.CreateAndroidVirtualDevices(
502 cfg,
503 args.build_target,
504 args.build_id,
505 args.num,
506 args.gce_image,
507 args.local_disk_image,
508 cleanup=not args.no_cleanup,
509 serial_log_file=args.serial_log_file,
Kevin Chengb5963882018-05-09 00:06:27 -0700510 logcat_file=args.logcat_file,
511 autoconnect=args.autoconnect)
512 elif args.which == CMD_CREATE_CUTTLEFISH:
513 report = create_cuttlefish_action.CreateDevices(
514 cfg=cfg,
515 build_target=args.build_target,
516 build_id=args.build_id,
517 kernel_build_id=args.kernel_build_id,
518 num=args.num,
519 serial_log_file=args.serial_log_file,
520 logcat_file=args.logcat_file,
521 autoconnect=args.autoconnect)
522 elif args.which == CMD_CREATE_GOLDFISH:
523 report = create_goldfish_action.CreateDevices(
524 cfg=cfg,
525 build_target=args.build_target,
526 build_id=args.build_id,
527 emulator_build_id=args.emulator_build_id,
528 gpu=args.gpu,
529 num=args.num,
530 serial_log_file=args.serial_log_file,
531 logcat_file=args.logcat_file,
532 autoconnect=args.autoconnect)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700533 elif args.which == CMD_DELETE:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700534 report = device_driver.DeleteAndroidVirtualDevices(
535 cfg, args.instance_names)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700536 elif args.which == CMD_CLEANUP:
537 report = device_driver.Cleanup(cfg, args.expiration_mins)
538 elif args.which == CMD_SSHKEY:
539 report = device_driver.AddSshRsa(cfg, args.user, args.ssh_rsa_path)
540 else:
541 sys.stderr.write("Invalid command %s" % args.which)
542 return 2
543
544 report.Dump(args.report_file)
545 if report.errors:
546 msg = "\n".join(report.errors)
547 sys.stderr.write("Encountered the following errors:\n%s\n" % msg)
548 return 1
549 return 0
Tri Vo8e292532016-10-01 16:55:51 -0700550
551
552if __name__ == "__main__":
553 main(sys.argv[1:])