xref: /aosp_15_r20/external/autotest/site_utils/deployment/cmdparse.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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 via
64     send ctrl_u to boot into USB, which only apply to initial DUT deployment.
65    `reinstall test image`: when true, enable installing test image through
66     recover mode.
67     `labstation`: when true, deploy labstation instead of DUT.
68
69The `dry_run` option is off by default.  The `upload` option is on by
70default, except for `deploy repair` and `repair_test`.  The values for
71all other options are determined by the subcommand.
72"""
73
74import argparse
75import os
76
77
78class _ArgumentParser(argparse.ArgumentParser):
79    """`argparse.ArgumentParser` extended with boolean option pairs."""
80
81    def add_boolean_argument(self, name, default, **kwargs):
82        """Add a pair of argument flags for a boolean option.
83
84        This add a pair of options, named `--<name>` and `--no<name>`.
85        The actions of the two options are 'store_true' and
86        'store_false', respectively, with the destination `<name>`.
87
88        If neither option is present on the command line, the default
89        value for destination `<name>` is given by `default`.
90
91        The given `kwargs` may be any arguments accepted by
92        `ArgumentParser.add_argument()`, except for `action` and `dest`.
93
94        @param name     The name of the boolean argument, used to
95                        construct the option names and destination field
96                        name.
97        @param default  Default setting for the option when not present
98                        on the command line.
99        """
100        exclusion_group = self.add_mutually_exclusive_group()
101        exclusion_group.add_argument('--%s' % name, action='store_true',
102                                     dest=name, **kwargs)
103        exclusion_group.add_argument('--no%s' % name, action='store_false',
104                                     dest=name, **kwargs)
105        self.set_defaults(**{name: bool(default)})
106
107
108def _add_common_options(parser):
109    # frontend.AFE(server=None) will use the default web server,
110    # so default for --web is `None`.
111    parser.add_argument('-w', '--web', metavar='SERVER', default=None,
112                        help='specify web server')
113    parser.add_argument('-d', '--dir', dest='logdir',
114                        help='directory for logs')
115    parser.add_argument('-n', '--dry-run', action='store_true',
116                        help='apply no changes, install nothing')
117    parser.add_argument('-i', '--build',
118                        help='select stable test build version')
119    parser.add_argument('-f', '--hostname_file',
120                        help='CSV file that contains a list of hostnames and '
121                             'their details to install with.')
122
123
124def _add_upload_option(parser, default):
125    """Add a boolean option for whether to upload logs.
126
127    @param parser   _ArgumentParser instance.
128    @param default  Default option value.
129    """
130    parser.add_boolean_argument('upload', default,
131                                help='whether to upload logs to GS bucket')
132
133
134def _add_subcommand(subcommands, name, upload_default, description):
135    """Add a subcommand plus standard arguments to the `deploy` command.
136
137    This creates a new argument parser for a subcommand (as for
138    `subcommands.add_parser()`).  The parser is populated with the
139    standard arguments required by all `deploy` subcommands.
140
141    @param subcommands      Subcommand object as returned by
142                            `ArgumentParser.add_subcommands`
143    @param name             Name of the new subcommand.
144    @param upload_default   Default setting for the `--upload` option.
145    @param description      Description for the subcommand, for help text.
146    @returns The argument parser for the new subcommand.
147    """
148    subparser = subcommands.add_parser(name, description=description)
149    _add_common_options(subparser)
150    _add_upload_option(subparser, upload_default)
151    subparser.add_argument('-b', '--board', metavar='BOARD',
152                           help='board for DUTs to be installed')
153    subparser.add_argument('-m', '--model', metavar='MODEL',
154                           help='model for DUTs to be installed.')
155    subparser.add_argument('hostnames', nargs='*', metavar='HOSTNAME',
156                           help='host names of DUTs to be installed')
157    return subparser
158
159
160def _add_servo_subcommand(subcommands):
161    """Add the `servo` subcommand to `subcommands`.
162
163    @param subcommands  Subcommand object as returned by
164                        `ArgumentParser.add_subcommands`
165    """
166    subparser = _add_subcommand(
167        subcommands, 'servo', True,
168        'Test servo and install the image on the USB stick')
169    subparser.set_defaults(stageusb=True,
170                           labstation=False,
171                           install_firmware=False,
172                           install_test_image=False,
173                           reinstall_test_image=False)
174
175
176def _add_stageusb_option(parser):
177    """Add a boolean option for whether to stage an image to USB.
178
179    @param parser   _ArgumentParser instance.
180    """
181    parser.add_boolean_argument('stageusb', False,
182                                help='Include USB stick setup')
183
184
185def _add_firmware_subcommand(subcommands):
186    """Add the `firmware` subcommand to `subcommands`.
187
188    @param subcommands  Subcommand object as returned by
189                        `ArgumentParser.add_subcommands`
190    """
191    subparser = _add_subcommand(
192        subcommands, 'firmware', True,
193        'Install firmware and initial test image on DUT')
194    _add_stageusb_option(subparser)
195    subparser.add_argument(
196            '--using-servo', action='store_true',
197            help='Flash DUT firmware directly using servo')
198    subparser.set_defaults(labstation=False,
199                           install_firmware=True,
200                           install_test_image=True,
201                           reinstall_test_image=False)
202
203
204def _add_test_image_subcommand(subcommands):
205    """Add the `test-image` subcommand to `subcommands`.
206
207    @param subcommands  Subcommand object as returned by
208                        `ArgumentParser.add_subcommands`
209    """
210    subparser = _add_subcommand(
211        subcommands, 'test-image', True,
212        'Install initial test image on DUT from servo')
213    _add_stageusb_option(subparser)
214    subparser.set_defaults(labstation=False,
215                           install_firmware=False,
216                           install_test_image=True,
217                           reinstall_test_image=False)
218
219
220def _add_repair_subcommand(subcommands):
221    """Add the `repair` subcommand to `subcommands`.
222
223    @param subcommands  Subcommand object as returned by
224                        `ArgumentParser.add_subcommands`
225    """
226    subparser = _add_subcommand(
227        subcommands, 'repair', False,
228        'Re-install test image on DUT from servo')
229    _add_stageusb_option(subparser)
230    subparser.set_defaults(labstation=False,
231                           install_firmware=False,
232                           install_test_image=False,
233                           reinstall_test_image=True)
234
235
236def _add_labstation_subcommand(subcommands):
237    """Add the `labstation` subcommand to `subcommands`.
238
239    @param subcommands  Subcommand object as returned by
240                        `ArgumentParser.add_subcommands`
241    """
242    subparser = _add_subcommand(
243        subcommands, 'labstation', False,
244        'Deploy a labstation to autotest, the labstation must be already'
245        ' imaged with a labstation test image.')
246    subparser.set_defaults(labstation=True,
247                           install_firmware=False,
248                           install_test_image=False,
249                           reinstall_test_image=False)
250
251
252def parse_command(argv):
253    """Parse arguments for the `deploy` command.
254
255    Create an argument parser for the `deploy` command and its
256    subcommands.  Then parse the command line arguments, and return an
257    `argparse.Namespace` object with the results.
258
259    @param argv         Standard command line argument vector;
260                        argv[0] is assumed to be the command name.
261    @return `Namespace` object with standard fields as described in the
262            module docstring.
263    """
264    parser = _ArgumentParser(
265            prog=os.path.basename(argv[0]),
266            description='DUT deployment and repair operations')
267    subcommands = parser.add_subparsers()
268    _add_servo_subcommand(subcommands)
269    _add_firmware_subcommand(subcommands)
270    _add_test_image_subcommand(subcommands)
271    _add_repair_subcommand(subcommands)
272    _add_labstation_subcommand(subcommands)
273    return parser.parse_args(argv[1:])
274