~landscape/charms/trusty/neutron-api-leadership-election/trunk

« back to all changes in this revision

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

  • Committer: james.page at ubuntu
  • Date: 2014-12-15 09:14:03 UTC
  • mfrom: (63.1.8 neutron-api)
  • Revision ID: james.page@ubuntu.com-20141215091403-af7ic1lxnnjsh4zu
[corey.bryant,r=james-page] Sort out charmhelpers issues.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import json
2
2
import os
3
3
import time
4
 
 
5
4
from base64 import b64decode
 
5
from subprocess import check_call
6
6
 
7
 
from subprocess import (
8
 
    check_call
9
 
)
 
7
import six
10
8
 
11
9
from charmhelpers.fetch import (
12
10
    apt_install,
13
11
    filter_installed_packages,
14
12
)
15
 
 
16
13
from charmhelpers.core.hookenv import (
17
14
    config,
 
15
    is_relation_made,
18
16
    local_unit,
19
17
    log,
20
18
    relation_get,
23
21
    relation_set,
24
22
    unit_get,
25
23
    unit_private_ip,
 
24
    DEBUG,
 
25
    INFO,
 
26
    WARNING,
26
27
    ERROR,
27
 
    INFO
28
28
)
29
 
 
30
29
from charmhelpers.core.host import (
31
30
    mkdir,
32
 
    write_file
 
31
    write_file,
33
32
)
34
 
 
35
33
from charmhelpers.contrib.hahelpers.cluster import (
36
34
    determine_apache_port,
37
35
    determine_api_port,
38
36
    https,
39
 
    is_clustered
 
37
    is_clustered,
40
38
)
41
 
 
42
39
from charmhelpers.contrib.hahelpers.apache import (
43
40
    get_cert,
44
41
    get_ca_cert,
45
42
    install_ca_cert,
46
43
)
47
 
 
48
44
from charmhelpers.contrib.openstack.neutron import (
49
45
    neutron_plugin_attribute,
50
46
)
51
 
 
52
47
from charmhelpers.contrib.network.ip import (
53
48
    get_address_in_network,
54
49
    get_ipv6_addr,
55
50
    get_netmask_for_address,
56
51
    format_ipv6_addr,
57
 
    is_address_in_network
 
52
    is_address_in_network,
58
53
)
59
 
 
60
54
from charmhelpers.contrib.openstack.utils import get_host_ip
61
55
 
62
56
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
 
57
ADDRESS_TYPES = ['admin', 'internal', 'public']
63
58
 
64
59
 
65
60
class OSContextError(Exception):
67
62
 
68
63
 
69
64
def ensure_packages(packages):
70
 
    '''Install but do not upgrade required plugin packages'''
 
65
    """Install but do not upgrade required plugin packages."""
71
66
    required = filter_installed_packages(packages)
72
67
    if required:
73
68
        apt_install(required, fatal=True)
75
70
 
76
71
def context_complete(ctxt):
77
72
    _missing = []
78
 
    for k, v in ctxt.iteritems():
 
73
    for k, v in six.iteritems(ctxt):
79
74
        if v is None or v == '':
80
75
            _missing.append(k)
 
76
 
81
77
    if _missing:
82
 
        log('Missing required data: %s' % ' '.join(_missing), level='INFO')
 
78
        log('Missing required data: %s' % ' '.join(_missing), level=INFO)
83
79
        return False
 
80
 
84
81
    return True
85
82
 
86
83
 
87
84
def config_flags_parser(config_flags):
 
85
    """Parses config flags string into dict.
 
86
 
 
87
    The provided config_flags string may be a list of comma-separated values
 
88
    which themselves may be comma-separated list of values.
 
89
    """
88
90
    if config_flags.find('==') >= 0:
89
 
        log("config_flags is not in expected format (key=value)",
90
 
            level=ERROR)
 
91
        log("config_flags is not in expected format (key=value)", level=ERROR)
91
92
        raise OSContextError
 
93
 
92
94
    # strip the following from each value.
93
95
    post_strippers = ' ,'
94
96
    # we strip any leading/trailing '=' or ' ' from the string then
96
98
    split = config_flags.strip(' =').split('=')
97
99
    limit = len(split)
98
100
    flags = {}
99
 
    for i in xrange(0, limit - 1):
 
101
    for i in range(0, limit - 1):
100
102
        current = split[i]
101
103
        next = split[i + 1]
102
104
        vindex = next.rfind(',')
111
113
            # if this not the first entry, expect an embedded key.
112
114
            index = current.rfind(',')
113
115
            if index < 0:
114
 
                log("invalid config value(s) at index %s" % (i),
115
 
                    level=ERROR)
 
116
                log("Invalid config value(s) at index %s" % (i), level=ERROR)
116
117
                raise OSContextError
117
118
            key = current[index + 1:]
118
119
 
119
120
        # Add to collection.
