~1chb1n/charms/trusty/nova-cloud-controller/15.10-stable-flip-tests-helper-syncs

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/openstack/context.py

Check in start of py redux.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os
 
2
 
 
3
from base64 import b64decode
 
4
 
 
5
from subprocess import (
 
6
    check_call
 
7
)
 
8
 
 
9
from charmhelpers.core.hookenv import (
 
10
    config,
 
11
    local_unit,
 
12
    log,
 
13
    relation_get,
 
14
    relation_ids,
 
15
    related_units,
 
16
    unit_get,
 
17
)
 
18
 
 
19
from charmhelpers.contrib.hahelpers.cluster import (
 
20
    determine_api_port,
 
21
    determine_haproxy_port,
 
22
    https,
 
23
    is_clustered,
 
24
    peer_units,
 
25
)
 
26
 
 
27
from charmhelpers.contrib.hahelpers.apache import (
 
28
    get_cert,
 
29
    get_ca_cert,
 
30
)
 
31
 
 
32
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
 
33
 
 
34
 
 
35
class OSContextError(Exception):
 
36
    pass
 
37
 
 
38
 
 
39
def context_complete(ctxt):
 
40
    _missing = []
 
41
    for k, v in ctxt.iteritems():
 
42
        if v is None or v == '':
 
43
            _missing.append(k)
 
44
    if _missing:
 
45
        log('Missing required data: %s' % ' '.join(_missing), level='INFO')
 
46
        return False
 
47
    return True
 
48
 
 
49
 
 
50
class OSContextGenerator(object):
 
51
    interfaces = []
 
52
 
 
53
    def __call__(self):
 
54
        raise NotImplementedError
 
55
 
 
56
 
 
57
class SharedDBContext(OSContextGenerator):
 
58
    interfaces = ['shared-db']
 
59
 
 
60
    def __call__(self):
 
61
        log('Generating template context for shared-db')
 
62
        conf = config()
 
63
        try:
 
64
            database = conf['database']
 
65
            username = conf['database-user']
 
66
        except KeyError as e:
 
67
            log('Could not generate shared_db context. '
 
68
                'Missing required charm config options: %s.' % e)
 
69
            raise OSContextError
 
70
        ctxt = {}
 
71
        for rid in relation_ids('shared-db'):
 
72
            for unit in related_units(rid):
 
73
                ctxt = {
 
74
                    'database_host': relation_get('db_host', rid=rid,
 
75
                                                  unit=unit),
 
76
                    'database': database,
 
77
                    'database_user': username,
 
78
                    'database_password': relation_get('password', rid=rid,
 
79
                                                      unit=unit)
 
80
                }
 
81
        if not context_complete(ctxt):
 
82
            return {}
 
83
        return ctxt
 
84
 
 
85
 
 
86
class IdentityServiceContext(OSContextGenerator):
 
87
    interfaces = ['identity-service']
 
88
 
 
89
    def __call__(self):
 
90
        log('Generating template context for identity-service')
 
91
        ctxt = {}
 
92
 
 
93
        for rid in relation_ids('identity-service'):
 
94
            for unit in related_units(rid):
 
95
                ctxt = {
 
96
                    'service_port': relation_get('service_port', rid=rid,
 
97
                                                 unit=unit),
 
98
                    'service_host': relation_get('service_host', rid=rid,
 
99
                                                 unit=unit),
 
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',
 
103
                                                      rid=rid, unit=unit),
 
104
                    'admin_user': relation_get('service_username', rid=rid,
 
105
                                               unit=unit),
 
106
                    'admin_password': relation_get('service_password', rid=rid,
 
107
                                                   unit=unit),
 
108
                    # XXX: Hard-coded http.
 
109
                    'service_protocol': 'http',
 
110
                    'auth_protocol': 'http',
 
111
                }
 
112
        if not context_complete(ctxt):
 
113
            return {}
 
114
        return ctxt
 
115
 
 
116
 
 
117
class AMQPContext(OSContextGenerator):
 
118
    interfaces = ['amqp']
 
119
 
 
120
    def __call__(self):
 
121
        log('Generating template context for amqp')
 
122
        conf = config()
 
123
        try:
 
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)
 
129
            raise OSContextError
 
130
 
 
131
        ctxt = {}
 
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)
 
136
                else:
 
137
                    rabbitmq_host = relation_get('private-address',
 
138
                                                 rid=rid, unit=unit)
 
139
                ctxt = {
 
140
                    'rabbitmq_host': rabbitmq_host,
 
141
                    'rabbitmq_user': username,
 
142
                    'rabbitmq_password': relation_get('password', rid=rid,
 
143
                                                      unit=unit),
 
144
                    'rabbitmq_virtual_host': vhost,
 
145
                }
 
