blob: eeb5a32839972a9cacd3190b82e57540310dcd6e [file] [log] [blame]
Matthew Treinish547e8432013-10-24 19:50:49 +00001# Copyright 2012 SINA Corporation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16
17"""Extracts OpenStack config option info from module(s)."""
18
19from __future__ import print_function
20
21import imp
22import os
23import re
24import socket
25import sys
26import textwrap
27
28from oslo.config import cfg
Sean Daguefc691e32014-01-03 08:51:54 -050029import six
Matthew Treinish547e8432013-10-24 19:50:49 +000030
31from tempest.openstack.common import gettextutils
32from tempest.openstack.common import importutils
33
34gettextutils.install('tempest')
35
36STROPT = "StrOpt"
37BOOLOPT = "BoolOpt"
38INTOPT = "IntOpt"
39FLOATOPT = "FloatOpt"
40LISTOPT = "ListOpt"
41MULTISTROPT = "MultiStrOpt"
42
43OPT_TYPES = {
44 STROPT: 'string value',
45 BOOLOPT: 'boolean value',
46 INTOPT: 'integer value',
47 FLOATOPT: 'floating point value',
48 LISTOPT: 'list value',
49 MULTISTROPT: 'multi valued',
50}
51
52OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
53 FLOATOPT, LISTOPT,
54 MULTISTROPT]))
55
56PY_EXT = ".py"
57BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
58 "../../../../"))
59WORDWRAP_WIDTH = 60
60
61
62def generate(srcfiles):
63 mods_by_pkg = dict()
64 for filepath in srcfiles:
65 pkg_name = filepath.split(os.sep)[1]
66 mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]),
67 os.path.basename(filepath).split('.')[0]])
68 mods_by_pkg.setdefault(pkg_name, list()).append(mod_str)
69 # NOTE(lzyeval): place top level modules before packages
70 pkg_names = filter(lambda x: x.endswith(PY_EXT), mods_by_pkg.keys())
71 pkg_names.sort()
72 ext_names = filter(lambda x: x not in pkg_names, mods_by_pkg.keys())
73 ext_names.sort()
74 pkg_names.extend(ext_names)
75
76 # opts_by_group is a mapping of group name to an options list
77 # The options list is a list of (module, options) tuples
78 opts_by_group = {'DEFAULT': []}
79
Sean Daguefc691e32014-01-03 08:51:54 -050080 extra_modules = os.getenv("TEMPEST_CONFIG_GENERATOR_EXTRA_MODULES", "")
81 if extra_modules:
82 for module_name in extra_modules.split(','):
83 module_name = module_name.strip()
84 module = _import_module(module_name)
85 if module:
86 for group, opts in _list_opts(module):
87 opts_by_group.setdefault(group, []).append((module_name,
88 opts))
Matthew Treinish547e8432013-10-24 19:50:49 +000089
90 for pkg_name in pkg_names:
91 mods = mods_by_pkg.get(pkg_name)
92 mods.sort()
93 for mod_str in mods:
94 if mod_str.endswith('.__init__'):
95 mod_str = mod_str[:mod_str.rfind(".")]
96
97 mod_obj = _import_module(mod_str)
98 if not mod_obj:
99 raise RuntimeError("Unable to import module %s" % mod_str)
100
101 for group, opts in _list_opts(mod_obj):
102 opts_by_group.setdefault(group, []).append((mod_str, opts))
103
104 print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', []))
Sean Daguefc691e32014-01-03 08:51:54 -0500105 for group in sorted(opts_by_group.keys()):
106 print_group_opts(group, opts_by_group[group])
Matthew Treinish547e8432013-10-24 19:50:49 +0000107
108
109def _import_module(mod_str):
110 try:
111 if mod_str.startswith('bin.'):
112 imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:]))
113 return sys.modules[mod_str[4:]]
114 else:
115 return importutils.import_module(mod_str)
Sean Daguefc691e32014-01-03 08:51:54 -0500116 except Exception as e:
117 sys.stderr.write("Error importing module %s: %s\n" % (mod_str, str(e)))
Matthew Treinish547e8432013-10-24 19:50:49 +0000118 return None
119
120
121def _is_in_group(opt, group):
122 "Check if opt is in group."
123 for key, value in group._opts.items():
Sean Daguefc691e32014-01-03 08:51:54 -0500124 # NOTE(llu): Temporary workaround for bug #1262148, wait until
125 # newly released oslo.config support '==' operator.
126 if not(value['opt'] != opt):
Matthew Treinish547e8432013-10-24 19:50:49 +0000127 return True
128 return False
129
130
131def _guess_groups(opt, mod_obj):
132 # is it in the DEFAULT group?
133 if _is_in_group(opt, cfg.CONF):
134 return 'DEFAULT'
135
136 # what other groups is it in?
137 for key, value in cfg.CONF.items():
138 if isinstance(value, cfg.CONF.GroupAttr):
139 if _is_in_group(opt, value._group):
140 return value._group.name
141
142 raise RuntimeError(
143 "Unable to find group for option %s, "
144 "maybe it's defined twice in the same group?"
145 % opt.name
146 )
147
148
149def _list_opts(obj):
150 def is_opt(o):
151 return (isinstance(o, cfg.Opt) and
152 not isinstance(o, cfg.SubCommandOpt))
153
154 opts = list()
155 for attr_str in dir(obj):
156 attr_obj = getattr(obj, attr_str)
157 if is_opt(attr_obj):
158 opts.append(attr_obj)
159 elif (isinstance(attr_obj, list) and
160 all(map(lambda x: is_opt(x), attr_obj))):
161 opts.extend(attr_obj)
162
163 ret = {}
164 for opt in opts:
165 ret.setdefault(_guess_groups(opt, obj), []).append(opt)
166 return ret.items()
167
168
169def print_group_opts(group, opts_by_module):
170 print("[%s]" % group)
171 print('')
172 for mod, opts in opts_by_module:
173 print('#')
174 print('# Options defined in %s' % mod)
175 print('#')
176 print('')
177 for opt in opts:
178 _print_opt(opt)
179 print('')
180
181
182def _get_my_ip():
183 try:
184 csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
185 csock.connect(('8.8.8.8', 80))
186 (addr, port) = csock.getsockname()
187 csock.close()
188 return addr
189 except socket.error:
190 return None
191
192
193def _sanitize_default(name, value):
194 """Set up a reasonably sensible default for pybasedir, my_ip and host."""
195 if value.startswith(sys.prefix):
196 # NOTE(jd) Don't use os.path.join, because it is likely to think the
197 # second part is an absolute pathname and therefore drop the first
198 # part.
199 value = os.path.normpath("/usr/" + value[len(sys.prefix):])
200 elif value.startswith(BASEDIR):
201 return value.replace(BASEDIR, '/usr/lib/python/site-packages')
202 elif BASEDIR in value:
203 return value.replace(BASEDIR, '')
204 elif value == _get_my_ip():
205 return '10.0.0.1'
206 elif value == socket.gethostname() and 'host' in name:
207 return 'tempest'
208 elif value.strip() != value:
209 return '"%s"' % value
210 return value
211
212
213def _print_opt(opt):
214 opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help
215 if not opt_help:
216 sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name)
217 opt_help = ""
218 opt_type = None
219 try:
220 opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
221 except (ValueError, AttributeError) as err:
222 sys.stderr.write("%s\n" % str(err))
223 sys.exit(1)
224 opt_help += ' (' + OPT_TYPES[opt_type] + ')'
225 print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH)))
226 if opt.deprecated_opts:
227 for deprecated_opt in opt.deprecated_opts:
228 if deprecated_opt.name:
229 deprecated_group = (deprecated_opt.group if
230 deprecated_opt.group else "DEFAULT")
231 print('# Deprecated group/name - [%s]/%s' %
232 (deprecated_group,
233 deprecated_opt.name))
234 try:
235 if opt_default is None:
236 print('#%s=<None>' % opt_name)
237 elif opt_type == STROPT:
Sean Daguefc691e32014-01-03 08:51:54 -0500238 assert(isinstance(opt_default, six.string_types))
Matthew Treinish547e8432013-10-24 19:50:49 +0000239 print('#%s=%s' % (opt_name, _sanitize_default(opt_name,
240 opt_default)))
241 elif opt_type == BOOLOPT:
242 assert(isinstance(opt_default, bool))
243 print('#%s=%s' % (opt_name, str(opt_default).lower()))
244 elif opt_type == INTOPT:
245 assert(isinstance(opt_default, int) and
246 not isinstance(opt_default, bool))
247 print('#%s=%s' % (opt_name, opt_default))
248 elif opt_type == FLOATOPT:
249 assert(isinstance(opt_default, float))
250 print('#%s=%s' % (opt_name, opt_default))
251 elif opt_type == LISTOPT:
252 assert(isinstance(opt_default, list))
253 print('#%s=%s' % (opt_name, ','.join(opt_default)))
254 elif opt_type == MULTISTROPT:
255 assert(isinstance(opt_default, list))
256 if not opt_default:
257 opt_default = ['']
258 for default in opt_default:
259 print('#%s=%s' % (opt_name, default))
260 print('')
261 except Exception:
262 sys.stderr.write('Error in option "%s"\n' % opt_name)
263 sys.exit(1)
264
265
266def main():
267 generate(sys.argv[1:])
268
269if __name__ == '__main__':
270 main()