xref: /aosp_15_r20/external/autotest/server/autoserv_parser.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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