blob: 88727e3d5c7751a93cbda5e614fd1bb9bdcd0779 [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"
xingdai8a00d462018-07-30 14:24:48 -070029 # Or
30 service_account_json_private_key_path: "/path/to/your-project.json"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070031
32 # If using OAuth2 authentication flow
33 client_id: <client id created in the project>
34 client_secret: <client secret for the client id>
35
36 # Optional
Fang Deng69498c32017-03-02 14:29:30 -080037 ssh_private_key_path: "~/.ssh/acloud_rsa"
38 ssh_public_key_path: "~/.ssh/acloud_rsa.pub"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070039 orientation: "portrait"
40 resolution: "800x1280x32x213"
41 network: "default"
42 machine_type: "n1-standard-1"
43 extra_data_disk_size_gb: 10 # 4G or 10G
44
45 # Required
46 project: "your-project"
47 zone: "us-central1-f"
48 storage_bucket_name: "your_google_storage_bucket_name"
49 <End of the file>
50
51 Save it at /path/to/acloud.config
52
53- Example calls:
54 - Create two instances:
Kevin Chengb5963882018-05-09 00:06:27 -070055 $ acloud.par create_cf
56 --build_target aosp_cf_x86_phone-userdebug \
Fang Dengfed6a6f2017-03-01 18:27:28 -080057 --build_id 3744001 --num 2 --config_file /path/to/acloud.config \
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070058 --report_file /tmp/acloud_report.json --log_file /tmp/acloud.log
59
60 - Delete two instances:
61 $ acloud.par delete --instance_names
Fang Dengfed6a6f2017-03-01 18:27:28 -080062 ins-b638cdba-3744001-gce-x86-phone-userdebug-fastbuild3c-linux
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070063 --config_file /path/to/acloud.config
64 --report_file /tmp/acloud_report.json --log_file /tmp/acloud.log
65"""
66import argparse
67import getpass
68import logging
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070069import sys
70
Kevin Chengf4137c62018-05-22 16:06:58 -070071# Needed to silence oauth2client.
72logging.basicConfig(level=logging.CRITICAL)
Kevin Chengb5963882018-05-09 00:06:27 -070073
Kevin Chengf4137c62018-05-22 16:06:58 -070074# pylint: disable=wrong-import-position
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070075from acloud.internal import constants
76from acloud.public import acloud_common
77from acloud.public import config
78from acloud.public import device_driver
79from acloud.public import errors
Kevin Chengb5963882018-05-09 00:06:27 -070080from acloud.public.actions import create_cuttlefish_action
81from acloud.public.actions import create_goldfish_action
Kevin Cheng3087af52018-08-13 13:26:50 -070082from acloud.create import create
83from acloud.create import create_args
Kevin Chengee6030f2018-06-26 10:55:30 -070084from acloud.setup import setup
85from acloud.setup import setup_args
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070086
87LOGGING_FMT = "%(asctime)s |%(levelname)s| %(module)s:%(lineno)s| %(message)s"
Tri Vo8e292532016-10-01 16:55:51 -070088LOGGER_NAME = "acloud_main"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070089
90# Commands
Kevin Chengb5963882018-05-09 00:06:27 -070091CMD_CREATE_CUTTLEFISH = "create_cf"
92CMD_CREATE_GOLDFISH = "create_gf"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070093CMD_DELETE = "delete"
94CMD_CLEANUP = "cleanup"
Fang Deng69498c32017-03-02 14:29:30 -080095CMD_SSHKEY = "project_sshkey"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070096
97
Kevin Cheng3031f8a2018-05-16 13:21:51 -070098# pylint: disable=too-many-statements
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070099def _ParseArgs(args):
100 """Parse args.
101
102 Args:
103 args: Argument list passed from main.
104
105 Returns:
106 Parsed args.
107 """
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700108 usage = ",".join([
Kevin Chengab0b36b2018-08-02 14:38:30 -0700109 CMD_CLEANUP,
Kevin Chengab0b36b2018-08-02 14:38:30 -0700110 CMD_CREATE_CUTTLEFISH,
111 CMD_CREATE_GOLDFISH,
112 CMD_DELETE,
113 CMD_SSHKEY,
Kevin Cheng3087af52018-08-13 13:26:50 -0700114 create_args.CMD_CREATE,
Kevin Chengee6030f2018-06-26 10:55:30 -0700115 setup_args.CMD_SETUP,
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700116 ])
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700117 parser = argparse.ArgumentParser(
118 description=__doc__,
119 formatter_class=argparse.RawDescriptionHelpFormatter,
Kevin Cheng7ff74be2018-05-23 14:18:55 -0700120 usage="acloud {" + usage + "} ...")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700121 subparsers = parser.add_subparsers()
122 subparser_list = []
123
124 # Command "create"
Kevin Cheng3087af52018-08-13 13:26:50 -0700125 subparser_list.append(create_args.GetCreateArgParser(subparsers))
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700126
Kevin Chengb5963882018-05-09 00:06:27 -0700127 # Command "create_cf", create cuttlefish instances
128 create_cf_parser = subparsers.add_parser(CMD_CREATE_CUTTLEFISH)
129 create_cf_parser.required = False
130 create_cf_parser.set_defaults(which=CMD_CREATE_CUTTLEFISH)
131 create_cf_parser.add_argument(
132 "--build_target",
133 type=str,
134 dest="build_target",
135 help="Android build target, should be a cuttlefish target name.")
136 create_cf_parser.add_argument(
137 "--branch",
138 type=str,
139 dest="branch",
140 help="Android branch, e.g. git_master")
141 create_cf_parser.add_argument(
142 "--build_id",
143 type=str,
144 dest="build_id",
145 help="Android build id, e.g. 2145099, P2804227")
146 create_cf_parser.add_argument(
147 "--kernel_build_id",
148 type=str,
149 dest="kernel_build_id",
150 required=False,
151 help="Android kernel build id, e.g. 4586590. This is to test a new"
152 " kernel build with a particular Android build (--build_id). If not"
153 " specified, the kernel that's bundled with the Android build would"
154 " be used.")
Kevin Chengb5963882018-05-09 00:06:27 -0700155
Kevin Cheng3087af52018-08-13 13:26:50 -0700156 create_args.AddCommonCreateArgs(create_cf_parser)
Kevin Chengb5963882018-05-09 00:06:27 -0700157 subparser_list.append(create_cf_parser)
158
159 # Command "create_gf", create goldfish instances
160 # In order to create a goldfish device we need the following parameters:
161 # 1. The emulator build we wish to use, this is the binary that emulates
162 # an android device. See go/emu-dev for more
163 # 2. A system-image. This is the android release we wish to run on the
164 # emulated hardware.
165 create_gf_parser = subparsers.add_parser(CMD_CREATE_GOLDFISH)
166 create_gf_parser.required = False
167 create_gf_parser.set_defaults(which=CMD_CREATE_GOLDFISH)
168 create_gf_parser.add_argument(
169 "--build_target",
170 type=str,
171 dest="build_target",
172 help="Android build target, should be a goldfish target name.")
173 create_gf_parser.add_argument(
174 "--branch",
175 type=str,
176 dest="branch",
177 help="Android branch, e.g. git_master")
178 create_gf_parser.add_argument(
179 "--build_id",
180 type=str,
181 dest="build_id",
182 help="Android build id, e.g. 4669424, P2804227")
183 create_gf_parser.add_argument(
184 "--emulator_build_id",
185 type=str,
186 dest="emulator_build_id",
187 required=False,
188 help="Emulator build used to run the images. e.g. 4669466.")
189 create_gf_parser.add_argument(
190 "--gpu",
191 type=str,
192 dest="gpu",
193 required=False,
194 default=None,
195 help="GPU accelerator to use if any."
196 " e.g. nvidia-tesla-k80, omit to use swiftshader")
197 create_gf_parser.add_argument(
Kevin Chengbced4af2018-06-26 10:35:01 -0700198 "--base_image",
199 type=str,
200 dest="base_image",
201 required=False,
202 help="Name of the goldfish base image to be used to create the instance. "
203 "This will override stable_goldfish_host_image_name from config. "
204 "e.g. emu-dev-cts-061118")
Kevin Chengb5963882018-05-09 00:06:27 -0700205
Kevin Cheng3087af52018-08-13 13:26:50 -0700206 create_args.AddCommonCreateArgs(create_gf_parser)
Kevin Chengb5963882018-05-09 00:06:27 -0700207 subparser_list.append(create_gf_parser)
208
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700209 # Command "Delete"
210 delete_parser = subparsers.add_parser(CMD_DELETE)
211 delete_parser.required = False
212 delete_parser.set_defaults(which=CMD_DELETE)
213 delete_parser.add_argument(
214 "--instance_names",
215 dest="instance_names",
216 nargs="+",
217 required=True,
218 help="The names of the instances that need to delete, "
219 "separated by spaces, e.g. --instance_names instance-1 instance-2")
220 subparser_list.append(delete_parser)
221
222 # Command "cleanup"
223 cleanup_parser = subparsers.add_parser(CMD_CLEANUP)
224 cleanup_parser.required = False
225 cleanup_parser.set_defaults(which=CMD_CLEANUP)
226 cleanup_parser.add_argument(
227 "--expiration_mins",
228 type=int,
229 dest="expiration_mins",
230 required=True,
231 help="Garbage collect all gce instances, gce images, cached disk "
232 "images that are older than |expiration_mins|.")
233 subparser_list.append(cleanup_parser)
234
Fang Deng69498c32017-03-02 14:29:30 -0800235 # Command "project_sshkey"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700236 sshkey_parser = subparsers.add_parser(CMD_SSHKEY)
237 sshkey_parser.required = False
238 sshkey_parser.set_defaults(which=CMD_SSHKEY)
239 sshkey_parser.add_argument(
240 "--user",
241 type=str,
242 dest="user",
243 default=getpass.getuser(),
244 help="The user name which the sshkey belongs to, default to: %s." %
245 getpass.getuser())
246 sshkey_parser.add_argument(
247 "--ssh_rsa_path",
248 type=str,
249 dest="ssh_rsa_path",
250 required=True,
Fang Deng69498c32017-03-02 14:29:30 -0800251 help="Absolute path to the file that contains the public rsa key "
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700252 "that will be added as project-wide ssh key.")
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700253 subparser_list.append(sshkey_parser)
254
Kevin Chengee6030f2018-06-26 10:55:30 -0700255 # Command "setup"
256 subparser_list.append(setup_args.GetSetupArgParser(subparsers))
257
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700258 # Add common arguments.
Kevin Chengb21d7712018-05-24 14:54:55 -0700259 for subparser in subparser_list:
260 acloud_common.AddCommonArguments(subparser)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700261
262 return parser.parse_args(args)
263
264
265def _TranslateAlias(parsed_args):
266 """Translate alias to Launch Control compatible values.
267
268 This method translates alias to Launch Control compatible values.
269 - branch: "git_" prefix will be added if branch name doesn't have it.
270 - build_target: For example, "phone" will be translated to full target
271 name "git_x86_phone-userdebug",
272
273 Args:
274 parsed_args: Parsed args.
275
276 Returns:
277 Parsed args with its values being translated.
278 """
Kevin Cheng3087af52018-08-13 13:26:50 -0700279 if parsed_args.which == create_args.CMD_CREATE:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700280 if (parsed_args.branch and
281 not parsed_args.branch.startswith(constants.BRANCH_PREFIX)):
282 parsed_args.branch = constants.BRANCH_PREFIX + parsed_args.branch
283 parsed_args.build_target = constants.BUILD_TARGET_MAPPING.get(
284 parsed_args.build_target, parsed_args.build_target)
285 return parsed_args
286
287
288def _VerifyArgs(parsed_args):
289 """Verify args.
290
291 Args:
292 parsed_args: Parsed args.
293
294 Raises:
295 errors.CommandArgError: If args are invalid.
296 """
Kevin Cheng3087af52018-08-13 13:26:50 -0700297 if (parsed_args.which == create_args.CMD_CREATE
298 and parsed_args.avd_type == constants.TYPE_GCE):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700299 if (parsed_args.spec and parsed_args.spec not in constants.SPEC_NAMES):
300 raise errors.CommandArgError(
301 "%s is not valid. Choose from: %s" %
302 (parsed_args.spec, ", ".join(constants.SPEC_NAMES)))
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700303 if not ((parsed_args.build_id and parsed_args.build_target)
304 or parsed_args.gce_image or parsed_args.local_disk_image):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700305 raise errors.CommandArgError(
306 "At least one of the following should be specified: "
307 "--build_id and --build_target, or --gce_image, or "
308 "--local_disk_image.")
309 if bool(parsed_args.build_id) != bool(parsed_args.build_target):
310 raise errors.CommandArgError(
311 "Must specify --build_id and --build_target at the same time.")
Kevin Chengb5963882018-05-09 00:06:27 -0700312
313 if parsed_args.which in [CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH]:
314 if not parsed_args.build_id or not parsed_args.build_target:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700315 raise errors.CommandArgError(
316 "Must specify --build_id and --build_target")
Kevin Chengb5963882018-05-09 00:06:27 -0700317
318 if parsed_args.which == CMD_CREATE_GOLDFISH:
319 if not parsed_args.emulator_build_id:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700320 raise errors.CommandArgError("Must specify --emulator_build_id")
Kevin Chengb5963882018-05-09 00:06:27 -0700321
322 if parsed_args.which in [
Kevin Cheng3087af52018-08-13 13:26:50 -0700323 create_args.CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH
Kevin Chengb5963882018-05-09 00:06:27 -0700324 ]:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700325 if (parsed_args.serial_log_file
326 and not parsed_args.serial_log_file.endswith(".tar.gz")):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700327 raise errors.CommandArgError(
328 "--serial_log_file must ends with .tar.gz")
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700329 if (parsed_args.logcat_file
330 and not parsed_args.logcat_file.endswith(".tar.gz")):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700331 raise errors.CommandArgError(
332 "--logcat_file must ends with .tar.gz")
333
334
335def _SetupLogging(log_file, verbose, very_verbose):
336 """Setup logging.
337
338 Args:
339 log_file: path to log file.
340 verbose: If True, log at DEBUG level, otherwise log at INFO level.
341 very_verbose: If True, log at DEBUG level and turn on logging on
342 all libraries. Take take precedence over |verbose|.
343 """
344 if very_verbose:
345 logger = logging.getLogger()
346 else:
347 logger = logging.getLogger(LOGGER_NAME)
348
349 logging_level = logging.DEBUG if verbose or very_verbose else logging.INFO
350 logger.setLevel(logging_level)
351
Sam Chiufde41e92018-08-07 18:37:02 +0800352 if log_file:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700353 handler = logging.FileHandler(filename=log_file)
Sam Chiufde41e92018-08-07 18:37:02 +0800354 log_formatter = logging.Formatter(LOGGING_FMT)
355 handler.setFormatter(log_formatter)
356 logger.addHandler(handler)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700357
358
359def main(argv):
360 """Main entry.
361
362 Args:
363 argv: A list of system arguments.
364
365 Returns:
366 0 if success. None-zero if fails.
367 """
368 args = _ParseArgs(argv)
369 _SetupLogging(args.log_file, args.verbose, args.very_verbose)
370 args = _TranslateAlias(args)
371 _VerifyArgs(args)
372
373 config_mgr = config.AcloudConfigManager(args.config_file)
374 cfg = config_mgr.Load()
375 cfg.OverrideWithArgs(args)
376
Kevin Cheng3087af52018-08-13 13:26:50 -0700377 # TODO: Move this check into the functions it is actually needed.
Fang Dengcef4b112017-03-02 11:20:17 -0800378 # Check access.
Kevin Cheng3087af52018-08-13 13:26:50 -0700379 # device_driver.CheckAccess(cfg)
Fang Dengcef4b112017-03-02 11:20:17 -0800380
Kevin Chengee6030f2018-06-26 10:55:30 -0700381 report = None
Kevin Cheng3087af52018-08-13 13:26:50 -0700382 if (args.which == create_args.CMD_CREATE
383 and args.avd_type == constants.TYPE_GCE):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700384 report = device_driver.CreateAndroidVirtualDevices(
385 cfg,
386 args.build_target,
387 args.build_id,
388 args.num,
389 args.gce_image,
390 args.local_disk_image,
391 cleanup=not args.no_cleanup,
392 serial_log_file=args.serial_log_file,
Kevin Chengb5963882018-05-09 00:06:27 -0700393 logcat_file=args.logcat_file,
394 autoconnect=args.autoconnect)
Kevin Cheng3087af52018-08-13 13:26:50 -0700395 elif args.which == create_args.CMD_CREATE:
396 create.Run()
Kevin Chengb5963882018-05-09 00:06:27 -0700397 elif args.which == CMD_CREATE_CUTTLEFISH:
398 report = create_cuttlefish_action.CreateDevices(
399 cfg=cfg,
400 build_target=args.build_target,
401 build_id=args.build_id,
402 kernel_build_id=args.kernel_build_id,
403 num=args.num,
404 serial_log_file=args.serial_log_file,
405 logcat_file=args.logcat_file,
406 autoconnect=args.autoconnect)
407 elif args.which == CMD_CREATE_GOLDFISH:
408 report = create_goldfish_action.CreateDevices(
409 cfg=cfg,
410 build_target=args.build_target,
411 build_id=args.build_id,
412 emulator_build_id=args.emulator_build_id,
413 gpu=args.gpu,
414 num=args.num,
415 serial_log_file=args.serial_log_file,
416 logcat_file=args.logcat_file,
417 autoconnect=args.autoconnect)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700418 elif args.which == CMD_DELETE:
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700419 report = device_driver.DeleteAndroidVirtualDevices(
420 cfg, args.instance_names)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700421 elif args.which == CMD_CLEANUP:
422 report = device_driver.Cleanup(cfg, args.expiration_mins)
423 elif args.which == CMD_SSHKEY:
424 report = device_driver.AddSshRsa(cfg, args.user, args.ssh_rsa_path)
Kevin Chengee6030f2018-06-26 10:55:30 -0700425 elif args.which == setup_args.CMD_SETUP:
Sam Chiu81bdc652018-06-29 18:45:08 +0800426 setup.Run()
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700427 else:
428 sys.stderr.write("Invalid command %s" % args.which)
429 return 2
430
Kevin Chengee6030f2018-06-26 10:55:30 -0700431 if report:
432 report.Dump(args.report_file)
433 if report.errors:
434 msg = "\n".join(report.errors)
435 sys.stderr.write("Encountered the following errors:\n%s\n" % msg)
436 return 1
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700437 return 0
Tri Vo8e292532016-10-01 16:55:51 -0700438
439
440if __name__ == "__main__":
441 main(sys.argv[1:])