Merge lp:~hopem/charms/trusty/ceph-radosgw/lp1513524 into lp:~openstack-charmers-archive/charms/trusty/ceph-radosgw/next

Proposed by Edward Hope-Morley
Status: Superseded
Proposed branch: lp:~hopem/charms/trusty/ceph-radosgw/lp1513524
Merge into: lp:~openstack-charmers-archive/charms/trusty/ceph-radosgw/next
Diff against target: 971 lines (+336/-275)
9 files modified
config.yaml (+12/-0)
hooks/ceph_radosgw_context.py (+110/-27)
hooks/hooks.py (+70/-86)
hooks/utils.py (+58/-33)
templates/ceph.conf (+3/-0)
templates/rgw.conf (+25/-0)
tests/basic_deployment.py (+26/-51)
unit_tests/test_ceph_radosgw_context.py (+32/-11)
unit_tests/test_hooks.py (+0/-67)
To merge this branch: bzr merge lp:~hopem/charms/trusty/ceph-radosgw/lp1513524
Reviewer Review Type Date Requested Status
Chris Holcombe (community) Approve
OpenStack Charmers Pending
Review via email: mp+285808@code.launchpad.net

This proposal has been superseded by a proposal from 2016-02-18.

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

charm_lint_check #574 ceph-radosgw-next for hopem mp285808
    LINT OK: passed

Build: http://10.245.162.36:8080/job/charm_lint_check/574/

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

charm_unit_test #488 ceph-radosgw-next for hopem mp285808
    UNIT OK: passed

Build: http://10.245.162.36:8080/job/charm_unit_test/488/

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

charm_amulet_test #235 ceph-radosgw-next for hopem mp285808
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/15073352/
Build: http://10.245.162.36:8080/job/charm_amulet_test/235/

63. By Edward Hope-Morley

fix amulet

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

charm_unit_test #489 ceph-radosgw-next for hopem mp285808
    UNIT OK: passed

Build: http://10.245.162.36:8080/job/charm_unit_test/489/

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

charm_lint_check #575 ceph-radosgw-next for hopem mp285808
    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/15074156/
Build: http://10.245.162.36:8080/job/charm_lint_check/575/

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

charm_amulet_test #236 ceph-radosgw-next for hopem mp285808
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/15074271/
Build: http://10.245.162.36:8080/job/charm_amulet_test/236/

64. By Edward Hope-Morley

fx amulet

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

charm_lint_check #576 ceph-radosgw-next for hopem mp285808
    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/15074967/
Build: http://10.245.162.36:8080/job/charm_lint_check/576/

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

charm_unit_test #490 ceph-radosgw-next for hopem mp285808
    UNIT OK: passed

Build: http://10.245.162.36:8080/job/charm_unit_test/490/

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

charm_amulet_test #237 ceph-radosgw-next for hopem mp285808
    AMULET OK: passed

Build: http://10.245.162.36:8080/job/charm_amulet_test/237/

65. By Edward Hope-Morley

fix lint error

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

charm_lint_check #577 ceph-radosgw-next for hopem mp285808
    LINT OK: passed

Build: http://10.245.162.36:8080/job/charm_lint_check/577/

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

charm_unit_test #491 ceph-radosgw-next for hopem mp285808
    UNIT OK: passed

Build: http://10.245.162.36:8080/job/charm_unit_test/491/

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

charm_amulet_test #238 ceph-radosgw-next for hopem mp285808
    AMULET OK: passed

Build: http://10.245.162.36:8080/job/charm_amulet_test/238/

Revision history for this message
Chris Holcombe (xfactor973) wrote :

Hey Ed thanks for writing this patch! This looks great. My only comments are just little nits about catching errors and what you would like to do with them. You could handle them locally or raise but it would be best to be explicit about it. Other than that this looks good and I'm looking forward to trying it out

review: Needs Fixing
66. By Edward Hope-Morley

post-review fixups

Revision history for this message
Edward Hope-Morley (hopem) wrote :

Thanks for the review Chris! I've fixed up based on your comments and added some inline responses.

Revision history for this message
Chris Holcombe (xfactor973) wrote :

Cool. Lets get this merged!

review: Approve
67. By Edward Hope-Morley

sync /next

68. By Edward Hope-Morley

very minor edit

69. By Edward Hope-Morley

only write /etc/hosts if hostname not v6 resolvable

70. By Edward Hope-Morley

fix logs

71. By Edward Hope-Morley

sync /next

Unmerged revisions

71. By Edward Hope-Morley

sync /next

70. By Edward Hope-Morley

fix logs

69. By Edward Hope-Morley

only write /etc/hosts if hostname not v6 resolvable

68. By Edward Hope-Morley

very minor edit

67. By Edward Hope-Morley

sync /next

66. By Edward Hope-Morley

post-review fixups

65. By Edward Hope-Morley

fix lint error

64. By Edward Hope-Morley

fx amulet

63. By Edward Hope-Morley

fix amulet

62. By Edward Hope-Morley

[hopem,r=]

