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', action='store', 117 type=int, dest='ssh_port', default=22, 118 help=('specify the port to use for ssh ' 119 'connections')) 120 self.parser.add_argument('--ssh-pass', action='store', 121 type=str, dest='ssh_pass', 122 default='', 123 help=('specify the password to use for ssh ' 124 'connections')) 125 self.parser.add_argument('--install-in-tmpdir', action='store_true', 126 dest='install_in_tmpdir', default=False, 127 help=('by default install autotest clients in ' 128 'a temporary directory')) 129 self.parser.add_argument('--collect-crashinfo', action='store_true', 130 dest='collect_crashinfo', default=False, 131 help='just run crashinfo collection') 132 self.parser.add_argument('--control-filename', action='store', 133 type=str, default=None, 134 help=('filename to use for the server control ' 135 'file in the results directory')) 136 self.parser.add_argument('--verify_job_repo_url', action='store_true', 137 dest='verify_job_repo_url', default=False, 138 help=('Verify that the job_repo_url of the ' 139 'host has staged packages for the job.')) 140 self.parser.add_argument('--no_collect_crashinfo', action='store_true', 141 dest='skip_crash_collection', default=False, 142 help=('Turns off crash collection to shave ' 143 'time off test runs.')) 144 self.parser.add_argument('--disable_sysinfo', action='store_true', 145 dest='disable_sysinfo', default=False, 146 help=('Turns off sysinfo collection to shave ' 147 'time off test runs.')) 148 self.parser.add_argument('--ssh_verbosity', action='store', 149 dest='ssh_verbosity', default=0, 150 type=str, choices=['0', '1', '2', '3'], 151 help=('Verbosity level for ssh, between 0 ' 152 'and 3 inclusive. ' 153 '[default: %(default)s]')) 154 self.parser.add_argument('--ssh_options', action='store', 155 dest='ssh_options', default='', 156 help=('A string giving command line flags ' 157 'that will be included in ssh commands')) 158 self.parser.add_argument('--require-ssp', action='store_true', 159 dest='require_ssp', default=False, 160 help=('Force the autoserv process to run with ' 161 'server-side packaging')) 162 self.parser.add_argument('--no_use_packaging', action='store_true', 163 dest='no_use_packaging', default=False, 164 help=('Disable install modes that use the ' 165 'packaging system.')) 166 self.parser.add_argument('--source_isolate', action='store', 167 type=str, default='', 168 dest='isolate', 169 help=('Hash for isolate containing build ' 170 'contents needed for server-side ' 171 'packaging. Takes precedence over ' 172 'test_source_build, if present.')) 173 self.parser.add_argument('--test_source_build', action='store', 174 type=str, default='', 175 dest='test_source_build', 176 help=('Alternative build that contains the ' 177 'test code for server-side packaging. ' 178 'Default is to use the build on the ' 179 'target DUT.')) 180 self.parser.add_argument('--parent_job_id', action='store', 181 type=str, default=None, 182 dest='parent_job_id', 183 help=('ID of the parent job. Default to None ' 184 'if the job does not have a parent job')) 185 self.parser.add_argument('--host_attributes', action='store', 186 dest='host_attributes', default='{}', 187 help=('Host attribute to be applied to all ' 188 'machines/hosts for this autoserv run. ' 189 'Must be a string-encoded dict. ' 190 'Example: {"key1":"value1", "key2":' 191 '"value2"}')) 192 self.parser.add_argument('--lab', action='store', type=str, 193 dest='lab', default='', 194 help=argparse.SUPPRESS) 195 self.parser.add_argument('--cloud_trace_context', type=str, default='', 196 action='store', dest='cloud_trace_context', 197 help=('Global trace context to configure ' 198 'emission of data to Cloud Trace.')) 199 self.parser.add_argument('--cloud_trace_context_enabled', type=str, 200 default='False', action='store', 201 dest='cloud_trace_context_enabled', 202 help=('Global trace context to configure ' 203 'emission of data to Cloud Trace.')) 204 self.parser.add_argument( 205 '--host-info-subdir', 206 default='host_info_store', 207 help='Optional path to a directory containing host ' 208 'information for the machines. The path is relative to ' 209 'the results directory (see -r) and must be inside ' 210 'the directory.' 211 ) 212 self.parser.add_argument( 213 '--local-only-host-info', 214 default='False', 215 help='By default, host status are immediately reported back to ' 216 'the AFE, shadowing the updates to disk. This flag ' 217 'disables the AFE interaction completely, and assumes ' 218 'that initial host information is supplied to autoserv. ' 219 'See also: --host-info-subdir', 220 ) 221 self.parser.add_argument( 222 '--control-name', 223 action='store', 224 help='NAME attribute of the control file to stage and run. ' 225 'This overrides the control file provided via the ' 226 'positional args.', 227 ) 228 self.parser.add_argument( 229 '--sync-offload-dir', action='store', type=str, default='', 230 help='Relative path from results directory to the sub-directory ' 231 'which should be offloaded synchronously', 232 ) 233 self.parser.add_argument( 234 '--ssp-base-image-name', 235 action='store', 236 help='Name of the base container image to use for' 237 ' Server Side Packaging (SSP). Only meaningful when SSP is' 238 ' enabled. The default value is provided via the global' 239 ' config setting for AUTOSERV/container_base_name.' 240 ) 241 242 # 243 # Warning! Please read before adding any new arguments! 244 # 245 # New arguments will be ignored if a test runs with server-side 246 # packaging and if the test source build does not have the new 247 # arguments. 248 # 249 # New arguments should NOT set action to `store_true`. A workaround is 250 # to use string value of `True` or `False`, then convert them to boolean 251 # in code. 252 # The reason is that parse_args will always ignore the argument name and 253 # value. An unknown argument without a value will lead to positional 254 # argument being removed unexpectedly. 255 # 256 257 258 def parse_args(self): 259 """Parse and process command line arguments. 260 """ 261 # Positional arguments from the end of the command line will be included 262 # in the list of unknown_args. 263 self.options, unknown_args = self.parser.parse_known_args() 264 # Filter out none-positional arguments 265 removed_args = [] 266 while unknown_args and unknown_args[0] and unknown_args[0][0] == '-': 267 removed_args.append(unknown_args.pop(0)) 268 # Always assume the argument has a value. 269 if unknown_args: 270 removed_args.append(unknown_args.pop(0)) 271 if removed_args: 272 logging.warn('Unknown arguments are removed from the options: %s', 273 removed_args) 274 275 self.args = unknown_args + shlex.split(self.options.args or '') 276 277 self.options.host_attributes = ast.literal_eval( 278 self.options.host_attributes) 279 if self.options.lab and self.options.host_attributes: 280 logging.warn( 281 '--lab and --host-attributes are mutually exclusive. ' 282 'Ignoring custom host attributes: %s', 283 str(self.options.host_attributes)) 284 self.options.host_attributes = [] 285 286 self.options.local_only_host_info = _interpret_bool_arg( 287 self.options.local_only_host_info) 288 if not self.options.execution_tag: 289 self.options.execution_tag = self.options.parse_job 290 291 292def _interpret_bool_arg(value): 293 return value.lower() in {'yes', 'true'} 294 295 296# create the one and only one instance of autoserv_parser 297autoserv_parser = autoserv_parser() 298