~niedbalski/charms/trusty/swift-storage/fix-lp-1308557

« back to all changes in this revision

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

  • Committer: James Page
  • Date: 2014-04-16 08:34:53 UTC
  • mfrom: (24.1.17 swift-storage)
  • Revision ID: james.page@canonical.com-20140416083453-3n4gz51lk3p8tojd
[james-page,r=james-page,t=*]

Support for Icehouse on 12.04 and 14.04

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import json
2
2
import os
 
3
import time
3
4
 
4
5
from base64 import b64decode
5
6
 
23
24
    unit_get,
24
25
    unit_private_ip,
25
26
    ERROR,
26
 
    WARNING,
27
27
)
28
28
 
29
29
from charmhelpers.contrib.hahelpers.cluster import (
 
30
    determine_apache_port,
30
31
    determine_api_port,
31
 
    determine_haproxy_port,
32
32
    https,
33
 
    is_clustered,
34
 
    peer_units,
 
33
    is_clustered
35
34
)
36
35
 
37
36
from charmhelpers.contrib.hahelpers.apache import (
68
67
    return True
69
68
 
70
69
 
 
70
def config_flags_parser(config_flags):
 
71
    if config_flags.find('==') >= 0:
 
72
        log("config_flags is not in expected format (key=value)",
 
73
            level=ERROR)
 
74
        raise OSContextError
 
75
    # strip the following from each value.
 
76
    post_strippers = ' ,'
 
77
    # we strip any leading/trailing '=' or ' ' from the string then
 
78
    # split on '='.
 
79
    split = config_flags.strip(' =').split('=')
 
80
    limit = len(split)
 
81
    flags = {}
 
82
    for i in xrange(0, limit - 1):
 
83
        current = split[i]
 
84
        next = split[i + 1]
 
85
        vindex = next.rfind(',')
 
86
        if (i == limit - 2) or (vindex < 0):
 
87
            value = next
 
88
        else:
 
89
            value = next[:vindex]
 
90
 
 
91
        if i == 0:
 
92
            key = current
 
93
        else:
 
94
            # if this not the first entry, expect an embedded key.
 
95
            index = current.rfind(',')
 
96
            if index < 0:
 
97
                log("invalid config value(s) at index %s" % (i),
 
98
                    level=ERROR)
 
99
                raise OSContextError
 
100
            key = current[index + 1:]
 
101
 
 
102
        # Add to collection.
 
103
        flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
 
104
    return flags
 
105
 
 
106
 
71
107
class OSContextGenerator(object):
72
108
    interfaces = []
73
109
 
78
114
class SharedDBContext(OSContextGenerator):
79
115
    interfaces = ['shared-db']
80
116
 
81
 
    def __init__(self, database=None, user=None, relation_prefix=None):
 
117
    def __init__(self,
 
118
                 database=None, user=None, relation_prefix=None, ssl_dir=None):
82
119
        '''
83
120
        Allows inspecting relation for settings prefixed with relation_prefix.
84
121
        This is useful for parsing access for multiple databases returned via
87
124
        self.relation_prefix = relation_prefix
88
125
        self.database = database
89
126
        self.user = user
 
127
        self.ssl_dir = ssl_dir
90
128
 
91
129
    def __call__(self):
92
130
        self.database = self.database or config('database')
104
142
 
105
143
        for rid in relation_ids('shared-db'):
106
144
            for unit in related_units(rid):
107
 
                passwd = relation_get(password_setting, rid=rid, unit=unit)
 
145
                rdata = relation_get(rid=rid, unit=unit)
108
146
                ctxt = {
109
 
                    'database_host': relation_get('db_host', rid=rid,
110
 
                                                  unit=unit),
 
147
                    'database_host': rdata.get('db_host'),
111
148
                    'database': self.database,
112
149
                    'database_user': self.user,
113
 
                    'database_password': passwd,
114
 
                }
115
 
                if context_complete(ctxt):
116
 
                    return ctxt
117
 
        return {}
 
150
                    'database_password': rdata.get(password_setting),
 
151
                    'database_type': 'mysql'
 
152
                }
 
153
                if context_complete(ctxt):
 
154
                    db_ssl(rdata, ctxt, self.ssl_dir)
 
155
                    return ctxt
 
156
        return {}
 
157
 
 
158
 
 
159
class PostgresqlDBContext(OSContextGenerator):
 
160
    interfaces = ['pgsql-db']
 
161
 
 
162
    def __init__(self, database=None):
 
163
        self.database = database
 
164
 
 
165
    def __call__(self):
 
166
        self.database = self.database or config('database')
 
167
        if self.database is None:
 
168
            log('Could not generate postgresql_db context. '
 
169
                'Missing required charm config options. '
 
170
                '(database name)')
 
171
            raise OSContextError
 
172
        ctxt = {}
 
173
 
 
174
        for rid in relation_ids(self.interfaces[0]):
 
175
            for unit in related_units(rid):
 
176
                ctxt = {
 
177
                    'database_host': relation_get('host', rid=rid, unit=unit),
 
178
                    'database': self.database,
 
179
                    'database_user': relation_get('user', rid=rid, unit=unit),
 
180
                    'database_password': relation_get('password', rid=rid, unit=unit),
 
181
                    'database_type': 'postgresql',
 
182
                }
 
183
                if context_complete(ctxt):
 
184
                    return ctxt
 
185
        return {}
 
186
 
 
187
 
 
188
def db_ssl(rdata, ctxt, ssl_dir):
 
189
    if 'ssl_ca' in rdata and ssl_dir:
 
190
        ca_path = os.path.join(ssl_dir, 'db-client.ca')
 
191
        with open(ca_path, 'w') as fh:
 
192
            fh.write(b64decode(rdata['ssl_ca']))
 
193
        ctxt['database_ssl_ca'] = ca_path
 
194
    elif 'ssl_ca' in rdata:
 
195
        log("Charm not setup for ssl support but ssl ca found")
 
196
        return ctxt
 
197
    if 'ssl_cert' in rdata:
 
198
        cert_path = os.path.join(
 
199
            ssl_dir, 'db-client.cert')
 
200
        if not os.path.exists(cert_path):
 
201
            log("Waiting 1m for ssl client cert validity")
 
202
            time.sleep(60)
 
203
        with open(cert_path, 'w') as fh:
 
204
            fh.write(b64decode(rdata['ssl_cert']))
 
205
        ctxt['database_ssl_cert'] = cert_path
 
206
        key_path = os.path.join(ssl_dir, 'db-client.key')
 
207
        with open(key_path, 'w') as fh:
 
208
            fh.write(b64decode(rdata['ssl_key']))
 
209
        ctxt['database_ssl_key'] = key_path
 
210
    return ctxt
118
211
 
119
212
 
120
213
class IdentityServiceContext(OSContextGenerator):
126
219
 
127
220
        for rid in relation_ids('identity-service'):
128
221
            for unit in related_units(rid):
 
222
                rdata = relation_get(rid=rid, unit=unit)
129
223
                ctxt = {
130
 
                    'service_port': relation_get('service_port', rid=rid,
131
 
                                                 unit=unit),
132
 
                    'service_host': relation_get('service_host', rid=rid,
133
 
                                                 unit=unit),
134
 
                    'auth_host': relation_get('auth_host', rid=rid, unit=unit),
135
 
                    'auth_port': relation_get('auth_port', rid=rid, unit=unit),
136
 
                    'admin_tenant_name': relation_get('service_tenant',
137
 
                                                      rid=rid, unit=unit),
138
 
                    'admin_user': relation_get('service_username', rid=rid,
139
 
                                               unit=unit),
140
 
                    'admin_password': relation_get('service_password', rid=rid,
141
 
                                                   unit=unit),
142
 
                    # XXX: Hard-coded http.
143
 
                    'service_protocol': 'http',
144
 
                    'auth_protocol': 'http',
 
224
                    'service_port': rdata.get('service_port'),
 
225
                    'service_host': rdata.get('service_host'),
 
226
                    'auth_host': rdata.get('auth_host'),
 
227
                    'auth_port': rdata.get('auth_port'),
 
228
                    'admin_tenant_name': rdata.get('service_tenant'),
 
229
                    'admin_user': rdata.get('service_username'),
 
230
                    'admin_password': rdata.get('service_password'),
 
231
                    'service_protocol':
 
232
                    rdata.get('service_protocol') or 'http',
 
233
                    'auth_protocol':
 
234
                    rdata.get('auth_protocol') or 'http',
145
235
                }
146
236
                if context_complete(ctxt):
 
237
                    # NOTE(jamespage) this is required for >= icehouse
 
238
                    # so a missing value just indicates keystone needs
 
239
                    # upgrading
 
240
                    ctxt['admin_tenant_id'] = rdata.get('service_tenant_id')
147
241
                    return ctxt
148
242
        return {}
149
243
 
151
245
class AMQPContext(OSContextGenerator):
152
246
    interfaces = ['amqp']
153
247
 
 
248
    def __init__(self, ssl_dir=None):
 
249
        self.ssl_dir = ssl_dir
 
250
 
154
251
    def __call__(self):
155
252
        log('Generating template context for amqp')
156
253
        conf = config()
161
258
            log('Could not generate shared_db context. '
162
259
                'Missing required charm config options: %s.' % e)
163
260
            raise OSContextError
164
 
 
165
261
        ctxt = {}
166
262
        for rid in relation_ids('amqp'):
 
263
            ha_vip_only = False
167
264
            for unit in related_units(rid):
168
265
                if relation_get('clustered', rid=rid, unit=unit):
169
266
                    ctxt['clustered'] = True
178
275
                                                      unit=unit),
179
276
                    'rabbitmq_virtual_host': vhost,
180
277
                })
 
278
 
 
279
                ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
 
280
                if ssl_port:
 
281
                    ctxt['rabbit_ssl_port'] = ssl_port
 
282
                ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
 
283
                if ssl_ca:
 
284
                    ctxt['rabbit_ssl_ca'] = ssl_ca
 
285
 
 
286
                if relation_get('ha_queues', rid=rid, unit=unit) is not None:
 
287
                    ctxt['rabbitmq_ha_queues'] = True
 
288
 
 
289
                ha_vip_only = relation_get('ha-vip-only',
 
290
                                           rid=rid, unit=unit) is not None
 
291
 
181
292
                if context_complete(ctxt):
 
293
                    if 'rabbit_ssl_ca' in ctxt:
 
294
                        if not self.ssl_dir:
 
295
                            log(("Charm not setup for ssl support "
 
296
                                 "but ssl ca found"))
 
297
                            break
 
298
                        ca_path = os.path.join(
 
299
                            self.ssl_dir, 'rabbit-client-ca.pem')
 
300
                        with open(ca_path, 'w') as fh:
 
301
                            fh.write(b64decode(ctxt['rabbit_ssl_ca']))
 
302
                            ctxt['rabbit_ssl_ca'] = ca_path
182
303
                    # Sufficient information found = break out!
183
304
                    break
184
305
            # Used for active/active rabbitmq >= grizzly
185
 
            ctxt['rabbitmq_hosts'] = []
186
 
            for unit in related_units(rid):
187
 
                ctxt['rabbitmq_hosts'].append(relation_get('private-address',
188
 
                                                           rid=rid, unit=unit))
 
306
            if ('clustered' not in ctxt or ha_vip_only) \
 
307
                    and len(related_units(rid)) > 1:
 
308
                rabbitmq_hosts = []
 
309
                for unit in related_units(rid):
 
310
                    rabbitmq_hosts.append(relation_get('private-address',
 
311
                                                       rid=rid, unit=unit))
 
312
                ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts)
189
313
        if not context_complete(ctxt):
190
314
            return {}
191
315
        else:
199
323
        '''This generates context for /etc/ceph/ceph.conf templates'''
200
324
        if not relation_ids('ceph'):
201
325
            return {}
 
326
 
202
327
        log('Generating template context for ceph')
 
328
 
203
329
        mon_hosts = []
204
330
        auth = None
205
331
        key = None
 
332
        use_syslog = str(config('use-syslog')).lower()
206
333
        for rid in relation_ids('ceph'):
207
334
            for unit in related_units(rid):
208
335
                mon_hosts.append(relation_get('private-address', rid=rid,
214
341
            'mon_hosts': ' '.join(mon_hosts),
215
342
            'auth': auth,
216
343
            'key': key,
 
344
            'use_syslog': use_syslog
217
345
        }
218
346
 
219
347
        if not os.path.isdir('/etc/ceph'):
286
414
 
287
415
 
288
416
class ApacheSSLContext(OSContextGenerator):
 
417
 
289
418
    """
290
419
    Generates a context for an apache vhost configuration that configures
291
420
    HTTPS reverse proxying for one or many endpoints.  Generated context
341
470
            'private_address': unit_get('private-address'),
342
471
            'endpoints': []
343
472
        }
344
 
        for ext_port in self.external_ports:
345
 
            if peer_units() or is_clustered():
346
 
                int_port = determine_haproxy_port(ext_port)
347
 
            else:
348
 
                int_port = determine_api_port(ext_port)
 
473
        if is_clustered():
 
474
            ctxt['private_address'] = config('vip')
 
475
        for api_port in self.external_ports:
 
476
            ext_port = determine_apache_port(api_port)
 
477
            int_port = determine_api_port(api_port)
349
478
            portmap = (int(ext_port), int(int_port))
350
479
            ctxt['endpoints'].append(portmap)
351
480
        return ctxt
352
481
 
353
482
 
354
 
class NeutronContext(object):
 
483
class NeutronContext(OSContextGenerator):
355
484
    interfaces = []
356
485
 
357
486
    @property
385
514
    def ovs_ctxt(self):
386
515
        driver = neutron_plugin_attribute(self.plugin, 'driver',
387
516
                                          self.network_manager)
388
 
 
 
517
        config = neutron_plugin_attribute(self.plugin, 'config',
 
518
                                          self.network_manager)
389
519
        ovs_ctxt = {
390
520
            'core_plugin': driver,
391
521
            'neutron_plugin': 'ovs',
392
522
            'neutron_security_groups': self.neutron_security_groups,
393
523
            'local_ip': unit_private_ip(),
 
524
            'config': config
394
525
        }
395
526
 
396
527
        return ovs_ctxt
397
528
 
 
529
    def nvp_ctxt(self):
 
530
        driver = neutron_plugin_attribute(self.plugin, 'driver',
 
531
                                          self.network_manager)
 
532
        config = neutron_plugin_attribute(self.plugin, 'config',
 
533
                                          self.network_manager)
 
534
        nvp_ctxt = {
 
535
            'core_plugin': driver,
 
536
            'neutron_plugin': 'nvp',
 
537
            'neutron_security_groups': self.neutron_security_groups,
 
538
            'local_ip': unit_private_ip(),
 
539
            'config': config
 
540
        }
 
541
 
 
542
        return nvp_ctxt
 
543
 
 
544
    def neutron_ctxt(self):
 
545
        if https():
 
546
            proto = 'https'
 
547
        else:
 
548
            proto = 'http'
 
549
        if is_clustered():
 
550
            host = config('vip')
 
551
        else:
 
552
            host = unit_get('private-address')
 
553
        url = '%s://%s:%s' % (proto, host, '9696')
 
554
        ctxt = {
 
555
            'network_manager': self.network_manager,
 
556
            'neutron_url': url,
 
557
        }
 
558
        return ctxt
 
559
 
398
560
    def __call__(self):
399
561
        self._ensure_packages()
400
562
 
404
566
        if not self.plugin:
405
567
            return {}
406
568
 
407
 
        ctxt = {'network_manager': self.network_manager}
 
569
        ctxt = self.neutron_ctxt()
408
570
 
409
571
        if self.plugin == 'ovs':
410
572
            ctxt.update(self.ovs_ctxt())
 
573
        elif self.plugin == 'nvp':
 
574
            ctxt.update(self.nvp_ctxt())
 
575
 
 
576
        alchemy_flags = config('neutron-alchemy-flags')
 
577
        if alchemy_flags:
 
578
            flags = config_flags_parser(alchemy_flags)
 
579
            ctxt['neutron_alchemy_flags'] = flags
411
580
 
412
581
        self._save_flag_file()
413
582
        return ctxt
414
583
 
415
584
 
416
585
class OSConfigFlagContext(OSContextGenerator):
417
 
        '''
418
 
        Responsible adding user-defined config-flags in charm config to a
419
 
        to a template context.
420
 
        '''
 
586
 
 
587
        """
 
588
        Responsible for adding user-defined config-flags in charm config to a
 
589
        template context.
 
590
 
 
591
        NOTE: the value of config-flags may be a comma-separated list of
 
592
              key=value pairs and some Openstack config files support
 
593
              comma-separated lists as values.
 
594
        """
 
595
 
421
596
        def __call__(self):
422
597
            config_flags = config('config-flags')
423
 
            if not config_flags or config_flags in ['None', '']:
 
598
            if not config_flags:
424
599
                return {}
425
 
            config_flags = config_flags.split(',')
426
 
            flags = {}
427
 
            for flag in config_flags:
428
 
                if '=' not in flag:
429
 
                    log('Improperly formatted config-flag, expected k=v '
430
 
                        'got %s' % flag, level=WARNING)
431
 
                    continue
432
 
                k, v = flag.split('=')
433
 
                flags[k.strip()] = v
434
 
            ctxt = {'user_config_flags': flags}
435
 
            return ctxt
 
600
 
 
601
            flags = config_flags_parser(config_flags)
 
602
            return {'user_config_flags': flags}
436
603
 
437
604
 
438
605
class SubordinateConfigContext(OSContextGenerator):
 
606
 
439
607
    """
440
608
    Responsible for inspecting relations to subordinates that
441
609
    may be exporting required config via a json blob.
476
644
        }
477
645
 
478
646
    """
 
647
 
479
648
    def __init__(self, service, config_file, interface):
480
649
        """
481
650
        :param service     : Service name key to query in any subordinate
520
689
            ctxt['sections'] = {}
521
690
 
522
691
        return ctxt
 
692
 
 
693
 
 
694
class SyslogContext(OSContextGenerator):
 
695
 
 
696
    def __call__(self):
 
697
        ctxt = {
 
698
            'use_syslog': config('use-syslog')
 
699
        }
 
700
        return ctxt