autoserv_parser.py revision cbeab1213ad41c3631a71c97ec3eddc1aee20049
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=('Parse the results of the job using this'
62                                       ' execution tag. Accessible in control '
63                                       'files as job.tag.'))
64        self.parser.add_argument('--execution-tag', action='store',
65                                 type=str, dest='execution_tag',
66                                 default='',
67                                 help=('Accessible in control files as job.tag;'
68                                       ' Defaults to the value passed to -P.'))
69        self.parser.add_argument('-i', action='store_true',
70                                 dest='install_before', default=False,
71                                 help=('reinstall machines before running the '
72                                       'job'))
73        self.parser.add_argument('-I', action='store_true',
74                                 dest='install_after', default=False,
75                                 help=('reinstall machines after running the '
76                                       'job'))
77        self.parser.add_argument('-v', action='store_true',
78                                 dest='verify', default=False,
79                                 help='verify the machines only')
80        self.parser.add_argument('-R', action='store_true',
81                                 dest='repair', default=False,
82                                 help='repair the machines')
83        self.parser.add_argument('-C', '--cleanup', action='store_true',
84                                 default=False,
85                                 help='cleanup all machines after the job')
86        self.parser.add_argument('--provision', action='store_true',
87                                 default=False,
88                                 help='Provision the machine.')
89        self.parser.add_argument('--job-labels', action='store',
90                                 help='Comma seperated job labels.')
91        self.parser.add_argument('-T', '--reset', action='store_true',
92                                 default=False,
93                                 help=('Reset (cleanup and verify) all machines'
94                                       ' after the job'))
95        self.parser.add_argument('-n', action='store_true',
96                                 dest='no_tee', default=False,
97                                 help='no teeing the status to stdout/err')
98        self.parser.add_argument('-N', action='store_true',
99                                 dest='no_logging', default=False,
100                                 help='no logging')
101        self.parser.add_argument('--verbose', action='store_true',
102                                 help=('Include DEBUG messages in console '
103                                       'output'))
104        self.parser.add_argument('--no_console_prefix', action='store_true',
105                                 help=('Disable the logging prefix on console '
106                                       'output'))
107        self.parser.add_argument('-p', '--write-pidfile', action='store_true',
108                                 dest='write_pidfile', default=False,
109                                 help=('write pidfile (pidfile name is '
110                                       'determined by --pidfile-label'))
111        self.parser.add_argument('--pidfile-label', action='store',
112                                 default='autoserv',
113                                 help=('Determines filename to use as pidfile '
114                                       '(if -p is specified). Pidfile will be '
115                                       '.<label>_execute. Default to '
116                                       'autoserv.'))
117        self.parser.add_argument('--use-existing-results', action='store_true',
118                                 help=('Indicates that autoserv is working with'
119                                       ' an existing results directory'))
120        self.parser.add_argument('-a', '--args', dest='args',
121                                 help='additional args to pass to control file')
122        self.parser.add_argument('--ssh-user', action='store',
123                                 type=str, dest='ssh_user', default='root',
124                                 help='specify the user for ssh connections')
125        self.parser.add_argument('--ssh-port', action='store',
126                                 type=int, dest='ssh_port', default=22,
127                                 help=('specify the port to use for ssh '
128                                       'connections'))
129        self.parser.add_argument('--ssh-pass', action='store',
130                                 type=str, dest='ssh_pass',
131                                 default='',
132                                 help=('specify the password to use for ssh '
133                                       'connections'))
134        self.parser.add_argument('--install-in-tmpdir', action='store_true',
135                                 dest='install_in_tmpdir', default=False,
136                                 help=('by default install autotest clients in '
137                                       'a temporary directory'))
138        self.parser.add_argument('--collect-crashinfo', action='store_true',
139                                 dest='collect_crashinfo', default=False,
140                                 help='just run crashinfo collection')
141        self.parser.add_argument('--control-filename', action='store',
142                                 type=str, default=None,
143                                 help=('filename to use for the server control '
144                                       'file in the results directory'))
145        self.parser.add_argument('--test-retry', action='store',
146                                 type=int, default=0,
147                                 help=('Num of times to retry a test that '
148                                       'failed [default: %(default)d]'))
149        self.parser.add_argument('--verify_job_repo_url', action='store_true',
150                                 dest='verify_job_repo_url', default=False,
151                                 help=('Verify that the job_repo_url of the '
152                                       'host has staged packages for the job.'))
153        self.parser.add_argument('--no_collect_crashinfo', action='store_true',
154                                 dest='skip_crash_collection', default=False,
155                                 help=('Turns off crash collection to shave '
156                                       'time off test runs.'))
157        self.parser.add_argument('--disable_sysinfo', action='store_true',
158                                 dest='disable_sysinfo', default=False,
159                                 help=('Turns off sysinfo collection to shave '
160                                       'time off test runs.'))
161        self.parser.add_argument('--ssh_verbosity', action='store',
162                                 dest='ssh_verbosity', default=0,
163                                 type=str, choices=['0', '1', '2', '3'],
164                                 help=('Verbosity level for ssh, between 0 '
165                                       'and 3 inclusive. '
166                                       '[default: %(default)s]'))
167        self.parser.add_argument('--ssh_options', action='store',
168                                 dest='ssh_options', default='',
169                                 help=('A string giving command line flags '
170                                       'that will be included in ssh commands'))
171        self.parser.add_argument('--require-ssp', action='store_true',
172                                 dest='require_ssp', default=False,
173                                 help=('Force the autoserv process to run with '
174                                       'server-side packaging'))
175        self.parser.add_argument('--warn-no-ssp', action='store_true',
176                                 dest='warn_no_ssp', default=False,
177                                 help=('Post a warning in autoserv log that '
178                                       'the process runs in a drone without '
179                                       'server-side packaging support, even '
180                                       'though the job requires server-side '
181                                       'packaging'))
182        self.parser.add_argument('--no_use_packaging', action='store_true',
183                                 dest='no_use_packaging', default=False,
184                                 help=('Disable install modes that use the '
185                                       'packaging system.'))
186        self.parser.add_argument('--test_source_build', action='store',
187                                 type=str, default='',
188                                 dest='test_source_build',
189                                 help=('Name of the build that contains the '
190                                       'test code. Default is empty, that is, '
191                                       'use the build specified in --image to '
192                                       'retrieve tests.'))
193        self.parser.add_argument('--parent_job_id', action='store',
194                                 type=str, default=None,
195                                 dest='parent_job_id',
196                                 help=('ID of the parent job. Default to None '
197                                       'if the job does not have a parent job'))
198        self.parser.add_argument('--image', action='store', type=str,
199                               default='', dest='image',
200                               help=('Full path of an OS image to install, e.g.'
201                                     ' http://devserver/update/alex-release/'
202                                     'R27-3837.0.0 or a build name: '
203                                     'x86-alex-release/R27-3837.0.0 to '
204                                     'utilize lab devservers automatically.'))
205        self.parser.add_argument('--host_attributes', action='store',
206                                 dest='host_attributes', default='{}',
207                                 help=('Host attribute to be applied to all '
208                                       'machines/hosts for this autoserv run. '
209                                       'Must be a string-encoded dict. '
210                                       'Example: {"key1":"value1", "key2":'
211                                       '"value2"}'))
212        self.parser.add_argument('--lab', action='store', type=str,
213                                 dest='lab', default='',
214                                 help=argparse.SUPPRESS)
215        #
216        # Warning! Please read before adding any new arguments!
217        #
218        # New arguments will be ignored if a test runs with server-side
219        # packaging and if the test source build does not have the new
220        # arguments.
221        #
222        # New argument should NOT set action to `store_true`. A workaround is to
223        # use string value of `True` or `False`, then convert them to boolean in
224        # code.
225        # The reason is that parse_args will always ignore the argument name and
226        # value. An unknown argument without a value will lead to positional
227        # argument being removed unexpectedly.
228        #
229
230
231    def parse_args(self):
232        """Parse and process command line arguments.
233        """
234        # Positional arguments from the end of the command line will be included
235        # in the list of unknown_args.
236        self.options, unknown_args = self.parser.parse_known_args()
237        # Filter out none-positional arguments
238        removed_args = []
239        while unknown_args and unknown_args[0][0] == '-':
240            removed_args.append(unknown_args.pop(0))
241            # Always assume the argument has a value.
242            if unknown_args:
243                removed_args.append(unknown_args.pop(0))
244        if removed_args:
245            logging.warn('Unknown arguments are removed from the options: %s',
246                         removed_args)
247
248        self.args = unknown_args + shlex.split(self.options.args or '')
249
250        if self.options.image:
251            self.options.install_before = True
252            self.options.image =  self.options.image.strip()
253
254        self.options.host_attributes = ast.literal_eval(
255                self.options.host_attributes)
256        if self.options.lab and self.options.host_attributes:
257            logging.warn(
258                    '--lab and --host-attributes are mutually exclusive. '
259                    'Ignoring custom host attributes: %s',
260                    str(self.options.host_attributes))
261            self.options.host_attributes = []
262
263
264# create the one and only one instance of autoserv_parser
265autoserv_parser = autoserv_parser()
266