120
121
        flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
 
122
 
121
123
    return flags
122
124
 
123
125
 
124
126
class OSContextGenerator(object):
 
127
    """Base class for all context generators."""
125
128
    interfaces = []
126
129
 
127
130
    def __call__(self):
133
136
 
134
137
    def __init__(self,
135
138
                 database=None, user=None, relation_prefix=None, ssl_dir=None):
136
 
        '''
137
 
        Allows inspecting relation for settings prefixed with relation_prefix.
138
 
        This is useful for parsing access for multiple databases returned via
139
 
        the shared-db interface (eg, nova_password, quantum_password)
140
 
        '''
 
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,
 
142
        quantum_password)
 
143
        """
141
144
        self.relation_prefix = relation_prefix
142
145
        self.database = database
143
146
        self.user = user
147
150
        self.database = self.database or config('database')
148
151
        self.user = self.user or config('database-user')
149
152
        if None in [self.database, self.user]:
150
 
            log('Could not generate shared_db context. '
151
 
                'Missing required charm config options. '
152
 
                '(database name and user)')
 
153
            log("Could not generate shared_db context. Missing required charm "
 
154
                "config options. (database name and user)", level=ERROR)
153
155
            raise OSContextError
154
156
 
155
157
        ctxt = {}
202
204
    def __call__(self):
203
205
        self.database = self.database or config('database')
204
206
        if self.database is None:
205
 
            log('Could not generate postgresql_db context. '
206
 
                'Missing required charm config options. '
207
 
                '(database name)')
 
207
            log('Could not generate postgresql_db context. Missing required '
 
208
                'charm config options. (database name)', level=ERROR)
208
209
            raise OSContextError
 
210
 
209
211
        ctxt = {}
210
 
 
211
212
        for rid in relation_ids(self.interfaces[0]):
212
213
            for unit in related_units(rid):
213
 
                ctxt = {
214
 
                    'database_host': relation_get('host', rid=rid, unit=unit),
215
 
                    'database': self.database,
216
 
                    'database_user': relation_get('user', rid=rid, unit=unit),
217
 
                    'database_password': relation_get('password', rid=rid, unit=unit),
218
 
                    'database_type': 'postgresql',
219
 
                }
 
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'}
220
222
                if context_complete(ctxt):
221
223
                    return ctxt
 
224
 
222
225
        return {}
223
226
 
224
227
 
227
230
        ca_path = os.path.join(ssl_dir, 'db-client.ca')
228
231
        with open(ca_path, 'w') as fh:
229
232
            fh.write(b64decode(rdata['ssl_ca']))
 
233
 
230
234
        ctxt['database_ssl_ca'] = ca_path
231
235
    elif 'ssl_ca' in rdata:
232
 
        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)
233
237
        return ctxt
 
238
 
234
239
    if 'ssl_cert' in rdata:
235
240
        cert_path = os.path.join(
236
241
            ssl_dir, 'db-client.cert')
237
242
        if not os.path.exists(cert_path):
238
 
            log("Waiting 1m for ssl client cert validity")
 
243
            log("Waiting 1m for ssl client cert validity", level=INFO)
239
244
            time.sleep(60)
 
245
 
240
246
        with open(cert_path, 'w') as fh:
241
247
            fh.write(b64decode(rdata['ssl_cert']))
 
248
 
242
249
        ctxt['database_ssl_cert'] = cert_path
243
250
        key_path = os.path.join(ssl_dir, 'db-client.key')
244
251
        with open(key_path, 'w') as fh:
245
252
            fh.write(b64decode(rdata['ssl_key']))
 
253
 
246
254
        ctxt['database_ssl_key'] = key_path
 
255
 
247
256
    return ctxt
248
257
 
249
258
 
251
260
    interfaces = ['identity-service']
252
261
 
253
262
    def __call__(self):
254
 
        log('Generating template context for identity-service')
 
263
        log('Generating template context for identity-service', level=DEBUG)
255
264
        ctxt = {}
256
 
 
257
265
        for rid in relation_ids('identity-service'):
258
266
            for unit in related_units(rid):
259
267
                rdata = relation_get(rid=rid, unit=unit)
261
269
                serv_host = format_ipv6_addr(serv_host) or serv_host
262
270
                auth_host = rdata.get('auth_host')
263
271
                auth_host = format_ipv6_addr(auth_host) or auth_host
264
 
 
265
 
                ctxt = {
266
 
                    'service_port': rdata.get('service_port'),
267
 
                    'service_host': serv_host,
268
 
                    'auth_host': auth_host,
269
 
                    'auth_port': rdata.get('auth_port'),
270
 
                    'admin_tenant_name': rdata.get('service_tenant'),
271
 
                    'admin_user': rdata.get('service_username'),
272
 
                    'admin_password': rdata.get('service_password'),
273
 
                    'service_protocol':
274
 
                    rdata.get('service_protocol') or 'http',
275
 
                    'auth_protocol':
276
 
                    rdata.get('auth_protocol') or 'http',
277
 
                }
 
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}
278
283
                if context_complete(ctxt):
279
284
                    # NOTE(jamespage) this is required for >= icehouse
280
285
                    # so a missing value just indicates keystone needs
281
286
                    # upgrading
282
287
                    ctxt['admin_tenant_id'] = rdata.get('service_tenant_id')
283
288
                    return ctxt
 
289
 
284
290
        return {}
285
291
 
286
292
 
293
299
        self.interfaces = [rel_name]
294
300
 
295
301
    def __call__(self):
296
 
        log('Generating template context for amqp')
 
302
        log('Generating template context for amqp', level=DEBUG)
297
303
        conf = config()
298
 
        user_setting = 'rabbit-user'
299
 
        vhost_setting = 'rabbit-vhost'
300
304
        if self.relation_prefix:
301
 
            user_setting = self.relation_prefix + '-rabbit-user'
302
 
            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)
 
307
        else:
 
308
            user_setting = 'rabbit-user'
 
309
            vhost_setting = 'rabbit-vhost'
303
310
 
304
311
        try:
305
312
            username = conf[user_setting]
306
313
            vhost = conf[vhost_setting]
307
314
        except KeyError as e:
308
 
            log('Could not generate shared_db context. '
309
 
                '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)
310
317
            raise OSContextError
 
318
 
311
319
        ctxt = {}
312
320
        for rid in relation_ids(self.rel_name):
313
321
            ha_vip_only = False
321
329
                    host = relation_get('private-address', rid=rid, unit=unit)
322
330
                    host = format_ipv6_addr(host) or host
323
331
                    ctxt['rabbitmq_host'] = host
 
332
 
324
333
                ctxt.update({
325
334
                    'rabbitmq_user': username,
326
335
                    'rabbitmq_password': relation_get('password', rid=rid,
331
340
                ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
332
341
                if ssl_port:
333
342
                    ctxt['rabbit_ssl_port'] = ssl_port
 
343
 
334
344
                ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
335
345
                if ssl_ca:
336
346
                    ctxt['rabbit_ssl_ca'] = ssl_ca
344
354
                if context_complete(ctxt):
345
355
                    if 'rabbit_ssl_ca' in ctxt:
346
356
                        if not self.ssl_dir:
347
 
                            log(("Charm not setup for ssl support "
348
 
                                 "but ssl ca found"))
 
357
                            log("Charm not setup for ssl support but ssl ca "
 
358
                                "found", level=INFO)
349
359
                            break
 
360
 
350
361
                        ca_path = os.path.join(
351
362
                            self.ssl_dir, 'rabbit-client-ca.pem')
352
363
                        with open(ca_path, 'w') as fh:
353
364
                            fh.write(b64decode(ctxt['rabbit_ssl_ca']))
354
365
                            ctxt['rabbit_ssl_ca'] = ca_path
 
366
 
355
367
                    # Sufficient information found = break out!
356
368
                    break
 
369
 
357
370
            # Used for active/active rabbitmq >= grizzly
358
 
            if ('clustered' not in ctxt or ha_vip_only) \
359
 
                    and len(related_units(rid)) > 1:
 
371
            if (('clustered' not in ctxt or ha_vip_only) and
 
372
                    len(related_units(rid)) > 1):
360
373
                rabbitmq_hosts = []
361
374
                for unit in related_units(rid):
362
375
                    host = relation_get('private-address', rid=rid, unit=unit)
363
376
                    host = format_ipv6_addr(host) or host
364
377
                    rabbitmq_hosts.append(host)
365
 
                ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts)
 
378
 
 
379
                ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))
 
380
 
366
381
        if not context_complete(ctxt):
367
382
            return {}
368
 
        else:
369
 
            return ctxt
 
383
 
 
384
        return ctxt
370
385
 
371
386
 
372
387
class CephContext(OSContextGenerator):
 
388
    """Generates context for /etc/ceph/ceph.conf templates."""
373
389
    interfaces = ['ceph']
374
390
 
375
391
    def __call__(self):
376
 
        '''This generates context for /etc/ceph/ceph.conf templates'''
377
392
        if not relation_ids('ceph'):
378
393
            return {}
379
394
 
380
 
        log('Generating template context for ceph')
381
 
 
 
395
        log('Generating template context for ceph', level=DEBUG)
382
396
        mon_hosts = []
383
397
        auth = None
384
398
        key = None
387
401
            for unit in related_units(rid):
388
402
                auth = relation_get('auth', rid=rid, unit=unit)
389
403
                key = relation_get('key', rid=rid, unit=unit)
390
 
                ceph_addr = \
391
 
                    relation_get('ceph-public-address', rid=rid, unit=unit) or \
392
 
                    relation_get('private-address', rid=rid, unit=unit)
 
404
                ceph_pub_addr = relation_get('ceph-public-address', rid=rid,
 
405
                                             unit=unit)
 
406
                unit_priv_addr = relation_get('private-address', rid=rid,
 
407
                                              unit=unit)
 
408
                ceph_addr = ceph_pub_addr or unit_priv_addr
393
409
                ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
394
410
                mon_hosts.append(ceph_addr)
395
411
 
396
 
        ctxt = {
397
 
            'mon_hosts': ' '.join(mon_hosts),
398
 
            'auth': auth,
399
 
            'key': key,
400
 
            'use_syslog': use_syslog
401
 
        }
 
412
        ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)),
 
413
                'auth': auth,
 
414
                'key': key,
 
415
                'use_syslog': use_syslog}
402
416
 
403
417
        if not os.path.isdir('/etc/ceph'):
404
418
            os.mkdir('/etc/ceph')
407
421
            return {}
408
422
 
409
423
        ensure_packages(['ceph-common'])
410
 
 
411
424
        return ctxt
412
425
 
413
426
 
414
 
ADDRESS_TYPES = ['admin', 'internal', 'public']
415
 
 
416
 
 
417
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.
 
431
    """
