1 import argparse 2 import ast 3 import logging 4 import os 5 import shlex 6 import sys 7 8 9 class 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 229 # 230 # Warning! Please read before adding any new arguments! 231 # 232 # New arguments will be ignored if a test runs with server-side 233 # packaging and if the test source build does not have the new 234 # arguments. 235 # 236 # New argument should NOT set action to `store_true`. A workaround is to 237 # use string value of `True` or `False`, then convert them to boolean in 238 # code. 239 # The reason is that parse_args will always ignore the argument name and 240 # value. An unknown argument without a value will lead to positional 241 # argument being removed unexpectedly. 242 # 243 244 245 def parse_args(self): 246 """Parse and process command line arguments. 247 """ 248 # Positional arguments from the end of the command line will be included 249 # in the list of unknown_args. 250 self.options, unknown_args = self.parser.parse_known_args() 251 # Filter out none-positional arguments 252 removed_args = [] 253 while unknown_args and unknown_args[0][0] == '-': 254 removed_args.append(unknown_args.pop(0)) 255 # Always assume the argument has a value. 256 if unknown_args: 257 removed_args.append(unknown_args.pop(0)) 258 if removed_args: 259 logging.warn('Unknown arguments are removed from the options: %s', 260 removed_args) 261 262 self.args = unknown_args + shlex.split(self.options.args or '') 263 264 self.options.host_attributes = ast.literal_eval( 265 self.options.host_attributes) 266 if self.options.lab and self.options.host_attributes: 267 logging.warn( 268 '--lab and --host-attributes are mutually exclusive. ' 269 'Ignoring custom host attributes: %s', 270 str(self.options.host_attributes)) 271 self.options.host_attributes = [] 272 273 self.options.local_only_host_info = _interpret_bool_arg( 274 self.options.local_only_host_info) 275 if not self.options.execution_tag: 276 self.options.execution_tag = self.options.parse_job 277 278 279 def _interpret_bool_arg(value): 280 return value.lower() in {'yes', 'true'} 281 282 283 # create the one and only one instance of autoserv_parser 284 autoserv_parser = autoserv_parser() 285