1# Copyright 2018 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14r"""Create args.
15
16Defines the create arg parser that holds create specific args.
17"""
18
19import argparse
20import os
21
22from acloud import errors
23from acloud.create import create_common
24from acloud.internal import constants
25from acloud.internal.lib import utils
26
27
28CMD_CREATE = "create"
29
30
31# TODO: Add this into main create args once create_cf/gf is deprecated.
32def AddCommonCreateArgs(parser):
33    """Adds arguments common to create parsers.
34
35    Args:
36        parser: ArgumentParser object, used to parse flags.
37    """
38    parser.add_argument(
39        "--num",
40        type=int,
41        dest="num",
42        required=False,
43        default=1,
44        help="Number of instances to create.")
45    parser.add_argument(
46        "--serial-log-file",
47        type=str,
48        dest="serial_log_file",
49        required=False,
50        help="Path to a *tar.gz file where serial logs will be saved "
51             "when a device fails on boot.")
52    parser.add_argument(
53        "--autoconnect",
54        type=str,
55        nargs="?",
56        const=constants.INS_KEY_VNC,
57        dest="autoconnect",
58        required=False,
59        choices=[constants.INS_KEY_VNC, constants.INS_KEY_ADB,
60                 constants.INS_KEY_WEBRTC],
61        help="Determines to establish a tunnel forwarding adb/vnc and "
62             "launch VNC/webrtc. Establish a tunnel forwarding adb and vnc "
63             "then launch vnc if --autoconnect vnc is provided. Establish a "
64             "tunnel forwarding adb if --autoconnect adb is provided. "
65             "Establish a tunnel forwarding adb and auto-launch on the browser "
66             "if --autoconnect webrtc is provided. For local goldfish "
67             "instance, create a window.")
68    parser.add_argument(
69        "--no-autoconnect",
70        action="store_false",
71        dest="autoconnect",
72        required=False,
73        help="Will not automatically create ssh tunnels forwarding adb & vnc "
74             "when instance created.")
75    parser.set_defaults(autoconnect=constants.INS_KEY_VNC)
76    parser.add_argument(
77        "--unlock",
78        action="store_true",
79        dest="unlock_screen",
80        required=False,
81        default=False,
82        help="This can unlock screen after invoke vnc client.")
83    parser.add_argument(
84        "--report-internal-ip",
85        action="store_true",
86        dest="report_internal_ip",
87        required=False,
88        help="Report internal ip of the created instance instead of external "
89             "ip. Using the internal ip is used when connecting from another "
90             "GCE instance.")
91    parser.add_argument(
92        "--network",
93        type=str,
94        dest="network",
95        required=False,
96        help="Set the network the GCE instance will utilize.")
97    parser.add_argument(
98        "--skip-pre-run-check",
99        action="store_true",
100        dest="skip_pre_run_check",
101        required=False,
102        help="Skip the pre-run check.")
103    parser.add_argument(
104        "--boot-timeout",
105        dest="boot_timeout_secs",
106        type=int,
107        required=False,
108        help="The maximum time in seconds used to wait for the AVD to boot.")
109    parser.add_argument(
110        "--wait-for-ins-stable",
111        dest="ins_timeout_secs",
112        type=int,
113        required=False,
114        help="The maximum time in seconds used to wait for the instance boot "
115             "up. The default value to wait for instance up time is 300 secs.")
116    parser.add_argument(
117        "--build-target",
118        type=str,
119        dest="build_target",
120        help="Android build target, e.g. aosp_cf_x86_phone-userdebug, "
121             "or short names: phone, tablet, or tablet_mobile.")
122    parser.add_argument(
123        "--branch",
124        type=str,
125        dest="branch",
126        help="Android branch, e.g. mnc-dev or git_mnc-dev")
127    parser.add_argument(
128        "--build-id",
129        type=str,
130        dest="build_id",
131        help="Android build id, e.g. 2145099, P2804227")
132    parser.add_argument(
133        "--kernel-build-id",
134        type=str,
135        dest="kernel_build_id",
136        required=False,
137        help="Android kernel build id, e.g. 4586590. This is to test a new"
138        " kernel build with a particular Android build (--build-id). If neither"
139        " kernel-branch nor kernel-build-id are specified, the kernel that's"
140        " bundled with the Android build would be used.")
141    parser.add_argument(
142        "--kernel-branch",
143        type=str,
144        dest="kernel_branch",
145        required=False,
146        help="Android kernel build branch name, e.g."
147        " kernel-common-android-4.14. This is to test a new kernel build with a"
148        " particular Android build (--build-id). If specified without"
149        " specifying kernel-build-id, the last green build in the branch will"
150        " be used. If neither kernel-branch nor kernel-build-id are specified,"
151        " the kernel that's bundled with the Android build would be used.")
152    parser.add_argument(
153        "--kernel-build-target",
154        type=str,
155        dest="kernel_build_target",
156        default="kernel",
157        help="Kernel build target, specify if different from 'kernel'")
158    parser.add_argument(
159        "--system-branch",
160        type=str,
161        dest="system_branch",
162        help="'cuttlefish only' Branch to consume the system image (system.img) "
163        "from, will default to what is defined by --branch. "
164        "That feature allows to (automatically) test various combinations "
165        "of vendor.img (CF, e.g.) and system images (GSI, e.g.). ",
166        required=False)
167    parser.add_argument(
168        "--system-build-id",
169        type=str,
170        dest="system_build_id",
171        help="'cuttlefish only' System image build id, e.g. 2145099, P2804227",
172        required=False)
173    parser.add_argument(
174        "--system-build-target",
175        type=str,
176        dest="system_build_target",
177        help="'cuttlefish only' System image build target, specify if different "
178        "from --build-target",
179        required=False)
180    # TODO(146314062): Remove --multi-stage-launch after infra don't use this
181    # args.
182    parser.add_argument(
183        "--multi-stage-launch",
184        dest="multi_stage_launch",
185        action="store_true",
186        required=False,
187        default=True,
188        help="Enable the multi-stage cuttlefish launch.")
189    parser.add_argument(
190        "--no-multi-stage-launch",
191        dest="multi_stage_launch",
192        action="store_false",
193        required=False,
194        default=None,
195        help="Disable the multi-stage cuttlefish launch.")
196    parser.add_argument(
197        "--no-pull-log",
198        dest="no_pull_log",
199        action="store_true",
200        required=False,
201        default=None,
202        help="Disable auto download logs when AVD booting up failed.")
203    # TODO(147335651): Add gpu in user config.
204    # TODO(147335651): Support "--gpu" without giving any value.
205    parser.add_argument(
206        "--gpu",
207        type=str,
208        dest="gpu",
209        required=False,
210        default=None,
211        help="GPU accelerator to use if any. e.g. nvidia-tesla-k80.")
212
213    # TODO(b/118439885): Old arg formats to support transition, delete when
214    # transistion is done.
215    parser.add_argument(
216        "--serial_log_file",
217        type=str,
218        dest="serial_log_file",
219        required=False,
220        help=argparse.SUPPRESS)
221    parser.add_argument(
222        "--build_id",
223        type=str,
224        dest="build_id",
225        required=False,
226        help=argparse.SUPPRESS)
227    parser.add_argument(
228        "--build_target",
229        type=str,
230        dest="build_target",
231        required=False,
232        help=argparse.SUPPRESS)
233    parser.add_argument(
234        "--system_branch",
235        type=str,
236        dest="system_branch",
237        required=False,
238        help=argparse.SUPPRESS)
239    parser.add_argument(
240        "--system_build_id",
241        type=str,
242        dest="system_build_id",
243        required=False,
244        help=argparse.SUPPRESS)
245    parser.add_argument(
246        "--system_build_target",
247        type=str,
248        dest="system_build_target",
249        required=False,
250        help=argparse.SUPPRESS)
251    parser.add_argument(
252        "--kernel_build_id",
253        type=str,
254        dest="kernel_build_id",
255        required=False,
256        help=argparse.SUPPRESS)
257    parser.add_argument(
258        "--kernel_branch",
259        type=str,
260        dest="kernel_branch",
261        required=False,
262        help=argparse.SUPPRESS)
263    parser.add_argument(
264        "--kernel_build_target",
265        type=str,
266        dest="kernel_build_target",
267        default="kernel",
268        help=argparse.SUPPRESS)
269
270
271def GetCreateArgParser(subparser):
272    """Return the create arg parser.
273
274    Args:
275       subparser: argparse.ArgumentParser that is attached to main acloud cmd.
276
277    Returns:
278        argparse.ArgumentParser with create options defined.
279    """
280    create_parser = subparser.add_parser(CMD_CREATE)
281    create_parser.required = False
282    create_parser.set_defaults(which=CMD_CREATE)
283    # Use default=0 to distinguish remote instance or local. The instance type
284    # will be remote if arg --local-instance is not provided.
285    create_parser.add_argument(
286        "--local-instance",
287        type=int,
288        const=1,
289        nargs="?",
290        dest="local_instance",
291        required=False,
292        help="Create a local AVD instance with the option to specify the local "
293             "instance ID (primarily for infra usage).")
294    create_parser.add_argument(
295        "--adb-port", "-p",
296        type=int,
297        default=None,
298        dest="adb_port",
299        required=False,
300        help="Specify port for adb forwarding.")
301    create_parser.add_argument(
302        "--avd-type",
303        type=str,
304        dest="avd_type",
305        default=constants.TYPE_CF,
306        choices=[constants.TYPE_GCE, constants.TYPE_CF, constants.TYPE_GF, constants.TYPE_CHEEPS],
307        help="Android Virtual Device type (default %s)." % constants.TYPE_CF)
308    create_parser.add_argument(
309        "--flavor",
310        type=str,
311        dest="flavor",
312        help="The device flavor of the AVD (default %s)." % constants.FLAVOR_PHONE)
313    create_parser.add_argument(
314        "--local-image",
315        type=str,
316        dest="local_image",
317        nargs="?",
318        default="",
319        required=False,
320        help="Use the locally built image for the AVD. Look for the image "
321        "artifact in $ANDROID_PRODUCT_OUT if no args value is provided."
322        "e.g --local-image or --local-image /path/to/dir or --local-image "
323        "/path/to/file")
324    create_parser.add_argument(
325        "--local-system-image",
326        type=str,
327        dest="local_system_image",
328        nargs="?",
329        default="",
330        required=False,
331        help="Use the locally built system images for the AVD. Look for the "
332        "images in $ANDROID_PRODUCT_OUT if no args value is provided. "
333        "e.g., --local-system-image or --local-system-image /path/to/dir")
334    create_parser.add_argument(
335        "--local-tool",
336        type=str,
337        dest="local_tool",
338        action="append",
339        default=[],
340        required=False,
341        help="Use the tools in the specified directory to create local "
342        "instances. The directory structure follows $ANDROID_HOST_OUT or "
343        "$ANDROID_EMULATOR_PREBUILTS.")
344    create_parser.add_argument(
345        "--image-download-dir",
346        type=str,
347        dest="image_download_dir",
348        required=False,
349        help="Define remote image download directory, e.g. /usr/local/dl.")
350    create_parser.add_argument(
351        "--yes", "-y",
352        action="store_true",
353        dest="no_prompt",
354        required=False,
355        help=("Automatic yes to prompts. Assume 'yes' as answer to all prompts "
356              "and run non-interactively."))
357    create_parser.add_argument(
358        "--reuse-gce",
359        type=str,
360        const=constants.SELECT_ONE_GCE_INSTANCE,
361        nargs="?",
362        dest="reuse_gce",
363        required=False,
364        help="'cuttlefish only' This can help users use their own instance. "
365        "Reusing specific gce instance if --reuse-gce [instance_name] is "
366        "provided. Select one gce instance to reuse if --reuse-gce is "
367        "provided.")
368    create_parser.add_argument(
369        "--host",
370        type=str,
371        dest="remote_host",
372        default=None,
373        help="'cuttlefish only' Provide host name to clean up the remote host. "
374        "For example: '--host 1.1.1.1'")
375    create_parser.add_argument(
376        "--host-user",
377        type=str,
378        dest="host_user",
379        default=constants.GCE_USER,
380        help="'remote host only' Provide host user for logging in to the host. "
381        "The default value is vsoc-01. For example: '--host 1.1.1.1 --host-user "
382        "vsoc-02'")
383    create_parser.add_argument(
384        "--host-ssh-private-key-path",
385        type=str,
386        dest="host_ssh_private_key_path",
387        default=None,
388        help="'remote host only' Provide host key for login on on this host.")
389    # User should not specify --spec and --hw_property at the same time.
390    hw_spec_group = create_parser.add_mutually_exclusive_group()
391    hw_spec_group.add_argument(
392        "--hw-property",
393        type=str,
394        dest="hw_property",
395        required=False,
396        help="Supported HW properties and example values: %s" %
397        constants.HW_PROPERTIES_CMD_EXAMPLE)
398    hw_spec_group.add_argument(
399        "--spec",
400        type=str,
401        dest="spec",
402        required=False,
403        choices=constants.SPEC_NAMES,
404        help="The name of a pre-configured device spec that we are "
405        "going to use.")
406    # Arguments for goldfish type.
407    # TODO(b/118439885): Verify args that are used in wrong avd_type.
408    # e.g. $acloud create --avd-type cuttlefish --emulator-build-id
409    create_parser.add_argument(
410        "--emulator-build-id",
411        type=int,
412        dest="emulator_build_id",
413        required=False,
414        help="'goldfish only' Emulator build used to run the images. "
415        "e.g. 4669466.")
416
417    # Arguments for cheeps type.
418    create_parser.add_argument(
419        "--stable-cheeps-host-image-name",
420        type=str,
421        dest="stable_cheeps_host_image_name",
422        required=False,
423        default=None,
424        help=("'cheeps only' The Cheeps host image from which instances are "
425              "launched. If specified here, the value set in Acloud config "
426              "file will be overridden."))
427    create_parser.add_argument(
428        "--stable-cheeps-host-image-project",
429        type=str,
430        dest="stable_cheeps_host_image_project",
431        required=False,
432        default=None,
433        help=("'cheeps only' The project hosting the specified Cheeps host "
434              "image. If specified here, the value set in Acloud config file "
435              "will be overridden."))
436    create_parser.add_argument(
437        "--user",
438        type=str,
439        dest="username",
440        required=False,
441        default=None,
442        help="'cheeps only' username to log in to Chrome OS as.")
443    create_parser.add_argument(
444        "--password",
445        type=str,
446        dest="password",
447        required=False,
448        default=None,
449        help="'cheeps only' password to log in to Chrome OS with.")
450
451    AddCommonCreateArgs(create_parser)
452    return create_parser
453
454
455def _VerifyLocalArgs(args):
456    """Verify args starting with --local.
457
458    Args:
459        args: Namespace object from argparse.parse_args.
460
461    Raises:
462        errors.CheckPathError: Image path doesn't exist.
463        errors.UnsupportedCreateArgs: The specified avd type does not support
464                                      --local-system-image.
465        errors.UnsupportedLocalInstanceId: Local instance ID is invalid.
466    """
467    if args.local_image and not os.path.exists(args.local_image):
468        raise errors.CheckPathError(
469            "Specified path doesn't exist: %s" % args.local_image)
470
471    # TODO(b/133211308): Support TYPE_CF.
472    if args.local_system_image != "" and args.avd_type != constants.TYPE_GF:
473        raise errors.UnsupportedCreateArgs("%s instance does not support "
474                                           "--local-system-image" %
475                                           args.avd_type)
476
477    if (args.local_system_image and
478            not os.path.exists(args.local_system_image)):
479        raise errors.CheckPathError(
480            "Specified path doesn't exist: %s" % args.local_system_image)
481
482    if args.local_instance is not None and args.local_instance < 1:
483        raise errors.UnsupportedLocalInstanceId("Local instance id can not be "
484                                                "less than 1. Actually passed:%d"
485                                                % args.local_instance)
486
487    for tool_dir in args.local_tool:
488        if not os.path.exists(tool_dir):
489            raise errors.CheckPathError(
490                "Specified path doesn't exist: %s" % tool_dir)
491
492    if args.autoconnect == constants.INS_KEY_WEBRTC:
493        if args.avd_type != constants.TYPE_CF:
494            raise errors.UnsupportedCreateArgs(
495                "'--autoconnect webrtc' only support cuttlefish.")
496
497
498def _VerifyHostArgs(args):
499    """Verify args starting with --host.
500
501    Args:
502        args: Namespace object from argparse.parse_args.
503
504    Raises:
505        errors.UnsupportedCreateArgs: When a create arg is specified but
506                                      unsupported for remote host mode.
507    """
508    if args.remote_host and args.local_instance is not None:
509        raise errors.UnsupportedCreateArgs(
510            "--host is not supported for local instance.")
511
512    if args.remote_host and args.num > 1:
513        raise errors.UnsupportedCreateArgs(
514            "--num is not supported for remote host.")
515
516    if args.host_user != constants.GCE_USER and args.remote_host is None:
517        raise errors.UnsupportedCreateArgs(
518            "--host-user only support for remote host.")
519
520    if args.host_ssh_private_key_path and args.remote_host is None:
521        raise errors.UnsupportedCreateArgs(
522            "--host-ssh-private-key-path only support for remote host.")
523
524
525def VerifyArgs(args):
526    """Verify args.
527
528    Args:
529        args: Namespace object from argparse.parse_args.
530
531    Raises:
532        errors.UnsupportedFlavor: Flavor doesn't support.
533        errors.UnsupportedMultiAdbPort: multi adb port doesn't support.
534        errors.UnsupportedCreateArgs: When a create arg is specified but
535                                      unsupported for a particular avd type.
536                                      (e.g. --system-build-id for gf)
537    """
538    # Verify that user specified flavor name is in support list.
539    # We don't use argparse's builtin validation because we need to be able to
540    # tell when a user doesn't specify a flavor.
541    if args.flavor and args.flavor not in constants.ALL_FLAVORS:
542        raise errors.UnsupportedFlavor(
543            "Flavor[%s] isn't in support list: %s" % (args.flavor,
544                                                      constants.ALL_FLAVORS))
545    if args.avd_type != constants.TYPE_CF:
546        if args.system_branch or args.system_build_id or args.system_build_target:
547            raise errors.UnsupportedCreateArgs(
548                "--system-* args are not supported for AVD type: %s"
549                % args.avd_type)
550
551    if args.num > 1 and args.adb_port:
552        raise errors.UnsupportedMultiAdbPort(
553            "--adb-port is not supported for multi-devices.")
554
555    if args.num > 1 and args.local_instance is not None:
556        raise errors.UnsupportedCreateArgs(
557            "--num is not supported for local instance.")
558
559    if args.adb_port:
560        utils.CheckPortFree(args.adb_port)
561
562    hw_properties = create_common.ParseHWPropertyArgs(args.hw_property)
563    for key in hw_properties:
564        if key not in constants.HW_PROPERTIES:
565            raise errors.InvalidHWPropertyError(
566                "[%s] is an invalid hw property, supported values are:%s. "
567                % (key, constants.HW_PROPERTIES))
568
569    if args.avd_type != constants.TYPE_CHEEPS and (
570            args.stable_cheeps_host_image_name or
571            args.stable_cheeps_host_image_project or
572            args.username or args.password):
573        raise errors.UnsupportedCreateArgs(
574            "--stable-cheeps-*, --username and --password are only valid with "
575            "avd_type == %s" % constants.TYPE_CHEEPS)
576    if (args.username or args.password) and not (args.username and args.password):
577        raise ValueError("--username and --password must both be set")
578    if not args.autoconnect and args.unlock_screen:
579        raise ValueError("--no-autoconnect and --unlock couldn't be "
580                         "passed in together.")
581
582    _VerifyLocalArgs(args)
583    _VerifyHostArgs(args)
584