418
432
    interfaces = ['cluster']
419
433
 
 
434
    def __init__(self, singlenode_mode=False):
 
435
        self.singlenode_mode = singlenode_mode
 
436
 
420
437
    def __call__(self):
421
 
        '''
422
 
        Builds half a context for the haproxy template, which describes
423
 
        all peers to be included in the cluster.  Each charm needs to include
424
 
        its own context generator that describes the port mapping.
425
 
        '''
426
 
        if not relation_ids('cluster'):
 
438
        if not relation_ids('cluster') and not self.singlenode_mode:
427
439
            return {}
428
440
 
429
 
        l_unit = local_unit().replace('/', '-')
430
 
 
431
441
        if config('prefer-ipv6'):
432
442
            addr = get_ipv6_addr(exc_list=[config('vip')])[0]
433
443
        else:
434
444
            addr = get_host_ip(unit_get('private-address'))
435
445
 
 
446
        l_unit = local_unit().replace('/', '-')
436
447
        cluster_hosts = {}
437
448
 
438
449
        # NOTE(jamespage): build out map of configured network endpoints
439
450
        # and associated backends
440
451
        for addr_type in ADDRESS_TYPES:
441
 
            laddr = get_address_in_network(
442
 
                config('os-{}-network'.format(addr_type)))
 
