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