Merge lp:~hopem/charm-helpers/lp1522375 into lp:charm-helpers

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 577
Proposed branch: lp:~hopem/charm-helpers/lp1522375
Merge into: lp:charm-helpers
Diff against target: 321 lines (+143/-83)
5 files modified
charmhelpers/contrib/openstack/context.py (+5/-83)
charmhelpers/contrib/openstack/exceptions.py (+6/-0)
charmhelpers/contrib/openstack/utils.py (+81/-0)
charmhelpers/contrib/storage/linux/ceph.py (+41/-0)
tests/contrib/storage/test_linux_ceph.py (+10/-0)
To merge this branch: bzr merge lp:~hopem/charm-helpers/lp1522375
Reviewer Review Type Date Requested Status
James Page Approve
Review via email: mp+295612@code.launchpad.net
To post a comment you must log in.
Revision history for this message
James Page (james-page) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/openstack/context.py'
2--- charmhelpers/contrib/openstack/context.py 2016-04-04 18:06:59 +0000
3+++ charmhelpers/contrib/openstack/context.py 2016-05-25 12:22:56 +0000
4@@ -23,7 +23,6 @@
5 from subprocess import check_call, CalledProcessError
6
7 import six
8-import yaml
9
10 from charmhelpers.fetch import (
11 apt_install,
12@@ -50,6 +49,7 @@
13
14 from charmhelpers.core.sysctl import create as sysctl_create
15 from charmhelpers.core.strutils import bool_from_string
16+from charmhelpers.contrib.openstack.exceptions import OSContextError
17
18 from charmhelpers.core.host import (
19 get_bond_master,
20@@ -88,7 +88,10 @@
21 is_address_in_network,
22 is_bridge_member,
23 )
24-from charmhelpers.contrib.openstack.utils import get_host_ip
25+from charmhelpers.contrib.openstack.utils import (
26+ config_flags_parser,
27+ get_host_ip,
28+)
29 from charmhelpers.core.unitdata import kv
30
31 try:
32@@ -101,10 +104,6 @@
33 ADDRESS_TYPES = ['admin', 'internal', 'public']
34
35
36-class OSContextError(Exception):
37- pass
38-
39-
40 def ensure_packages(packages):
41 """Install but do not upgrade required plugin packages."""
42 required = filter_installed_packages(packages)
43@@ -125,83 +124,6 @@
44 return True
45
46
47-def config_flags_parser(config_flags):
48- """Parses config flags string into dict.
49-
50- This parsing method supports a few different formats for the config
51- flag values to be parsed:
52-
53- 1. A string in the simple format of key=value pairs, with the possibility
54- of specifying multiple key value pairs within the same string. For
55- example, a string in the format of 'key1=value1, key2=value2' will
56- return a dict of:
57-
58- {'key1': 'value1',
59- 'key2': 'value2'}.
60-
61- 2. A string in the above format, but supporting a comma-delimited list
62- of values for the same key. For example, a string in the format of
63- 'key1=value1, key2=value3,value4,value5' will return a dict of:
64-
65- {'key1', 'value1',
66- 'key2', 'value2,value3,value4'}
67-
68- 3. A string containing a colon character (:) prior to an equal
69- character (=) will be treated as yaml and parsed as such. This can be
70- used to specify more complex key value pairs. For example,
71- a string in the format of 'key1: subkey1=value1, subkey2=value2' will
72- return a dict of:
73-
74- {'key1', 'subkey1=value1, subkey2=value2'}
75-
76- The provided config_flags string may be a list of comma-separated values
77- which themselves may be comma-separated list of values.
78- """
79- # If we find a colon before an equals sign then treat it as yaml.
80- # Note: limit it to finding the colon first since this indicates assignment
81- # for inline yaml.
82- colon = config_flags.find(':')
83- equals = config_flags.find('=')
84- if colon > 0:
85- if colon < equals or equals < 0:
86- return yaml.safe_load(config_flags)
87-
88- if config_flags.find('==') >= 0:
89- log("config_flags is not in expected format (key=value)", level=ERROR)
90- raise OSContextError
91-
92- # strip the following from each value.
93- post_strippers = ' ,'
94- # we strip any leading/trailing '=' or ' ' from the string then
95- # split on '='.
96- split = config_flags.strip(' =').split('=')
97- limit = len(split)
98- flags = {}
99- for i in range(0, limit - 1):
100- current = split[i]
101- next = split[i + 1]
102- vindex = next.rfind(',')
103- if (i == limit - 2) or (vindex < 0):
104- value = next
105- else:
106- value = next[:vindex]
107-
108- if i == 0:
109- key = current
110- else:
111- # if this not the first entry, expect an embedded key.
112- index = current.rfind(',')
113- if index < 0:
114- log("Invalid config value(s) at index %s" % (i), level=ERROR)
115- raise OSContextError
116- key = current[index + 1:]
117-
118- # Add to collection.
119- flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
120-
121- return flags
122-
123-
124 class OSContextGenerator(object):
125 """Base class for all context generators."""
126 interfaces = []
127
128=== added file 'charmhelpers/contrib/openstack/exceptions.py'
129--- charmhelpers/contrib/openstack/exceptions.py 1970-01-01 00:00:00 +0000
130+++ charmhelpers/contrib/openstack/exceptions.py 2016-05-25 12:22:56 +0000
131@@ -0,0 +1,6 @@
132+class OSContextError(Exception):
133+ """Raised when an error occurs during context generation.
134+
135+ This exception is principally used in contrib.openstack.context
136+ """
137+ pass
138
139=== modified file 'charmhelpers/contrib/openstack/utils.py'
140--- charmhelpers/contrib/openstack/utils.py 2016-05-20 18:40:40 +0000
141+++ charmhelpers/contrib/openstack/utils.py 2016-05-25 12:22:56 +0000
142@@ -47,6 +47,7 @@
143 charm_dir,
144 DEBUG,
145 INFO,
146+ ERROR,
147 related_units,
148 relation_ids,
149 relation_set,
150@@ -83,6 +84,7 @@
151 from charmhelpers.fetch import apt_install, apt_cache, install_remote
152 from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
153 from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
154+from charmhelpers.contrib.openstack.exceptions import OSContextError
155
156 CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
157 CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
158@@ -1616,3 +1618,82 @@
159 restart_functions)
160 return wrapped_f
161 return wrap
162+
163+
164+def config_flags_parser(config_flags):
165+ """Parses config flags string into dict.
166+
167+ This parsing method supports a few different formats for the config
168+ flag values to be parsed:
169+
170+ 1. A string in the simple format of key=value pairs, with the possibility
171+ of specifying multiple key value pairs within the same string. For
172+ example, a string in the format of 'key1=value1, key2=value2' will
173+ return a dict of:
174+
175+ {'key1': 'value1',
176+ 'key2': 'value2'}.
177+
178+ 2. A string in the above format, but supporting a comma-delimited list
179+ of values for the same key. For example, a string in the format of
180+ 'key1=value1, key2=value3,value4,value5' will return a dict of:
181+
182+ {'key1', 'value1',
183+ 'key2', 'value2,value3,value4'}
184+
185+ 3. A string containing a colon character (:) prior to an equal
186+ character (=) will be treated as yaml and parsed as such. This can be
187+ used to specify more complex key value pairs. For example,
188+ a string in the format of 'key1: subkey1=value1, subkey2=value2' will
189+ return a dict of:
190+
191+ {'key1', 'subkey1=value1, subkey2=value2'}
192+
193+ The provided config_flags string may be a list of comma-separated values
194+ which themselves may be comma-separated list of values.
195+ """
196+ # If we find a colon before an equals sign then treat it as yaml.
197+ # Note: limit it to finding the colon first since this indicates assignment
198+ # for inline yaml.
199+ colon = config_flags.find(':')
200+ equals = config_flags.find('=')
201+ if colon > 0:
202+ if colon < equals or equals < 0:
203+ return yaml.safe_load(config_flags)
204+
205+ if config_flags.find('==') >= 0:
206+ juju_log("config_flags is not in expected format (key=value)",
207+ level=ERROR)
208+ raise OSContextError
209+
210+ # strip the following from each value.
211+ post_strippers = ' ,'
212+ # we strip any leading/trailing '=' or ' ' from the string then
213+ # split on '='.
214+ split = config_flags.strip(' =').split('=')
215+ limit = len(split)
216+ flags = {}
217+ for i in range(0, limit - 1):
218+ current = split[i]
219+ next = split[i + 1]
220+ vindex = next.rfind(',')
221+ if (i == limit - 2) or (vindex < 0):
222+ value = next
223+ else:
224+ value = next[:vindex]
225+
226+ if i == 0:
227+ key = current
228+ else:
229+ # if this not the first entry, expect an embedded key.
230+ index = current.rfind(',')
231+ if index < 0:
232+ juju_log("Invalid config value(s) at index %s" % (i),
233+ level=ERROR)
234+ raise OSContextError
235+ key = current[index + 1:]
236+
237+ # Add to collection.
238+ flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
239+
240+ return flags
241
242=== modified file 'charmhelpers/contrib/storage/linux/ceph.py'
243--- charmhelpers/contrib/storage/linux/ceph.py 2016-04-20 10:51:23 +0000
244+++ charmhelpers/contrib/storage/linux/ceph.py 2016-05-25 12:22:56 +0000
245@@ -40,6 +40,7 @@
246 CalledProcessError,
247 )
248 from charmhelpers.core.hookenv import (
249+ config,
250 local_unit,
251 relation_get,
252 relation_ids,
253@@ -64,6 +65,7 @@
254 )
255
256 from charmhelpers.core.kernel import modprobe
257+from charmhelpers.contrib.openstack.utils import config_flags_parser
258
259 KEYRING = '/etc/ceph/ceph.client.{}.keyring'
260 KEYFILE = '/etc/ceph/ceph.client.{}.key'
261@@ -1204,3 +1206,42 @@
262 for rid in relation_ids(relation):
263 log('Sending request {}'.format(request.request_id), level=DEBUG)
264 relation_set(relation_id=rid, broker_req=request.request)
265+
266+
267+class CephConfContext(object):
268+ """Ceph config (ceph.conf) context.
269+
270+ Supports user-provided Ceph configuration settings. Use can provide a
271+ dictionary as the value for the config-flags charm option containing
272+ Ceph configuration settings keyede by their section in ceph.conf.
273+ """
274+ def __init__(self, permitted_sections=None):
275+ self.permitted_sections = permitted_sections or []
276+
277+ def __call__(self):
278+ conf = config('config-flags')
279+ if not conf:
280+ return {}
281+
282+ conf = config_flags_parser(conf)
283+ if type(conf) != dict:
284+ log("Provided config-flags is not a dictionary - ignoring",
285+ level=WARNING)
286+ return {}
287+
288+ permitted = self.permitted_sections
289+ if permitted:
290+ diff = set(conf.keys()).symmetric_difference(set(permitted))
291+ if diff:
292+ log("Config-flags contains invalid keys '%s' - they will be "
293+ "ignored" % (', '.join(diff)), level=WARNING)
294+
295+ ceph_conf = {}
296+ for key in conf:
297+ if permitted and key not in permitted:
298+ log("Ignoring key '%s'" % key, level=WARNING)
299+ continue
300+
301+ ceph_conf[key] = conf[key]
302+
303+ return ceph_conf
304
305=== modified file 'tests/contrib/storage/test_linux_ceph.py'
306--- tests/contrib/storage/test_linux_ceph.py 2016-04-20 10:51:23 +0000
307+++ tests/contrib/storage/test_linux_ceph.py 2016-05-25 12:22:56 +0000
308@@ -1298,3 +1298,13 @@
309 self.assertEqual(actual['ops'][0]['replicas'], 4)
310 self.assertEqual(actual['ops'][0]['op'], 'create-pool')
311 self.assertEqual(actual['ops'][0]['name'], 'glance')
312+
313+ @patch.object(ceph_utils, 'config')
314+ def test_ceph_conf_context(self, mock_config):
315+ mock_config.return_value = "{'osd': {'foo': 1}}"
316+ ctxt = ceph_utils.CephConfContext()()
317+ self.assertEqual({'osd': {'foo': 1}}, ctxt)
318+ ctxt = ceph_utils.CephConfContext(['osd', 'mon'])()
319+ mock_config.return_value = ("{'osd': {'foo': 1},"
320+ "'unknown': {'blah': 1}}")
321+ self.assertEqual({'osd': {'foo': 1}}, ctxt)

Subscribers

People subscribed via source and target branches