Merge lp:~sdn-charmers/charms/trusty/openstack-dashboard/add-settings into lp:~openstack-charmers-archive/charms/trusty/openstack-dashboard/next

Proposed by Michał Sawicz
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
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:

lp:~sdn-charmers/charms/trusty/horizon-contrail/trunk/

To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

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/
Build: http://10.245.162.77:8080/job/charm_lint_check/4847/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #4527 openstack-dashboard-next for saviq mp260461
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/4527/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #4338 openstack-dashboard-next for saviq mp260461
    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://paste.ubuntu.com/11410924/
Build: http://10.245.162.77:8080/job/charm_amulet_test/4338/

Revision history for this message
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_dashboard/local/enabled as described here:
http://docs.openstack.org/developer/horizon/topics/settings.html#pluggable-settings-label

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.

Revision history for this message
Corey Bryant (corey.bryant) wrote :

There's one comment inline below too.

Revision history for this message
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.

Revision history for this message
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_xyz.py

Revision history for this message
Corey Bryant (corey.bryant) wrote :

Responded to inline comment

Revision history for this message
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_xyz.py

Yup, that was exactly my plan.

Revision history for this message
Michał Sawicz (saviq) :
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #4342 openstack-dashboard-next for saviq mp260461
    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://paste.ubuntu.com/11438028/
Build: http://10.245.162.77:8080/job/charm_amulet_test/4342/

Revision history for this message
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_stanzas' or so to let subordinates fall back to plain text python as opposed to structured relation data.

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://docs.openstack.org/developer/horizon/topics/settings.html#pluggable-settings-label

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #4994 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/11543523/
Build: http://10.245.162.77:8080/job/charm_lint_check/4994/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #4674 openstack-dashboard-next for saviq mp260461
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/4674/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #4400 openstack-dashboard-next for saviq mp260461
    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://paste.ubuntu.com/11543632/
Build: http://10.245.162.77:8080/job/charm_amulet_test/4400/

Revision history for this message
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.

review: Needs Fixing
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #8755 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/12197832/
Build: http://10.245.162.77:8080/job/charm_lint_check/8755/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #8086 openstack-dashboard-next for saviq mp260461
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/8086/

Revision history for this message
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?

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6032 openstack-dashboard-next for saviq mp260461
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/6032/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #8819 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/12205038/
Build: http://10.245.162.77:8080/job/charm_lint_check/8819/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #8147 openstack-dashboard-next for saviq mp260461
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/8147/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #8820 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/12205136/
Build: http://10.245.162.77:8080/job/charm_lint_check/8820/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #8148 openstack-dashboard-next for saviq mp260461
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/8148/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #8149 openstack-dashboard-next for saviq mp260461
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/8149/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #8822 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/12205246/
Build: http://10.245.162.77:8080/job/charm_lint_check/8822/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6054 openstack-dashboard-next for saviq mp260461
    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://paste.ubuntu.com/12205259/
Build: http://10.245.162.77:8080/job/charm_amulet_test/6054/

Revision history for this message
Michał Sawicz (saviq) wrote :
review: Disapprove
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6056 openstack-dashboard-next for saviq mp260461
    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://paste.ubuntu.com/12205357/
Build: http://10.245.162.77:8080/job/charm_amulet_test/6056/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6053 openstack-dashboard-next for saviq mp260461
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/6053/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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')

Subscribers

People subscribed via source and target branches