Merge lp:~sdn-charmers/charms/trusty/openstack-dashboard/add-settings into lp:~openstack-charmers-archive/charms/trusty/openstack-dashboard/next
- Trusty Tahr (14.04)
- add-settings
- Merge into next
Status: | Merged |
---|---|
Merged at revision: | 85 |
Proposed branch: | lp:~sdn-charmers/charms/trusty/openstack-dashboard/add-settings |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/openstack-dashboard/next |
Diff against target: |
589 lines (+279/-16) 16 files modified
hooks/charmhelpers/contrib/openstack/context.py (+9/-0) hooks/charmhelpers/contrib/openstack/templating.py (+96/-11) hooks/horizon_contexts.py (+51/-0) hooks/horizon_hooks.py (+13/-1) hooks/horizon_utils.py (+19/-2) metadata.yaml (+3/-0) templates/essex/local_settings.py (+2/-0) templates/folsom/local_settings.py (+2/-0) templates/grizzly/local_settings.py (+2/-0) templates/havana/local_settings.py (+2/-0) templates/icehouse/_{}_juju_{}.py (+2/-0) templates/icehouse/local_settings.py (+2/-0) templates/juno/local_settings.py (+2/-0) unit_tests/test_horizon_contexts.py (+43/-0) unit_tests/test_horizon_hooks.py (+2/-2) unit_tests/test_horizon_utils.py (+29/-0) |
To merge this branch: | bzr merge lp:~sdn-charmers/charms/trusty/openstack-dashboard/add-settings |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michał Sawicz (community) | Disapprove | ||
Billy Olsen | Needs Fixing | ||
OpenStack Charmers | Pending | ||
Robert Ayres | Pending | ||
James Page | Pending | ||
Corey Bryant | Pending | ||
Review via email: mp+260461@code.launchpad.net |
Commit message
[saviq] Add facilities for a container-scoped relation to affect dashboard settings
Description of the change
NOTE: this includes lp:~saviq/charm-helpers/glob-restart and lp:~saviq/charm-helpers/confg-patterns
This is a first, naive try at making the dashboard charm support a subordinate charm to modify the dashboard settings. Its purpose is to allow adding dashboard plugins and such as subordinate charms.
The first charm using this:
uosci-testing-bot (uosci-testing-bot) wrote : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #4527 openstack-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #4338 openstack-
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Corey Bryant (corey.bryant) wrote : | # |
I'm wondering if we can limit subordinate charms to only updating pluggable settings by adding *.py files to openstack_
http://
If we don't allow subordinates to change default settings in local_settings.py, we should be able to at least avoid some conflicts in the case where multiple subordinate charms are deployed.
There still seems to be opportunity for conflicts and precedence issues if multiple subordinates are deployed and I'm not sure the best way to avoid those other than some general guidance in the openstack-dashboard README to help developers of subordinate plugins best avoid conflicts.
Corey Bryant (corey.bryant) wrote : | # |
There's one comment inline below too.
Michał Sawicz (saviq) wrote : | # |
Indeed, after thinking a bit more, I could model the relation after the pluggable setting mechanism, even be smart and "backport" some of those to previous releases.
As for conflicts and ordering, I can expose the precedence so that you can set it per-subordinate, and warn about conflicts in the logs, that's the best we can do I think.
Corey Bryant (corey.bryant) wrote : | # |
> Indeed, after thinking a bit more, I could model the relation after the
> pluggable setting mechanism, even be smart and "backport" some of those to
> previous releases.
>
> As for conflicts and ordering, I can expose the precedence so that you can set
> it per-subordinate, and warn about conflicts in the logs, that's the best we
> can do I think.
Yes, a precedence option makes sense. Perhaps that could be a number that ends up being used in the *.py file name. e.g. _##_pluggable_
Corey Bryant (corey.bryant) wrote : | # |
Responded to inline comment
Michał Sawicz (saviq) wrote : | # |
> Yes, a precedence option makes sense. Perhaps that could be a number
> that ends up being used in the *.py file name. e.g. _##_pluggable_
Yup, that was exactly my plan.
Michał Sawicz (saviq) : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #4342 openstack-
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Michał Sawicz (saviq) wrote : | # |
I've added support for file-based plugins and priority. I'm no longer sure we should try and interpret what the subordinate charm wants... ultimately those are just python files, we'd need to add bits like 'imports' to the relation data, and likely free-form 'additional_
I feel that adding all the possible settings from [1] could lead to maintenance strain for relatively little gain.
Let me know please if you still feel this is the route we should take.
http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #4994 openstack-
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #4674 openstack-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #4400 openstack-
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Billy Olsen (billy-olsen) wrote : | # |
Hey Michal, can you fix the lint errors and re-propose? I'm not sure if the amulet tests are passing or not due to issues beyond your control during the last osci run for this branch.
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8755 openstack-
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #8086 openstack-
UNIT OK: passed
Michał Sawicz (saviq) wrote : | # |
@Billy: I'm not sure what to do, lint passes locally, and if I change the indentation, it fails. It's as if flake8 on jenkins is old or something?
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6032 openstack-
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8819 openstack-
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #8147 openstack-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8820 openstack-
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #8148 openstack-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #8149 openstack-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8822 openstack-
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6054 openstack-
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 124
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Michał Sawicz (saviq) wrote : | # |
There's a simpler way we want to approach this now:
https:/
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6056 openstack-
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6053 openstack-
AMULET OK: passed
Build: http://
Preview Diff
1 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' |
2 | --- hooks/charmhelpers/contrib/openstack/context.py 2015-08-19 13:51:03 +0000 |
3 | +++ hooks/charmhelpers/contrib/openstack/context.py 2015-08-27 11:49:36 +0000 |
4 | @@ -199,6 +199,15 @@ |
5 | raise NotImplementedError |
6 | |
7 | |
8 | +class OSPatternContextGenerator(OSContextGenerator): |
9 | + """Base class for pattern context generators. |
10 | + |
11 | + __call__ should return a dictionary of { tuple: dict }, where the tuple |
12 | + will be used as input for format() for the filename pattern as registered |
13 | + by OSConfigRenderer.register_pattern(). |
14 | + """ |
15 | + |
16 | + |
17 | class SharedDBContext(OSContextGenerator): |
18 | interfaces = ['shared-db'] |
19 | |
20 | |
21 | === modified file 'hooks/charmhelpers/contrib/openstack/templating.py' |
22 | --- hooks/charmhelpers/contrib/openstack/templating.py 2015-07-29 10:48:39 +0000 |
23 | +++ hooks/charmhelpers/contrib/openstack/templating.py 2015-08-27 11:49:36 +0000 |
24 | @@ -14,7 +14,9 @@ |
25 | # You should have received a copy of the GNU Lesser General Public License |
26 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
27 | |
28 | +import glob |
29 | import os |
30 | +import string |
31 | |
32 | import six |
33 | |
34 | @@ -25,6 +27,7 @@ |
35 | INFO |
36 | ) |
37 | from charmhelpers.contrib.openstack.utils import OPENSTACK_CODENAMES |
38 | +from charmhelpers.contrib.openstack.context import OSPatternContextGenerator |
39 | |
40 | try: |
41 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions |
42 | @@ -96,7 +99,7 @@ |
43 | else: |
44 | self.contexts = contexts |
45 | |
46 | - self._complete_contexts = [] |
47 | + self._complete_contexts = set() |
48 | |
49 | def context(self): |
50 | ctxt = {} |
51 | @@ -105,9 +108,7 @@ |
52 | if _ctxt: |
53 | ctxt.update(_ctxt) |
54 | # track interfaces for every complete context. |
55 | - [self._complete_contexts.append(interface) |
56 | - for interface in context.interfaces |
57 | - if interface not in self._complete_contexts] |
58 | + self._complete_contexts.update(context.interfaces) |
59 | return ctxt |
60 | |
61 | def complete_contexts(self): |
62 | @@ -120,6 +121,41 @@ |
63 | return self._complete_contexts |
64 | |
65 | |
66 | +class OSPatternConfigTemplate(OSConfigTemplate): |
67 | + """ |
68 | + Associates a config pattern template with a list of context generators. |
69 | + Responsible for constructing a template context based on those generators. |
70 | + """ |
71 | + def __init__(self, pattern, contexts): |
72 | + self.pattern = pattern |
73 | + super(OSPatternConfigTemplate, self).__init__(config_file=None, |
74 | + contexts=contexts) |
75 | + |
76 | + def context(self): |
77 | + base_ctxt = {} |
78 | + ctxt = {} |
79 | + for context in self.contexts: |
80 | + _ctxt = context() |
81 | + if not _ctxt: |
82 | + continue |
83 | + elif isinstance(context, OSPatternContextGenerator): |
84 | + # for each returned key initialize its context with base_ctxt |
85 | + # if not defined before and update with new data |
86 | + for k, v in _ctxt.items(): |
87 | + if k not in ctxt: |
88 | + ctxt[k] = base_ctxt.copy() |
89 | + ctxt[k].update(v) |
90 | + else: |
91 | + # update the base context and all pre-existing file-specific |
92 | + # contexts |
93 | + base_ctxt.update(_ctxt) |
94 | + for key in ctxt: |
95 | + ctxt[key].update(_ctxt) |
96 | + # track interfaces for every complete context. |
97 | + self._complete_contexts.update(context.interfaces) |
98 | + return ctxt |
99 | + |
100 | + |
101 | class OSConfigRenderer(object): |
102 | """ |
103 | This class provides a common templating system to be used by OpenStack |
104 | @@ -132,6 +168,13 @@ |
105 | # import some common context generates from charmhelpers |
106 | from charmhelpers.contrib.openstack import context |
107 | |
108 | + # or create your own |
109 | + class SimpleContextGenerator(OSContextGenerator): |
110 | + def __call__(): |
111 | + return { |
112 | + 'key': 'value' |
113 | + } |
114 | + |
115 | # Create a renderer object for a specific OS release. |
116 | configs = OSConfigRenderer(templates_dir='/tmp/templates', |
117 | openstack_release='folsom') |
118 | @@ -142,12 +185,31 @@ |
119 | configs.register(config_file='/etc/nova/api-paste.ini', |
120 | contexts=[context.IdentityServiceContext()]) |
121 | configs.register(config_file='/etc/haproxy/haproxy.conf', |
122 | - contexts=[context.HAProxyContext()]) |
123 | + contexts=[context.HAProxyContext(), |
124 | + SimpleContextGenerator()]) |
125 | # write out a single config |
126 | configs.write('/etc/nova/nova.conf') |
127 | # write out all registered configs |
128 | configs.write_all() |
129 | |
130 | + Using patterns:: |
131 | + class DashboardContextGenerator(OSPatternContextGenerator): |
132 | + def __call__(): |
133 | + return { |
134 | + (40, 'router'): { 'DISABLED': True } |
135 | + } |
136 | + |
137 | + configs.register_pattern( |
138 | + pattern='/usr/share/openstack-dashboard/openstack_dashboard' |
139 | + '/enabled/_{}_juju_{}.py', |
140 | + contexts=[ |
141 | + DashboardContextGenerator() |
142 | + ]) |
143 | + # delete all files matching the pattern and |
144 | + # write _40_juju_router.py anew |
145 | + configs.write('/usr/share/openstack-dashboard/openstack_dashboard' |
146 | + '/enabled/_{}_juju_{}.py') |
147 | + |
148 | **OpenStack Releases and template loading** |
149 | |
150 | When the object is instantiated, it is associated with a specific OS |
151 | @@ -219,6 +281,15 @@ |
152 | contexts=contexts) |
153 | log('Registered config file: %s' % config_file, level=INFO) |
154 | |
155 | + def register_pattern(self, pattern, contexts): |
156 | + """ |
157 | + Register a config file name pattern with a list of context generators |
158 | + to be called during rendering. Use standard format() specification. |
159 | + """ |
160 | + self.templates[pattern] = OSPatternConfigTemplate(pattern=pattern, |
161 | + contexts=contexts) |
162 | + log('Registered config pattern: %s' % pattern, level=INFO) |
163 | + |
164 | def _get_tmpl_env(self): |
165 | if not self._tmpl_env: |
166 | loader = get_loader(self.templates_dir, self.openstack_release) |
167 | @@ -234,7 +305,8 @@ |
168 | if config_file not in self.templates: |
169 | log('Config not registered: %s' % config_file, level=ERROR) |
170 | raise OSConfigException |
171 | - ctxt = self.templates[config_file].context() |
172 | + ostemplate = self.templates[config_file] |
173 | + ctxt = ostemplate.context() |
174 | |
175 | _tmpl = os.path.basename(config_file) |
176 | try: |
177 | @@ -253,7 +325,14 @@ |
178 | raise e |
179 | |
180 | log('Rendering from template: %s' % _tmpl, level=INFO) |
181 | - return template.render(ctxt) |
182 | + |
183 | + if not isinstance(ostemplate, OSPatternConfigTemplate): |
184 | + ctxt = {(): ctxt} |
185 | + |
186 | + renders = {} |
187 | + for args, file_ctxt in ctxt.items(): |
188 | + renders[args] = template.render(file_ctxt) |
189 | + return renders |
190 | |
191 | def write(self, config_file): |
192 | """ |
193 | @@ -263,10 +342,16 @@ |
194 | log('Config not registered: %s' % config_file, level=ERROR) |
195 | raise OSConfigException |
196 | |
197 | - _out = self.render(config_file) |
198 | - |
199 | - with open(config_file, 'wb') as out: |
200 | - out.write(_out) |
201 | + renders = self.render(config_file) |
202 | + |
203 | + files = glob.glob(''.join([t[0] + ('' if t[1] is None else '*') |
204 | + for t in string.Formatter().parse(config_file)])) |
205 | + for name in files: |
206 | + os.unlink(name) |
207 | + |
208 | + for args, render in renders.items(): |
209 | + with open(config_file.format(*args), 'wb') as out: |
210 | + out.write(render) |
211 | |
212 | log('Wrote template %s.' % config_file, level=INFO) |
213 | |
214 | |
215 | === modified file 'hooks/charmhelpers/core/host.py' |
216 | === modified file 'hooks/horizon_contexts.py' |
217 | --- hooks/horizon_contexts.py 2015-04-09 14:56:00 +0000 |
218 | +++ hooks/horizon_contexts.py 2015-08-27 11:49:36 +0000 |
219 | @@ -10,6 +10,7 @@ |
220 | ) |
221 | from charmhelpers.contrib.openstack.context import ( |
222 | OSContextGenerator, |
223 | + OSPatternContextGenerator, |
224 | HAProxyContext, |
225 | context_complete |
226 | ) |
227 | @@ -27,6 +28,7 @@ |
228 | |
229 | from base64 import b64decode |
230 | import os |
231 | +import re |
232 | |
233 | |
234 | class HorizonHAProxyContext(HAProxyContext): |
235 | @@ -172,3 +174,52 @@ |
236 | 'disable_router': False if config('profile') in ['cisco'] else True |
237 | } |
238 | return ctxt |
239 | + |
240 | + |
241 | +class LocalSettingsContext(OSContextGenerator): |
242 | + def __call__(self): |
243 | + ''' Additional config stanzas to be appended to local_settings.py ''' |
244 | + |
245 | + relations = [] |
246 | + |
247 | + for rid in relation_ids("plugin"): |
248 | + try: |
249 | + unit = related_units(rid)[0] |
250 | + except IndexError: |
251 | + pass |
252 | + else: |
253 | + rdata = relation_get(unit=unit, rid=rid) |
254 | + if set(('local-settings', 'priority')) <= set(rdata.keys()): |
255 | + relations.append((unit, rdata)) |
256 | + |
257 | + ctxt = { |
258 | + 'settings': [ |
259 | + '# {0}\n{1}'.format(u, rd['local-settings']) |
260 | + for u, rd in sorted(relations, |
261 | + key=lambda r: r[1]['priority'])] |
262 | + } |
263 | + return ctxt |
264 | + |
265 | + |
266 | +class PluginsContext(OSPatternContextGenerator): |
267 | + def __call__(self): |
268 | + |
269 | + plugins = {} |
270 | + |
271 | + for rid in relation_ids("plugin"): |
272 | + try: |
273 | + unit = related_units(rid)[0] |
274 | + except IndexError: |
275 | + pass |
276 | + else: |
277 | + rdata = relation_get(unit=unit, rid=rid) |
278 | + try: |
279 | + if rdata['priority'] is not None and rdata['plugin-file']: |
280 | + service = re.sub('[^a-z0-9_]', '_', unit.split('/')[0]) |
281 | + plugins[(rdata['priority'], service)] = { |
282 | + 'unit': unit, |
283 | + 'plugin_file': rdata['plugin-file']} |
284 | + except KeyError: |
285 | + pass |
286 | + |
287 | + return plugins |
288 | |
289 | === modified file 'hooks/horizon_hooks.py' |
290 | --- hooks/horizon_hooks.py 2015-05-07 14:33:05 +0000 |
291 | +++ hooks/horizon_hooks.py 2015-08-27 11:49:36 +0000 |
292 | @@ -33,7 +33,7 @@ |
293 | register_configs, |
294 | restart_map, |
295 | services, |
296 | - LOCAL_SETTINGS, HAPROXY_CONF, |
297 | + LOCAL_SETTINGS, HAPROXY_CONF, PLUGIN_SETTINGS, |
298 | enable_ssl, |
299 | do_openstack_upgrade, |
300 | git_install, |
301 | @@ -244,6 +244,18 @@ |
302 | nrpe_setup.write() |
303 | |
304 | |
305 | +@hooks.hook('plugin-relation-joined') |
306 | +def plugin_relation_joined(): |
307 | + relation_set(release=os_release("openstack-dashboard")) |
308 | + |
309 | + |
310 | +@hooks.hook('plugin-relation-changed') |
311 | +@restart_on_change(restart_map()) |
312 | +def update_plugin_config(): |
313 | + CONFIGS.write(LOCAL_SETTINGS) |
314 | + CONFIGS.write(PLUGIN_SETTINGS) |
315 | + |
316 | + |
317 | def main(): |
318 | try: |
319 | hooks.execute(sys.argv) |
320 | |
321 | === modified file 'hooks/horizon_utils.py' |
322 | --- hooks/horizon_utils.py 2015-07-15 18:46:45 +0000 |
323 | +++ hooks/horizon_utils.py 2015-08-27 11:49:36 +0000 |
324 | @@ -5,6 +5,7 @@ |
325 | import pwd |
326 | import subprocess |
327 | import shutil |
328 | +import string |
329 | from collections import OrderedDict |
330 | |
331 | import charmhelpers.contrib.openstack.context as context |
332 | @@ -93,6 +94,9 @@ |
333 | APACHE_DEFAULT = "%s/sites-available/default" % (APACHE_CONF_DIR) |
334 | ROUTER_SETTING = \ |
335 | "/usr/share/openstack-dashboard/openstack_dashboard/enabled/_40_router.py" |
336 | +PLUGIN_SETTINGS = \ |
337 | + "/usr/share/openstack-dashboard/openstack_dashboard/local/enabled" \ |
338 | + "/_{}_juju_{}.py" |
339 | |
340 | TEMPLATES = 'templates' |
341 | |
342 | @@ -100,7 +104,8 @@ |
343 | (LOCAL_SETTINGS, { |
344 | 'hook_contexts': [horizon_contexts.HorizonContext(), |
345 | horizon_contexts.IdentityServiceContext(), |
346 | - context.SyslogContext()], |
347 | + context.SyslogContext(), |
348 | + horizon_contexts.LocalSettingsContext()], |
349 | 'services': ['apache2'] |
350 | }), |
351 | (APACHE_CONF, { |
352 | @@ -143,6 +148,10 @@ |
353 | 'hook_contexts': [horizon_contexts.RouterSettingContext()], |
354 | 'services': ['apache2'], |
355 | }), |
356 | + (PLUGIN_SETTINGS, { |
357 | + 'hook_contexts': [horizon_contexts.PluginsContext()], |
358 | + 'services': ['apache2'], |
359 | + }), |
360 | ]) |
361 | |
362 | |
363 | @@ -181,6 +190,11 @@ |
364 | if os.path.exists(os.path.dirname(ROUTER_SETTING)): |
365 | configs.register(ROUTER_SETTING, |
366 | CONFIG_FILES[ROUTER_SETTING]['hook_contexts']) |
367 | + |
368 | + if os_release('openstack_dashboard') >= 'icehouse': |
369 | + configs.register_pattern(PLUGIN_SETTINGS, |
370 | + CONFIG_FILES[PLUGIN_SETTINGS] |
371 | + ['hook_contexts']) |
372 | return configs |
373 | |
374 | |
375 | @@ -198,7 +212,10 @@ |
376 | for svc in ctxt['services']: |
377 | svcs.append(svc) |
378 | if svcs: |
379 | - _map.append((f, svcs)) |
380 | + # replace all formatting in path with asterisks for glob |
381 | + path = ''.join([t[0] + ('' if t[1] is None else '*') |
382 | + for t in string.Formatter().parse(f)]) |
383 | + _map.append((path, svcs)) |
384 | return OrderedDict(_map) |
385 | |
386 | |
387 | |
388 | === added symlink 'hooks/plugin-relation-changed' |
389 | === target is u'horizon_hooks.py' |
390 | === added symlink 'hooks/plugin-relation-joined' |
391 | === target is u'horizon_hooks.py' |
392 | === modified file 'metadata.yaml' |
393 | --- metadata.yaml 2014-10-30 03:30:36 +0000 |
394 | +++ metadata.yaml 2015-08-27 11:49:36 +0000 |
395 | @@ -17,6 +17,9 @@ |
396 | ha: |
397 | interface: hacluster |
398 | scope: container |
399 | + plugin: |
400 | + interface: horizon-plugin |
401 | + scope: container |
402 | peers: |
403 | cluster: |
404 | interface: openstack-dashboard-ha |
405 | |
406 | === modified file 'templates/essex/local_settings.py' |
407 | --- templates/essex/local_settings.py 2014-02-27 10:07:57 +0000 |
408 | +++ templates/essex/local_settings.py 2015-08-27 11:49:36 +0000 |
409 | @@ -118,3 +118,5 @@ |
410 | } |
411 | } |
412 | } |
413 | + |
414 | +{{ settings|join('\n\n') }} |
415 | |
416 | === modified file 'templates/folsom/local_settings.py' |
417 | --- templates/folsom/local_settings.py 2014-05-28 16:05:37 +0000 |
418 | +++ templates/folsom/local_settings.py 2015-08-27 11:49:36 +0000 |
419 | @@ -167,3 +167,5 @@ |
420 | # offline compression by default. To enable online compression, install |
421 | # the node-less package and enable the following option. |
422 | COMPRESS_OFFLINE = {{ compress_offline }} |
423 | + |
424 | +{{ settings|join('\n\n') }} |
425 | \ No newline at end of file |
426 | |
427 | === modified file 'templates/grizzly/local_settings.py' |
428 | --- templates/grizzly/local_settings.py 2014-05-28 16:05:37 +0000 |
429 | +++ templates/grizzly/local_settings.py 2015-08-27 11:49:36 +0000 |
430 | @@ -263,3 +263,5 @@ |
431 | } |
432 | } |
433 | } |
434 | + |
435 | +{{ settings|join('\n\n') }} |
436 | |
437 | === modified file 'templates/havana/local_settings.py' |
438 | --- templates/havana/local_settings.py 2014-07-22 07:18:55 +0000 |
439 | +++ templates/havana/local_settings.py 2015-08-27 11:49:36 +0000 |
440 | @@ -483,3 +483,5 @@ |
441 | # installations should have this set accordingly. For more information |
442 | # see https://docs.djangoproject.com/en/dev/ref/settings/. |
443 | ALLOWED_HOSTS = '*' |
444 | + |
445 | +{{ settings|join('\n\n') }} |
446 | \ No newline at end of file |
447 | |
448 | === added file 'templates/icehouse/_{}_juju_{}.py' |
449 | --- templates/icehouse/_{}_juju_{}.py 1970-01-01 00:00:00 +0000 |
450 | +++ templates/icehouse/_{}_juju_{}.py 2015-08-27 11:49:36 +0000 |
451 | @@ -0,0 +1,2 @@ |
452 | +# {{ unit }} |
453 | +{{ plugin_file }} |
454 | \ No newline at end of file |
455 | |
456 | === modified file 'templates/icehouse/local_settings.py' |
457 | --- templates/icehouse/local_settings.py 2014-12-01 23:45:41 +0000 |
458 | +++ templates/icehouse/local_settings.py 2015-08-27 11:49:36 +0000 |
459 | @@ -514,3 +514,5 @@ |
460 | # installations should have this set accordingly. For more information |
461 | # see https://docs.djangoproject.com/en/dev/ref/settings/. |
462 | ALLOWED_HOSTS = '*' |
463 | + |
464 | +{{ settings|join('\n\n') }} |
465 | \ No newline at end of file |
466 | |
467 | === modified file 'templates/juno/local_settings.py' |
468 | --- templates/juno/local_settings.py 2014-12-01 23:45:41 +0000 |
469 | +++ templates/juno/local_settings.py 2015-08-27 11:49:36 +0000 |
470 | @@ -619,3 +619,5 @@ |
471 | # installations should have this set accordingly. For more information |
472 | # see https://docs.djangoproject.com/en/dev/ref/settings/. |
473 | ALLOWED_HOSTS = '*' |
474 | + |
475 | +{{ settings|join('\n\n') }} |
476 | \ No newline at end of file |
477 | |
478 | === modified file 'unit_tests/test_horizon_contexts.py' |
479 | --- unit_tests/test_horizon_contexts.py 2015-04-09 14:34:44 +0000 |
480 | +++ unit_tests/test_horizon_contexts.py 2015-08-27 11:49:36 +0000 |
481 | @@ -251,3 +251,46 @@ |
482 | self.test_config.set('profile', None) |
483 | self.assertEquals(horizon_contexts.RouterSettingContext()(), |
484 | {'disable_router': True, }) |
485 | + |
486 | + def test_LocalSettingsContext(self): |
487 | + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] |
488 | + self.related_units.side_effect = [['horizon-plugin/0'], |
489 | + ['horizon-plugin-too/0']] |
490 | + self.relation_get.side_effect = [{'priority': 99, |
491 | + 'local-settings': 'FOO = True'}, |
492 | + {'priority': 60, |
493 | + 'local-settings': 'BAR = False'}] |
494 | + |
495 | + self.assertEquals(horizon_contexts.LocalSettingsContext()(), |
496 | + {'settings': ['# horizon-plugin-too/0\n' |
497 | + 'BAR = False', |
498 | + '# horizon-plugin/0\n' |
499 | + 'FOO = True']}) |
500 | + |
501 | + def test_PluginsContext(self): |
502 | + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] |
503 | + self.related_units.side_effect = [['horizon-plugin/0'], |
504 | + ['horizon-plugin-too/0']] |
505 | + self.relation_get.side_effect = [ |
506 | + { |
507 | + 'priority': 99, |
508 | + 'plugin-file': 'DASHBOARD = \'some_dashboard\'\n' |
509 | + 'DISABLED = True'}, |
510 | + { |
511 | + 'priority': 60, |
512 | + 'plugin-file': 'PANEL = \'some_panel\'\n' |
513 | + 'PANEL_DASHBOARD = \'admin\'\n' |
514 | + 'PANEL_GROUP = \'admin\'\n' |
515 | + 'REMOVE_PANEL = True'}] |
516 | + |
517 | + self.assertEquals(horizon_contexts.PluginsContext()(), |
518 | + {(99, 'horizon_plugin'): { |
519 | + 'unit': 'horizon-plugin/0', |
520 | + 'plugin_file': 'DASHBOARD = \'some_dashboard\'\n' |
521 | + 'DISABLED = True'}, |
522 | + (60, 'horizon_plugin_too'): { |
523 | + 'unit': 'horizon-plugin-too/0', |
524 | + 'plugin_file': 'PANEL = \'some_panel\'\n' |
525 | + 'PANEL_DASHBOARD = \'admin\'\n' |
526 | + 'PANEL_GROUP = \'admin\'\n' |
527 | + 'REMOVE_PANEL = True'}}) |
528 | |
529 | === modified file 'unit_tests/test_horizon_hooks.py' |
530 | --- unit_tests/test_horizon_hooks.py 2015-06-19 16:08:08 +0000 |
531 | +++ unit_tests/test_horizon_hooks.py 2015-08-27 11:49:36 +0000 |
532 | @@ -135,8 +135,8 @@ |
533 | def test_upgrade_charm_hook(self, _git_requested, _service, _hash): |
534 | _git_requested.return_value = False |
535 | side_effects = [] |
536 | - [side_effects.append(None) for f in RESTART_MAP.keys()] |
537 | - [side_effects.append('bar') for f in RESTART_MAP.keys()] |
538 | + [side_effects.append({}) for f in RESTART_MAP.keys()] |
539 | + [side_effects.append({f: 'bar'}) for f in RESTART_MAP.keys()] |
540 | _hash.side_effect = side_effects |
541 | self.filter_installed_packages.return_value = ['foo'] |
542 | self._call_hook('upgrade-charm') |
543 | |
544 | === modified file 'unit_tests/test_horizon_utils.py' |
545 | --- unit_tests/test_horizon_utils.py 2015-05-08 12:28:54 +0000 |
546 | +++ unit_tests/test_horizon_utils.py 2015-08-27 11:49:36 +0000 |
547 | @@ -57,6 +57,8 @@ |
548 | ('/etc/haproxy/haproxy.cfg', ['haproxy']), |
549 | ('/usr/share/openstack-dashboard/openstack_dashboard/enabled/' |
550 | '_40_router.py', ['apache2']), |
551 | + ('/usr/share/openstack-dashboard/openstack_dashboard/local/' |
552 | + 'enabled/_*_juju_*.py', ['apache2']) |
553 | ]) |
554 | self.assertEquals(horizon_utils.restart_map(), ex_map) |
555 | |
556 | @@ -142,6 +144,33 @@ |
557 | call(conf, horizon_utils.CONFIG_FILES[conf]['hook_contexts'])) |
558 | configs.register.assert_has_calls(calls) |
559 | |
560 | + @patch('os.path.isdir') |
561 | + @patch('os.path.exists') |
562 | + def test_register_configs_icehouse(self, _exists, _isdir): |
563 | + _exists.return_value = True |
564 | + _isdir.return_value = True |
565 | + self.os_release.return_value = 'icehouse' |
566 | + self.cmp_pkgrevno.return_value = -1 |
567 | + configs = horizon_utils.register_configs() |
568 | + confs = [horizon_utils.LOCAL_SETTINGS, |
569 | + horizon_utils.HAPROXY_CONF, |
570 | + horizon_utils.PORTS_CONF, |
571 | + horizon_utils.APACHE_DEFAULT, |
572 | + horizon_utils.APACHE_CONF, |
573 | + horizon_utils.APACHE_SSL, |
574 | + horizon_utils.ROUTER_SETTING] |
575 | + calls = [] |
576 | + for conf in confs: |
577 | + calls.append( |
578 | + call(conf, |
579 | + horizon_utils.CONFIG_FILES[conf]['hook_contexts'])) |
580 | + configs.register.assert_has_calls(calls) |
581 | + |
582 | + configs.register_pattern.assert_has_calls([ |
583 | + call(horizon_utils.PLUGIN_SETTINGS, |
584 | + horizon_utils.CONFIG_FILES[horizon_utils.PLUGIN_SETTINGS] |
585 | + ['hook_contexts'])]) |
586 | + |
587 | @patch.object(horizon_utils, 'git_install_requested') |
588 | @patch.object(horizon_utils, 'git_clone_and_install') |
589 | @patch.object(horizon_utils, 'git_post_install') |
charm_lint_check #4847 openstack- dashboard- next for saviq mp260461
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http:// paste.ubuntu. com/11410405/ 10.245. 162.77: 8080/job/ charm_lint_ check/4847/
Build: http://