Home | History | Annotate | Download | only in deployment
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Command-line parsing for the DUT deployment tool.
      6 
      7 This contains parsing for the legacy `repair_test` and `deployment_test`
      8 commands, and for the new `deploy` command.
      9 
     10 The syntax for the two legacy commands is identical; the difference in
     11 the two commands is merely slightly different default options.
     12 
     13 The full deployment flow performs all of the following actions:
     14   * Stage the USB image:  Install the DUT's assigned repair image onto
     15     the Servo USB stick.
     16   * Install firmware:  Boot the DUT from the USB stick, and run
     17     `chromeos-firmwareupdate` to install dev-signed RO and RW firmware.
     18     The DUT must begin in dev-mode, with hardware write-protect
     19     disabled.  At successful completion, the DUT is in verified boot
     20     mode.
     21   * Install test image:  Boot the DUT in recovery mode from the USB
     22     stick, and run `chromeos-install` to install the OS.
     23 
     24 The new `deploy` command chooses particular combinations of the steps
     25 above based on a subcommand and options:
     26     `deploy servo`:  Only stage the USB image.
     27     `deploy firmware`:  Install both the firmware and the test image,
     28         in that order.  Optionally, first stage the USB image.
     29     `deploy test-image`: Install the test image.  Optionally, first
     30         stage the USB image.
     31     `deploy repair`:  Equivalent to `deploy test-image`, except that
     32         by default it doesn't upload its logs to storage.
     33 
     34 This module exports two functions, `parse_deprecated_command()` (for the
     35 two legacy commands) and `parse_command()` (for the new `deploy`
     36 command).  Although the functions parse slightly different syntaxes,
     37 they return `argparse.Namespace` objects with identical fields, described
     38 below.
     39 
     40 The following fields represent parameter inputs to the underlying
     41 deployment:
     42     `web`:  Server name (or URL) for the AFE RPC service.
     43     `logdir`:  The directory where logs are to be stored.
     44     `board`:  Specifies the board to be used when creating DUTs.
     45     `build`:  When provided, the repair image assigned to the board for
     46         the target DUT will be updated to this value prior to staging
     47         USB image.  The build is in a form like 'R66-10447.0.0'.
     48     `hostname_file`:  Name of a file in CSV format with information
     49         about the hosts and servos to be deployed/repaired.
     50     `hostnames`:  List of DUT host names.
     51 
     52 The following fields specify options that are used to enable or disable
     53 specific deployment steps:
     54     `upload`:  When true, logs will be uploaded to googlestorage after
     55         the command completes.
     56     `dry_run`:  When true, disables operations with any kind of
     57         side-effect.  This option implicitly overrides and disables all
     58         of the deployment steps below.
     59     `stageusb`:  When true, enable staging the USB image.  Disabling
     60         this will speed up operations when the stick is known to already
     61         have the proper image.
     62     `install_firmware`:  When true, enable firmware installation.
     63     `install_test_image`:  When true, enable installing the test image.
     64 
     65 The `dry_run` option is off by default.  The `upload` option is on by
     66 default, except for `deploy repair` and `repair_test`.  The values for
     67 all other options are determined by the subcommand.
     68 """
     69 
     70 import argparse
     71 import os
     72 
     73 
     74 class _ArgumentParser(argparse.ArgumentParser):
     75     """`argparse.ArgumentParser` extended with boolean option pairs."""
     76 
     77     def add_boolean_argument(self, name, default, **kwargs):
     78         """Add a pair of argument flags for a boolean option.
     79 
     80         This add a pair of options, named `--<name>` and `--no<name>`.
     81         The actions of the two options are 'store_true' and
     82         'store_false', respectively, with the destination `<name>`.
     83 
     84         If neither option is present on the command line, the default
     85         value for destination `<name>` is given by `default`.
     86 
     87         The given `kwargs` may be any arguments accepted by
     88         `ArgumentParser.add_argument()`, except for `action` and `dest`.
     89 
     90         @param name     The name of the boolean argument, used to
     91                         construct the option names and destination field
     92                         name.
     93         @param default  Default setting for the option when not present
     94                         on the command line.
     95         """
     96         exclusion_group = self.add_mutually_exclusive_group()
     97         exclusion_group.add_argument('--%s' % name, action='store_true',
     98                                      dest=name, **kwargs)
     99         exclusion_group.add_argument('--no%s' % name, action='store_false',
    100                                      dest=name, **kwargs)
    101         self.set_defaults(**{name: bool(default)})
    102 
    103 
    104 def _add_common_options(parser):
    105     # frontend.AFE(server=None) will use the default web server,
    106     # so default for --web is `None`.
    107     parser.add_argument('-w', '--web', metavar='SERVER', default=None,
    108                         help='specify web server')
    109     parser.add_argument('-d', '--dir', dest='logdir',
    110                         help='directory for logs')
    111     parser.add_argument('-n', '--dry-run', action='store_true',
    112                         help='apply no changes, install nothing')
    113     parser.add_argument('-i', '--build',
    114                         help='select stable test build version')
    115     parser.add_argument('-f', '--hostname_file',
    116                         help='CSV file that contains a list of hostnames and '
    117                              'their details to install with.')
    118 
    119 
    120 def _add_upload_option(parser, default):
    121     """Add a boolean option for whether to upload logs.
    122 
    123     @param parser   _ArgumentParser instance.
    124     @param default  Default option value.
    125     """
    126     parser.add_boolean_argument('upload', default,
    127                                 help='whether to upload logs to GS bucket')
    128 
    129 
    130 def _add_subcommand(subcommands, name, upload_default, description):
    131     """Add a subcommand plus standard arguments to the `deploy` command.
    132 
    133     This creates a new argument parser for a subcommand (as for
    134     `subcommands.add_parser()`).  The parser is populated with the
    135     standard arguments required by all `deploy` subcommands.
    136 
    137     @param subcommands      Subcommand object as returned by
    138                             `ArgumentParser.add_subcommands`
    139     @param name             Name of the new subcommand.
    140     @param upload_default   Default setting for the `--upload` option.
    141     @param description      Description for the subcommand, for help text.
    142     @returns The argument parser for the new subcommand.
    143     """
    144     subparser = subcommands.add_parser(name, description=description)
    145     _add_common_options(subparser)
    146     _add_upload_option(subparser, upload_default)
    147     subparser.add_argument('-b', '--board', metavar='BOARD',
    148                            help='board for DUTs to be installed')
    149     subparser.add_argument('-m', '--model', metavar='MODEL',
    150                            help='model for DUTs to be installed.')
    151     subparser.add_argument('hostnames', nargs='*', metavar='HOSTNAME',
    152                            help='host names of DUTs to be installed')
    153     return subparser
    154 
    155 
    156 def _add_servo_subcommand(subcommands):
    157     """Add the `servo` subcommand to `subcommands`.
    158 
    159     @param subcommands  Subcommand object as returned by
    160                         `ArgumentParser.add_subcommands`
    161     """
    162     subparser = _add_subcommand(
    163         subcommands, 'servo', True,
    164         'Test servo and install the image on the USB stick')
    165     subparser.set_defaults(stageusb=True,
    166                            install_firmware=False,
    167                            install_test_image=False)
    168 
    169 
    170 def _add_stageusb_option(parser):
    171     """Add a boolean option for whether to stage an image to USB.
    172 
    173     @param parser   _ArgumentParser instance.
    174     """
    175     parser.add_boolean_argument('stageusb', False,
    176                                 help='Include USB stick setup')
    177 
    178 
    179 def _add_firmware_subcommand(subcommands):
    180     """Add the `firmware` subcommand to `subcommands`.
    181 
    182     @param subcommands  Subcommand object as returned by
    183                         `ArgumentParser.add_subcommands`
    184     """
    185     subparser = _add_subcommand(
    186         subcommands, 'firmware', True,
    187         'Install firmware and initial test image on DUT')
    188     _add_stageusb_option(subparser)
    189     subparser.add_argument(
    190             '--using-servo', action='store_true',
    191             help='Flash DUT firmware directly using servo')
    192     subparser.add_argument(
    193             '--force-firmware', action='store_true', default=False,
    194             help='Force firmware installation using chromeos-installfirmware.')
    195     subparser.set_defaults(install_firmware=True,
    196                            install_test_image=True)
    197 
    198 
    199 def _add_test_image_subcommand(subcommands):
    200     """Add the `test-image` subcommand to `subcommands`.
    201 
    202     @param subcommands  Subcommand object as returned by
    203                         `ArgumentParser.add_subcommands`
    204     """
    205     subparser = _add_subcommand(
    206         subcommands, 'test-image', True,
    207         'Install initial test image on DUT from servo')
    208     _add_stageusb_option(subparser)
    209     subparser.set_defaults(install_firmware=False,
    210                            install_test_image=True)
    211 
    212 
    213 def _add_repair_subcommand(subcommands):
    214     """Add the `repair` subcommand to `subcommands`.
    215 
    216     @param subcommands  Subcommand object as returned by
    217                         `ArgumentParser.add_subcommands`
    218     """
    219     subparser = _add_subcommand(
    220         subcommands, 'repair', False,
    221         'Re-install test image on DUT from servo')
    222     _add_stageusb_option(subparser)
    223     subparser.set_defaults(install_firmware=False,
    224                            install_test_image=True)
    225 
    226 
    227 def parse_command(argv):
    228     """Parse arguments for the `deploy` command.
    229 
    230     Create an argument parser for the `deploy` command and its
    231     subcommands.  Then parse the command line arguments, and return an
    232     `argparse.Namespace` object with the results.
    233 
    234     @param argv         Standard command line argument vector;
    235                         argv[0] is assumed to be the command name.
    236     @return `Namespace` object with standard fields as described in the
    237             module docstring.
    238     """
    239     parser = _ArgumentParser(
    240             prog=os.path.basename(argv[0]),
    241             description='DUT deployment and repair operations')
    242     subcommands = parser.add_subparsers()
    243     _add_servo_subcommand(subcommands)
    244     _add_firmware_subcommand(subcommands)
    245     _add_test_image_subcommand(subcommands)
    246     _add_repair_subcommand(subcommands)
    247     return parser.parse_args(argv[1:])
    248