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