Add ipv6 support
Closes-Bug: 1513524

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2016-01-22 13:29:40 +0000
3+++ config.yaml 2016-02-18 09:59:16 +0000
4@@ -153,3 +153,15 @@
5 description: |
6 Connect timeout configuration in ms for haproxy, used in HA
7 configurations. If not provided, default value of 5000ms is used.
8+ prefer-ipv6:
9+ type: boolean
10+ default: False
11+ description: |
12+ If True enables IPv6 support. The charm will expect network interfaces
13+ to be configured with an IPv6 address. If set to False (default) IPv4
14+ is expected.
15+ .
16+ NOTE: these charms do not currently support IPv6 privacy extension. In
17+ order for this charm to function correctly, the privacy extension must be
18+ disabled and a non-temporary address must be configured/available on
19+ your network interface.
20
21=== modified file 'hooks/ceph_radosgw_context.py'
22--- hooks/ceph_radosgw_context.py 2016-01-11 12:21:07 +0000
23+++ hooks/ceph_radosgw_context.py 2016-02-18 09:59:16 +0000
24@@ -1,3 +1,11 @@
25+import os
26+import re
27+import socket
28+import tempfile
29+import glob
30+import shutil
31+import subprocess
32+
33 from charmhelpers.contrib.openstack import context
34 from charmhelpers.contrib.hahelpers.cluster import (
35 determine_api_port,
36@@ -5,17 +13,69 @@
37 )
38 from charmhelpers.core.host import cmp_pkgrevno
39 from charmhelpers.core.hookenv import (
40+ DEBUG,
41 WARNING,
42 config,
43 log,
44 relation_ids,
45 related_units,
46 relation_get,
47- unit_get,
48-)
49-import os
50-import socket
51-import dns.resolver
52+ status_set,
53+)
54+from charmhelpers.contrib.network.ip import (
55+ format_ipv6_addr,
56+ get_host_ip,
57+ get_ipv6_addr,
58+)
59+
60+
61+def is_apache_24():
62+ if os.path.exists('/etc/apache2/conf-available'):
63+ return True
64+ else:
65+ return False
66+
67+
68+class ApacheContext(context.OSContextGenerator):
69+ interfaces = ['http']
70+ service_namespace = 'ceph-radosgw'
71+
72+ def __call__(self):
73+ ctxt = {}
74+ if config('use-embedded-webserver'):
75+ log("Skipping ApacheContext since we are using the embedded "
76+ "webserver")
77+ return {}
78+
79+ status_set('maintenance', 'configuring apache')
80+
81+ src = 'files/www/*'
82+ dst = '/var/www/'
83+ log("Installing www scripts", level=DEBUG)
84+ try:
85+ for x in glob.glob(src):
86+ shutil.copy(x, dst)
87+ except IOError as e:
88+ log("Error copying files from '%s' to '%s': %s" % (src, dst, e),
89+ level=WARNING)
90+
91+ try:
92+ subprocess.check_call(['a2enmod', 'fastcgi'])
93+ subprocess.check_call(['a2enmod', 'rewrite'])
94+ except subprocess.CalledProcessError as e:
95+ log("Error enabling apache modules - %s" % e, level=WARNING)
96+
97+ try:
98+ if is_apache_24():
99+ subprocess.check_call(['a2dissite', '000-default'])
100+ else:
101+ subprocess.check_call(['a2dissite', 'default'])
102+ except subprocess.CalledProcessError as e:
103+ log("Error disabling apache sites - %s" % e, level=WARNING)
104+
105+ ctxt['hostname'] = socket.gethostname()
106+ ctxt['port'] = determine_api_port(config('port'), singlenode_mode=True)
107+ return ctxt
108
109
110 class HAProxyContext(context.HAProxyContext):
111@@ -66,24 +126,50 @@
112 return {}
113
114
115+def ensure_host_resolvable_v6(hostname):
116+ """Ensure that we can resolve our hostname to an IPv6 address by adding it
117+ to /etc/hosts.
118+ """
119+ # This must be the backend address used by haproxy
120+ host_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
121+ dtmp = tempfile.mkdtemp()
122+ try:
123+ tmp_hosts = os.path.join(dtmp, 'hosts')
124+ shutil.copy('/etc/hosts', tmp_hosts)
125+ with open(tmp_hosts, 'a+') as fd:
126+ lines = fd.readlines()
127+ for line in lines:
128+ key = "^%s\s+" % (host_addr)
129+ if re.search(key, line):
130+ break
131+ else:
132+ fd.write("%s\t%s\n" % (host_addr, hostname))
133+
134+ os.rename(tmp_hosts, '/etc/hosts')
135+ finally:
136+ shutil.rmtree(dtmp)
137+
138+
139 class MonContext(context.OSContextGenerator):
140 interfaces = ['ceph-radosgw']
141
142 def __call__(self):
143 if not relation_ids('mon'):
144 return {}
145- hosts = []
146+ mon_hosts = []
147 auths = []
148 for relid in relation_ids('mon'):
149 for unit in related_units(relid):
150 ceph_public_addr = relation_get('ceph-public-address', unit,
151 relid)
152 if ceph_public_addr:
153- host_ip = self.get_host_ip(ceph_public_addr)
154- hosts.append('{}:6789'.format(host_ip))
155+ host_ip = format_ipv6_addr(ceph_public_addr) or \
156+ get_host_ip(ceph_public_addr)
157+ mon_hosts.append('{}:6789'.format(host_ip))
158 _auth = relation_get('auth', unit, relid)
159 if _auth:
160 auths.append(_auth)
161+
162 if len(set(auths)) != 1:
163 e = ("Inconsistent or absent auth returned by mon units. Setting "
164 "auth_supported to 'none'")
165@@ -91,17 +177,28 @@
166 auth = 'none'
167 else:
168 auth = auths[0]
169- hosts.sort()
170+
171+ # /etc/init.d/radosgw mandates that a dns name is used for this
172+ # parameter so ensure that address is resolvable
173+ host = socket.gethostname()
174+ if config('prefer-ipv6'):
175+ ensure_host_resolvable_v6(host)
176+
177+ port = determine_apache_port(config('port'), singlenode_mode=True)
178+ if config('prefer-ipv6'):
179+ port = "[::]:%s" % (port)
180+
181+ mon_hosts.sort()
182 ctxt = {
183 'auth_supported': auth,
184- 'mon_hosts': ' '.join(hosts),
185- 'hostname': socket.gethostname(),
186+ 'mon_hosts': ' '.join(mon_hosts),
187+ 'hostname': format_ipv6_addr(host) or host,
188 'old_auth': cmp_pkgrevno('radosgw', "0.51") < 0,
189 'use_syslog': str(config('use-syslog')).lower(),
190 'embedded_webserver': config('use-embedded-webserver'),
191 'loglevel': config('loglevel'),
192- 'port': determine_apache_port(config('port'),
193- singlenode_mode=True)
194+ 'port': port,
195+ 'ipv6': config('prefer-ipv6')
196 }
197
198 certs_path = '/var/lib/ceph/nss'
199@@ -121,17 +218,3 @@
200 return ctxt
201
202 return {}
203-
204- def get_host_ip(self, hostname=None):
205- try:
206- if not hostname:
207- hostname = unit_get('private-address')
208- # Test to see if already an IPv4 address
209- socket.inet_aton(hostname)
210- return hostname
211- except socket.error:
212- # This may throw an NXDOMAIN exception; in which case
213- # things are badly broken so just let it kill the hook
214- answers = dns.resolver.query(hostname, 'A')
215- if answers:
216- return answers[0].address
217
218=== modified file 'hooks/hooks.py'
219--- hooks/hooks.py 2016-01-22 13:29:40 +0000
220+++ hooks/hooks.py 2016-02-18 09:59:16 +0000
221@@ -1,17 +1,16 @@
222 #!/usr/bin/python
223-
224 #
225-# Copyright 2012 Canonical Ltd.
226+# Copyright 2016 Canonical Ltd.
227 #
228 # Authors:
229 # James Page <james.page@ubuntu.com>
230+# Edward Hope-Morley <edward.hope-morley@canonical.com>
231 #
232
233-import shutil
234+import os
235 import subprocess
236 import sys
237-import glob
238-import os
239+
240 import ceph
241
242 from charmhelpers.core.hookenv import (
243@@ -39,27 +38,17 @@
244 lsb_release,
245 restart_on_change,
246 )
247-from charmhelpers.contrib.hahelpers.cluster import (
248- determine_apache_port,
249-)
250-from utils import (
251- render_template,
252- enable_pocket,
253- is_apache_24,
254- CEPHRG_HA_RES,
255- register_configs,
256- REQUIRED_INTERFACES,
257- check_optional_relations,
258-)
259 from charmhelpers.payload.execd import execd_preinstall
260 from charmhelpers.core.host import (
261 cmp_pkgrevno,
262 mkdir,
263 )
264-
265 from charmhelpers.contrib.network.ip import (
266+ format_ipv6_addr,
267+ get_ipv6_addr,
268 get_iface_for_address,
269 get_netmask_for_address,
270+ is_ipv6,
271 )
272 from charmhelpers.contrib.openstack.ip import (
273 canonical_url,
274@@ -72,18 +61,17 @@
275 send_request_if_needed,
276 is_request_complete,
277 )
278-
279-APACHE_PORTS_CONF = '/etc/apache2/ports.conf'
280+from utils import (
281+ enable_pocket,
282+ CEPHRG_HA_RES,
283+ register_configs,
284+ REQUIRED_INTERFACES,
285+ check_optional_relations,
286+ setup_ipv6,
287+)
288
289 hooks = Hooks()
290 CONFIGS = register_configs()
291-
292-
293-def install_www_scripts():
294- for x in glob.glob('files/www/*'):
295- shutil.copy(x, '/var/www/')
296-
297-
298 NSS_DIR = '/var/lib/ceph/nss'
299
300
301@@ -145,43 +133,6 @@
302 os.makedirs('/etc/ceph')
303
304
305-def emit_apacheconf():
306- apachecontext = {
307- "hostname": unit_get('private-address'),
308- "port": determine_apache_port(config('port'), singlenode_mode=True)
309- }
310- site_conf = '/etc/apache2/sites-available/rgw'
311- if is_apache_24():
312- site_conf = '/etc/apache2/sites-available/rgw.conf'
313- with open(site_conf, 'w') as apacheconf:
314- apacheconf.write(render_template('rgw', apachecontext))
315-
316-
317-def apache_sites():
318- if is_apache_24():
319- subprocess.check_call(['a2dissite', '000-default'])
320- else:
321- subprocess.check_call(['a2dissite', 'default'])
322- subprocess.check_call(['a2ensite', 'rgw'])
323-
324-
325-def apache_modules():
326- subprocess.check_call(['a2enmod', 'fastcgi'])
327- subprocess.check_call(['a2enmod', 'rewrite'])
328-
329-
330-def apache_reload():
331- subprocess.call(['service', 'apache2', 'reload'])
332-
333-
334-def apache_ports():
335- portscontext = {
336- "port": determine_apache_port(config('port'), singlenode_mode=True)
337- }
338- with open(APACHE_PORTS_CONF, 'w') as portsconf:
339- portsconf.write(render_template('ports.conf', portscontext))
340-
341-
342 def setup_keystone_certs(unit=None, rid=None):
343 """
344 Get CA and signing certs from Keystone used to decrypt revoked token list.
345@@ -208,6 +159,9 @@
346 for key in required_keys:
347 settings[key] = rdata.get(key)
348
349+ if is_ipv6(settings.get('auth_host')):
350+ settings['auth_host'] = format_ipv6_addr(settings.get('auth_host'))
351+
352 if not all(settings.values()):
353 log("Missing relation settings (%s) - skipping cert setup" %
354 (', '.join([k for k in settings.keys() if not settings[k]])),
355@@ -283,18 +237,28 @@
356 '/etc/haproxy/haproxy.cfg': ['haproxy']})
357 def config_changed():
358 install_packages()
359+
360+ if config('prefer-ipv6'):
361+ status_set('maintenance', 'configuring ipv6')
362+ setup_ipv6()
363+
364+ for r_id in relation_ids('identity-service'):
365+ identity_changed(relid=r_id)
366+
367+ for r_id in relation_ids('cluster'):
368+ cluster_joined(rid=r_id)
369+
370 CONFIGS.write_all()
371+
372 if not config('use-embedded-webserver'):
373- status_set('maintenance', 'configuring apache')
374- emit_apacheconf()
375- install_www_scripts()
376- apache_sites()
377- apache_modules()
378- apache_ports()
379- apache_reload()
380+ try:
381+ subprocess.check_call(['a2ensite', 'rgw'])
382+ except subprocess.CalledProcessError as e:
383+ log("Error enabling apache module 'rgw' - %s" % e, level=WARNING)
384
385- for r_id in relation_ids('identity-service'):
386- identity_changed(relid=r_id)
387+ # Ensure started but do a soft reload
388+ subprocess.call(['service', 'apache2', 'start'])
389+ subprocess.call(['service', 'apache2', 'reload'])
390
391
392 @hooks.hook('mon-relation-departed',
393@@ -368,8 +332,18 @@
394 restart()
395
396
397-@hooks.hook('cluster-relation-changed',
398- 'cluster-relation-joined')
399+@hooks.hook('cluster-relation-joined')
400+@restart_on_change({'/etc/haproxy/haproxy.cfg': ['haproxy']})
401+def cluster_joined(rid=None):
402+ settings = {}
403+ if config('prefer-ipv6'):
404+ private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
405+ settings['private-address'] = private_addr
406+
407+ relation_set(relation_id=rid, **settings)
408+
409+
410+@hooks.hook('cluster-relation-changed')
411 @restart_on_change({'/etc/haproxy/haproxy.cfg': ['haproxy']})
412 def cluster_changed():
413 CONFIGS.write_all()
414@@ -379,17 +353,12 @@
415
416 @hooks.hook('ha-relation-joined')
417 def ha_relation_joined():
418- # Obtain the config values necessary for the cluster config. These
419- # include multicast port and interface to bind to.
420- corosync_bindiface = config('ha-bindiface')
421- corosync_mcastport = config('ha-mcastport')
422 vip = config('vip')
423 if not vip:
424- log('Unable to configure hacluster as vip not provided',
425- level=ERROR)
426+ log('Unable to configure hacluster as vip not provided', level=ERROR)
427 sys.exit(1)
428+
429 # Obtain resources
430- # SWIFT_HA_RES = 'grp_swift_vips'
431 resources = {
432 'res_cephrg_haproxy': 'lsb:haproxy'
433 }
434@@ -399,15 +368,25 @@
435
436 vip_group = []
437 for vip in vip.split():
438+ if is_ipv6(vip):
439+ res_rgw_vip = 'ocf:heartbeat:IPv6addr'
440+ vip_params = 'ipv6addr'
441+ else:
442+ res_rgw_vip = 'ocf:heartbeat:IPaddr2'
443+ vip_params = 'ip'
444+
445 iface = get_iface_for_address(vip)
446+ netmask = get_netmask_for_address(vip)
447+
448 if iface is not None:
449 vip_key = 'res_cephrg_{}_vip'.format(iface)
450- resources[vip_key] = 'ocf:heartbeat:IPaddr2'
451+ resources[vip_key] = res_rgw_vip
452 resource_params[vip_key] = (
453- 'params ip="{vip}" cidr_netmask="{netmask}"'
454- ' nic="{iface}"'.format(vip=vip,
455+ 'params {ip}="{vip}" cidr_netmask="{netmask}"'
456+ ' nic="{iface}"'.format(ip=vip_params,
457+ vip=vip,
458 iface=iface,
459- netmask=get_netmask_for_address(vip))
460+ netmask=netmask)
461 )
462 vip_group.append(vip_key)
463
464@@ -421,6 +400,11 @@
465 'cl_cephrg_haproxy': 'res_cephrg_haproxy'
466 }
467
468+ # Obtain the config values necessary for the cluster config. These
469+ # include multicast port and interface to bind to.
470+ corosync_bindiface = config('ha-bindiface')
471+ corosync_mcastport = config('ha-mcastport')
472+
473 relation_set(init_services=init_services,
474 corosync_bindiface=corosync_bindiface,
475 corosync_mcastport=corosync_mcastport,
476
477=== modified file 'hooks/utils.py'
478--- hooks/utils.py 2015-10-12 10:56:01 +0000
479+++ hooks/utils.py 2016-02-18 09:59:16 +0000
480@@ -1,26 +1,43 @@
481 #
482-# Copyright 2012 Canonical Ltd.
483+# Copyright 2016 Canonical Ltd.
484 #
485 # Authors:
486 # James Page <james.page@ubuntu.com>
487 # Paul Collins <paul.collins@canonical.com>
488+# Edward Hope-Morley <edward.hope-morley@canonical.com>
489 #
490
491-import socket
492 import re
493-import os
494-import dns.resolver
495 import jinja2
496+
497 from copy import deepcopy
498 from collections import OrderedDict
499-from charmhelpers.core.hookenv import unit_get, relation_ids, status_get
500-from charmhelpers.contrib.openstack import context, templating
501-from charmhelpers.contrib.openstack.utils import set_os_workload_status
502+
503+import ceph_radosgw_context
504+
505+from charmhelpers.core.hookenv import (
506+ relation_ids,
507+ status_get,
508+)
509+from charmhelpers.contrib.openstack import (
510+ context,
511+ templating,
512+)
513+from charmhelpers.contrib.openstack.utils import (
514+ os_release,
515+ set_os_workload_status,
516+)
517 from charmhelpers.contrib.hahelpers.cluster import get_hacluster_config
518-from charmhelpers.core.host import cmp_pkgrevno
519-from charmhelpers.fetch import filter_installed_packages
520-
521-import ceph_radosgw_context
522+from charmhelpers.core.host import (
523+ cmp_pkgrevno,
524+ lsb_release,
525+)
526+from charmhelpers.fetch import (
527+ apt_install,
528+ apt_update,
529+ add_source,
530+ filter_installed_packages,
531+)
532
533 # The interface is said to be satisfied if anyone of the interfaces in the
534 # list has a complete context.
535@@ -32,6 +49,9 @@
536 TEMPLATES = 'templates/'
537 HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
538 CEPH_CONF = '/etc/ceph/ceph.conf'
539+APACHE_CONF = '/etc/apache2/sites-available/rgw'
540+APACHE_24_CONF = '/etc/apache2/sites-available/rgw.conf'
541+APACHE_PORTS_CONF = '/etc/apache2/ports.conf'
542
543 BASE_RESOURCE_MAP = OrderedDict([
544 (HAPROXY_CONF, {
545@@ -39,6 +59,18 @@
546 ceph_radosgw_context.HAProxyContext()],
547 'services': ['haproxy'],
548 }),
549+ (APACHE_CONF, {
550+ 'contexts': [ceph_radosgw_context.ApacheContext()],
551+ 'services': ['apache2'],
552+ }),
553+ (APACHE_24_CONF, {
554+ 'contexts': [ceph_radosgw_context.ApacheContext()],
555+ 'services': ['apache2'],
556+ }),
557+ (APACHE_PORTS_CONF, {
558+ 'contexts': [ceph_radosgw_context.ApacheContext()],
559+ 'services': ['apache2'],
560+ }),
561 (CEPH_CONF, {
562 'contexts': [ceph_radosgw_context.MonContext()],
563 'services': ['radosgw'],
564@@ -92,28 +124,6 @@
565 sources.write(line)
566
567
568-def get_host_ip(hostname=None):
569- try:
570- if not hostname:
571- hostname = unit_get('private-address')
572- # Test to see if already an IPv4 address
573- socket.inet_aton(hostname)
574- return hostname
575- except socket.error:
576- # This may throw an NXDOMAIN exception; in which case
577- # things are badly broken so just let it kill the hook
578- answers = dns.resolver.query(hostname, 'A')
579- if answers:
580- return answers[0].address
581-
582-
583-def is_apache_24():
584- if os.path.exists('/etc/apache2/conf-available'):
585- return True
586- else:
587- return False
588-
589-
590 def check_optional_relations(configs):
591 required_interfaces = {}
592 if relation_ids('ha'):
593@@ -132,3 +142,18 @@
594 return status_get()
595 else:
596 return 'unknown', 'No optional relations'
597+
598+
599+def setup_ipv6():
600+ ubuntu_rel = lsb_release()['DISTRIB_CODENAME'].lower()
601+ if ubuntu_rel < "trusty":
602+ raise Exception("IPv6 is not supported in the charms for Ubuntu "
603+ "versions less than Trusty 14.04")
604+
605+ # Need haproxy >= 1.5.3 for ipv6 so for Trusty if we are <= Kilo we need to
606+ # use trusty-backports otherwise we can use the UCA.
607+ if ubuntu_rel == 'trusty' and os_release('ceph-common') < 'liberty':
608+ add_source('deb http://archive.ubuntu.com/ubuntu trusty-backports '
609+ 'main')
610+ apt_update()
611+ apt_install('haproxy/trusty-backports', fatal=True)
612
613=== modified file 'templates/ceph.conf'
614--- templates/ceph.conf 2016-01-11 12:21:07 +0000
615+++ templates/ceph.conf 2016-02-18 09:59:16 +0000
616@@ -11,6 +11,9 @@
617 err to syslog = {{ use_syslog }}
618 clog to syslog = {{ use_syslog }}
619 debug rgw = {{ loglevel }}/5
620+{% if ipv6 -%}
621+ms bind ipv6 = true
622+{% endif %}
623
624 [client.radosgw.gateway]
625 host = {{ hostname }}
626
627=== added file 'templates/rgw.conf'
628--- templates/rgw.conf 1970-01-01 00:00:00 +0000
629+++ templates/rgw.conf 2016-02-18 09:59:16 +0000
630@@ -0,0 +1,25 @@
631+<IfModule mod_fastcgi.c>
632+ FastCgiExternalServer /var/www/s3gw.fcgi -socket /tmp/radosgw.sock
633+</IfModule>
634+
635+<VirtualHost *:{{ port }}>
636+ ServerName {{ hostname }}
637+ ServerAdmin ceph@ubuntu.com
638+ DocumentRoot /var/www
639+ RewriteEngine On
640+ RewriteRule ^/([a-zA-Z0-9-_.]*)([/]?.*) /s3gw.fcgi?page=$1&params=$2&%{QUERY_STRING} [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
641+ <IfModule mod_fastcgi.c>
642+ <Directory /var/www>
643+ Options +ExecCGI
644+ AllowOverride All
645+ SetHandler fastcgi-script
646+ Order allow,deny
647+ Allow from all
648+ AuthBasicAuthoritative Off
649+ </Directory>
650+ </IfModule>
651+ AllowEncodedSlashes On
652+ ErrorLog /var/log/apache2/error.log
653+ CustomLog /var/log/apache2/access.log combined
654+ ServerSignature Off
655+</VirtualHost>
656
657=== modified file 'tests/basic_deployment.py'
658--- tests/basic_deployment.py 2015-07-02 15:15:08 +0000
659+++ tests/basic_deployment.py 2016-02-18 09:59:16 +0000
660@@ -228,58 +228,33 @@
661 message = u.relation_error('ceph-radosgw to ceph', ret)
662 amulet.raise_status(amulet.FAIL, msg=message)
663
664- def test_201_ceph0_ceph_radosgw_relation(self):
665- """Verify the ceph0 to ceph-radosgw relation data."""
666+ def test_201_ceph_radosgw_relation(self):
667+ """Verify the ceph to ceph-radosgw relation data.
668+
669+ At least one unit (the leader) must have all data provided by the ceph
670+ charm.
671+ """
672 u.log.debug('Checking ceph0:radosgw radosgw:mon relation data...')
673- unit = self.ceph0_sentry
674- relation = ['radosgw', 'ceph-radosgw:mon']
675- expected = {
676- 'private-address': u.valid_ip,
677- 'radosgw_key': u.not_null,
678- 'auth': 'none',
679- 'ceph-public-address': u.valid_ip,
680- 'fsid': u'6547bd3e-1397-11e2-82e5-53567c8d32dc'
681- }
682-
683- ret = u.validate_relation_data(unit, relation, expected)
684- if ret:
685- message = u.relation_error('ceph0 to ceph-radosgw', ret)
686- amulet.raise_status(amulet.FAIL, msg=message)
687-
688- def test_202_ceph1_ceph_radosgw_relation(self):
689- """Verify the ceph1 to ceph-radosgw relation data."""
690- u.log.debug('Checking ceph1:radosgw ceph-radosgw:mon relation data...')
691- unit = self.ceph1_sentry
692- relation = ['radosgw', 'ceph-radosgw:mon']
693- expected = {
694- 'private-address': u.valid_ip,
695- 'radosgw_key': u.not_null,
696- 'auth': 'none',
697- 'ceph-public-address': u.valid_ip,
698- 'fsid': u'6547bd3e-1397-11e2-82e5-53567c8d32dc'
699- }
700-
701- ret = u.validate_relation_data(unit, relation, expected)
702- if ret:
703- message = u.relation_error('ceph1 to ceph-radosgw', ret)
704- amulet.raise_status(amulet.FAIL, msg=message)
705-
706- def test_203_ceph2_ceph_radosgw_relation(self):
707- """Verify the ceph2 to ceph-radosgw relation data."""
708- u.log.debug('Checking ceph2:radosgw ceph-radosgw:mon relation data...')
709- unit = self.ceph2_sentry
710- relation = ['radosgw', 'ceph-radosgw:mon']
711- expected = {
712- 'private-address': u.valid_ip,
713- 'radosgw_key': u.not_null,
714- 'auth': 'none',
715- 'ceph-public-address': u.valid_ip,
716- 'fsid': u'6547bd3e-1397-11e2-82e5-53567c8d32dc'
717- }
718-
719- ret = u.validate_relation_data(unit, relation, expected)
720- if ret:
721- message = u.relation_error('ceph2 to ceph-radosgw', ret)
722+ s_entries = [
723+ self.ceph0_sentry,
724+ self.ceph1_sentry,
725+ self.ceph2_sentry
726+ ]
727+ relation = ['radosgw', 'ceph-radosgw:mon']
728+ expected = {
729+ 'private-address': u.valid_ip,
730+ 'radosgw_key': u.not_null,
731+ 'auth': 'none',
732+ 'ceph-public-address': u.valid_ip,
733+ 'fsid': u'6547bd3e-1397-11e2-82e5-53567c8d32dc'
734+ }
735+
736+ ret = []
737+ for unit in s_entries:
738+ ret.append(u.validate_relation_data(unit, relation, expected))
739+
740+ if not any(ret):
741+ message = u.relation_error('ceph to ceph-radosgw', ret)
742 amulet.raise_status(amulet.FAIL, msg=message)
743
744 def test_204_ceph_radosgw_keystone_relation(self):
745
746=== modified file 'unit_tests/test_ceph_radosgw_context.py'
747--- unit_tests/test_ceph_radosgw_context.py 2016-01-11 12:21:07 +0000
748+++ unit_tests/test_ceph_radosgw_context.py 2016-02-18 09:59:16 +0000
749@@ -13,6 +13,7 @@
750 'related_units',
751 'cmp_pkgrevno',
752 'socket',
753+ 'is_apache_24',
754 ]
755
756
757@@ -146,8 +147,9 @@
758 super(MonContextTest, self).setUp(context, TO_PATCH)
759 self.config.side_effect = self.test_config.get
760
761- def test_ctxt(self):
762- self.socket.gethostname.return_value = '10.0.0.10'
763+ @patch.object(context, 'ensure_host_resolvable_v6')
764+ def test_ctxt(self, mock_ensure_rsv_v6):
765+ self.socket.gethostname.return_value = 'testhost'
766 mon_ctxt = context.MonContext()
767 addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
768
769@@ -156,6 +158,7 @@
770 return addresses.pop()
771 elif attr == 'auth':
772 return 'cephx'
773+
774 self.relation_get.side_effect = _relation_get
775 self.relation_ids.return_value = ['mon:6']
776 self.related_units.return_value = ['ceph/0', 'ceph/1', 'ceph/2']
777@@ -163,17 +166,26 @@
778 'auth_supported': 'cephx',
779 'embedded_webserver': False,
780 'disable_100_continue': True,
781- 'hostname': '10.0.0.10',
782+ 'hostname': 'testhost',
783 'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789',
784 'old_auth': False,
785 'use_syslog': 'false',
786 'loglevel': 1,
787- 'port': 70
788+ 'port': 70,
789+ 'ipv6': False
790 }
791 self.assertEqual(expect, mon_ctxt())
792+ self.assertFalse(mock_ensure_rsv_v6.called)
793+
794+ self.test_config.set('prefer-ipv6', True)
795+ addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
796+ expect['ipv6'] = True
797+ expect['port'] = "[::]:%s" % (70)
798+ self.assertEqual(expect, mon_ctxt())
799+ self.assertTrue(mock_ensure_rsv_v6.called)
800
801 def test_ctxt_missing_data(self):
802- self.socket.gethostname.return_value = '10.0.0.10'
803+ self.socket.gethostname.return_value = 'testhost'
804 mon_ctxt = context.MonContext()
805 self.relation_get.return_value = None
806 self.relation_ids.return_value = ['mon:6']
807@@ -181,7 +193,7 @@
808 self.assertEqual({}, mon_ctxt())
809
810 def test_ctxt_inconsistent_auths(self):
811- self.socket.gethostname.return_value = '10.0.0.10'
812+ self.socket.gethostname.return_value = 'testhost'
813 mon_ctxt = context.MonContext()
814 addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
815 auths = ['cephx', 'cephy', 'cephz']
816@@ -198,17 +210,18 @@
817 'auth_supported': 'none',
818 'embedded_webserver': False,
819 'disable_100_continue': True,
820- 'hostname': '10.0.0.10',
821+ 'hostname': 'testhost',
822 'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789',
823 'old_auth': False,
824 'use_syslog': 'false',
825 'loglevel': 1,
826- 'port': 70
827+ 'port': 70,
828+ 'ipv6': False
829 }
830 self.assertEqual(expect, mon_ctxt())
831
832 def test_ctxt_consistent_auths(self):
833- self.socket.gethostname.return_value = '10.0.0.10'
834+ self.socket.gethostname.return_value = 'testhost'
835 mon_ctxt = context.MonContext()
836 addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
837 auths = ['cephx', 'cephx', 'cephx']
838@@ -225,11 +238,19 @@
839 'auth_supported': 'cephx',
840 'embedded_webserver': False,
841 'disable_100_continue': True,
842- 'hostname': '10.0.0.10',
843+ 'hostname': 'testhost',
844 'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789',
845 'old_auth': False,
846 'use_syslog': 'false',
847 'loglevel': 1,
848- 'port': 70
849+ 'port': 70,
850+ 'ipv6': False
851 }
852 self.assertEqual(expect, mon_ctxt())
853+
854+
855+class ApacheContextTest(CharmTestCase):
856+
857+ def setUp(self):
858+ super(ApacheContextTest, self).setUp(context, TO_PATCH)
859+ self.config.side_effect = self.test_config.get
860
861=== modified file 'unit_tests/test_hooks.py'
862--- unit_tests/test_hooks.py 2016-01-22 13:29:40 +0000
863+++ unit_tests/test_hooks.py 2016-02-18 09:59:16 +0000
864@@ -6,7 +6,6 @@
865
866 from test_utils import (
867 CharmTestCase,
868- patch_open
869 )
870 from charmhelpers.contrib.openstack.ip import PUBLIC
871
872@@ -34,8 +33,6 @@
873 'enable_pocket',
874 'get_iface_for_address',
875 'get_netmask_for_address',
876- 'glob',
877- 'is_apache_24',
878 'log',
879 'lsb_release',
880 'open_port',
881@@ -44,8 +41,6 @@
882 'relation_set',
883 'relation_get',
884 'related_units',
885- 'render_template',
886- 'shutil',
887 'status_set',
888 'subprocess',
889 'sys',
890@@ -62,11 +57,6 @@
891 self.test_config.set('key', 'secretkey')
892 self.test_config.set('use-syslog', False)
893
894- def test_install_www_scripts(self):
895- self.glob.glob.return_value = ['files/www/bob']
896- ceph_hooks.install_www_scripts()
897- self.shutil.copy.assert_called_with('files/www/bob', '/var/www/')
898-
899 def test_install_ceph_optimised_packages(self):
900 self.lsb_release.return_value = {'DISTRIB_CODENAME': 'vivid'}
901 fastcgi_source = (
902@@ -122,69 +112,12 @@
903 self.enable_pocket.assert_called_with('multiverse')
904 self.os.makedirs.called_with('/var/lib/ceph/nss')
905
906- def test_emit_apacheconf(self):
907- self.is_apache_24.return_value = True
908- self.unit_get.return_value = '10.0.0.1'
909- apachecontext = {
910- "hostname": '10.0.0.1',
911- "port": 70,
912- }
913- vhost_file = '/etc/apache2/sites-available/rgw.conf'
914- with patch_open() as (_open, _file):
915- ceph_hooks.emit_apacheconf()
916- _open.assert_called_with(vhost_file, 'w')
917- self.render_template.assert_called_with('rgw', apachecontext)
918-
919- def test_apache_sites24(self):
920- self.is_apache_24.return_value = True
921- ceph_hooks.apache_sites()
922- calls = [
923- call(['a2dissite', '000-default']),
924- call(['a2ensite', 'rgw']),
925- ]
926- self.subprocess.check_call.assert_has_calls(calls)
927-
928- def test_apache_sites22(self):
929- self.is_apache_24.return_value = False
930- ceph_hooks.apache_sites()
931- calls = [
932- call(['a2dissite', 'default']),
933- call(['a2ensite', 'rgw']),
934- ]
935- self.subprocess.check_call.assert_has_calls(calls)
936-
937- def test_apache_modules(self):
938- ceph_hooks.apache_modules()
939- calls = [
940- call(['a2enmod', 'fastcgi']),
941- call(['a2enmod', 'rewrite']),
942- ]
943- self.subprocess.check_call.assert_has_calls(calls)
944-
945- def test_apache_reload(self):
946- ceph_hooks.apache_reload()
947- calls = [
948- call(['service', 'apache2', 'reload']),
949- ]
950- self.subprocess.call.assert_has_calls(calls)
951-
952- @patch.object(ceph_hooks, 'apache_ports', lambda *args: True)
953 @patch.object(ceph_hooks, 'mkdir', lambda *args: None)
954 def test_config_changed(self):
955 _install_packages = self.patch('install_packages')
956- _emit_apacheconf = self.patch('emit_apacheconf')
957- _install_www_scripts = self.patch('install_www_scripts')
958- _apache_sites = self.patch('apache_sites')
959- _apache_modules = self.patch('apache_modules')
960- _apache_reload = self.patch('apache_reload')
961 ceph_hooks.config_changed()
962 self.assertTrue(_install_packages.called)
963 self.CONFIGS.write_all.assert_called_with()
964- self.assertTrue(_emit_apacheconf.called)
965- self.assertTrue(_install_www_scripts.called)
966- self.assertTrue(_apache_sites.called)
967- self.assertTrue(_apache_modules.called)
968- self.assertTrue(_apache_reload.called)
969
970 @patch.object(ceph_hooks, 'is_request_complete',
971 lambda *args, **kwargs: True)

Subscribers

People subscribed via source and target branches