452
            cfg_opt = 'os-{}-network'.format(addr_type)
 
453
            laddr = get_address_in_network(config(cfg_opt))
443
454
            if laddr:
444
 
                cluster_hosts[laddr] = {}
445
 
                cluster_hosts[laddr]['network'] = "{}/{}".format(
446
 
                    laddr,
447
 
                    get_netmask_for_address(laddr)
448
 
                )
449
 
                cluster_hosts[laddr]['backends'] = {}
450
 
                cluster_hosts[laddr]['backends'][l_unit] = laddr
 
455
                netmask = get_netmask_for_address(laddr)
 
456
                cluster_hosts[laddr] = {'network': "{}/{}".format(laddr,
 
457
                                                                  netmask),
 
458
                                        'backends': {l_unit: laddr}}
451
459
                for rid in relation_ids('cluster'):
452
460
                    for unit in related_units(rid):
453
 
                        _unit = unit.replace('/', '-')
454
461
                        _laddr = relation_get('{}-address'.format(addr_type),
455
462
                                              rid=rid, unit=unit)
456
463
                        if _laddr:
 
464
                            _unit = unit.replace('/', '-')
457
465
                            cluster_hosts[laddr]['backends'][_unit] = _laddr
458
466
 
459
467
        # NOTE(jamespage) no split configurations found, just use
460
468
        # private addresses
461
469
        if not cluster_hosts:
462
 
            cluster_hosts[addr] = {}
463
 
            cluster_hosts[addr]['network'] = "{}/{}".format(
464
 
                addr,
465
 
                get_netmask_for_address(addr)
466
 
            )
467
 
            cluster_hosts[addr]['backends'] = {}
468
 
            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}}
469
473
            for rid in relation_ids('cluster'):
470
474
                for unit in related_units(rid):
471
 
                    _unit = unit.replace('/', '-')
472
475
                    _laddr = relation_get('private-address',
473
476
                                          rid=rid, unit=unit)
474
477
                    if _laddr:
 
478
                        _unit = unit.replace('/', '-')
475
479
                        cluster_hosts[addr]['backends'][_unit] = _laddr
476
480
 
477
 
        ctxt = {
478
 
            'frontends': cluster_hosts,
479
 
        }
 
481
        ctxt = {'frontends': cluster_hosts}
480
482
 
481
483
        if config('haproxy-server-timeout'):
