• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7This contains parsing for the legacy `repair_test` and `deployment_test`
8commands, and for the new `deploy` command.
9
10The syntax for the two legacy commands is identical; the difference in
11the two commands is merely slightly different default options.
12
13The 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
24The new `deploy` command chooses particular combinations of the steps
25above 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
34This module exports two functions, `parse_deprecated_command()` (for the
35two legacy commands) and `parse_command()` (for the new `deploy`
36command).  Although the functions parse slightly different syntaxes,
37they return `argparse.Namespace` objects with identical fields, described
38below.
39
40The following fields represent parameter inputs to the underlying
41deployment:
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
52The following fields specify options that are used to enable or disable
53specific 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
65The `dry_run` option is off by default.  The `upload` option is on by
66default, except for `deploy repair` and `repair_test`.  The values for
67all other options are determined by the subcommand.
68"""
69
70import argparse
71import os
72
73
74class _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
104def _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
120def _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
130def _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
156def _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
170def _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
179def _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
199def _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
213def _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
227def 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