Merge lp:~james-page/charms/raring/ceph-radosgw/trunk into lp:~charmers/charms/precise/ceph-radosgw/trunk
- Raring Ringtail (13.04)
- trunk
- Merge into trunk
Proposed by
James Page
Status: | Merged |
---|---|
Approved by: | Mark Mims |
Approved revision: | 13 |
Merged at revision: | 10 |
Proposed branch: | lp:~james-page/charms/raring/ceph-radosgw/trunk |
Merge into: | lp:~charmers/charms/precise/ceph-radosgw/trunk |
Diff against target: |
547 lines (+247/-54) 8 files modified
README.md (+21/-6) config.yaml (+21/-0) hooks/ceph.py (+86/-31) hooks/hooks.py (+58/-4) hooks/utils.py (+41/-12) metadata.yaml (+2/-0) revision (+1/-1) templates/ceph.conf (+17/-0) |
To merge this branch: | bzr merge lp:~james-page/charms/raring/ceph-radosgw/trunk |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mark Mims (community) | Approve | ||
Review via email: mp+142899@code.launchpad.net |
Commit message
Description of the change
Updates to support newer releases of ceph.
Updates are backwards compatibile with precise/0.48.x series and have been testing using the following combos:
precise/0.48.2
quantal/0.48.2
raring/0.56.1
This branch also adds support for using the RADOS gateway with Openstack Keystone for swift authentication.
To post a comment you must log in.
- 12. By James Page
-
Improve host ip resolution - fixes issues with maas managed DNS
- 13. By James Page
-
Allow DNS problems to bubble up and break hooks as things are broken if this
is the case.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === renamed file 'README' => 'README.md' |
2 | --- README 2012-10-10 09:03:18 +0000 |
3 | +++ README.md 2013-02-07 16:03:24 +0000 |
4 | @@ -29,15 +29,31 @@ |
5 | The gateway can be accessed over port 80 (as show in juju status exposed |
6 | ports). |
7 | |
8 | +Access |
9 | +====== |
10 | + |
11 | Note that you will need to login to one of the service units supporting the |
12 | ceph charm to generate some access credentials:: |
13 | |
14 | juju ssh ceph/0 \ |
15 | 'sudo radosgw-admin user create --uid="ubuntu" --display-name="Ubuntu Ceph"' |
16 | - |
17 | + |
18 | For security reasons the ceph-radosgw charm is not setup with appropriate |
19 | permissions to administer the ceph cluster. |
20 | |
21 | +Keystone Integration |
22 | +==================== |
23 | + |
24 | +Ceph >= 0.55 integrates with Openstack Keystone for authentication of Swift requests. |
25 | + |
26 | +This is enabled by relating the ceph-radosgw service with keystone:: |
27 | + |
28 | + juju deploy keystone |
29 | + juju add-relation keystone ceph-radosgw |
30 | + |
31 | +If you try to relate the radosgw to keystone with an earlier version of ceph the hook |
32 | +will error out to let you know. |
33 | + |
34 | Scale-out |
35 | ========= |
36 | |
37 | @@ -62,8 +78,7 @@ |
38 | Bootnotes |
39 | ========= |
40 | |
41 | -The Ceph RADOS Gateway makes use of a multiverse package, |
42 | -libapache2-mod-fastcgi. As such it will try to automatically enable the |
43 | -multiverse pocket in /etc/apt/sources.list. Note that there is noting |
44 | -'wrong' with multiverse components - they typically have less liberal |
45 | -licensing policies or suchlike. |
46 | +The Ceph RADOS Gateway makes use of a multiverse package libapache2-mod-fastcgi. |
47 | +As such it will try to automatically enable the multiverse pocket in |
48 | +/etc/apt/sources.list. Note that there is noting 'wrong' with multiverse |
49 | +components - they typically have less liberal licensing policies or suchlike. |
50 | |
51 | === modified file 'config.yaml' |
52 | --- config.yaml 2012-11-22 10:47:49 +0000 |
53 | +++ config.yaml 2013-02-07 16:03:24 +0000 |
54 | @@ -20,3 +20,24 @@ |
55 | description: | |
56 | Key ID to import to the apt keyring to support use with arbitary source |
57 | configuration from outside of Launchpad archives or PPA's. |
58 | + # Keystone integration |
59 | + operator-roles: |
60 | + default: "Member,Admin" |
61 | + type: string |
62 | + description: | |
63 | + Comma-separated list of Swift operator roles; used when integrating with |
64 | + OpenStack Keystone. |
65 | + region: |
66 | + default: RegionOne |
67 | + type: string |
68 | + description: | |
69 | + OpenStack region that the RADOS gateway supports; used when integrating with |
70 | + OpenStack Keystone. |
71 | + cache-size: |
72 | + default: 500 |
73 | + type: int |
74 | + description: Number of keystone tokens to hold in local cache. |
75 | + revocation-check-interval: |
76 | + default: 600 |
77 | + type: int |
78 | + description: Interval between revocation checks to keystone. |
79 | |
80 | === modified file 'hooks/ceph.py' |
81 | --- hooks/ceph.py 2012-10-09 15:12:25 +0000 |
82 | +++ hooks/ceph.py 2013-02-07 16:03:24 +0000 |
83 | @@ -12,8 +12,11 @@ |
84 | import time |
85 | import utils |
86 | import os |
87 | +import apt_pkg as apt |
88 | |
89 | -QUORUM = ['leader', 'peon'] |
90 | +LEADER = 'leader' |
91 | +PEON = 'peon' |
92 | +QUORUM = [LEADER, PEON] |
93 | |
94 | |
95 | def is_quorum(): |
96 | @@ -40,6 +43,30 @@ |
97 | return False |
98 | |
99 | |
100 | +def is_leader(): |
101 | + asok = "/var/run/ceph/ceph-mon.{}.asok".format(utils.get_unit_hostname()) |
102 | + cmd = [ |
103 | + "ceph", |
104 | + "--admin-daemon", |
105 | + asok, |
106 | + "mon_status" |
107 | + ] |
108 | + if os.path.exists(asok): |
109 | + try: |
110 | + result = json.loads(subprocess.check_output(cmd)) |
111 | + except subprocess.CalledProcessError: |
112 | + return False |
113 | + except ValueError: |
114 | + # Non JSON response from mon_status |
115 | + return False |
116 | + if result['state'] == LEADER: |
117 | + return True |
118 | + else: |
119 | + return False |
120 | + else: |
121 | + return False |
122 | + |
123 | + |
124 | def wait_for_quorum(): |
125 | while not is_quorum(): |
126 | time.sleep(3) |
127 | @@ -58,6 +85,12 @@ |
128 | # Ignore any errors for this call |
129 | subprocess.call(cmd) |
130 | |
131 | +DISK_FORMATS = [ |
132 | + 'xfs', |
133 | + 'ext4', |
134 | + 'btrfs' |
135 | + ] |
136 | + |
137 | |
138 | def is_osd_disk(dev): |
139 | try: |
140 | @@ -72,9 +105,33 @@ |
141 | pass |
142 | return False |
143 | |
144 | + |
145 | +def rescan_osd_devices(): |
146 | + cmd = [ |
147 | + 'udevadm', 'trigger', |
148 | + '--subsystem-match=block', '--action=add' |
149 | + ] |
150 | + |
151 | + subprocess.call(cmd) |
152 | + |
153 | + |
154 | +def zap_disk(dev): |
155 | + cmd = ['sgdisk', '--zap-all', dev] |
156 | + subprocess.check_call(cmd) |
157 | + |
158 | + |
159 | _bootstrap_keyring = "/var/lib/ceph/bootstrap-osd/ceph.keyring" |
160 | |
161 | |
162 | +def is_bootstrapped(): |
163 | + return os.path.exists(_bootstrap_keyring) |
164 | + |
165 | + |
166 | +def wait_for_bootstrap(): |
167 | + while (not is_bootstrapped()): |
168 | + time.sleep(3) |
169 | + |
170 | + |
171 | def import_osd_bootstrap_key(key): |
172 | if not os.path.exists(_bootstrap_keyring): |
173 | cmd = [ |
174 | @@ -98,34 +155,7 @@ |
175 | |
176 | |
177 | def get_osd_bootstrap_key(): |
178 | - cmd = [ |
179 | - 'ceph', |
180 | - '--name', 'mon.', |
181 | - '--keyring', |
182 | - '/var/lib/ceph/mon/ceph-{}/keyring'.format( |
183 | - utils.get_unit_hostname() |
184 | - ), |
185 | - 'auth', 'get-or-create', 'client.bootstrap-osd', |
186 | - ] |
187 | - # Add capabilities |
188 | - for subsystem, subcaps in _osd_bootstrap_caps.iteritems(): |
189 | - cmd.extend([ |
190 | - subsystem, |
191 | - '; '.join(subcaps), |
192 | - ]) |
193 | - output = subprocess.check_output(cmd).strip() # IGNORE:E1103 |
194 | - # get-or-create appears to have different output depending |
195 | - # on whether its 'get' or 'create' |
196 | - # 'create' just returns the key, 'get' is more verbose and |
197 | - # needs parsing |
198 | - key = None |
199 | - if len(output.splitlines()) == 1: |
200 | - key = output |
201 | - else: |
202 | - for element in output.splitlines(): |
203 | - if 'key' in element: |
204 | - key = element.split(' = ')[1].strip() # IGNORE:E1103 |
205 | - return key |
206 | + return get_named_key('bootstrap-osd', _osd_bootstrap_caps) |
207 | |
208 | |
209 | _radosgw_keyring = "/etc/ceph/keyring.rados.gateway" |
210 | @@ -150,6 +180,17 @@ |
211 | |
212 | |
213 | def get_radosgw_key(): |
214 | + return get_named_key('radosgw.gateway', _radosgw_caps) |
215 | + |
216 | + |
217 | +_default_caps = { |
218 | + 'mon': ['allow r'], |
219 | + 'osd': ['allow rwx'] |
220 | + } |
221 | + |
222 | + |
223 | +def get_named_key(name, caps=None): |
224 | + caps = caps or _default_caps |
225 | cmd = [ |
226 | 'ceph', |
227 | '--name', 'mon.', |
228 | @@ -157,10 +198,10 @@ |
229 | '/var/lib/ceph/mon/ceph-{}/keyring'.format( |
230 | utils.get_unit_hostname() |
231 | ), |
232 | - 'auth', 'get-or-create', 'client.radosgw.gateway', |
233 | + 'auth', 'get-or-create', 'client.{}'.format(name), |
234 | ] |
235 | # Add capabilities |
236 | - for subsystem, subcaps in _radosgw_caps.iteritems(): |
237 | + for subsystem, subcaps in caps.iteritems(): |
238 | cmd.extend([ |
239 | subsystem, |
240 | '; '.join(subcaps), |
241 | @@ -178,3 +219,17 @@ |
242 | if 'key' in element: |
243 | key = element.split(' = ')[1].strip() # IGNORE:E1103 |
244 | return key |
245 | + |
246 | + |
247 | +def get_ceph_version(package=None): |
248 | + apt.init() |
249 | + cache = apt.Cache() |
250 | + pkg = cache[package or 'ceph'] |
251 | + if pkg.current_ver: |
252 | + return apt.upstream_version(pkg.current_ver.ver_str) |
253 | + else: |
254 | + return None |
255 | + |
256 | + |
257 | +def version_compare(a, b): |
258 | + return apt.version_compare(a, b) |
259 | |
260 | === modified file 'hooks/hooks.py' |
261 | --- hooks/hooks.py 2012-10-19 15:51:24 +0000 |
262 | +++ hooks/hooks.py 2013-02-07 16:03:24 +0000 |
263 | @@ -22,6 +22,9 @@ |
264 | shutil.copy(x, '/var/www/') |
265 | |
266 | |
267 | +NSS_DIR='/var/lib/ceph/nss' |
268 | + |
269 | + |
270 | def install(): |
271 | utils.juju_log('INFO', 'Begin install hook.') |
272 | utils.enable_pocket('multiverse') |
273 | @@ -30,6 +33,7 @@ |
274 | 'libapache2-mod-fastcgi', |
275 | 'apache2', |
276 | 'ntp') |
277 | + os.makedirs(NSS_DIR) |
278 | utils.juju_log('INFO', 'End install hook.') |
279 | |
280 | |
281 | @@ -41,8 +45,17 @@ |
282 | cephcontext = { |
283 | 'auth_supported': get_auth() or 'none', |
284 | 'mon_hosts': ' '.join(get_mon_hosts()), |
285 | - 'hostname': utils.get_unit_hostname() |
286 | + 'hostname': utils.get_unit_hostname(), |
287 | + 'version': ceph.get_ceph_version('radosgw') |
288 | } |
289 | + |
290 | + # Check to ensure that correct version of ceph is |
291 | + # in use |
292 | + if ceph.get_ceph_version('radosgw') >= "0.55": |
293 | + # Add keystone configuration if found |
294 | + ks_conf = get_keystone_conf() |
295 | + if ks_conf: |
296 | + cephcontext.update(ks_conf) |
297 | |
298 | with open('/etc/ceph/ceph.conf', 'w') as cephconf: |
299 | cephconf.write(utils.render_template('ceph.conf', cephcontext)) |
300 | @@ -108,16 +121,33 @@ |
301 | for unit in utils.relation_list(relid): |
302 | conf = utils.relation_get(name, |
303 | unit, relid) |
304 | - if conf != "": |
305 | + if conf: |
306 | return conf |
307 | return None |
308 | |
309 | +def get_keystone_conf(): |
310 | + for relid in utils.relation_ids('identity-service'): |
311 | + for unit in utils.relation_list(relid): |
312 | + ks_auth = { |
313 | + 'auth_type': 'keystone', |
314 | + 'auth_protocol': 'http', |
315 | + 'auth_host': utils.relation_get('auth_host', unit, relid), |
316 | + 'auth_port': utils.relation_get('auth_port', unit, relid), |
317 | + 'admin_token': utils.relation_get('admin_token', unit, relid), |
318 | + 'user_roles': utils.config_get('operator-roles'), |
319 | + 'cache_size': utils.config_get('cache-size'), |
320 | + 'revocation_check_interval': utils.config_get('revocation-check-interval') |
321 | + } |
322 | + if None not in ks_auth.itervalues(): |
323 | + return ks_auth |
324 | + return None |
325 | + |
326 | |
327 | def mon_relation(): |
328 | utils.juju_log('INFO', 'Begin mon-relation hook.') |
329 | emit_cephconf() |
330 | key = utils.relation_get('radosgw_key') |
331 | - if key != "": |
332 | + if key: |
333 | ceph.import_radosgw_key(key) |
334 | restart() # TODO figure out a better way todo this |
335 | utils.juju_log('INFO', 'End mon-relation hook.') |
336 | @@ -141,7 +171,7 @@ |
337 | |
338 | |
339 | def stop(): |
340 | - subprocess.call(['service', 'radosgw', 'start']) |
341 | + subprocess.call(['service', 'radosgw', 'stop']) |
342 | utils.expose(port=80) |
343 | |
344 | |
345 | @@ -150,6 +180,28 @@ |
346 | utils.expose(port=80) |
347 | |
348 | |
349 | +def identity_joined(relid=None): |
350 | + if ceph.get_ceph_version('radosgw') < "0.55": |
351 | + utils.juju_log('ERROR', |
352 | + 'Integration with keystone requires ceph >= 0.55') |
353 | + sys.exit(1) |
354 | + |
355 | + hostname = utils.unit_get('private-address') |
356 | + admin_url = 'http://{}:80/swift'.format(hostname) |
357 | + internal_url = public_url = '{}/v1'.format(admin_url) |
358 | + utils.relation_set(service='swift', |
359 | + region=utils.config_get('region'), |
360 | + public_url=public_url, internal_url=internal_url, |
361 | + admin_url=admin_url, |
362 | + requested_roles=utils.config_get('operator-roles'), |
363 | + rid=relid) |
364 | + |
365 | + |
366 | +def identity_changed(): |
367 | + emit_cephconf() |
368 | + restart() |
369 | + |
370 | + |
371 | utils.do_hooks({ |
372 | 'install': install, |
373 | 'config-changed': config_changed, |
374 | @@ -157,6 +209,8 @@ |
375 | 'mon-relation-changed': mon_relation, |
376 | 'gateway-relation-joined': gateway_relation, |
377 | 'upgrade-charm': config_changed, # same function ATM |
378 | + 'identity-service-relation-joined': identity_joined, |
379 | + 'identity-service-relation-changed': identity_changed |
380 | }) |
381 | |
382 | sys.exit(0) |
383 | |
384 | === modified file 'hooks/utils.py' |
385 | --- hooks/utils.py 2012-11-12 09:46:54 +0000 |
386 | +++ hooks/utils.py 2013-02-07 16:03:24 +0000 |
387 | @@ -18,10 +18,12 @@ |
388 | hook = os.path.basename(sys.argv[0]) |
389 | |
390 | try: |
391 | - hooks[hook]() |
392 | + hook_func = hooks[hook] |
393 | except KeyError: |
394 | juju_log('INFO', |
395 | "This charm doesn't know how to handle '{}'.".format(hook)) |
396 | + else: |
397 | + hook_func() |
398 | |
399 | |
400 | def install(*pkgs): |
401 | @@ -42,6 +44,12 @@ |
402 | install('python-jinja2') |
403 | import jinja2 |
404 | |
405 | +try: |
406 | + import dns.resolver |
407 | +except ImportError: |
408 | + install('python-dnspython') |
409 | + import dns.resolver |
410 | + |
411 | |
412 | def render_template(template_name, context, template_dir=TEMPLATES_DIR): |
413 | templates = jinja2.Environment( |
414 | @@ -101,7 +109,6 @@ |
415 | else: |
416 | sources.write(line) |
417 | |
418 | - |
419 | # Protocols |
420 | TCP = 'TCP' |
421 | UDP = 'UDP' |
422 | @@ -150,15 +157,25 @@ |
423 | cmd.append(attribute) |
424 | if unit: |
425 | cmd.append(unit) |
426 | - return subprocess.check_output(cmd).strip() # IGNORE:E1103 |
427 | + value = str(subprocess.check_output(cmd)).strip() |
428 | + if value == "": |
429 | + return None |
430 | + else: |
431 | + return value |
432 | |
433 | |
434 | def relation_set(**kwargs): |
435 | cmd = [ |
436 | 'relation-set' |
437 | ] |
438 | + args = [] |
439 | for k, v in kwargs.items(): |
440 | - cmd.append('{}={}'.format(k, v)) |
441 | + if k == 'rid': |
442 | + cmd.append('-r') |
443 | + cmd.append(v) |
444 | + else: |
445 | + args.append('{}={}'.format(k, v)) |
446 | + cmd += args |
447 | subprocess.check_call(cmd) |
448 | |
449 | |
450 | @@ -167,7 +184,11 @@ |
451 | 'unit-get', |
452 | attribute |
453 | ] |
454 | - return subprocess.check_output(cmd).strip() # IGNORE:E1103 |
455 | + value = str(subprocess.check_output(cmd)).strip() |
456 | + if value == "": |
457 | + return None |
458 | + else: |
459 | + return value |
460 | |
461 | |
462 | def config_get(attribute): |
463 | @@ -175,7 +196,11 @@ |
464 | 'config-get', |
465 | attribute |
466 | ] |
467 | - return subprocess.check_output(cmd).strip() # IGNORE:E1103 |
468 | + value = str(subprocess.check_output(cmd)).strip() |
469 | + if value == "": |
470 | + return None |
471 | + else: |
472 | + return value |
473 | |
474 | |
475 | def get_unit_hostname(): |
476 | @@ -183,9 +208,13 @@ |
477 | |
478 | |
479 | def get_host_ip(hostname=unit_get('private-address')): |
480 | - cmd = [ |
481 | - 'dig', |
482 | - '+short', |
483 | - hostname |
484 | - ] |
485 | - return subprocess.check_output(cmd).strip() # IGNORE:E1103 |
486 | + try: |
487 | + # Test to see if already an IPv4 address |
488 | + socket.inet_aton(hostname) |
489 | + return hostname |
490 | + except socket.error: |
491 | + # This may throw an NXDOMAIN exception; in which case |
492 | + # things are badly broken so just let it kill the hook |
493 | + answers = dns.resolver.query(hostname, 'A') |
494 | + if answers: |
495 | + return answers[0].address |
496 | |
497 | === modified file 'metadata.yaml' |
498 | --- metadata.yaml 2012-10-08 15:58:16 +0000 |
499 | +++ metadata.yaml 2013-02-07 16:03:24 +0000 |
500 | @@ -10,6 +10,8 @@ |
501 | requires: |
502 | mon: |
503 | interface: ceph-radosgw |
504 | + identity-service: |
505 | + interface: keystone |
506 | provides: |
507 | gateway: |
508 | interface: http |
509 | |
510 | === modified file 'revision' |
511 | --- revision 2012-11-22 01:09:33 +0000 |
512 | +++ revision 2013-02-07 16:03:24 +0000 |
513 | @@ -1,1 +1,1 @@ |
514 | -15 |
515 | +21 |
516 | |
517 | === modified file 'templates/ceph.conf' |
518 | --- templates/ceph.conf 2012-10-19 15:51:24 +0000 |
519 | +++ templates/ceph.conf 2013-02-07 16:03:24 +0000 |
520 | @@ -1,5 +1,11 @@ |
521 | [global] |
522 | +{% if version < "0.51" %} |
523 | auth supported = {{ auth_supported }} |
524 | +{% else %} |
525 | + auth cluster required = {{ auth_supported }} |
526 | + auth service required = {{ auth_supported }} |
527 | + auth client required = {{ auth_supported }} |
528 | +{% endif %} |
529 | mon host = {{ mon_hosts }} |
530 | |
531 | [client.radosgw.gateway] |
532 | @@ -7,3 +13,14 @@ |
533 | keyring = /etc/ceph/keyring.rados.gateway |
534 | rgw socket path = /tmp/radosgw.sock |
535 | log file = /var/log/ceph/radosgw.log |
536 | + # Turn off 100-continue optimization as stock mod_fastcgi |
537 | + # does not support it |
538 | + rgw print continue = false |
539 | +{% if auth_type == 'keystone' %} |
540 | + rgw keystone url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/ |
541 | + rgw keystone admin token = {{ admin_token }} |
542 | + rgw keystone accepted roles = {{ user_roles }} |
543 | + rgw keystone token cache size = {{ cache_size }} |
544 | + rgw keystone revocation interval = {{ revocation_check_interval }} |
545 | +#nss db path = /var/lib/ceph/nss |
546 | +{% endif %} |
547 | \ No newline at end of file |
this looks great.
This MP exposes a flaw in our current charm subnmission process. The alias lp:charms/ceph points to lp:~charmers/charms/precise/ceph/trunk. However, the proposed merge is from a raring branch. Should it go to precise, quantal, or should we do a new lp:~charmers/charms/raring/ceph/trunk and leave this merge out of the past series branches?
I'd reject because there's ambiguity in which branch(es) you'd like this merged into... but after talking on IRC, the branches are intended for precise even though they're raring branches. In the future we should submit against a specific series branch if that's possible and what you mean to do... otherwise, at least note the intended destination in the MP somewhere.
Thanks!