482
484
            ctxt['haproxy_server_timeout'] = config('haproxy-server-timeout')
 
485
 
483
486
        if config('haproxy-client-timeout'):
484
487
            ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')
485
488
 
493
496
            ctxt['stat_port'] = ':8888'
494
497
 
495
498
        for frontend in cluster_hosts:
496
 
            if len(cluster_hosts[frontend]['backends']) > 1:
 
499
            if (len(cluster_hosts[frontend]['backends']) > 1 or
 
500
                    self.singlenode_mode):
497
501
                # Enable haproxy when we have enough peers.
498
 
                log('Ensuring haproxy enabled in /etc/default/haproxy.')
 
502
                log('Ensuring haproxy enabled in /etc/default/haproxy.',
 
503
                    level=DEBUG)
499
504
                with open('/etc/default/haproxy', 'w') as out:
500
505
                    out.write('ENABLED=1\n')
 
506
 
501
507
                return ctxt
502
 
        log('HAProxy context is incomplete, this unit has no peers.')
 
508
 
 
509
        log('HAProxy context is incomplete, this unit has no peers.',
 
510
            level=INFO)
503
511
        return {}
504
512
 
505
513
 
507
515
    interfaces = ['image-service']
508
516
 
509
517
    def __call__(self):
510
 
        '''
511
 
        Obtains the glance API server from the image-service relation.  Useful
512
 
        in nova and cinder (currently).
513
 
        '''
514
 
        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).
 
520
        """
 
521
        log('Generating template context for image-service.', level=DEBUG)
515
522
        rids = relation_ids('image-service')
516
523
        if not rids:
517
524
            return {}
 
525
 
518
526
        for rid in rids:
519
527
            for unit in related_units(rid):
520
528
                api_server = relation_get('glance-api-server',
521
529
                                          rid=rid, unit=unit)
522
530
                if api_server:
523
531
                    return {'glance_api_servers': api_server}
524
 
        log('ImageService context is incomplete. '
525
 
            'Missing required relation data.')
 
532
 
 
533
        log("ImageService context is incomplete. Missing required relation "
 
534
            "data.", level=INFO)
526
535
        return {}
527
536
 
528
537
 
529
538
class ApacheSSLContext(OSContextGenerator):
530
 
 
531
 
    """
532
 
    Generates a context for an apache vhost configuration that configures
 
539
    """Generates a context for an apache vhost configuration that configures
533
540
    HTTPS reverse proxying for one or many endpoints.  Generated context
534
541
    looks something like::
535
542
 
563
570
        else:
564
571
            cert_filename = 'cert'
565
572
            key_filename = 'key'
 
573
 
566
574
        write_file(path=os.path.join(ssl_dir, cert_filename),
567
575
                   content=b64decode(cert))
568
576
        write_file(path=os.path.join(ssl_dir, key_filename),
574
582
            install_ca_cert(b64decode(ca_cert))
575
583
 
576
584
    def canonical_names(self):
577
 
        '''Figure out which canonical names clients will access this service'''
 
585
        """Figure out which canonical names clients will access this service.
 
586
        """
578
587
        cns = []
579
588
        for r_id in relation_ids('identity-service'):
580
589
            for unit in related_units(r_id):
582
591
                for k in rdata:
583
592
                    if k.startswith('ssl_key_'):
584
593
                        cns.append(k.lstrip('ssl_key_'))
585
 
        return list(set(cns))
 
594
 
 
595
        return sorted(list(set(cns)))
 
596
 
 
597
    def get_network_addresses(self):
 
598
        """For each network configured, return corresponding address and vip
 
599
           (if available).
 
600
 
 
601
        Returns a list of tuples of the form:
 
602
 
 
603
            [(address_in_net_a, vip_in_net_a),
 
604
             (address_in_net_b, vip_in_net_b),
 
605
             ...]
 
606
 
 
607
            or, if no vip(s) available:
 
608
 
 
609
            [(address_in_net_a, address_in_net_a),
 
610
             (address_in_net_b, address_in_net_b),
 
611
             ...]
 
