3
from base64 import b64decode
5
from subprocess import (
9
from charmhelpers.core.hookenv import (
19
from charmhelpers.contrib.hahelpers.cluster import (
21
determine_haproxy_port,
27
from charmhelpers.contrib.hahelpers.apache import (
32
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
35
class OSContextError(Exception):
39
def context_complete(ctxt):
41
for k, v in ctxt.iteritems():
42
if v is None or v == '':
45
log('Missing required data: %s' % ' '.join(_missing), level='INFO')
50
class OSContextGenerator(object):
54
raise NotImplementedError
57
class SharedDBContext(OSContextGenerator):
58
interfaces = ['shared-db']
61
log('Generating template context for shared-db')
64
database = conf['database']
65
username = conf['database-user']
67
log('Could not generate shared_db context. '
68
'Missing required charm config options: %s.' % e)
71
for rid in relation_ids('shared-db'):
72
for unit in related_units(rid):
74
'database_host': relation_get('db_host', rid=rid,
77
'database_user': username,
78
'database_password': relation_get('password', rid=rid,
81
if not context_complete(ctxt):
86
class IdentityServiceContext(OSContextGenerator):
87
interfaces = ['identity-service']
90
log('Generating template context for identity-service')
93
for rid in relation_ids('identity-service'):
94
for unit in related_units(rid):
96
'service_port': relation_get('service_port', rid=rid,
98
'service_host': relation_get('service_host', rid=rid,
100
'auth_host': relation_get('auth_host', rid=rid, unit=unit),
101
'auth_port': relation_get('auth_port', rid=rid, unit=unit),
102
'admin_tenant_name': relation_get('service_tenant',
104
'admin_user': relation_get('service_username', rid=rid,
106
'admin_password': relation_get('service_password', rid=rid,
108
# XXX: Hard-coded http.
109
'service_protocol': 'http',
110
'auth_protocol': 'http',
112
if not context_complete(ctxt):
117
class AMQPContext(OSContextGenerator):
118
interfaces = ['amqp']
121
log('Generating template context for amqp')
124
username = conf['rabbit-user']
125
vhost = conf['rabbit-vhost']
126
except KeyError as e:
127
log('Could not generate shared_db context. '
128
'Missing required charm config options: %s.' % e)
132
for rid in relation_ids('amqp'):
133
for unit in related_units(rid):
134
if relation_get('clustered', rid=rid, unit=unit):
135
rabbitmq_host = relation_get('vip', rid=rid, unit=unit)
137
rabbitmq_host = relation_get('private-address',
140
'rabbitmq_host': rabbitmq_host,
141
'rabbitmq_user': username,
142
'rabbitmq_password': relation_get('password', rid=rid,
144
'rabbitmq_virtual_host': vhost,
146
if not context_complete(ctxt):
151
class CephContext(OSContextGenerator):
152
interfaces = ['ceph']
155
'''This generates context for /etc/ceph/ceph.conf templates'''
156
log('Generating tmeplate context for ceph')
159
for rid in relation_ids('ceph'):
160
for unit in related_units(rid):
161
mon_hosts.append(relation_get('private-address', rid=rid,
163
auth = relation_get('auth', rid=rid, unit=unit)
166
'mon_hosts': ' '.join(mon_hosts),
169
if not context_complete(ctxt):
174
class HAProxyContext(OSContextGenerator):
175
interfaces = ['cluster']
179
Builds half a context for the haproxy template, which describes
180
all peers to be included in the cluster. Each charm needs to include
181
its own context generator that describes the port mapping.
183
if not relation_ids('cluster'):
187
l_unit = local_unit().replace('/', '-')
188
cluster_hosts[l_unit] = unit_get('private-address')
190
for rid in relation_ids('cluster'):
191
for unit in related_units(rid):
192
_unit = unit.replace('/', '-')
193
addr = relation_get('private-address', rid=rid, unit=unit)
194
cluster_hosts[_unit] = addr
197
'units': cluster_hosts,
199
if len(cluster_hosts.keys()) > 1:
200
# Enable haproxy when we have enough peers.
201
log('Ensuring haproxy enabled in /etc/default/haproxy.')
202
with open('/etc/default/haproxy', 'w') as out:
203
out.write('ENABLED=1\n')
205
log('HAProxy context is incomplete, this unit has no peers.')
209
class ImageServiceContext(OSContextGenerator):
210
interfaces = ['image-servce']
214
Obtains the glance API server from the image-service relation. Useful
215
in nova and cinder (currently).
217
log('Generating template context for image-service.')
218
rids = relation_ids('image-service')
222
for unit in related_units(rid):
223
api_server = relation_get('glance-api-server',
226
return {'glance_api_servers': api_server}
227
log('ImageService context is incomplete. '
228
'Missing required relation data.')
232
class ApacheSSLContext(OSContextGenerator):
234
Generates a context for an apache vhost configuration that configures
235
HTTPS reverse proxying for one or many endpoints. Generated context
236
looks something like:
238
'namespace': 'cinder',
239
'private_address': 'iscsi.mycinderhost.com',
240
'endpoints': [(8776, 8766), (8777, 8767)]
243
The endpoints list consists of a tuples mapping external ports
246
interfaces = ['https']
248
# charms should inherit this context and set external ports
249
# and service namespace accordingly.
251
service_namespace = None
253
def enable_modules(self):
254
cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']
257
def configure_cert(self):
258
if not os.path.isdir('/etc/apache2/ssl'):
259
os.mkdir('/etc/apache2/ssl')
260
ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
261
if not os.path.isdir(ssl_dir):
263
cert, key = get_cert()
264
with open(os.path.join(ssl_dir, 'cert'), 'w') as cert_out:
265
cert_out.write(b64decode(cert))
266
with open(os.path.join(ssl_dir, 'key'), 'w') as key_out:
267
key_out.write(b64decode(key))
268
ca_cert = get_ca_cert()
270
with open(CA_CERT_PATH, 'w') as ca_out:
271
ca_out.write(b64decode(ca_cert))
274
if isinstance(self.external_ports, basestring):
275
self.external_ports = [self.external_ports]
276
if (not self.external_ports or not https()):
279
self.configure_cert()
280
self.enable_modules()
283
'namespace': self.service_namespace,
284
'private_address': unit_get('private-address'),
287
for ext_port in self.external_ports:
288
if peer_units() or is_clustered():
289
int_port = determine_haproxy_port(ext_port)
291
int_port = determine_api_port(ext_port)
292
portmap = (int(ext_port), int(int_port))
293
ctxt['endpoints'].append(portmap)