1import argparse 2import ast 3import logging 4import os 5import shlex 6import sys 7 8 9class autoserv_parser(object): 10 """Custom command-line options parser for autoserv. 11 12 We can't use the general getopt methods here, as there will be unknown 13 extra arguments that we pass down into the control file instead. 14 Thus we process the arguments by hand, for which we are duly repentant. 15 Making a single function here just makes it harder to read. Suck it up. 16 """ 17 def __init__(self): 18 self.args = sys.argv[1:] 19 self.parser = argparse.ArgumentParser( 20 usage='%(prog)s [options] [control-file]') 21 self.setup_options() 22 23 # parse an empty list of arguments in order to set self.options 24 # to default values so that codepaths that assume they are always 25 # reached from an autoserv process (when they actually are not) 26 # will still work 27 self.options = self.parser.parse_args(args=[]) 28 29 30 def setup_options(self): 31 """Setup options to call autoserv command. 32 """ 33 self.parser.add_argument('-m', action='store', type=str, 34 dest='machines', 35 help='list of machines') 36 self.parser.add_argument('-M', action='store', type=str, 37 dest='machines_file', 38 help='list of machines from file') 39 self.parser.add_argument('-c', action='store_true', 40 dest='client', default=False, 41 help='control file is client side') 42 self.parser.add_argument('-s', action='store_true', 43 dest='server', default=False, 44 help='control file is server side') 45 self.parser.add_argument('-r', action='store', type=str, 46 dest='results', default=None, 47 help='specify results directory') 48 self.parser.add_argument('-l', action='store', type=str, 49 dest='label', default='', 50 help='label for the job') 51 self.parser.add_argument('-G', action='store', type=str, 52 dest='group_name', default='', 53 help='The host_group_name to store in keyvals') 54 self.parser.add_argument('-u', action='store', type=str, 55 dest='user', 56 default=os.environ.get('USER'), 57 help='username for the job') 58 self.parser.add_argument('-P', action='store', type=str, 59 dest='parse_job', 60 default='', 61 help=('DEPRECATED.' 62 ' Use --execution-tag instead.')) 63 self.parser.add_argument('--execution-tag', action='store', 64 type=str, dest='execution_tag', 65 default='', 66 help=('Accessible in control files as job.tag;' 67 ' Defaults to the value passed to -P.')) 68 self.parser.add_argument('-v', action='store_true', 69 dest='verify', default=False, 70 help='verify the machines only') 71 self.parser.add_argument('-R', action='store_true', 72 dest='repair', default=False, 73 help='repair the machines') 74 self.parser.add_argument('-C', '--cleanup', action='store_true', 75 default=False, 76 help='cleanup all machines after the job') 77 self.parser.add_argument('--provision', action='store_true', 78 default=False, 79 help='Provision the machine.') 80 self.parser.add_argument('--job-labels', action='store', 81 help='Comma seperated job labels.') 82 self.parser.add_argument('-T', '--reset', action='store_true', 83 default=False, 84 help=('Reset (cleanup and verify) all machines' 85 ' after the job')) 86 self.parser.add_argument('-n', action='store_true', 87 dest='no_tee', default=False, 88 help='no teeing the status to stdout/err') 89 self.parser.add_argument('-N', action='store_true', 90 dest='no_logging', default=False, 91 help='no logging') 92 self.parser.add_argument('--verbose', action='store_true', 93 help=('Include DEBUG messages in console ' 94 'output')) 95 self.parser.add_argument('--no_console_prefix', action='store_true', 96 help=('Disable the logging prefix on console ' 97 'output')) 98 self.parser.add_argument('-p', '--write-pidfile', action='store_true', 99 dest='write_pidfile', default=False, 100 help=('write pidfile (pidfile name is ' 101 'determined by --pidfile-label')) 102 self.parser.add_argument('--pidfile-label', action='store', 103 default='autoserv', 104 help=('Determines filename to use as pidfile ' 105 '(if -p is specified). Pidfile will be ' 106 '.<label>_execute. Default to ' 107 'autoserv.')) 108 self.parser.add_argument('--use-existing-results', action='store_true', 109 help=('Indicates that autoserv is working with' 110 ' an existing results directory')) 111 self.parser.add_argument('-a', '--args', dest='args', 112 help='additional args to pass to control file') 113 self.parser.add_argument('--ssh-user', action='store', 114 type=str, dest='ssh_user', default='root', 115 help='specify the user for ssh connections') 116 self.parser.add_argument('--ssh-port', 117 action='store', 118 type=int, 119 dest='ssh_port', 120 default=None, 121 help=('specify the port to use for ssh ' 122 'connections')) 123 self.parser.add_argument('--ssh-pass', action='store', 124 type=str, dest='ssh_pass', 125 default='', 126 help=('specify the password to use for ssh ' 127 'connections')) 128 self.parser.add_argument('--install-in-tmpdir', action='store_true', 129 dest='install_in_tmpdir', default=False, 130 help=('by default install autotest clients in ' 131 'a temporary directory')) 132 self.parser.add_argument('--collect-crashinfo', action='store_true', 133 dest='collect_crashinfo', default=False, 134 help='just run crashinfo collection') 135 self.parser.add_argument('--control-filename', action='store', 136 type=str, default=None, 137 help=('filename to use for the server control ' 138 'file in the results directory')) 139 self.parser.add_argument('--verify_job_repo_url', action='store_true', 140 dest='verify_job_repo_url', default=False, 141 help=('Verify that the job_repo_url of the ' 142 'host has staged packages for the job.')) 143 self.parser.add_argument('--no_collect_crashinfo', action='store_true', 144 dest='skip_crash_collection', default=False, 145 help=('Turns off crash collection to shave ' 146 'time off test runs.')) 147 self.parser.add_argument('--disable_sysinfo', action='store_true', 148 dest='disable_sysinfo', default=False, 149 help=('Turns off sysinfo collection to shave ' 150 'time off test runs.')) 151 self.parser.add_argument('--ssh_verbosity', action='store', 152 dest='ssh_verbosity', default=0, 153 type=str, choices=['0', '1', '2', '3'], 154 help=('Verbosity level for ssh, between 0 ' 155 'and 3 inclusive. ' 156 '[default: %(default)s]')) 157 self.parser.add_argument('--ssh_options', action='store', 158 dest='ssh_options', default='', 159 help=('A string giving command line flags ' 160 'that will be included in ssh commands')) 161 self.parser.add_argument('--require-ssp', action='store_true', 162 dest='require_ssp', default=False, 163 help=('Force the autoserv process to run with ' 164 'server-side packaging')) 165 self.parser.add_argument('--no_use_packaging', action='store_true', 166 dest='no_use_packaging', default=False, 167 help=('Disable install modes that use the ' 168 'packaging system.')) 169 self.parser.add_argument('--test_source_build', action='store', 170 type=str, default='', 171 dest='test_source_build', 172 help=('Alternative build that contains the ' 173 'test code for server-side packaging. ' 174 'Default is to use the build on the ' 175 'target DUT.')) 176 self.parser.add_argument('--parent_job_id', action='store', 177 type=str, default=None, 178 dest='parent_job_id', 179 help=('ID of the parent job. Default to None ' 180 'if the job does not have a parent job')) 181 self.parser.add_argument('--host_attributes', action='store', 182 dest='host_attributes', default='{}', 183 help=('Host attribute to be applied to all ' 184 'machines/hosts for this autoserv run. ' 185 'Must be a string-encoded dict. ' 186 'Example: {"key1":"value1", "key2":' 187 '"value2"}')) 188 self.parser.add_argument('--lab', action='store', type=str, 189 dest='lab', default='', 190 help=argparse.SUPPRESS) 191 self.parser.add_argument('--CFT', 192 action='store_true', 193 dest='cft', 194 default=False, 195 help=('If running in, or mocking, ' 196 'the CFT env.')) 197 self.parser.add_argument('--cloud_trace_context', type=str, default='', 198 action='store', dest='cloud_trace_context', 199 help=('Global trace context to configure ' 200 'emission of data to Cloud Trace.')) 201 self.parser.add_argument('--cloud_trace_context_enabled', type=str, 202 default='False', action='store', 203 dest='cloud_trace_context_enabled', 204 help=('Global trace context to configure ' 205 'emission of data to Cloud Trace.')) 206 self.parser.add_argument( 207 '--host-info-subdir', 208 default='host_info_store', 209 help='Optional path to a directory containing host ' 210 'information for the machines. The path is relative to ' 211 'the results directory (see -r) and must be inside ' 212 'the directory.' 213 ) 214 self.parser.add_argument( 215 '--local-only-host-info', 216 default='False', 217 help='By default, host status are immediately reported back to ' 218 'the AFE, shadowing the updates to disk. This flag ' 219 'disables the AFE interaction completely, and assumes ' 220 'that initial host information is supplied to autoserv. ' 221 'See also: --host-info-subdir', 222 ) 223 self.parser.add_argument( 224 '--control-name', 225 action='store', 226 help='NAME attribute of the control file to stage and run. ' 227 'This overrides the control file provided via the ' 228 'positional args.', 229 ) 230 self.parser.add_argument( 231 '--sync-offload-dir', action='store', type=str, default='', 232 help='Relative path from results directory to the sub-directory ' 233 'which should be offloaded synchronously', 234 ) 235 self.parser.add_argument( 236 '--ssp-base-image-name', 237 action='store', 238 help='Name of the base container image to use for' 239 ' Server Side Packaging (SSP). Only meaningful when SSP is' 240 ' enabled. The default value is provided via the global' 241 ' config setting for AUTOSERV/container_base_name.' 242 ) 243 self.parser.add_argument( 244 '--image-storage-server', 245 action='store', 246 type=str, 247 default='', 248 help='The gs path to the image storage server to be used' 249 ' for this autoserv invocation. This overrides the' 250 ' default provided by CROS/image_storage_server.') 251 self.parser.add_argument('--py_version', 252 action='store', 253 dest='py_version', 254 default='2', 255 type=str, 256 choices=['2', '3']) 257 self.parser.add_argument('-ch', 258 action='store', 259 type=str, 260 dest='companion_hosts', 261 help='list of companion hosts for the test.') 262 self.parser.add_argument('--dut_servers', 263 action='store', 264 type=str, 265 dest='dut_servers', 266 help='list of DUT servers for the test.') 267 self.parser.add_argument('--force_full_log_collection', 268 action='store_true', 269 dest='force_full_log_collection', 270 default=False, 271 help='Force full log collection on tests.') 272 273 # 274 # Warning! Please read before adding any new arguments! 275 # 276 # New arguments will be ignored if a test runs with server-side 277 # packaging and if the test source build does not have the new 278 # arguments. 279 # 280 # New arguments should NOT set action to `store_true`. A workaround is 281 # to use string value of `True` or `False`, then convert them to boolean 282 # in code. 283 # The reason is that parse_args will always ignore the argument name and 284 # value. An unknown argument without a value will lead to positional 285 # argument being removed unexpectedly. 286 # 287 288 289 def parse_args(self): 290 """Parse and process command line arguments. 291 """ 292 # Positional arguments from the end of the command line will be included 293 # in the list of unknown_args. 294 self.options, unknown_args = self.parser.parse_known_args() 295 # Filter out none-positional arguments 296 removed_args = [] 297 while unknown_args and unknown_args[0] and unknown_args[0][0] == '-': 298 removed_args.append(unknown_args.pop(0)) 299 # Always assume the argument has a value. 300 if unknown_args: 301 removed_args.append(unknown_args.pop(0)) 302 if removed_args: 303 logging.warning('Unknown arguments are removed from the options: %s', 304 removed_args) 305 306 self.args = unknown_args + shlex.split(self.options.args or '') 307 308 self.options.host_attributes = ast.literal_eval( 309 self.options.host_attributes) 310 if self.options.lab and self.options.host_attributes: 311 logging.warning( 312 '--lab and --host-attributes are mutually exclusive. ' 313 'Ignoring custom host attributes: %s', 314 str(self.options.host_attributes)) 315 self.options.host_attributes = [] 316 317 self.options.local_only_host_info = _interpret_bool_arg( 318 self.options.local_only_host_info) 319 if not self.options.execution_tag: 320 self.options.execution_tag = self.options.parse_job 321 322 323def _interpret_bool_arg(value): 324 return value.lower() in {'yes', 'true'} 325 326 327# create the one and only one instance of autoserv_parser 328autoserv_parser = autoserv_parser() 329