612
        """
 
613
        addresses = []
 
614
        if config('vip'):
 
615
            vips = config('vip').split()
 
616
        else:
 
617
            vips = []
 
618
 
 
619
        for net_type in ['os-internal-network', 'os-admin-network',
 
620
                         'os-public-network']:
 
621
            addr = get_address_in_network(config(net_type),
 
622
                                          unit_get('private-address'))
 
623
            if len(vips) > 1 and is_clustered():
 
624
                if not config(net_type):
 
625
                    log("Multiple networks configured but net_type "
 
626
                        "is None (%s)." % net_type, level=WARNING)
 
627
                    continue
 
628
 
 
629
                for vip in vips:
 
630
                    if is_address_in_network(config(net_type), vip):
 
631
                        addresses.append((addr, vip))
 
632
                        break
 
633
 
 
634
            elif is_clustered() and config('vip'):
 
635
                addresses.append((addr, config('vip')))
 
636
            else:
 
637
                addresses.append((addr, addr))
 
638
 
 
639
        return sorted(addresses)
586
640
 
587
641
    def __call__(self):
588
 
        if isinstance(self.external_ports, basestring):
 
642
        if isinstance(self.external_ports, six.string_types):
589
643
            self.external_ports = [self.external_ports]
590
 
        if (not self.external_ports or not https()):
 
644
 
 
645
        if not self.external_ports or not https():
591
646
            return {}
592
647
 
593
648
        self.configure_ca()
594
649
        self.enable_modules()
595
650
 
596
 
        ctxt = {
597
 
            'namespace': self.service_namespace,
598
 
            'endpoints': [],
599
 
            'ext_ports': []
600
 
        }
 
651
        ctxt = {'namespace': self.service_namespace,
 
652
                'endpoints': [],
 
653
                'ext_ports': []}
601
654
 
602
655
        for cn in self.canonical_names():
603
656
            self.configure_cert(cn)
604
657
 
605
 
        addresses = []
606
 
        vips = []
607
 
        if config('vip'):
608
 
            vips = config('vip').split()
609
 
 
610
 
        for network_type in ['os-internal-network',
611
 
                             'os-admin-network',
612
 
                             'os-public-network']:
613
 
            address = get_address_in_network(config(network_type),
614
 
                                             unit_get('private-address'))
615
 
            if len(vips) > 0 and is_clustered():
616
 
                for vip in vips:
617
 
                    if is_address_in_network(config(network_type),
618
 
                                             vip):
619
 
                        addresses.append((address, vip))
620
 
                        break
621
 
            elif is_clustered():
622
 
                addresses.append((address, config('vip')))
623
 
            else:
624
 
                addresses.append((address, address))
625
 
 
626
 
        for address, endpoint in set(addresses):
 
658
        addresses = self.get_network_addresses()
 
659
        for address, endpoint in sorted(set(addresses)):
627
660
            for api_port in self.external_ports:
628
661
                ext_port = determine_apache_port(api_port)
629
662
                int_port = determine_api_port(api_port)
630
663
                portmap = (address, endpoint, int(ext_port), int(int_port))
631
664
                ctxt['endpoints'].append(portmap)
632
665
                ctxt['ext_ports'].append(int(ext_port))
633
 
        ctxt['ext_ports'] = list(set(ctxt['ext_ports']))
 
666
 
 
667
        ctxt['ext_ports'] = sorted(list(set(ctxt['ext_ports'])))
634
668
        return ctxt
635
669
 
636
670
 
647
681
 
648
682
    @property
649
683
    def packages(self):
650
 
        return neutron_plugin_attribute(
651
 
            self.plugin, 'packages', self.network_manager)
 
684
        return neutron_plugin_attribute(self.plugin, 'packages',
 
685
                                        self.network_manager)
652
686
 
653
687
    @property
654
688
    def neutron_security_groups(self):
655
689
        return None
656
690
 
657
691
    def _ensure_packages(self):
658
 
        [ensure_packages(pkgs) for pkgs in self.packages]
 
692
        for pkgs in self.packages:
 
693
            ensure_packages(pkgs)
659
694
 
660
695
    def _save_flag_file(self):
661
696
        if self.network_manager == 'quantum':
662
697
            _file = '/etc/nova/quantum_plugin.conf'
663
698
        else:
664
699
            _file = '/etc/nova/neutron_plugin.conf'
 
700
 
665
701
        with open(_file, 'wb') as out:
666
702
            out.write(self.plugin + '\n')
667
703
 
670
706
                                          self.network_manager)
671
707
        config = neutron_plugin_attribute(self.plugin, 'config',
672
708
                                          self.network_manager)
673
 
        ovs_ctxt = {
674
 
            'core_plugin': driver,
675
 
            'neutron_plugin': 'ovs',
676
 
            'neutron_security_groups': self.neutron_security_groups,
677
 
            'local_ip': unit_private_ip(),
678
 
            'config': config
679
 
        }
 
709
        ovs_ctxt = {'core_plugin': driver,
 
710
                    'neutron_plugin': 'ovs',
 
711
                    'neutron_security_groups': self.neutron_security_groups,
 
712
                    'local_ip': unit_private_ip(),
 
713
                    'config': config}
680
714
 
681
715
        return ovs_ctxt
682
716
 
685
719
                                          self.network_manager)
686
720
        config = neutron_plugin_attribute(self.plugin, 'config',
687
721
                                          self.network_manager)
688
 
        nvp_ctxt = {
689
 
            'core_plugin': driver,
690
 
            'neutron_plugin': 'nvp',
691
 
            'neutron_security_groups': self.neutron_security_groups,
692
 
            'local_ip': unit_private_ip(),
693
 
            'config': config
694
 
        }
 
722
        nvp_ctxt = {'core_plugin': driver,
 
723
                    'neutron_plugin': 'nvp',
 
724
                    'neutron_security_groups': self.neutron_security_groups,
 
725
                    'local_ip': unit_private_ip(),
 
726
                    'config': config}
695
727
 
696
728
        return nvp_ctxt
697
729
 
700
732
                                          self.network_manager)
701
733
        n1kv_config = neutron_plugin_attribute(self.plugin, 'config',
702
734
                                               self.network_manager)
703
 
        n1kv_ctxt = {
704
 
            'core_plugin': driver,
705
 
            'neutron_plugin': 'n1kv',
706
 
            'neutron_security_groups': self.neutron_security_groups,
707
 
            'local_ip': unit_private_ip(),
708
 
            'config': n1kv_config,
709
 
            'vsm_ip': config('n1kv-vsm-ip'),
710
 
            'vsm_username': config('n1kv-vsm-username'),
711
 
            'vsm_password': config('n1kv-vsm-password'),
712
 
            'restrict_policy_profiles': config(
713
 
                'n1kv_restrict_policy_profiles'),
714
 
        }
 
735
        n1kv_user_config_flags = config('n1kv-config-flags')
 
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}
 
746
 
 
747
        if n1kv_user_config_flags:
 
748
            flags = config_flags_parser(n1kv_user_config_flags)
 
749
            n1kv_ctxt['user_config_flags'] = flags
715
750
 
716
751
        return n1kv_ctxt
717
752
 
 
753
    def calico_ctxt(self):
 
754
        driver = neutron_plugin_attribute(self.plugin, 'driver',
 
755
                                          self.network_manager)
 
756
        config = neutron_plugin_attribute(self.plugin, 'config',
 
757
                                          self.network_manager)
 
758
        calico_ctxt = {'core_plugin': driver,
 
759
                       'neutron_plugin': 'Calico',
 
760
                       'neutron_security_groups': self.neutron_security_groups,
 
761
                       'local_ip': unit_private_ip(),
 
762
                       'config': config}
 
763
 
 
764
        return calico_ctxt
 
765
 
718
766
    def neutron_ctxt(self):
719
767
        if https():
720
768
            proto = 'https'
721
769
        else:
722
770
            proto = 'http'
 
771
 
723
772
        if is_clustered():
724
773
            host = config('vip')
725
774
        else:
726
775
            host = unit_get('private-address')
727
 
        url = '%s://%s:%s' % (proto, host, '9696')
728
 
        ctxt = {
729
 
            'network_manager': self.network_manager,
730
 
            'neutron_url': url,
731
 
        }
 
776
 
 
777
        ctxt = {'network_manager': self.network_manager,
 
778
                'neutron_url': '%s://%s:%s' % (proto, host, '9696')}
732
779
        return ctxt
733
780
 
734
781
    def __call__(self):
748
795
            ctxt.update(self.nvp_ctxt())
749
796
        elif self.plugin == 'n1kv':
750
797
            ctxt.update(self.n1kv_ctxt())
 
798
        elif self.plugin == 'Calico':
 
799
            ctxt.update(self.calico_ctxt())
751
800
 
752
801
        alchemy_flags = config('neutron-alchemy-flags')
753
802
        if alchemy_flags:
759
808
 
760
809
 
761
810
class OSConfigFlagContext(OSContextGenerator):
762
 
 
763
 
    """
