Merge lp:~hopem/charm-helpers/lp1522375 into lp:charm-helpers
- lp1522375
- Merge into devel
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Page | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 | 23 | from subprocess import check_call, CalledProcessError | 23 | from subprocess import check_call, CalledProcessError |
6 | 24 | 24 | ||
7 | 25 | import six | 25 | import six |
8 | 26 | import yaml | ||
9 | 27 | 26 | ||
10 | 28 | from charmhelpers.fetch import ( | 27 | from charmhelpers.fetch import ( |
11 | 29 | apt_install, | 28 | apt_install, |
12 | @@ -50,6 +49,7 @@ | |||
13 | 50 | 49 | ||
14 | 51 | from charmhelpers.core.sysctl import create as sysctl_create | 50 | from charmhelpers.core.sysctl import create as sysctl_create |
15 | 52 | from charmhelpers.core.strutils import bool_from_string | 51 | from charmhelpers.core.strutils import bool_from_string |
16 | 52 | from charmhelpers.contrib.openstack.exceptions import OSContextError | ||
17 | 53 | 53 | ||
18 | 54 | from charmhelpers.core.host import ( | 54 | from charmhelpers.core.host import ( |
19 | 55 | get_bond_master, | 55 | get_bond_master, |
20 | @@ -88,7 +88,10 @@ | |||
21 | 88 | is_address_in_network, | 88 | is_address_in_network, |
22 | 89 | is_bridge_member, | 89 | is_bridge_member, |
23 | 90 | ) | 90 | ) |
25 | 91 | from charmhelpers.contrib.openstack.utils import get_host_ip | 91 | from charmhelpers.contrib.openstack.utils import ( |
26 | 92 | config_flags_parser, | ||
27 | 93 | get_host_ip, | ||
28 | 94 | ) | ||
29 | 92 | from charmhelpers.core.unitdata import kv | 95 | from charmhelpers.core.unitdata import kv |
30 | 93 | 96 | ||
31 | 94 | try: | 97 | try: |
32 | @@ -101,10 +104,6 @@ | |||
33 | 101 | ADDRESS_TYPES = ['admin', 'internal', 'public'] | 104 | ADDRESS_TYPES = ['admin', 'internal', 'public'] |
34 | 102 | 105 | ||
35 | 103 | 106 | ||
36 | 104 | class OSContextError(Exception): | ||
37 | 105 | pass | ||
38 | 106 | |||
39 | 107 | |||
40 | 108 | def ensure_packages(packages): | 107 | def ensure_packages(packages): |
41 | 109 | """Install but do not upgrade required plugin packages.""" | 108 | """Install but do not upgrade required plugin packages.""" |
42 | 110 | required = filter_installed_packages(packages) | 109 | required = filter_installed_packages(packages) |
43 | @@ -125,83 +124,6 @@ | |||
44 | 125 | return True | 124 | return True |
45 | 126 | 125 | ||
46 | 127 | 126 | ||
47 | 128 | def config_flags_parser(config_flags): | ||
48 | 129 | """Parses config flags string into dict. | ||
49 | 130 | |||
50 | 131 | This parsing method supports a few different formats for the config | ||
51 | 132 | flag values to be parsed: | ||
52 | 133 | |||
53 | 134 | 1. A string in the simple format of key=value pairs, with the possibility | ||
54 | 135 | of specifying multiple key value pairs within the same string. For | ||
55 | 136 | example, a string in the format of 'key1=value1, key2=value2' will | ||
56 | 137 | return a dict of: | ||
57 | 138 | |||
58 | 139 | {'key1': 'value1', | ||
59 | 140 | 'key2': 'value2'}. | ||
60 | 141 | |||
61 | 142 | 2. A string in the above format, but supporting a comma-delimited list | ||
62 | 143 | of values for the same key. For example, a string in the format of | ||
63 | 144 | 'key1=value1, key2=value3,value4,value5' will return a dict of: | ||
64 | 145 | |||
65 | 146 | {'key1', 'value1', | ||
66 | 147 | 'key2', 'value2,value3,value4'} | ||
67 | 148 | |||
68 | 149 | 3. A string containing a colon character (:) prior to an equal | ||
69 | 150 | character (=) will be treated as yaml and parsed as such. This can be | ||
70 | 151 | used to specify more complex key value pairs. For example, | ||
71 | 152 | a string in the format of 'key1: subkey1=value1, subkey2=value2' will | ||
72 | 153 | return a dict of: | ||
73 | 154 | |||
74 | 155 | {'key1', 'subkey1=value1, subkey2=value2'} | ||
75 | 156 | |||
76 | 157 | The provided config_flags string may be a list of comma-separated values | ||
77 | 158 | which themselves may be comma-separated list of values. | ||
78 | 159 | """ | ||
79 | 160 | # If we find a colon before an equals sign then treat it as yaml. | ||
80 | 161 | # Note: limit it to finding the colon first since this indicates assignment | ||
81 | 162 | # for inline yaml. | ||
82 | 163 | colon = config_flags.find(':') | ||
83 | 164 | equals = config_flags.find('=') | ||
84 | 165 | if colon > 0: | ||
85 | 166 | if colon < equals or equals < 0: | ||
86 | 167 | return yaml.safe_load(config_flags) | ||
87 | 168 | |||
88 | 169 | if config_flags.find('==') >= 0: | ||
89 | 170 | log("config_flags is not in expected format (key=value)", level=ERROR) | ||
90 | 171 | raise OSContextError | ||
91 | 172 | |||
92 | 173 | # strip the following from each value. | ||
93 | 174 | post_strippers = ' ,' | ||
94 | 175 | # we strip any leading/trailing '=' or ' ' from the string then | ||
95 | 176 | # split on '='. | ||
96 | 177 | split = config_flags.strip(' =').split('=') | ||
97 | 178 | limit = len(split) | ||
98 | 179 | flags = {} | ||
99 | 180 | for i in range(0, limit - 1): | ||
100 | 181 | current = split[i] | ||
101 | 182 | next = split[i + 1] | ||
102 | 183 | vindex = next.rfind(',') | ||
103 | 184 | if (i == limit - 2) or (vindex < 0): | ||
104 | 185 | value = next | ||
105 | 186 | else: | ||
106 | 187 | value = next[:vindex] | ||
107 | 188 | |||
108 | 189 | if i == 0: | ||
109 | 190 | key = current | ||
110 | 191 | else: | ||
111 | 192 | # if this not the first entry, expect an embedded key. | ||
112 | 193 | index = current.rfind(',') | ||
113 | 194 | if index < 0: | ||
114 | 195 | log("Invalid config value(s) at index %s" % (i), level=ERROR) | ||
115 | 196 | raise OSContextError | ||
116 | 197 | key = current[index + 1:] | ||
117 | 198 | |||
118 | 199 | # Add to collection. | ||
119 | 200 | flags[key.strip(post_strippers)] = value.rstrip(post_strippers) | ||
120 | 201 | |||
121 | 202 | return flags | ||
122 | 203 | |||
123 | 204 | |||
124 | 205 | class OSContextGenerator(object): | 127 | class OSContextGenerator(object): |
125 | 206 | """Base class for all context generators.""" | 128 | """Base class for all context generators.""" |
126 | 207 | interfaces = [] | 129 | interfaces = [] |
127 | 208 | 130 | ||
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 | 1 | class OSContextError(Exception): | ||
133 | 2 | """Raised when an error occurs during context generation. | ||
134 | 3 | |||
135 | 4 | This exception is principally used in contrib.openstack.context | ||
136 | 5 | """ | ||
137 | 6 | pass | ||
138 | 0 | 7 | ||
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 | 47 | charm_dir, | 47 | charm_dir, |
144 | 48 | DEBUG, | 48 | DEBUG, |
145 | 49 | INFO, | 49 | INFO, |
146 | 50 | ERROR, | ||
147 | 50 | related_units, | 51 | related_units, |
148 | 51 | relation_ids, | 52 | relation_ids, |
149 | 52 | relation_set, | 53 | relation_set, |
150 | @@ -83,6 +84,7 @@ | |||
151 | 83 | from charmhelpers.fetch import apt_install, apt_cache, install_remote | 84 | from charmhelpers.fetch import apt_install, apt_cache, install_remote |
152 | 84 | from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk | 85 | from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk |
153 | 85 | from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device | 86 | from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device |
154 | 87 | from charmhelpers.contrib.openstack.exceptions import OSContextError | ||
155 | 86 | 88 | ||
156 | 87 | CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" | 89 | CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" |
157 | 88 | CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' | 90 | CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' |
158 | @@ -1616,3 +1618,82 @@ | |||
159 | 1616 | restart_functions) | 1618 | restart_functions) |
160 | 1617 | return wrapped_f | 1619 | return wrapped_f |
161 | 1618 | return wrap | 1620 | return wrap |
162 | 1621 | |||
163 | 1622 | |||
164 | 1623 | def config_flags_parser(config_flags): | ||
165 | 1624 | """Parses config flags string into dict. | ||
166 | 1625 | |||
167 | 1626 | This parsing method supports a few different formats for the config | ||
168 | 1627 | flag values to be parsed: | ||
169 | 1628 | |||
170 | 1629 | 1. A string in the simple format of key=value pairs, with the possibility | ||
171 | 1630 | of specifying multiple key value pairs within the same string. For | ||
172 | 1631 | example, a string in the format of 'key1=value1, key2=value2' will | ||
173 | 1632 | return a dict of: | ||
174 | 1633 | |||
175 | 1634 | {'key1': 'value1', | ||
176 | 1635 | 'key2': 'value2'}. | ||
177 | 1636 | |||
178 | 1637 | 2. A string in the above format, but supporting a comma-delimited list | ||
179 | 1638 | of values for the same key. For example, a string in the format of | ||
180 | 1639 | 'key1=value1, key2=value3,value4,value5' will return a dict of: | ||
181 | 1640 | |||
182 | 1641 | {'key1', 'value1', | ||
183 | 1642 | 'key2', 'value2,value3,value4'} | ||
184 | 1643 | |||
185 | 1644 | 3. A string containing a colon character (:) prior to an equal | ||
186 | 1645 | character (=) will be treated as yaml and parsed as such. This can be | ||
187 | 1646 | used to specify more complex key value pairs. For example, | ||
188 | 1647 | a string in the format of 'key1: subkey1=value1, subkey2=value2' will | ||
189 | 1648 | return a dict of: | ||
190 | 1649 | |||
191 | 1650 | {'key1', 'subkey1=value1, subkey2=value2'} | ||
192 | 1651 | |||
193 | 1652 | The provided config_flags string may be a list of comma-separated values | ||
194 | 1653 | which themselves may be comma-separated list of values. | ||
195 | 1654 | """ | ||
196 | 1655 | # If we find a colon before an equals sign then treat it as yaml. | ||
197 | 1656 | # Note: limit it to finding the colon first since this indicates assignment | ||
198 | 1657 | # for inline yaml. | ||
199 | 1658 | colon = config_flags.find(':') | ||
200 | 1659 | equals = config_flags.find('=') | ||
201 | 1660 | if colon > 0: | ||
202 | 1661 | if colon < equals or equals < 0: | ||
203 | 1662 | return yaml.safe_load(config_flags) | ||
204 | 1663 | |||
205 | 1664 | if config_flags.find('==') >= 0: | ||
206 | 1665 | juju_log("config_flags is not in expected format (key=value)", | ||
207 | 1666 | level=ERROR) | ||
208 | 1667 | raise OSContextError | ||
209 | 1668 | |||
210 | 1669 | # strip the following from each value. | ||
211 | 1670 | post_strippers = ' ,' | ||
212 | 1671 | # we strip any leading/trailing '=' or ' ' from the string then | ||
213 | 1672 | # split on '='. | ||
214 | 1673 | split = config_flags.strip(' =').split('=') | ||
215 | 1674 | limit = len(split) | ||
216 | 1675 | flags = {} | ||
217 | 1676 | for i in range(0, limit - 1): | ||
218 | 1677 | current = split[i] | ||
219 | 1678 | next = split[i + 1] | ||
220 | 1679 | vindex = next.rfind(',') | ||
221 | 1680 | if (i == limit - 2) or (vindex < 0): | ||
222 | 1681 | value = next | ||
223 | 1682 | else: | ||
224 | 1683 | value = next[:vindex] | ||
225 | 1684 | |||
226 | 1685 | if i == 0: | ||
227 | 1686 | key = current | ||
228 | 1687 | else: | ||
229 | 1688 | # if this not the first entry, expect an embedded key. | ||
230 | 1689 | index = current.rfind(',') | ||
231 | 1690 | if index < 0: | ||
232 | 1691 | juju_log("Invalid config value(s) at index %s" % (i), | ||
233 | 1692 | level=ERROR) | ||
234 | 1693 | raise OSContextError | ||
235 | 1694 | key = current[index + 1:] | ||
236 | 1695 | |||
237 | 1696 | # Add to collection. | ||
238 | 1697 | flags[key.strip(post_strippers)] = value.rstrip(post_strippers) | ||
239 | 1698 | |||
240 | 1699 | return flags | ||
241 | 1619 | 1700 | ||
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 | 40 | CalledProcessError, | 40 | CalledProcessError, |
247 | 41 | ) | 41 | ) |
248 | 42 | from charmhelpers.core.hookenv import ( | 42 | from charmhelpers.core.hookenv import ( |
249 | 43 | config, | ||
250 | 43 | local_unit, | 44 | local_unit, |
251 | 44 | relation_get, | 45 | relation_get, |
252 | 45 | relation_ids, | 46 | relation_ids, |
253 | @@ -64,6 +65,7 @@ | |||
254 | 64 | ) | 65 | ) |
255 | 65 | 66 | ||
256 | 66 | from charmhelpers.core.kernel import modprobe | 67 | from charmhelpers.core.kernel import modprobe |
257 | 68 | from charmhelpers.contrib.openstack.utils import config_flags_parser | ||
258 | 67 | 69 | ||
259 | 68 | KEYRING = '/etc/ceph/ceph.client.{}.keyring' | 70 | KEYRING = '/etc/ceph/ceph.client.{}.keyring' |
260 | 69 | KEYFILE = '/etc/ceph/ceph.client.{}.key' | 71 | KEYFILE = '/etc/ceph/ceph.client.{}.key' |
261 | @@ -1204,3 +1206,42 @@ | |||
262 | 1204 | for rid in relation_ids(relation): | 1206 | for rid in relation_ids(relation): |
263 | 1205 | log('Sending request {}'.format(request.request_id), level=DEBUG) | 1207 | log('Sending request {}'.format(request.request_id), level=DEBUG) |
264 | 1206 | relation_set(relation_id=rid, broker_req=request.request) | 1208 | relation_set(relation_id=rid, broker_req=request.request) |
265 | 1209 | |||
266 | 1210 | |||
267 | 1211 | class CephConfContext(object): | ||
268 | 1212 | """Ceph config (ceph.conf) context. | ||
269 | 1213 | |||
270 | 1214 | Supports user-provided Ceph configuration settings. Use can provide a | ||
271 | 1215 | dictionary as the value for the config-flags charm option containing | ||
272 | 1216 | Ceph configuration settings keyede by their section in ceph.conf. | ||
273 | 1217 | """ | ||
274 | 1218 | def __init__(self, permitted_sections=None): | ||
275 | 1219 | self.permitted_sections = permitted_sections or [] | ||
276 | 1220 | |||
277 | 1221 | def __call__(self): | ||
278 | 1222 | conf = config('config-flags') | ||
279 | 1223 | if not conf: | ||
280 | 1224 | return {} | ||
281 | 1225 | |||
282 | 1226 | conf = config_flags_parser(conf) | ||
283 | 1227 | if type(conf) != dict: | ||
284 | 1228 | log("Provided config-flags is not a dictionary - ignoring", | ||
285 | 1229 | level=WARNING) | ||
286 | 1230 | return {} | ||
287 | 1231 | |||
288 | 1232 | permitted = self.permitted_sections | ||
289 | 1233 | if permitted: | ||
290 | 1234 | diff = set(conf.keys()).symmetric_difference(set(permitted)) | ||
291 | 1235 | if diff: | ||
292 | 1236 | log("Config-flags contains invalid keys '%s' - they will be " | ||
293 | 1237 | "ignored" % (', '.join(diff)), level=WARNING) | ||
294 | 1238 | |||
295 | 1239 | ceph_conf = {} | ||
296 | 1240 | for key in conf: | ||
297 | 1241 | if permitted and key not in permitted: | ||
298 | 1242 | log("Ignoring key '%s'" % key, level=WARNING) | ||
299 | 1243 | continue | ||
300 | 1244 | |||
301 | 1245 | ceph_conf[key] = conf[key] | ||
302 | 1246 | |||
303 | 1247 | return ceph_conf | ||
304 | 1207 | 1248 | ||
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 | 1298 | self.assertEqual(actual['ops'][0]['replicas'], 4) | 1298 | self.assertEqual(actual['ops'][0]['replicas'], 4) |
310 | 1299 | self.assertEqual(actual['ops'][0]['op'], 'create-pool') | 1299 | self.assertEqual(actual['ops'][0]['op'], 'create-pool') |
311 | 1300 | self.assertEqual(actual['ops'][0]['name'], 'glance') | 1300 | self.assertEqual(actual['ops'][0]['name'], 'glance') |
312 | 1301 | |||
313 | 1302 | @patch.object(ceph_utils, 'config') | ||
314 | 1303 | def test_ceph_conf_context(self, mock_config): | ||
315 | 1304 | mock_config.return_value = "{'osd': {'foo': 1}}" | ||
316 | 1305 | ctxt = ceph_utils.CephConfContext()() | ||
317 | 1306 | self.assertEqual({'osd': {'foo': 1}}, ctxt) | ||
318 | 1307 | ctxt = ceph_utils.CephConfContext(['osd', 'mon'])() | ||
319 | 1308 | mock_config.return_value = ("{'osd': {'foo': 1}," | ||
320 | 1309 | "'unknown': {'blah': 1}}") | ||
321 | 1310 | self.assertEqual({'osd': {'foo': 1}}, ctxt) |