Merge lp:~hopem/charms/trusty/ceph-radosgw/lp1513524 into lp:~openstack-charmers-archive/charms/trusty/ceph-radosgw/next
- Trusty Tahr (14.04)
- lp1513524
- Merge into next
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chris Holcombe (community) | Approve | ||
OpenStack Charmers | Pending | ||
Review via email:
|
This proposal has been superseded by a proposal from 2016-02-18.
Commit message
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #488 ceph-radosgw-next for hopem mp285808
UNIT OK: passed
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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://
Build: http://
- 63. By Edward Hope-Morley
-
fix amulet
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #489 ceph-radosgw-next for hopem mp285808
UNIT OK: passed
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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://
Build: http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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://
Build: http://
- 64. By Edward Hope-Morley
-
fx amulet
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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://
Build: http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #490 ceph-radosgw-next for hopem mp285808
UNIT OK: passed
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #237 ceph-radosgw-next for hopem mp285808
AMULET OK: passed
- 65. By Edward Hope-Morley
-
fix lint error
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #577 ceph-radosgw-next for hopem mp285808
LINT OK: passed
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #491 ceph-radosgw-next for hopem mp285808
UNIT OK: passed
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #238 ceph-radosgw-next for hopem mp285808
AMULET OK: passed
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
- 66. By Edward Hope-Morley
-
post-review fixups
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Edward Hope-Morley (hopem) wrote : | # |
Thanks for the review Chris! I've fixed up based on your comments and added some inline responses.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Holcombe (xfactor973) wrote : | # |
Cool. Lets get this merged!
- 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
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¶ms=$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) |
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/