764
 
    Responsible for adding user-defined config-flags in charm config to a
765
 
    template context.
 
811
    """Provides support for user-defined config flags.
 
812
 
 
813
    Users can define a comma-seperated list of key=value pairs
 
814
    in the charm configuration and apply them at any point in
 
815
    any file by using a template flag.
 
816
 
 
817
    Sometimes users might want config flags inserted within a
 
818
    specific section so this class allows users to specify the
 
819
    template flag name, allowing for multiple template flags
 
820
    (sections) within the same context.
766
821
 
767
822
    NOTE: the value of config-flags may be a comma-separated list of
768
823
          key=value pairs and some Openstack config files support
769
824
          comma-separated lists as values.
770
825
    """
771
826
 
 
827
    def __init__(self, charm_flag='config-flags',
 
828
                 template_flag='user_config_flags'):
 
829
        """
 
830
        :param charm_flag: config flags in charm configuration.
 
831
        :param template_flag: insert point for user-defined flags in template
 
832
                              file.
 
833
        """
 
834
        super(OSConfigFlagContext, self).__init__()
 
835
        self._charm_flag = charm_flag
 
836
        self._template_flag = template_flag
 
837
 
772
838
    def __call__(self):
773
 
        config_flags = config('config-flags')
 
839
        config_flags = config(self._charm_flag)
774
840
        if not config_flags:
775
841
            return {}
776
842
 
777
 
        flags = config_flags_parser(config_flags)
778
 
        return {'user_config_flags': flags}
 