146
        if not context_complete(ctxt):
 
147
            return {}
 
148
        return ctxt
 
149
 
 
150
 
 
151
class CephContext(OSContextGenerator):
 
152
    interfaces = ['ceph']
 
153
 
 
154
    def __call__(self):
 
155
        '''This generates context for /etc/ceph/ceph.conf templates'''
 
156
        log('Generating tmeplate context for ceph')
 
157
        mon_hosts = []
 
158
        auth = None
 
159
        for rid in relation_ids('ceph'):
 
160
            for unit in related_units(rid):
 
161
                mon_hosts.append(relation_get('private-address', rid=rid,
 
162
                                              unit=unit))
 
163
                auth = relation_get('auth', rid=rid, unit=unit)
 
164
 
 
165
        ctxt = {
 
166
            'mon_hosts': ' '.join(mon_hosts),
 
167
            'auth': auth,
 
168
        }
 
169
        if not context_complete(ctxt):
 
170
            return {}
 
171
        return ctxt
 
172
 
 
173
 
 
174
class HAProxyContext(OSContextGenerator):
 
175
    interfaces = ['cluster']
 
176
 
 
177
    def __call__(self):
 
178
        '''
 
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.
 
182
        '''
 
183
        if not relation_ids('cluster'):
 
184
            return {}
 
185
 
 
186
        cluster_hosts = {}
 
187
        l_unit = local_unit().replace('/', '-')
 
188
        cluster_hosts[l_unit] = unit_get('private-address')
 
189
 
 
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
 
195
 
 
196
        ctxt = {
 
197
            'units': cluster_hosts,
 
198
        }
 
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')
 
204
            return ctxt
 
205
        log('HAProxy context is incomplete, this unit has no peers.')
 
206
        return {}
 
207
 
 
208
 
 
209
class ImageServiceContext(OSContextGenerator):
 
210
    interfaces = ['image-servce']
 
211
 
 
212
    def __call__(self):
 
213
        '''
 
214
        Obtains the glance API server from the image-service relation.  Useful
 
215
        in nova and cinder (currently).
 
216
        '''
 
217
        log('Generating template context for image-service.')
 
218
        rids = relation_ids('image-service')
 
219
        if not rids:
 
220
            return {}
 
221
        for rid in rids:
 
222
            for unit in related_units(rid):
 
223
                api_server = relation_get('glance-api-server',
 
224
                                          rid=rid, unit=unit)
 
225
                if api_server:
 
226
                    return {'glance_api_servers': api_server}
 
227
        log('ImageService context is incomplete. '
 
228
            'Missing required relation data.')
 
229
        return {}
 
230
 
 
231
 
 
232
class ApacheSSLContext(OSContextGenerator):
 
233
    """
 
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:
 
237
    {
 
238
        'namespace': 'cinder',
 
239
        'private_address': 'iscsi.mycinderhost.com',
 
240
        'endpoints': [(8776, 8766), (8777, 8767)]
 
241
    }
 
242
 
 
243
    The endpoints list consists of a tuples mapping external ports
 
244
    to internal ports.
 
245
    """
 
246
    interfaces = ['https']
 
247
 
 
248
    # charms should inherit this context and set external ports
 
249
    # and service namespace accordingly.
 
250
    external_ports = []
 
251
    service_namespace = None
 
252
 
 
253
    def enable_modules(self):
 
254
        cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']
 
255
        check_call(cmd)
 
256
 
 
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):
 
262
            os.mkdir(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()
 
269
        if ca_cert:
 
270
            with open(CA_CERT_PATH, 'w') as ca_out:
 
271
                ca_out.write(b64decode(ca_cert))
 
272
 
 
273
    def __call__(self):
 
274
        if isinstance(self.external_ports, basestring):
 
275
            self.external_ports = [self.external_ports]
 
276
        if (not self.external_ports or not https()):
 
277
            return {}
 
278
 
 
279
        self.configure_cert()
 
280
        self.enable_modules()
 
281
 
 
282
        ctxt = {
 
283
            'namespace': self.service_namespace,
 
284
            'private_address': unit_get('private-address'),
 
285
            'endpoints': []
 
286
        }
 
287
        for ext_port in self.external_ports:
 
288
            if peer_units() or is_clustered():
 
289
                int_port = determine_haproxy_port(ext_port)
 
290
            else:
 
291
                int_port = determine_api_port(ext_port)
 
292
            portmap = (int(ext_port), int(int_port))
 
293
            ctxt['endpoints'].append(portmap)
 
294
        return ctxt