blob: 403a0afdc9ced1bd79da15d07c7c3f4af5bbbfbf [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.")
320
321 subparser_list.append(create_gf_parser)
322
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700323 # Command "Delete"
324 delete_parser = subparsers.add_parser(CMD_DELETE)
325 delete_parser.required = False
326 delete_parser.set_defaults(which=CMD_DELETE)
327 delete_parser.add_argument(
328 "--instance_names",
329 dest="instance_names",
330 nargs="+",
331 required=True,
332 help="The names of the instances that need to delete, "
333 "separated by spaces, e.g. --instance_names instance-1 instance-2")
334 subparser_list.append(delete_parser)
335
336 # Command "cleanup"
337 cleanup_parser = subparsers.add_parser(CMD_CLEANUP)
338 cleanup_parser.required = False
339 cleanup_parser.set_defaults(which=CMD_CLEANUP)
340 cleanup_parser.add_argument(
341 "--expiration_mins",
342 type=int,
343 dest="expiration_mins",
344 required=True,
345 help="Garbage collect all gce instances, gce images, cached disk "
346 "images that are older than |expiration_mins|.")
347 subparser_list.append(cleanup_parser)
348
Fang Deng69498c32017-03-02 14:29:30 -0800349 # Command "project_sshkey"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700350 sshkey_parser = subparsers.add_parser(CMD_SSHKEY)
351 sshkey_parser.required = False
352 sshkey_parser.set_defaults(which=CMD_SSHKEY)
353 sshkey_parser.add_argument(
354 "--user",
355 type=str,
356 dest="user",
357 default=getpass.getuser(),
358 help="The user name which the sshkey belongs to, default to: %s." %
359 getpass.getuser())
360 sshkey_parser.add_argument(
361 "--ssh_rsa_path",
362 type=str,
363 dest="ssh_rsa_path",
364 required=True,
Fang Deng69498c32017-03-02 14:29:30 -0800365 help="Absolute path to the file that contains the public rsa key "
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700366 "that will be added as project-wide ssh key.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700367 subparser_list.append(sshkey_parser)
368
369 # Add common arguments.
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700370 for parser in subparser_list:
371 acloud_common.AddCommonArguments(parser)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700372
373 return parser.parse_args(args)
374
375
376def _TranslateAlias(parsed_args):
377 """Translate alias to Launch Control compatible values.
378
379 This method translates alias to Launch Control compatible values.
380 - branch: "git_" prefix will be added if branch name doesn't have it.
381 - build_target: For example, "phone" will be translated to full target
382 name "git_x86_phone-userdebug",
383
384 Args:
385 parsed_args: Parsed args.
386
387 Returns:
388 Parsed args with its values being translated.
389 """
390 if parsed_args.which == CMD_CREATE:
391 if (parsed_args.branch and
392 not parsed_args.branch.startswith(constants.BRANCH_PREFIX)):
393 parsed_args.branch = constants.BRANCH_PREFIX + parsed_args.branch
394 parsed_args.build_target = constants.BUILD_TARGET_MAPPING.get(
395 parsed_args.build_target, parsed_args.build_target)
396 return parsed_args
397
398
399def _VerifyArgs(parsed_args):
400 """Verify args.
401
402 Args:
403 parsed_args: Parsed args.
404
405 Raises:
406 errors.CommandArgError: If args are invalid.
407 """
408 if parsed_args.which == CMD_CREATE:
409 if (parsed_args.spec and parsed_args.spec not in constants.SPEC_NAMES):
410 raise errors.CommandArgError(
411 "%s is not valid. Choose from: %s" %
412 (parsed_args.spec, ", ".join(constants.SPEC_NAMES)))
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700413 if not ((parsed_args.build_id and parsed_args.build_target)
414 or parsed_args.gce_image or parsed_args.local_disk_image):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700415 raise errors.CommandArgError(
416 "At least one of the following should be specified: "
417 "--build_id and --build_target, or --gce_image, or "
418 "--local_disk_image.")
419 if bool(parsed_args.build_id) != bool(parsed_args.build_target):
420 raise errors.CommandArgError(
421 "Must specify --build_id and --build_target at the same time.")
Kevin Chengb5963882018-05-09 00:06:27 -0700422
423 if parsed_args.which in [CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH]:
424 if not parsed_args.build_id or not parsed_args.build_target:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700425 raise errors.CommandArgError(
426 "Must specify --build_id and --build_target")
Kevin Chengb5963882018-05-09 00:06:27 -0700427
428 if parsed_args.which == CMD_CREATE_GOLDFISH:
429 if not parsed_args.emulator_build_id:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700430 raise errors.CommandArgError("Must specify --emulator_build_id")
Kevin Chengb5963882018-05-09 00:06:27 -0700431
432 if parsed_args.which in [
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700433 CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH
Kevin Chengb5963882018-05-09 00:06:27 -0700434 ]:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700435 if (parsed_args.serial_log_file
436 and not parsed_args.serial_log_file.endswith(".tar.gz")):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700437 raise errors.CommandArgError(
438 "--serial_log_file must ends with .tar.gz")
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700439 if (parsed_args.logcat_file
440 and not parsed_args.logcat_file.endswith(".tar.gz")):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700441 raise errors.CommandArgError(
442 "--logcat_file must ends with .tar.gz")
443
444
445def _SetupLogging(log_file, verbose, very_verbose):
446 """Setup logging.
447
448 Args:
449 log_file: path to log file.
450 verbose: If True, log at DEBUG level, otherwise log at INFO level.
451 very_verbose: If True, log at DEBUG level and turn on logging on
452 all libraries. Take take precedence over |verbose|.
453 """
454 if very_verbose:
455 logger = logging.getLogger()
456 else:
457 logger = logging.getLogger(LOGGER_NAME)
458
459 logging_level = logging.DEBUG if verbose or very_verbose else logging.INFO
460 logger.setLevel(logging_level)
461
462 if not log_file:
463 handler = logging.StreamHandler()
464 else:
465 handler = logging.FileHandler(filename=log_file)
466 log_formatter = logging.Formatter(LOGGING_FMT)
467 handler.setFormatter(log_formatter)
468 logger.addHandler(handler)
469
470
471def main(argv):
472 """Main entry.
473
474 Args:
475 argv: A list of system arguments.
476
477 Returns:
478 0 if success. None-zero if fails.
479 """
480 args = _ParseArgs(argv)
481 _SetupLogging(args.log_file, args.verbose, args.very_verbose)
482 args = _TranslateAlias(args)
483 _VerifyArgs(args)
484
485 config_mgr = config.AcloudConfigManager(args.config_file)
486 cfg = config_mgr.Load()
487 cfg.OverrideWithArgs(args)
488
Fang Dengcef4b112017-03-02 11:20:17 -0800489 # Check access.
490 device_driver.CheckAccess(cfg)
491
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700492 if args.which == CMD_CREATE:
493 report = device_driver.CreateAndroidVirtualDevices(
494 cfg,
495 args.build_target,
496 args.build_id,
497 args.num,
498 args.gce_image,
499 args.local_disk_image,
500 cleanup=not args.no_cleanup,
501 serial_log_file=args.serial_log_file,
Kevin Chengb5963882018-05-09 00:06:27 -0700502 logcat_file=args.logcat_file,
503 autoconnect=args.autoconnect)
504 elif args.which == CMD_CREATE_CUTTLEFISH:
505 report = create_cuttlefish_action.CreateDevices(
506 cfg=cfg,
507 build_target=args.build_target,
508 build_id=args.build_id,
509 kernel_build_id=args.kernel_build_id,
510 num=args.num,
511 serial_log_file=args.serial_log_file,
512 logcat_file=args.logcat_file,
513 autoconnect=args.autoconnect)
514 elif args.which == CMD_CREATE_GOLDFISH:
515 report = create_goldfish_action.CreateDevices(
516 cfg=cfg,
517 build_target=args.build_target,
518 build_id=args.build_id,
519 emulator_build_id=args.emulator_build_id,
520 gpu=args.gpu,
521 num=args.num,
522 serial_log_file=args.serial_log_file,
523 logcat_file=args.logcat_file,
524 autoconnect=args.autoconnect)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700525 elif args.which == CMD_DELETE:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700526 report = device_driver.DeleteAndroidVirtualDevices(
527 cfg, args.instance_names)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700528 elif args.which == CMD_CLEANUP:
529 report = device_driver.Cleanup(cfg, args.expiration_mins)
530 elif args.which == CMD_SSHKEY:
531 report = device_driver.AddSshRsa(cfg, args.user, args.ssh_rsa_path)
532 else:
533 sys.stderr.write("Invalid command %s" % args.which)
534 return 2
535
536 report.Dump(args.report_file)
537 if report.errors:
538 msg = "\n".join(report.errors)
539 sys.stderr.write("Encountered the following errors:\n%s\n" % msg)
540 return 1
541 return 0
Tri Vo8e292532016-10-01 16:55:51 -0700542
543
544if __name__ == "__main__":
545 main(sys.argv[1:])