843
        return {self._template_flag:
 
844
                config_flags_parser(config_flags)}
779
845
 
780
846
 
781
847
class SubordinateConfigContext(OSContextGenerator):
819
885
                },
820
886
            }
821
887
        }
822
 
 
823
888
    """
824
889
 
825
890
    def __init__(self, service, config_file, interface):
849
914
 
850
915
                    if self.service not in sub_config:
851
916
                        log('Found subordinate_config on %s but it contained'
852
 
                            'nothing for %s service' % (rid, self.service))
 
917
                            'nothing for %s service' % (rid, self.service),
 
918
                            level=INFO)
853
919
                        continue
854
920
 
855
921
                    sub_config = sub_config[self.service]
856
922
                    if self.config_file not in sub_config:
857
923
                        log('Found subordinate_config on %s but it contained'
858
 
                            'nothing for %s' % (rid, self.config_file))
 
924
                            'nothing for %s' % (rid, self.config_file),
 
925
                            level=INFO)
859
926
                        continue
860
927
 
861
928
                    sub_config = sub_config[self.config_file]
862
 
                    for k, v in sub_config.iteritems():
 
929
                    for k, v in six.iteritems(sub_config):
863
930
                        if k == 'sections':
864
 
                            for section, config_dict in v.iteritems():
865
 
                                log("adding section '%s'" % (section))
 
931
                            for section, config_dict in six.iteritems(v):
 
932
                                log("adding section '%s'" % (section),
 
933
                                    level=DEBUG)
866
934
                                ctxt[k][section] = config_dict
867
935
                        else:
868
936
                            ctxt[k] = v
869
937
 
870
 
        log("%d section(s) found" % (len(ctxt['sections'])), level=INFO)
871
 
 
 
938
        log("%d section(s) found" % (len(ctxt['sections'])), level=DEBUG)
872
939
        return ctxt
873
940
 
874
941
 
880
947
            False if config('debug') is None else config('debug')
881
948
        ctxt['verbose'] = \
882
949
            False if config('verbose') is None else config('verbose')
 
950
 
883
951
        return ctxt
884
952
 
885
953
 
886
954
class SyslogContext(OSContextGenerator):
887
955
 
888
956
    def __call__(self):
889
 
        ctxt = {
890
 
            'use_syslog': config('use-syslog')
891
 
        }
 
957
        ctxt = {'use_syslog': config('use-syslog')}
892
958
        return ctxt
893
959
 
894
960
 
896
962
 
897
963
    def __call__(self):
898
964
        if config('prefer-ipv6'):
899
 
            return {
900
 
                'bind_host': '::'
901
 
            }
 
965
            return {'bind_host': '::'}
902
966
        else:
903
 
            return {
904
 
                'bind_host': '0.0.0.0'
905
 
            }
 
967
            return {'bind_host': '0.0.0.0'}
906
968
 
907
969
 
908
970
class WorkerConfigContext(OSContextGenerator):
914
976
        except ImportError:
915
977
            apt_install('python-psutil', fatal=True)
916
978
            from psutil import NUM_CPUS
 
979
 
917
980
        return NUM_CPUS
918
981
 
919
982
    def __call__(self):
920
 
        multiplier = config('worker-multiplier') or 1
921
 
        ctxt = {
922
 
            "workers": self.num_cpus * multiplier
923
 
        }
 
983
        multiplier = config('worker-multiplier') or 0
 
984
        ctxt = {"workers": self.num_cpus * multiplier}
 
985
        return ctxt
 
986
 
 
987
 
 
988
class ZeroMQContext(OSContextGenerator):
 
989
    interfaces = ['zeromq-configuration']
 
990
 
 
991
    def __call__(self):
 
992
        ctxt = {}
 
993
        if is_relation_made('zeromq-configuration', 'host'):
 
994
            for rid in relation_ids('zeromq-configuration'):
 
995
                    for unit in related_units(rid):
 
996
                        ctxt['zmq_nonce'] = relation_get('nonce', unit, rid)
 
997
                        ctxt['zmq_host'] = relation_get('host', unit, rid)
 
998
 
 
999
        return ctxt
 
1000
 
 
1001
 
 
1002
class NotificationDriverContext(OSContextGenerator):
 
1003
 
 
1004
    def __init__(self, zmq_relation='zeromq-configuration',
 
1005
                 amqp_relation='amqp'):
 
1006
        """
 
1007
        :param zmq_relation: Name of Zeromq relation to check
 
1008
        """
 
1009
        self.zmq_relation = zmq_relation
 
1010
        self.amqp_relation = amqp_relation
 
1011
 
 
1012
    def __call__(self):
 
1013
        ctxt = {'notifications': 'False'}
 
1014
        if is_relation_made(self.amqp_relation):
 
1015
            ctxt['notifications'] = "True"
 
1016
 
924
1017
        return ctxt