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