blob: 823bafbd688a1bbc912b83d66532964a06e21253 [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 Chengb5963882018-05-09 00:06:27 -070069# TODO: Find a better way to handling this import logic.
70
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070071from acloud.internal import constants
72from acloud.public import acloud_common
73from acloud.public import config
74from acloud.public import device_driver
75from acloud.public import errors
Kevin Chengb5963882018-05-09 00:06:27 -070076from acloud.public.actions import create_cuttlefish_action
77from acloud.public.actions import create_goldfish_action
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070078
79LOGGING_FMT = "%(asctime)s |%(levelname)s| %(module)s:%(lineno)s| %(message)s"
Tri Vo8e292532016-10-01 16:55:51 -070080LOGGER_NAME = "acloud_main"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070081
82# Commands
83CMD_CREATE = "create"
Kevin Chengb5963882018-05-09 00:06:27 -070084CMD_CREATE_CUTTLEFISH = "create_cf"
85CMD_CREATE_GOLDFISH = "create_gf"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070086CMD_DELETE = "delete"
87CMD_CLEANUP = "cleanup"
Fang Deng69498c32017-03-02 14:29:30 -080088CMD_SSHKEY = "project_sshkey"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070089
90
Kevin Cheng3031f8a2018-05-16 13:21:51 -070091# pylint: disable=too-many-statements
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070092def _ParseArgs(args):
93 """Parse args.
94
95 Args:
96 args: Argument list passed from main.
97
98 Returns:
99 Parsed args.
100 """
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700101 usage = ",".join([
102 CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_DELETE, CMD_CLEANUP, CMD_SSHKEY
103 ])
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700104 parser = argparse.ArgumentParser(
105 description=__doc__,
106 formatter_class=argparse.RawDescriptionHelpFormatter,
107 usage="%(prog)s {" + usage + "} ...")
108 subparsers = parser.add_subparsers()
109 subparser_list = []
110
111 # Command "create"
112 create_parser = subparsers.add_parser(CMD_CREATE)
113 create_parser.required = False
114 create_parser.set_defaults(which=CMD_CREATE)
115 create_parser.add_argument(
116 "--build_target",
117 type=str,
118 dest="build_target",
Kevin Chengb5963882018-05-09 00:06:27 -0700119 help="Android build target, e.g. aosp_cf_x86_phone-userdebug, "
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700120 "or short names: phone, tablet, or tablet_mobile.")
121 create_parser.add_argument(
122 "--branch",
123 type=str,
124 dest="branch",
125 help="Android branch, e.g. mnc-dev or git_mnc-dev")
Kevin Chengb5963882018-05-09 00:06:27 -0700126 # TODO: Support HEAD (the latest build)
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700127 create_parser.add_argument(
128 "--build_id",
129 type=str,
130 dest="build_id",
131 help="Android build id, e.g. 2145099, P2804227")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700132 create_parser.add_argument(
133 "--spec",
134 type=str,
135 dest="spec",
136 required=False,
137 help="The name of a pre-configured device spec that we are "
138 "going to use. Choose from: %s" % ", ".join(constants.SPEC_NAMES))
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700139 create_parser.add_argument(
140 "--num",
141 type=int,
142 dest="num",
143 required=False,
144 default=1,
145 help="Number of instances to create.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700146 create_parser.add_argument(
147 "--gce_image",
148 type=str,
149 dest="gce_image",
150 required=False,
151 help="Name of an existing compute engine image to reuse.")
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700152 create_parser.add_argument(
153 "--local_disk_image",
154 type=str,
155 dest="local_disk_image",
156 required=False,
157 help="Path to a local disk image to use, "
158 "e.g /tmp/avd-system.tar.gz")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700159 create_parser.add_argument(
160 "--no_cleanup",
161 dest="no_cleanup",
162 default=False,
163 action="store_true",
164 help="Do not clean up temporary disk image and compute engine image. "
165 "For debugging purposes.")
166 create_parser.add_argument(
167 "--serial_log_file",
168 type=str,
169 dest="serial_log_file",
170 required=False,
171 help="Path to a *tar.gz file where serial logs will be saved "
172 "when a device fails on boot.")
173 create_parser.add_argument(
174 "--logcat_file",
175 type=str,
176 dest="logcat_file",
177 required=False,
178 help="Path to a *tar.gz file where logcat logs will be saved "
179 "when a device fails on boot.")
Kevin Chengb5963882018-05-09 00:06:27 -0700180 create_parser.add_argument(
181 "--autoconnect",
182 action="store_true",
183 dest="autoconnect",
184 required=False,
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700185 help=
186 "For each instance created, we will automatically creates both 2 ssh"
Kevin Chengb5963882018-05-09 00:06:27 -0700187 " tunnels forwarding both adb & vnc. Then add the device to adb.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700188
189 subparser_list.append(create_parser)
190
Kevin Chengb5963882018-05-09 00:06:27 -0700191 # Command "create_cf", create cuttlefish instances
192 create_cf_parser = subparsers.add_parser(CMD_CREATE_CUTTLEFISH)
193 create_cf_parser.required = False
194 create_cf_parser.set_defaults(which=CMD_CREATE_CUTTLEFISH)
195 create_cf_parser.add_argument(
196 "--build_target",
197 type=str,
198 dest="build_target",
199 help="Android build target, should be a cuttlefish target name.")
200 create_cf_parser.add_argument(
201 "--branch",
202 type=str,
203 dest="branch",
204 help="Android branch, e.g. git_master")
205 create_cf_parser.add_argument(
206 "--build_id",
207 type=str,
208 dest="build_id",
209 help="Android build id, e.g. 2145099, P2804227")
210 create_cf_parser.add_argument(
211 "--kernel_build_id",
212 type=str,
213 dest="kernel_build_id",
214 required=False,
215 help="Android kernel build id, e.g. 4586590. This is to test a new"
216 " kernel build with a particular Android build (--build_id). If not"
217 " specified, the kernel that's bundled with the Android build would"
218 " be used.")
219 create_cf_parser.add_argument(
220 "--num",
221 type=int,
222 dest="num",
223 required=False,
224 default=1,
225 help="Number of instances to create.")
226 create_cf_parser.add_argument(
227 "--serial_log_file",
228 type=str,
229 dest="serial_log_file",
230 required=False,
231 help="Path to a *tar.gz file where serial logs will be saved "
232 "when a device fails on boot.")
233 create_cf_parser.add_argument(
234 "--logcat_file",
235 type=str,
236 dest="logcat_file",
237 required=False,
238 help="Path to a *tar.gz file where logcat logs will be saved "
239 "when a device fails on boot.")
240 create_cf_parser.add_argument(
241 "--autoconnect",
242 action="store_true",
243 dest="autoconnect",
244 required=False,
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700245 help=
246 "For each instance created, we will automatically creates both 2 ssh"
Kevin Chengb5963882018-05-09 00:06:27 -0700247 " tunnels forwarding both adb & vnc. Then add the device to adb.")
248
249 subparser_list.append(create_cf_parser)
250
251 # Command "create_gf", create goldfish instances
252 # In order to create a goldfish device we need the following parameters:
253 # 1. The emulator build we wish to use, this is the binary that emulates
254 # an android device. See go/emu-dev for more
255 # 2. A system-image. This is the android release we wish to run on the
256 # emulated hardware.
257 create_gf_parser = subparsers.add_parser(CMD_CREATE_GOLDFISH)
258 create_gf_parser.required = False
259 create_gf_parser.set_defaults(which=CMD_CREATE_GOLDFISH)
260 create_gf_parser.add_argument(
261 "--build_target",
262 type=str,
263 dest="build_target",
264 help="Android build target, should be a goldfish target name.")
265 create_gf_parser.add_argument(
266 "--branch",
267 type=str,
268 dest="branch",
269 help="Android branch, e.g. git_master")
270 create_gf_parser.add_argument(
271 "--build_id",
272 type=str,
273 dest="build_id",
274 help="Android build id, e.g. 4669424, P2804227")
275 create_gf_parser.add_argument(
276 "--emulator_build_id",
277 type=str,
278 dest="emulator_build_id",
279 required=False,
280 help="Emulator build used to run the images. e.g. 4669466.")
281 create_gf_parser.add_argument(
282 "--gpu",
283 type=str,
284 dest="gpu",
285 required=False,
286 default=None,
287 help="GPU accelerator to use if any."
288 " e.g. nvidia-tesla-k80, omit to use swiftshader")
289 create_gf_parser.add_argument(
290 "--num",
291 type=int,
292 dest="num",
293 required=False,
294 default=1,
295 help="Number of instances to create.")
296 create_gf_parser.add_argument(
297 "--serial_log_file",
298 type=str,
299 dest="serial_log_file",
300 required=False,
301 help="Path to a *tar.gz file where serial logs will be saved "
302 "when a device fails on boot.")
303 create_gf_parser.add_argument(
304 "--logcat_file",
305 type=str,
306 dest="logcat_file",
307 required=False,
308 help="Path to a *tar.gz file where logcat logs will be saved "
309 "when a device fails on boot.")
310 create_gf_parser.add_argument(
311 "--autoconnect",
312 action="store_true",
313 dest="autoconnect",
314 required=False,
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700315 help=
316 "For each instance created, we will automatically creates both 2 ssh"
Kevin Chengb5963882018-05-09 00:06:27 -0700317 " tunnels forwarding both adb & vnc. Then add the device to adb.")
318
319 subparser_list.append(create_gf_parser)
320
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700321 # Command "Delete"
322 delete_parser = subparsers.add_parser(CMD_DELETE)
323 delete_parser.required = False
324 delete_parser.set_defaults(which=CMD_DELETE)
325 delete_parser.add_argument(
326 "--instance_names",
327 dest="instance_names",
328 nargs="+",
329 required=True,
330 help="The names of the instances that need to delete, "
331 "separated by spaces, e.g. --instance_names instance-1 instance-2")
332 subparser_list.append(delete_parser)
333
334 # Command "cleanup"
335 cleanup_parser = subparsers.add_parser(CMD_CLEANUP)
336 cleanup_parser.required = False
337 cleanup_parser.set_defaults(which=CMD_CLEANUP)
338 cleanup_parser.add_argument(
339 "--expiration_mins",
340 type=int,
341 dest="expiration_mins",
342 required=True,
343 help="Garbage collect all gce instances, gce images, cached disk "
344 "images that are older than |expiration_mins|.")
345 subparser_list.append(cleanup_parser)
346
Fang Deng69498c32017-03-02 14:29:30 -0800347 # Command "project_sshkey"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700348 sshkey_parser = subparsers.add_parser(CMD_SSHKEY)
349 sshkey_parser.required = False
350 sshkey_parser.set_defaults(which=CMD_SSHKEY)
351 sshkey_parser.add_argument(
352 "--user",
353 type=str,
354 dest="user",
355 default=getpass.getuser(),
356 help="The user name which the sshkey belongs to, default to: %s." %
357 getpass.getuser())
358 sshkey_parser.add_argument(
359 "--ssh_rsa_path",
360 type=str,
361 dest="ssh_rsa_path",
362 required=True,
Fang Deng69498c32017-03-02 14:29:30 -0800363 help="Absolute path to the file that contains the public rsa key "
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700364 "that will be added as project-wide ssh key.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700365 subparser_list.append(sshkey_parser)
366
367 # Add common arguments.
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700368 for parser in subparser_list:
369 acloud_common.AddCommonArguments(parser)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700370
371 return parser.parse_args(args)
372
373
374def _TranslateAlias(parsed_args):
375 """Translate alias to Launch Control compatible values.
376
377 This method translates alias to Launch Control compatible values.
378 - branch: "git_" prefix will be added if branch name doesn't have it.
379 - build_target: For example, "phone" will be translated to full target
380 name "git_x86_phone-userdebug",
381
382 Args:
383 parsed_args: Parsed args.
384
385 Returns:
386 Parsed args with its values being translated.
387 """
388 if parsed_args.which == CMD_CREATE:
389 if (parsed_args.branch and
390 not parsed_args.branch.startswith(constants.BRANCH_PREFIX)):
391 parsed_args.branch = constants.BRANCH_PREFIX + parsed_args.branch
392 parsed_args.build_target = constants.BUILD_TARGET_MAPPING.get(
393 parsed_args.build_target, parsed_args.build_target)
394 return parsed_args
395
396
397def _VerifyArgs(parsed_args):
398 """Verify args.
399
400 Args:
401 parsed_args: Parsed args.
402
403 Raises:
404 errors.CommandArgError: If args are invalid.
405 """
406 if parsed_args.which == CMD_CREATE:
407 if (parsed_args.spec and parsed_args.spec not in constants.SPEC_NAMES):
408 raise errors.CommandArgError(
409 "%s is not valid. Choose from: %s" %
410 (parsed_args.spec, ", ".join(constants.SPEC_NAMES)))
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700411 if not ((parsed_args.build_id and parsed_args.build_target)
412 or parsed_args.gce_image or parsed_args.local_disk_image):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700413 raise errors.CommandArgError(
414 "At least one of the following should be specified: "
415 "--build_id and --build_target, or --gce_image, or "
416 "--local_disk_image.")
417 if bool(parsed_args.build_id) != bool(parsed_args.build_target):
418 raise errors.CommandArgError(
419 "Must specify --build_id and --build_target at the same time.")
Kevin Chengb5963882018-05-09 00:06:27 -0700420
421 if parsed_args.which in [CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH]:
422 if not parsed_args.build_id or not parsed_args.build_target:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700423 raise errors.CommandArgError(
424 "Must specify --build_id and --build_target")
Kevin Chengb5963882018-05-09 00:06:27 -0700425
426 if parsed_args.which == CMD_CREATE_GOLDFISH:
427 if not parsed_args.emulator_build_id:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700428 raise errors.CommandArgError("Must specify --emulator_build_id")
Kevin Chengb5963882018-05-09 00:06:27 -0700429
430 if parsed_args.which in [
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700431 CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH
Kevin Chengb5963882018-05-09 00:06:27 -0700432 ]:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700433 if (parsed_args.serial_log_file
434 and not parsed_args.serial_log_file.endswith(".tar.gz")):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700435 raise errors.CommandArgError(
436 "--serial_log_file must ends with .tar.gz")
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700437 if (parsed_args.logcat_file
438 and not parsed_args.logcat_file.endswith(".tar.gz")):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700439 raise errors.CommandArgError(
440 "--logcat_file must ends with .tar.gz")
441
442
443def _SetupLogging(log_file, verbose, very_verbose):
444 """Setup logging.
445
446 Args:
447 log_file: path to log file.
448 verbose: If True, log at DEBUG level, otherwise log at INFO level.
449 very_verbose: If True, log at DEBUG level and turn on logging on
450 all libraries. Take take precedence over |verbose|.
451 """
452 if very_verbose:
453 logger = logging.getLogger()
454 else:
455 logger = logging.getLogger(LOGGER_NAME)
456
457 logging_level = logging.DEBUG if verbose or very_verbose else logging.INFO
458 logger.setLevel(logging_level)
459
460 if not log_file:
461 handler = logging.StreamHandler()
462 else:
463 handler = logging.FileHandler(filename=log_file)
464 log_formatter = logging.Formatter(LOGGING_FMT)
465 handler.setFormatter(log_formatter)
466 logger.addHandler(handler)
467
468
469def main(argv):
470 """Main entry.
471
472 Args:
473 argv: A list of system arguments.
474
475 Returns:
476 0 if success. None-zero if fails.
477 """
478 args = _ParseArgs(argv)
479 _SetupLogging(args.log_file, args.verbose, args.very_verbose)
480 args = _TranslateAlias(args)
481 _VerifyArgs(args)
482
483 config_mgr = config.AcloudConfigManager(args.config_file)
484 cfg = config_mgr.Load()
485 cfg.OverrideWithArgs(args)
486
Fang Dengcef4b112017-03-02 11:20:17 -0800487 # Check access.
488 device_driver.CheckAccess(cfg)
489
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700490 if args.which == CMD_CREATE:
491 report = device_driver.CreateAndroidVirtualDevices(
492 cfg,
493 args.build_target,
494 args.build_id,
495 args.num,
496 args.gce_image,
497 args.local_disk_image,
498 cleanup=not args.no_cleanup,
499 serial_log_file=args.serial_log_file,
Kevin Chengb5963882018-05-09 00:06:27 -0700500 logcat_file=args.logcat_file,
501 autoconnect=args.autoconnect)
502 elif args.which == CMD_CREATE_CUTTLEFISH:
503 report = create_cuttlefish_action.CreateDevices(
504 cfg=cfg,
505 build_target=args.build_target,
506 build_id=args.build_id,
507 kernel_build_id=args.kernel_build_id,
508 num=args.num,
509 serial_log_file=args.serial_log_file,
510 logcat_file=args.logcat_file,
511 autoconnect=args.autoconnect)
512 elif args.which == CMD_CREATE_GOLDFISH:
513 report = create_goldfish_action.CreateDevices(
514 cfg=cfg,
515 build_target=args.build_target,
516 build_id=args.build_id,
517 emulator_build_id=args.emulator_build_id,
518 gpu=args.gpu,
519 num=args.num,
520 serial_log_file=args.serial_log_file,
521 logcat_file=args.logcat_file,
522 autoconnect=args.autoconnect)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700523 elif args.which == CMD_DELETE:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700524 report = device_driver.DeleteAndroidVirtualDevices(
525 cfg, args.instance_names)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700526 elif args.which == CMD_CLEANUP:
527 report = device_driver.Cleanup(cfg, args.expiration_mins)
528 elif args.which == CMD_SSHKEY:
529 report = device_driver.AddSshRsa(cfg, args.user, args.ssh_rsa_path)
530 else:
531 sys.stderr.write("Invalid command %s" % args.which)
532 return 2
533
534 report.Dump(args.report_file)
535 if report.errors:
536 msg = "\n".join(report.errors)
537 sys.stderr.write("Encountered the following errors:\n%s\n" % msg)
538 return 1
539 return 0
Tri Vo8e292532016-10-01 16:55:51 -0700540
541
542if __name__ == "__main__":
543 main(sys.argv[1:])