blob: cdf37eb42cec1ec4d5f0a52b402993462e34ee83 [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.
16
Fang Dengfed6a6f2017-03-01 18:27:28 -080017r"""Cloud Android Driver.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070018
19This CLI manages google compute engine project for android devices.
20
21- Prerequisites:
22 See: go/acloud-manual
23
24- Configuration:
25 The script takes a required configuration file, which should look like
26 <Start of the file>
27 # If using service account
28 service_account_name: "your_account@developer.gserviceaccount.com"
29 service_account_private_key_path: "/path/to/your-project.p12"
30
31 # If using OAuth2 authentication flow
32 client_id: <client id created in the project>
33 client_secret: <client secret for the client id>
34
35 # Optional
Fang Deng69498c32017-03-02 14:29:30 -080036 ssh_private_key_path: "~/.ssh/acloud_rsa"
37 ssh_public_key_path: "~/.ssh/acloud_rsa.pub"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070038 orientation: "portrait"
39 resolution: "800x1280x32x213"
40 network: "default"
41 machine_type: "n1-standard-1"
42 extra_data_disk_size_gb: 10 # 4G or 10G
43
44 # Required
45 project: "your-project"
46 zone: "us-central1-f"
47 storage_bucket_name: "your_google_storage_bucket_name"
48 <End of the file>
49
50 Save it at /path/to/acloud.config
51
52- Example calls:
53 - Create two instances:
Kevin Chengb5963882018-05-09 00:06:27 -070054 $ acloud.par create_cf
55 --build_target aosp_cf_x86_phone-userdebug \
Fang Dengfed6a6f2017-03-01 18:27:28 -080056 --build_id 3744001 --num 2 --config_file /path/to/acloud.config \
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070057 --report_file /tmp/acloud_report.json --log_file /tmp/acloud.log
58
59 - Delete two instances:
60 $ acloud.par delete --instance_names
Fang Dengfed6a6f2017-03-01 18:27:28 -080061 ins-b638cdba-3744001-gce-x86-phone-userdebug-fastbuild3c-linux
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070062 --config_file /path/to/acloud.config
63 --report_file /tmp/acloud_report.json --log_file /tmp/acloud.log
64"""
65import argparse
66import getpass
67import logging
68import os
69import sys
70
Kevin Chengb5963882018-05-09 00:06:27 -070071# TODO: Find a better way to handling this import logic.
72
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
93def _ParseArgs(args):
94 """Parse args.
95
96 Args:
97 args: Argument list passed from main.
98
99 Returns:
100 Parsed args.
101 """
Kevin Chengb5963882018-05-09 00:06:27 -0700102 usage = ",".join([CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_DELETE,
103 CMD_CLEANUP, CMD_SSHKEY])
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)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700127 create_parser.add_argument("--build_id",
128 type=str,
129 dest="build_id",
130 help="Android build id, e.g. 2145099, P2804227")
131 create_parser.add_argument(
132 "--spec",
133 type=str,
134 dest="spec",
135 required=False,
136 help="The name of a pre-configured device spec that we are "
137 "going to use. Choose from: %s" % ", ".join(constants.SPEC_NAMES))
138 create_parser.add_argument("--num",
139 type=int,
140 dest="num",
141 required=False,
142 default=1,
143 help="Number of instances to create.")
144 create_parser.add_argument(
145 "--gce_image",
146 type=str,
147 dest="gce_image",
148 required=False,
149 help="Name of an existing compute engine image to reuse.")
150 create_parser.add_argument("--local_disk_image",
151 type=str,
152 dest="local_disk_image",
153 required=False,
154 help="Path to a local disk image to use, "
155 "e.g /tmp/avd-system.tar.gz")
156 create_parser.add_argument(
157 "--no_cleanup",
158 dest="no_cleanup",
159 default=False,
160 action="store_true",
161 help="Do not clean up temporary disk image and compute engine image. "
162 "For debugging purposes.")
163 create_parser.add_argument(
164 "--serial_log_file",
165 type=str,
166 dest="serial_log_file",
167 required=False,
168 help="Path to a *tar.gz file where serial logs will be saved "
169 "when a device fails on boot.")
170 create_parser.add_argument(
171 "--logcat_file",
172 type=str,
173 dest="logcat_file",
174 required=False,
175 help="Path to a *tar.gz file where logcat logs will be saved "
176 "when a device fails on boot.")
Kevin Chengb5963882018-05-09 00:06:27 -0700177 create_parser.add_argument(
178 "--autoconnect",
179 action="store_true",
180 dest="autoconnect",
181 required=False,
182 help="For each instance created, we will automatically creates both 2 ssh"
183 " tunnels forwarding both adb & vnc. Then add the device to adb.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700184
185 subparser_list.append(create_parser)
186
Kevin Chengb5963882018-05-09 00:06:27 -0700187 # Command "create_cf", create cuttlefish instances
188 create_cf_parser = subparsers.add_parser(CMD_CREATE_CUTTLEFISH)
189 create_cf_parser.required = False
190 create_cf_parser.set_defaults(which=CMD_CREATE_CUTTLEFISH)
191 create_cf_parser.add_argument(
192 "--build_target",
193 type=str,
194 dest="build_target",
195 help="Android build target, should be a cuttlefish target name.")
196 create_cf_parser.add_argument(
197 "--branch",
198 type=str,
199 dest="branch",
200 help="Android branch, e.g. git_master")
201 create_cf_parser.add_argument(
202 "--build_id",
203 type=str,
204 dest="build_id",
205 help="Android build id, e.g. 2145099, P2804227")
206 create_cf_parser.add_argument(
207 "--kernel_build_id",
208 type=str,
209 dest="kernel_build_id",
210 required=False,
211 help="Android kernel build id, e.g. 4586590. This is to test a new"
212 " kernel build with a particular Android build (--build_id). If not"
213 " specified, the kernel that's bundled with the Android build would"
214 " be used.")
215 create_cf_parser.add_argument(
216 "--num",
217 type=int,
218 dest="num",
219 required=False,
220 default=1,
221 help="Number of instances to create.")
222 create_cf_parser.add_argument(
223 "--serial_log_file",
224 type=str,
225 dest="serial_log_file",
226 required=False,
227 help="Path to a *tar.gz file where serial logs will be saved "
228 "when a device fails on boot.")
229 create_cf_parser.add_argument(
230 "--logcat_file",
231 type=str,
232 dest="logcat_file",
233 required=False,
234 help="Path to a *tar.gz file where logcat logs will be saved "
235 "when a device fails on boot.")
236 create_cf_parser.add_argument(
237 "--autoconnect",
238 action="store_true",
239 dest="autoconnect",
240 required=False,
241 help="For each instance created, we will automatically creates both 2 ssh"
242 " tunnels forwarding both adb & vnc. Then add the device to adb.")
243
244 subparser_list.append(create_cf_parser)
245
246 # Command "create_gf", create goldfish instances
247 # In order to create a goldfish device we need the following parameters:
248 # 1. The emulator build we wish to use, this is the binary that emulates
249 # an android device. See go/emu-dev for more
250 # 2. A system-image. This is the android release we wish to run on the
251 # emulated hardware.
252 create_gf_parser = subparsers.add_parser(CMD_CREATE_GOLDFISH)
253 create_gf_parser.required = False
254 create_gf_parser.set_defaults(which=CMD_CREATE_GOLDFISH)
255 create_gf_parser.add_argument(
256 "--build_target",
257 type=str,
258 dest="build_target",
259 help="Android build target, should be a goldfish target name.")
260 create_gf_parser.add_argument(
261 "--branch",
262 type=str,
263 dest="branch",
264 help="Android branch, e.g. git_master")
265 create_gf_parser.add_argument(
266 "--build_id",
267 type=str,
268 dest="build_id",
269 help="Android build id, e.g. 4669424, P2804227")
270 create_gf_parser.add_argument(
271 "--emulator_build_id",
272 type=str,
273 dest="emulator_build_id",
274 required=False,
275 help="Emulator build used to run the images. e.g. 4669466.")
276 create_gf_parser.add_argument(
277 "--gpu",
278 type=str,
279 dest="gpu",
280 required=False,
281 default=None,
282 help="GPU accelerator to use if any."
283 " e.g. nvidia-tesla-k80, omit to use swiftshader")
284 create_gf_parser.add_argument(
285 "--num",
286 type=int,
287 dest="num",
288 required=False,
289 default=1,
290 help="Number of instances to create.")
291 create_gf_parser.add_argument(
292 "--serial_log_file",
293 type=str,
294 dest="serial_log_file",
295 required=False,
296 help="Path to a *tar.gz file where serial logs will be saved "
297 "when a device fails on boot.")
298 create_gf_parser.add_argument(
299 "--logcat_file",
300 type=str,
301 dest="logcat_file",
302 required=False,
303 help="Path to a *tar.gz file where logcat logs will be saved "
304 "when a device fails on boot.")
305 create_gf_parser.add_argument(
306 "--autoconnect",
307 action="store_true",
308 dest="autoconnect",
309 required=False,
310 help="For each instance created, we will automatically creates both 2 ssh"
311 " tunnels forwarding both adb & vnc. Then add the device to adb.")
312
313 subparser_list.append(create_gf_parser)
314
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700315 # Command "Delete"
316 delete_parser = subparsers.add_parser(CMD_DELETE)
317 delete_parser.required = False
318 delete_parser.set_defaults(which=CMD_DELETE)
319 delete_parser.add_argument(
320 "--instance_names",
321 dest="instance_names",
322 nargs="+",
323 required=True,
324 help="The names of the instances that need to delete, "
325 "separated by spaces, e.g. --instance_names instance-1 instance-2")
326 subparser_list.append(delete_parser)
327
328 # Command "cleanup"
329 cleanup_parser = subparsers.add_parser(CMD_CLEANUP)
330 cleanup_parser.required = False
331 cleanup_parser.set_defaults(which=CMD_CLEANUP)
332 cleanup_parser.add_argument(
333 "--expiration_mins",
334 type=int,
335 dest="expiration_mins",
336 required=True,
337 help="Garbage collect all gce instances, gce images, cached disk "
338 "images that are older than |expiration_mins|.")
339 subparser_list.append(cleanup_parser)
340
Fang Deng69498c32017-03-02 14:29:30 -0800341 # Command "project_sshkey"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700342 sshkey_parser = subparsers.add_parser(CMD_SSHKEY)
343 sshkey_parser.required = False
344 sshkey_parser.set_defaults(which=CMD_SSHKEY)
345 sshkey_parser.add_argument(
346 "--user",
347 type=str,
348 dest="user",
349 default=getpass.getuser(),
350 help="The user name which the sshkey belongs to, default to: %s." %
351 getpass.getuser())
352 sshkey_parser.add_argument(
353 "--ssh_rsa_path",
354 type=str,
355 dest="ssh_rsa_path",
356 required=True,
Fang Deng69498c32017-03-02 14:29:30 -0800357 help="Absolute path to the file that contains the public rsa key "
358 "that will be added as project-wide ssh key.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700359 subparser_list.append(sshkey_parser)
360
361 # Add common arguments.
362 for p in subparser_list:
363 acloud_common.AddCommonArguments(p)
364
365 return parser.parse_args(args)
366
367
368def _TranslateAlias(parsed_args):
369 """Translate alias to Launch Control compatible values.
370
371 This method translates alias to Launch Control compatible values.
372 - branch: "git_" prefix will be added if branch name doesn't have it.
373 - build_target: For example, "phone" will be translated to full target
374 name "git_x86_phone-userdebug",
375
376 Args:
377 parsed_args: Parsed args.
378
379 Returns:
380 Parsed args with its values being translated.
381 """
382 if parsed_args.which == CMD_CREATE:
383 if (parsed_args.branch and
384 not parsed_args.branch.startswith(constants.BRANCH_PREFIX)):
385 parsed_args.branch = constants.BRANCH_PREFIX + parsed_args.branch
386 parsed_args.build_target = constants.BUILD_TARGET_MAPPING.get(
387 parsed_args.build_target, parsed_args.build_target)
388 return parsed_args
389
390
391def _VerifyArgs(parsed_args):
392 """Verify args.
393
394 Args:
395 parsed_args: Parsed args.
396
397 Raises:
398 errors.CommandArgError: If args are invalid.
399 """
400 if parsed_args.which == CMD_CREATE:
401 if (parsed_args.spec and parsed_args.spec not in constants.SPEC_NAMES):
402 raise errors.CommandArgError(
403 "%s is not valid. Choose from: %s" %
404 (parsed_args.spec, ", ".join(constants.SPEC_NAMES)))
405 if not ((parsed_args.build_id and parsed_args.build_target) or
406 parsed_args.gce_image or parsed_args.local_disk_image):
407 raise errors.CommandArgError(
408 "At least one of the following should be specified: "
409 "--build_id and --build_target, or --gce_image, or "
410 "--local_disk_image.")
411 if bool(parsed_args.build_id) != bool(parsed_args.build_target):
412 raise errors.CommandArgError(
413 "Must specify --build_id and --build_target at the same time.")
Kevin Chengb5963882018-05-09 00:06:27 -0700414
415 if parsed_args.which in [CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH]:
416 if not parsed_args.build_id or not parsed_args.build_target:
417 raise errors.CommandArgError("Must specify --build_id and --build_target")
418
419 if parsed_args.which == CMD_CREATE_GOLDFISH:
420 if not parsed_args.emulator_build_id:
421 raise errors.CommandArgError("Must specify --emulator_build_id")
422
423 if parsed_args.which in [
424 CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH
425 ]:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700426 if (parsed_args.serial_log_file and
427 not parsed_args.serial_log_file.endswith(".tar.gz")):
428 raise errors.CommandArgError(
429 "--serial_log_file must ends with .tar.gz")
430 if (parsed_args.logcat_file and
431 not parsed_args.logcat_file.endswith(".tar.gz")):
432 raise errors.CommandArgError(
433 "--logcat_file must ends with .tar.gz")
434
435
436def _SetupLogging(log_file, verbose, very_verbose):
437 """Setup logging.
438
439 Args:
440 log_file: path to log file.
441 verbose: If True, log at DEBUG level, otherwise log at INFO level.
442 very_verbose: If True, log at DEBUG level and turn on logging on
443 all libraries. Take take precedence over |verbose|.
444 """
445 if very_verbose:
446 logger = logging.getLogger()
447 else:
448 logger = logging.getLogger(LOGGER_NAME)
449
450 logging_level = logging.DEBUG if verbose or very_verbose else logging.INFO
451 logger.setLevel(logging_level)
452
453 if not log_file:
454 handler = logging.StreamHandler()
455 else:
456 handler = logging.FileHandler(filename=log_file)
457 log_formatter = logging.Formatter(LOGGING_FMT)
458 handler.setFormatter(log_formatter)
459 logger.addHandler(handler)
460
461
462def main(argv):
463 """Main entry.
464
465 Args:
466 argv: A list of system arguments.
467
468 Returns:
469 0 if success. None-zero if fails.
470 """
471 args = _ParseArgs(argv)
472 _SetupLogging(args.log_file, args.verbose, args.very_verbose)
473 args = _TranslateAlias(args)
474 _VerifyArgs(args)
475
476 config_mgr = config.AcloudConfigManager(args.config_file)
477 cfg = config_mgr.Load()
478 cfg.OverrideWithArgs(args)
479
Fang Dengcef4b112017-03-02 11:20:17 -0800480 # Check access.
481 device_driver.CheckAccess(cfg)
482
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700483 if args.which == CMD_CREATE:
484 report = device_driver.CreateAndroidVirtualDevices(
485 cfg,
486 args.build_target,
487 args.build_id,
488 args.num,
489 args.gce_image,
490 args.local_disk_image,
491 cleanup=not args.no_cleanup,
492 serial_log_file=args.serial_log_file,
Kevin Chengb5963882018-05-09 00:06:27 -0700493 logcat_file=args.logcat_file,
494 autoconnect=args.autoconnect)
495 elif args.which == CMD_CREATE_CUTTLEFISH:
496 report = create_cuttlefish_action.CreateDevices(
497 cfg=cfg,
498 build_target=args.build_target,
499 build_id=args.build_id,
500 kernel_build_id=args.kernel_build_id,
501 num=args.num,
502 serial_log_file=args.serial_log_file,
503 logcat_file=args.logcat_file,
504 autoconnect=args.autoconnect)
505 elif args.which == CMD_CREATE_GOLDFISH:
506 report = create_goldfish_action.CreateDevices(
507 cfg=cfg,
508 build_target=args.build_target,
509 build_id=args.build_id,
510 emulator_build_id=args.emulator_build_id,
511 gpu=args.gpu,
512 num=args.num,
513 serial_log_file=args.serial_log_file,
514 logcat_file=args.logcat_file,
515 autoconnect=args.autoconnect)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700516 elif args.which == CMD_DELETE:
517 report = device_driver.DeleteAndroidVirtualDevices(cfg,
518 args.instance_names)
519 elif args.which == CMD_CLEANUP:
520 report = device_driver.Cleanup(cfg, args.expiration_mins)
521 elif args.which == CMD_SSHKEY:
522 report = device_driver.AddSshRsa(cfg, args.user, args.ssh_rsa_path)
523 else:
524 sys.stderr.write("Invalid command %s" % args.which)
525 return 2
526
527 report.Dump(args.report_file)
528 if report.errors:
529 msg = "\n".join(report.errors)
530 sys.stderr.write("Encountered the following errors:\n%s\n" % msg)
531 return 1
532 return 0
Tri Vo8e292532016-10-01 16:55:51 -0700533
534
535if __name__ == "__main__":
536 main(sys.argv[1:])