Merge lp:~james-page/charms/raring/ceph-radosgw/trunk into lp:~charmers/charms/precise/ceph-radosgw/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
Reviewer Review Type Date Requested Status
Mark Mims (community) Approve
Review via email: mp+142899@code.launchpad.net

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.

Revision history for this message
Mark Mims (mark-mims) wrote :

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!

review: Approve

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

Subscribers

People subscribed via source and target branches

to all changes: