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