31
29
from charmhelpers.core.host import (
36
33
from charmhelpers.contrib.hahelpers.cluster import (
37
34
determine_apache_port,
38
35
determine_api_port,
43
39
from charmhelpers.contrib.hahelpers.apache import (
49
44
from charmhelpers.contrib.openstack.neutron import (
50
45
neutron_plugin_attribute,
53
47
from charmhelpers.contrib.network.ip import (
54
48
get_address_in_network,
56
50
get_netmask_for_address,
52
is_address_in_network,
54
from charmhelpers.contrib.openstack.utils import get_host_ip
61
from charmhelpers.contrib.openstack.utils import (
64
56
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
57
ADDRESS_TYPES = ['admin', 'internal', 'public']
67
60
class OSContextError(Exception):
78
71
def context_complete(ctxt):
80
for k, v in ctxt.iteritems():
73
for k, v in six.iteritems(ctxt):
81
74
if v is None or v == '':
84
log('Missing required data: %s' % ' '.join(_missing), level='INFO')
78
log('Missing required data: %s' % ' '.join(_missing), level=INFO)
89
84
def config_flags_parser(config_flags):
85
"""Parses config flags string into dict.
87
The provided config_flags string may be a list of comma-separated values
88
which themselves may be comma-separated list of values.
90
90
if config_flags.find('==') >= 0:
91
log("config_flags is not in expected format (key=value)",
91
log("config_flags is not in expected format (key=value)", level=ERROR)
93
92
raise OSContextError
94
94
# strip the following from each value.
95
95
post_strippers = ' ,'
96
96
# we strip any leading/trailing '=' or ' ' from the string then
136
137
def __init__(self,
137
138
database=None, user=None, relation_prefix=None, ssl_dir=None):
139
Allows inspecting relation for settings prefixed with relation_prefix.
140
This is useful for parsing access for multiple databases returned via
141
the shared-db interface (eg, nova_password, quantum_password)
139
"""Allows inspecting relation for settings prefixed with
140
relation_prefix. This is useful for parsing access for multiple
141
databases returned via the shared-db interface (eg, nova_password,
143
144
self.relation_prefix = relation_prefix
144
145
self.database = database
204
204
def __call__(self):
205
205
self.database = self.database or config('database')
206
206
if self.database is None:
207
log('Could not generate postgresql_db context. '
208
'Missing required charm config options. '
207
log('Could not generate postgresql_db context. Missing required '
208
'charm config options. (database name)', level=ERROR)
210
209
raise OSContextError
213
212
for rid in relation_ids(self.interfaces[0]):
214
213
for unit in related_units(rid):
216
'database_host': relation_get('host', rid=rid, unit=unit),
217
'database': self.database,
218
'database_user': relation_get('user', rid=rid, unit=unit),
219
'database_password': relation_get('password', rid=rid, unit=unit),
220
'database_type': 'postgresql',
214
rel_host = relation_get('host', rid=rid, unit=unit)
215
rel_user = relation_get('user', rid=rid, unit=unit)
216
rel_passwd = relation_get('password', rid=rid, unit=unit)
217
ctxt = {'database_host': rel_host,
218
'database': self.database,
219
'database_user': rel_user,
220
'database_password': rel_passwd,
221
'database_type': 'postgresql'}
222
222
if context_complete(ctxt):
229
230
ca_path = os.path.join(ssl_dir, 'db-client.ca')
230
231
with open(ca_path, 'w') as fh:
231
232
fh.write(b64decode(rdata['ssl_ca']))
232
234
ctxt['database_ssl_ca'] = ca_path
233
235
elif 'ssl_ca' in rdata:
234
log("Charm not setup for ssl support but ssl ca found")
236
log("Charm not setup for ssl support but ssl ca found", level=INFO)
236
239
if 'ssl_cert' in rdata:
237
240
cert_path = os.path.join(
238
241
ssl_dir, 'db-client.cert')
239
242
if not os.path.exists(cert_path):
240
log("Waiting 1m for ssl client cert validity")
243
log("Waiting 1m for ssl client cert validity", level=INFO)
242
246
with open(cert_path, 'w') as fh:
243
247
fh.write(b64decode(rdata['ssl_cert']))
244
249
ctxt['database_ssl_cert'] = cert_path
245
250
key_path = os.path.join(ssl_dir, 'db-client.key')
246
251
with open(key_path, 'w') as fh:
247
252
fh.write(b64decode(rdata['ssl_key']))
248
254
ctxt['database_ssl_key'] = key_path
263
269
serv_host = format_ipv6_addr(serv_host) or serv_host
264
270
auth_host = rdata.get('auth_host')
265
271
auth_host = format_ipv6_addr(auth_host) or auth_host
268
'service_port': rdata.get('service_port'),
269
'service_host': serv_host,
270
'auth_host': auth_host,
271
'auth_port': rdata.get('auth_port'),
272
'admin_tenant_name': rdata.get('service_tenant'),
273
'admin_user': rdata.get('service_username'),
274
'admin_password': rdata.get('service_password'),
276
rdata.get('service_protocol') or 'http',
278
rdata.get('auth_protocol') or 'http',
272
svc_protocol = rdata.get('service_protocol') or 'http'
273
auth_protocol = rdata.get('auth_protocol') or 'http'
274
ctxt = {'service_port': rdata.get('service_port'),
275
'service_host': serv_host,
276
'auth_host': auth_host,
277
'auth_port': rdata.get('auth_port'),
278
'admin_tenant_name': rdata.get('service_tenant'),
279
'admin_user': rdata.get('service_username'),
280
'admin_password': rdata.get('service_password'),
281
'service_protocol': svc_protocol,
282
'auth_protocol': auth_protocol}
280
283
if context_complete(ctxt):
281
284
# NOTE(jamespage) this is required for >= icehouse
282
285
# so a missing value just indicates keystone needs
284
287
ctxt['admin_tenant_id'] = rdata.get('service_tenant_id')
295
299
self.interfaces = [rel_name]
297
301
def __call__(self):
298
log('Generating template context for amqp')
302
log('Generating template context for amqp', level=DEBUG)
300
user_setting = 'rabbit-user'
301
vhost_setting = 'rabbit-vhost'
302
304
if self.relation_prefix:
303
user_setting = self.relation_prefix + '-rabbit-user'
304
vhost_setting = self.relation_prefix + '-rabbit-vhost'
305
user_setting = '%s-rabbit-user' % (self.relation_prefix)
306
vhost_setting = '%s-rabbit-vhost' % (self.relation_prefix)
308
user_setting = 'rabbit-user'
309
vhost_setting = 'rabbit-vhost'
307
312
username = conf[user_setting]
308
313
vhost = conf[vhost_setting]
309
314
except KeyError as e:
310
log('Could not generate shared_db context. '
311
'Missing required charm config options: %s.' % e)
315
log('Could not generate shared_db context. Missing required charm '
316
'config options: %s.' % e, level=ERROR)
312
317
raise OSContextError
314
320
for rid in relation_ids(self.rel_name):
315
321
ha_vip_only = False
346
354
if context_complete(ctxt):
347
355
if 'rabbit_ssl_ca' in ctxt:
348
356
if not self.ssl_dir:
349
log(("Charm not setup for ssl support "
357
log("Charm not setup for ssl support but ssl ca "
352
361
ca_path = os.path.join(
353
362
self.ssl_dir, 'rabbit-client-ca.pem')
354
363
with open(ca_path, 'w') as fh:
355
364
fh.write(b64decode(ctxt['rabbit_ssl_ca']))
356
365
ctxt['rabbit_ssl_ca'] = ca_path
357
367
# Sufficient information found = break out!
359
370
# Used for active/active rabbitmq >= grizzly
360
if ('clustered' not in ctxt or ha_vip_only) \
361
and len(related_units(rid)) > 1:
371
if (('clustered' not in ctxt or ha_vip_only) and
372
len(related_units(rid)) > 1):
362
373
rabbitmq_hosts = []
363
374
for unit in related_units(rid):
364
375
host = relation_get('private-address', rid=rid, unit=unit)
365
376
host = format_ipv6_addr(host) or host
366
377
rabbitmq_hosts.append(host)
367
ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts)
379
ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))
368
381
if not context_complete(ctxt):
374
387
class CephContext(OSContextGenerator):
388
"""Generates context for /etc/ceph/ceph.conf templates."""
375
389
interfaces = ['ceph']
377
391
def __call__(self):
378
'''This generates context for /etc/ceph/ceph.conf templates'''
379
392
if not relation_ids('ceph'):
382
log('Generating template context for ceph')
395
log('Generating template context for ceph', level=DEBUG)
389
401
for unit in related_units(rid):
390
402
auth = relation_get('auth', rid=rid, unit=unit)
391
403
key = relation_get('key', rid=rid, unit=unit)
393
relation_get('ceph-public-address', rid=rid, unit=unit) or \
394
relation_get('private-address', rid=rid, unit=unit)
404
ceph_pub_addr = relation_get('ceph-public-address', rid=rid,
406
unit_priv_addr = relation_get('private-address', rid=rid,
408
ceph_addr = ceph_pub_addr or unit_priv_addr
395
409
ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
396
410
mon_hosts.append(ceph_addr)
399
'mon_hosts': ' '.join(mon_hosts),
402
'use_syslog': use_syslog
412
ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)),
415
'use_syslog': use_syslog}
405
417
if not os.path.isdir('/etc/ceph'):
406
418
os.mkdir('/etc/ceph')
411
423
ensure_packages(['ceph-common'])
416
ADDRESS_TYPES = ['admin', 'internal', 'public']
419
427
class HAProxyContext(OSContextGenerator):
428
"""Provides half a context for the haproxy template, which describes
429
all peers to be included in the cluster. Each charm needs to include
430
its own context generator that describes the port mapping.
420
432
interfaces = ['cluster']
434
def __init__(self, singlenode_mode=False):
435
self.singlenode_mode = singlenode_mode
422
437
def __call__(self):
424
Builds half a context for the haproxy template, which describes
425
all peers to be included in the cluster. Each charm needs to include
426
its own context generator that describes the port mapping.
428
if not relation_ids('cluster'):
438
if not relation_ids('cluster') and not self.singlenode_mode:
431
l_unit = local_unit().replace('/', '-')
433
441
if config('prefer-ipv6'):
434
442
addr = get_ipv6_addr(exc_list=[config('vip')])[0]
436
444
addr = get_host_ip(unit_get('private-address'))
446
l_unit = local_unit().replace('/', '-')
438
447
cluster_hosts = {}
440
449
# NOTE(jamespage): build out map of configured network endpoints
441
450
# and associated backends
442
451
for addr_type in ADDRESS_TYPES:
443
laddr = get_address_in_network(
444
config('os-{}-network'.format(addr_type)))
452
cfg_opt = 'os-{}-network'.format(addr_type)
453
laddr = get_address_in_network(config(cfg_opt))
446
cluster_hosts[laddr] = {}
447
cluster_hosts[laddr]['network'] = "{}/{}".format(
449
get_netmask_for_address(laddr)
451
cluster_hosts[laddr]['backends'] = {}
452
cluster_hosts[laddr]['backends'][l_unit] = laddr
455
netmask = get_netmask_for_address(laddr)
456
cluster_hosts[laddr] = {'network': "{}/{}".format(laddr,
458
'backends': {l_unit: laddr}}
453
459
for rid in relation_ids('cluster'):
454
460
for unit in related_units(rid):
455
_unit = unit.replace('/', '-')
456
461
_laddr = relation_get('{}-address'.format(addr_type),
457
462
rid=rid, unit=unit)
464
_unit = unit.replace('/', '-')
459
465
cluster_hosts[laddr]['backends'][_unit] = _laddr
461
467
# NOTE(jamespage) no split configurations found, just use
462
468
# private addresses
463
469
if not cluster_hosts:
464
cluster_hosts[addr] = {}
465
cluster_hosts[addr]['network'] = "{}/{}".format(
467
get_netmask_for_address(addr)
469
cluster_hosts[addr]['backends'] = {}
470
cluster_hosts[addr]['backends'][l_unit] = addr
470
netmask = get_netmask_for_address(addr)
471
cluster_hosts[addr] = {'network': "{}/{}".format(addr, netmask),
472
'backends': {l_unit: addr}}
471
473
for rid in relation_ids('cluster'):
472
474
for unit in related_units(rid):
473
_unit = unit.replace('/', '-')
474
475
_laddr = relation_get('private-address',
475
476
rid=rid, unit=unit)
478
_unit = unit.replace('/', '-')
477
479
cluster_hosts[addr]['backends'][_unit] = _laddr
480
'frontends': cluster_hosts,
481
ctxt = {'frontends': cluster_hosts}
483
483
if config('haproxy-server-timeout'):
484
484
ctxt['haproxy_server_timeout'] = config('haproxy-server-timeout')
485
486
if config('haproxy-client-timeout'):
486
487
ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')
495
496
ctxt['stat_port'] = ':8888'
497
498
for frontend in cluster_hosts:
498
if len(cluster_hosts[frontend]['backends']) > 1:
499
if (len(cluster_hosts[frontend]['backends']) > 1 or
500
self.singlenode_mode):
499
501
# Enable haproxy when we have enough peers.
500
log('Ensuring haproxy enabled in /etc/default/haproxy.')
502
log('Ensuring haproxy enabled in /etc/default/haproxy.',
501
504
with open('/etc/default/haproxy', 'w') as out:
502
505
out.write('ENABLED=1\n')
504
log('HAProxy context is incomplete, this unit has no peers.')
509
log('HAProxy context is incomplete, this unit has no peers.',
509
515
interfaces = ['image-service']
511
517
def __call__(self):
513
Obtains the glance API server from the image-service relation. Useful
514
in nova and cinder (currently).
516
log('Generating template context for image-service.')
518
"""Obtains the glance API server from the image-service relation.
519
Useful in nova and cinder (currently).
521
log('Generating template context for image-service.', level=DEBUG)
517
522
rids = relation_ids('image-service')
521
527
for unit in related_units(rid):
522
528
api_server = relation_get('glance-api-server',
523
529
rid=rid, unit=unit)
525
531
return {'glance_api_servers': api_server}
526
log('ImageService context is incomplete. '
527
'Missing required relation data.')
533
log("ImageService context is incomplete. Missing required relation "
531
538
class ApacheSSLContext(OSContextGenerator):
534
Generates a context for an apache vhost configuration that configures
539
"""Generates a context for an apache vhost configuration that configures
535
540
HTTPS reverse proxying for one or many endpoints. Generated context
536
541
looks something like::
628
637
addresses.append((addr, addr))
639
return sorted(addresses)
632
641
def __call__(self):
633
if isinstance(self.external_ports, basestring):
642
if isinstance(self.external_ports, six.string_types):
634
643
self.external_ports = [self.external_ports]
635
if (not self.external_ports or not https()):
645
if not self.external_ports or not https():
638
648
self.configure_ca()
639
649
self.enable_modules()
642
'namespace': self.service_namespace,
651
ctxt = {'namespace': self.service_namespace,
647
655
for cn in self.canonical_names():
648
656
self.configure_cert(cn)
650
658
addresses = self.get_network_addresses()
651
for address, endpoint in set(addresses):
659
for address, endpoint in sorted(set(addresses)):
652
660
for api_port in self.external_ports:
653
661
ext_port = determine_apache_port(api_port)
654
662
int_port = determine_api_port(api_port)
655
663
portmap = (address, endpoint, int(ext_port), int(int_port))
656
664
ctxt['endpoints'].append(portmap)
657
665
ctxt['ext_ports'].append(int(ext_port))
658
ctxt['ext_ports'] = list(set(ctxt['ext_ports']))
667
ctxt['ext_ports'] = sorted(list(set(ctxt['ext_ports'])))
674
683
def packages(self):
675
return neutron_plugin_attribute(
676
self.plugin, 'packages', self.network_manager)
684
return neutron_plugin_attribute(self.plugin, 'packages',
685
self.network_manager)
679
688
def neutron_security_groups(self):
682
691
def _ensure_packages(self):
683
[ensure_packages(pkgs) for pkgs in self.packages]
692
for pkgs in self.packages:
693
ensure_packages(pkgs)
685
695
def _save_flag_file(self):
686
696
if self.network_manager == 'quantum':
687
697
_file = '/etc/nova/quantum_plugin.conf'
689
699
_file = '/etc/nova/neutron_plugin.conf'
690
701
with open(_file, 'wb') as out:
691
702
out.write(self.plugin + '\n')
726
733
n1kv_config = neutron_plugin_attribute(self.plugin, 'config',
727
734
self.network_manager)
728
735
n1kv_user_config_flags = config('n1kv-config-flags')
730
'core_plugin': driver,
731
'neutron_plugin': 'n1kv',
732
'neutron_security_groups': self.neutron_security_groups,
733
'local_ip': unit_private_ip(),
734
'config': n1kv_config,
735
'vsm_ip': config('n1kv-vsm-ip'),
736
'vsm_username': config('n1kv-vsm-username'),
737
'vsm_password': config('n1kv-vsm-password'),
738
'restrict_policy_profiles': config(
739
'n1kv-restrict-policy-profiles'),
736
restrict_policy_profiles = config('n1kv-restrict-policy-profiles')
737
n1kv_ctxt = {'core_plugin': driver,
738
'neutron_plugin': 'n1kv',
739
'neutron_security_groups': self.neutron_security_groups,
740
'local_ip': unit_private_ip(),
741
'config': n1kv_config,
742
'vsm_ip': config('n1kv-vsm-ip'),
743
'vsm_username': config('n1kv-vsm-username'),
744
'vsm_password': config('n1kv-vsm-password'),
745
'restrict_policy_profiles': restrict_policy_profiles}
741
747
if n1kv_user_config_flags:
742
748
flags = config_flags_parser(n1kv_user_config_flags)
743
749
n1kv_ctxt['user_config_flags'] = flags
749
755
self.network_manager)
750
756
config = neutron_plugin_attribute(self.plugin, 'config',
751
757
self.network_manager)
753
'core_plugin': driver,
754
'neutron_plugin': 'Calico',
755
'neutron_security_groups': self.neutron_security_groups,
756
'local_ip': unit_private_ip(),
758
calico_ctxt = {'core_plugin': driver,
759
'neutron_plugin': 'Calico',
760
'neutron_security_groups': self.neutron_security_groups,
761
'local_ip': unit_private_ip(),
760
764
return calico_ctxt
914
915
if self.service not in sub_config:
915
916
log('Found subordinate_config on %s but it contained'
916
'nothing for %s service' % (rid, self.service))
917
'nothing for %s service' % (rid, self.service),
919
921
sub_config = sub_config[self.service]
920
922
if self.config_file not in sub_config:
921
923
log('Found subordinate_config on %s but it contained'
922
'nothing for %s' % (rid, self.config_file))
924
'nothing for %s' % (rid, self.config_file),
925
928
sub_config = sub_config[self.config_file]
926
for k, v in sub_config.iteritems():
929
for k, v in six.iteritems(sub_config):
927
930
if k == 'sections':
928
for section, config_dict in v.iteritems():
929
log("adding section '%s'" % (section))
931
for section, config_dict in six.iteritems(v):
932
log("adding section '%s'" % (section),
930
934
ctxt[k][section] = config_dict
934
938
log("%d section(s) found" % (len(ctxt['sections'])), level=DEBUG)
998
995
for unit in related_units(rid):
999
996
ctxt['zmq_nonce'] = relation_get('nonce', unit, rid)
1000
997
ctxt['zmq_host'] = relation_get('host', unit, rid)
1004
1002
class NotificationDriverContext(OSContextGenerator):
1006
def __init__(self, zmq_relation='zeromq-configuration', amqp_relation='amqp'):
1004
def __init__(self, zmq_relation='zeromq-configuration',
1005
amqp_relation='amqp'):
1008
:param zmq_relation : Name of Zeromq relation to check
1007
:param zmq_relation: Name of Zeromq relation to check
1010
1009
self.zmq_relation = zmq_relation
1011
1010
self.amqp_relation = amqp_relation
1013
1012
def __call__(self):
1015
'notifications': 'False',
1013
ctxt = {'notifications': 'False'}
1017
1014
if is_relation_made(self.amqp_relation):
1018
1015
ctxt['notifications'] = "True"