blob: 8156cc54742144cc7fc87b2029c4a9f8457c12cf [file] [log] [blame]
Matthew Treinish547e8432013-10-24 19:50:49 +00001# Copyright 2012 SINA Corporation
Matthew Treinish90ac9142014-03-17 14:58:37 +00002# Copyright 2014 Cisco Systems, Inc.
Matthew Treinish547e8432013-10-24 19:50:49 +00003# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16#
17
18"""Extracts OpenStack config option info from module(s)."""
19
20from __future__ import print_function
21
Matthew Treinish90ac9142014-03-17 14:58:37 +000022import argparse
Matthew Treinish547e8432013-10-24 19:50:49 +000023import imp
24import os
25import re
26import socket
27import sys
28import textwrap
29
30from oslo.config import cfg
Sean Daguefc691e32014-01-03 08:51:54 -050031import six
Matthew Treinish90ac9142014-03-17 14:58:37 +000032import stevedore.named
Matthew Treinish547e8432013-10-24 19:50:49 +000033
34from tempest.openstack.common import gettextutils
35from tempest.openstack.common import importutils
36
37gettextutils.install('tempest')
38
39STROPT = "StrOpt"
40BOOLOPT = "BoolOpt"
41INTOPT = "IntOpt"
42FLOATOPT = "FloatOpt"
43LISTOPT = "ListOpt"
Matthew Treinish90ac9142014-03-17 14:58:37 +000044DICTOPT = "DictOpt"
Matthew Treinish547e8432013-10-24 19:50:49 +000045MULTISTROPT = "MultiStrOpt"
46
47OPT_TYPES = {
48 STROPT: 'string value',
49 BOOLOPT: 'boolean value',
50 INTOPT: 'integer value',
51 FLOATOPT: 'floating point value',
52 LISTOPT: 'list value',
Matthew Treinish90ac9142014-03-17 14:58:37 +000053 DICTOPT: 'dict value',
Matthew Treinish547e8432013-10-24 19:50:49 +000054 MULTISTROPT: 'multi valued',
55}
56
57OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
Matthew Treinish90ac9142014-03-17 14:58:37 +000058 FLOATOPT, LISTOPT, DICTOPT,
Matthew Treinish547e8432013-10-24 19:50:49 +000059 MULTISTROPT]))
60
61PY_EXT = ".py"
62BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
63 "../../../../"))
64WORDWRAP_WIDTH = 60
65
66
Matthew Treinish90ac9142014-03-17 14:58:37 +000067def raise_extension_exception(extmanager, ep, err):
68 raise
69
70
71def generate(argv):
72 parser = argparse.ArgumentParser(
73 description='generate sample configuration file',
74 )
75 parser.add_argument('-m', dest='modules', action='append')
76 parser.add_argument('-l', dest='libraries', action='append')
77 parser.add_argument('srcfiles', nargs='*')
78 parsed_args = parser.parse_args(argv)
79
Matthew Treinish547e8432013-10-24 19:50:49 +000080 mods_by_pkg = dict()
Matthew Treinish90ac9142014-03-17 14:58:37 +000081 for filepath in parsed_args.srcfiles:
Matthew Treinish547e8432013-10-24 19:50:49 +000082 pkg_name = filepath.split(os.sep)[1]
83 mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]),
84 os.path.basename(filepath).split('.')[0]])
85 mods_by_pkg.setdefault(pkg_name, list()).append(mod_str)
86 # NOTE(lzyeval): place top level modules before packages
Matthew Treinish90ac9142014-03-17 14:58:37 +000087 pkg_names = sorted(pkg for pkg in mods_by_pkg if pkg.endswith(PY_EXT))
88 ext_names = sorted(pkg for pkg in mods_by_pkg if pkg not in pkg_names)
Matthew Treinish547e8432013-10-24 19:50:49 +000089 pkg_names.extend(ext_names)
90
91 # opts_by_group is a mapping of group name to an options list
92 # The options list is a list of (module, options) tuples
93 opts_by_group = {'DEFAULT': []}
94
Matthew Treinish90ac9142014-03-17 14:58:37 +000095 if parsed_args.modules:
96 for module_name in parsed_args.modules:
Sean Daguefc691e32014-01-03 08:51:54 -050097 module = _import_module(module_name)
98 if module:
99 for group, opts in _list_opts(module):
100 opts_by_group.setdefault(group, []).append((module_name,
101 opts))
Matthew Treinish547e8432013-10-24 19:50:49 +0000102
Matthew Treinish90ac9142014-03-17 14:58:37 +0000103 # Look for entry points defined in libraries (or applications) for
104 # option discovery, and include their return values in the output.
105 #
106 # Each entry point should be a function returning an iterable
107 # of pairs with the group name (or None for the default group)
108 # and the list of Opt instances for that group.
109 if parsed_args.libraries:
110 loader = stevedore.named.NamedExtensionManager(
111 'oslo.config.opts',
112 names=list(set(parsed_args.libraries)),
113 invoke_on_load=False,
114 on_load_failure_callback=raise_extension_exception
115 )
116 for ext in loader:
117 for group, opts in ext.plugin():
118 opt_list = opts_by_group.setdefault(group or 'DEFAULT', [])
119 opt_list.append((ext.name, opts))
120
Matthew Treinish547e8432013-10-24 19:50:49 +0000121 for pkg_name in pkg_names:
122 mods = mods_by_pkg.get(pkg_name)
123 mods.sort()
124 for mod_str in mods:
125 if mod_str.endswith('.__init__'):
126 mod_str = mod_str[:mod_str.rfind(".")]
127
128 mod_obj = _import_module(mod_str)
129 if not mod_obj:
130 raise RuntimeError("Unable to import module %s" % mod_str)
131
132 for group, opts in _list_opts(mod_obj):
133 opts_by_group.setdefault(group, []).append((mod_str, opts))
134
135 print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', []))
Sean Daguefc691e32014-01-03 08:51:54 -0500136 for group in sorted(opts_by_group.keys()):
137 print_group_opts(group, opts_by_group[group])
Matthew Treinish547e8432013-10-24 19:50:49 +0000138
139
140def _import_module(mod_str):
141 try:
142 if mod_str.startswith('bin.'):
143 imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:]))
144 return sys.modules[mod_str[4:]]
145 else:
146 return importutils.import_module(mod_str)
Sean Daguefc691e32014-01-03 08:51:54 -0500147 except Exception as e:
148 sys.stderr.write("Error importing module %s: %s\n" % (mod_str, str(e)))
Matthew Treinish547e8432013-10-24 19:50:49 +0000149 return None
150
151
152def _is_in_group(opt, group):
153 "Check if opt is in group."
Matthew Treinish90ac9142014-03-17 14:58:37 +0000154 for value in group._opts.values():
Sean Daguefc691e32014-01-03 08:51:54 -0500155 # NOTE(llu): Temporary workaround for bug #1262148, wait until
156 # newly released oslo.config support '==' operator.
157 if not(value['opt'] != opt):
Matthew Treinish547e8432013-10-24 19:50:49 +0000158 return True
159 return False
160
161
162def _guess_groups(opt, mod_obj):
163 # is it in the DEFAULT group?
164 if _is_in_group(opt, cfg.CONF):
165 return 'DEFAULT'
166
167 # what other groups is it in?
Matthew Treinish90ac9142014-03-17 14:58:37 +0000168 for value in cfg.CONF.values():
Matthew Treinish547e8432013-10-24 19:50:49 +0000169 if isinstance(value, cfg.CONF.GroupAttr):
170 if _is_in_group(opt, value._group):
171 return value._group.name
172
173 raise RuntimeError(
174 "Unable to find group for option %s, "
175 "maybe it's defined twice in the same group?"
176 % opt.name
177 )
178
179
180def _list_opts(obj):
181 def is_opt(o):
182 return (isinstance(o, cfg.Opt) and
183 not isinstance(o, cfg.SubCommandOpt))
184
185 opts = list()
186 for attr_str in dir(obj):
187 attr_obj = getattr(obj, attr_str)
188 if is_opt(attr_obj):
189 opts.append(attr_obj)
190 elif (isinstance(attr_obj, list) and
191 all(map(lambda x: is_opt(x), attr_obj))):
192 opts.extend(attr_obj)
193
194 ret = {}
195 for opt in opts:
196 ret.setdefault(_guess_groups(opt, obj), []).append(opt)
197 return ret.items()
198
199
200def print_group_opts(group, opts_by_module):
201 print("[%s]" % group)
202 print('')
203 for mod, opts in opts_by_module:
204 print('#')
205 print('# Options defined in %s' % mod)
206 print('#')
207 print('')
208 for opt in opts:
209 _print_opt(opt)
210 print('')
211
212
213def _get_my_ip():
214 try:
215 csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
216 csock.connect(('8.8.8.8', 80))
217 (addr, port) = csock.getsockname()
218 csock.close()
219 return addr
220 except socket.error:
221 return None
222
223
224def _sanitize_default(name, value):
225 """Set up a reasonably sensible default for pybasedir, my_ip and host."""
226 if value.startswith(sys.prefix):
227 # NOTE(jd) Don't use os.path.join, because it is likely to think the
228 # second part is an absolute pathname and therefore drop the first
229 # part.
230 value = os.path.normpath("/usr/" + value[len(sys.prefix):])
231 elif value.startswith(BASEDIR):
232 return value.replace(BASEDIR, '/usr/lib/python/site-packages')
233 elif BASEDIR in value:
234 return value.replace(BASEDIR, '')
235 elif value == _get_my_ip():
236 return '10.0.0.1'
Matthew Treinish90ac9142014-03-17 14:58:37 +0000237 elif value in (socket.gethostname(), socket.getfqdn()) and 'host' in name:
Matthew Treinish547e8432013-10-24 19:50:49 +0000238 return 'tempest'
239 elif value.strip() != value:
240 return '"%s"' % value
241 return value
242
243
244def _print_opt(opt):
245 opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help
246 if not opt_help:
247 sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name)
248 opt_help = ""
249 opt_type = None
250 try:
251 opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
252 except (ValueError, AttributeError) as err:
253 sys.stderr.write("%s\n" % str(err))
254 sys.exit(1)
Matthew Treinish90ac9142014-03-17 14:58:37 +0000255 opt_help = u'%s (%s)' % (opt_help,
256 OPT_TYPES[opt_type])
Matthew Treinish547e8432013-10-24 19:50:49 +0000257 print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH)))
258 if opt.deprecated_opts:
259 for deprecated_opt in opt.deprecated_opts:
260 if deprecated_opt.name:
261 deprecated_group = (deprecated_opt.group if
262 deprecated_opt.group else "DEFAULT")
263 print('# Deprecated group/name - [%s]/%s' %
264 (deprecated_group,
265 deprecated_opt.name))
266 try:
267 if opt_default is None:
268 print('#%s=<None>' % opt_name)
269 elif opt_type == STROPT:
Sean Daguefc691e32014-01-03 08:51:54 -0500270 assert(isinstance(opt_default, six.string_types))
Matthew Treinish547e8432013-10-24 19:50:49 +0000271 print('#%s=%s' % (opt_name, _sanitize_default(opt_name,
272 opt_default)))
273 elif opt_type == BOOLOPT:
274 assert(isinstance(opt_default, bool))
275 print('#%s=%s' % (opt_name, str(opt_default).lower()))
276 elif opt_type == INTOPT:
277 assert(isinstance(opt_default, int) and
278 not isinstance(opt_default, bool))
279 print('#%s=%s' % (opt_name, opt_default))
280 elif opt_type == FLOATOPT:
281 assert(isinstance(opt_default, float))
282 print('#%s=%s' % (opt_name, opt_default))
283 elif opt_type == LISTOPT:
284 assert(isinstance(opt_default, list))
285 print('#%s=%s' % (opt_name, ','.join(opt_default)))
Matthew Treinish90ac9142014-03-17 14:58:37 +0000286 elif opt_type == DICTOPT:
287 assert(isinstance(opt_default, dict))
288 opt_default_strlist = [str(key) + ':' + str(value)
289 for (key, value) in opt_default.items()]
290 print('#%s=%s' % (opt_name, ','.join(opt_default_strlist)))
Matthew Treinish547e8432013-10-24 19:50:49 +0000291 elif opt_type == MULTISTROPT:
292 assert(isinstance(opt_default, list))
293 if not opt_default:
294 opt_default = ['']
295 for default in opt_default:
296 print('#%s=%s' % (opt_name, default))
297 print('')
298 except Exception:
299 sys.stderr.write('Error in option "%s"\n' % opt_name)
300 sys.exit(1)
301
302
303def main():
304 generate(sys.argv[1:])
305
306if __name__ == '__main__':
307 main()