~hopem/charms/trusty/keystone/reloads

« 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:15:00 UTC
  • mfrom: (88.2.8 keystone)
  • Revision ID: james.page@ubuntu.com-20141215091500-774h7qn0fcnhuna3
[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,
18
15
    is_relation_made,
24
21
    relation_set,
25
22
    unit_get,
26
23
    unit_private_ip,
 
24
    DEBUG,
 
25
    INFO,
 
26
    WARNING,
27
27
    ERROR,
28
 
    DEBUG
29
28
)
30
 
 
31
29
from charmhelpers.core.host import (
32
30
    mkdir,
33
 
    write_file
 
31
    write_file,
34
32
)
35
 
 
36
33
from charmhelpers.contrib.hahelpers.cluster import (
37
34
    determine_apache_port,
38
35
    determine_api_port,
39
36
    https,
40
 
    is_clustered
 
37
    is_clustered,
41
38
)
42
 
 
43
39
from charmhelpers.contrib.hahelpers.apache import (
44
40
    get_cert,
45
41
    get_ca_cert,
46
42
    install_ca_cert,
47
43
)
48
 
 
49
44
from charmhelpers.contrib.openstack.neutron import (
50
45
    neutron_plugin_attribute,
51
46
)
52
 
 
53
47
from charmhelpers.contrib.network.ip import (
54
48
    get_address_in_network,
55
49
    get_ipv6_addr,
56
50
    get_netmask_for_address,
57
51
    format_ipv6_addr,
58
 
    is_address_in_network
 
52
    is_address_in_network,
59
53
)
 
54
from charmhelpers.contrib.openstack.utils import get_host_ip
60
55
 
61
 
from charmhelpers.contrib.openstack.utils import (
62
 
    get_host_ip,
63
 
)
64
56
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
 
57
ADDRESS_TYPES = ['admin', 'internal', 'public']
65
58
 
66
59
 
67
60
class OSContextError(Exception):
69
62
 
70
63
 
71
64
def ensure_packages(packages):
72
 
    '''Install but do not upgrade required plugin packages'''
 
65
    """Install but do not upgrade required plugin packages."""
73
66
    required = filter_installed_packages(packages)
74
67
    if required:
75
68
        apt_install(required, fatal=True)
77
70
 
78
71
def context_complete(ctxt):
79
72
    _missing = []
80
 
    for k, v in ctxt.iteritems():
 
73
    for k, v in six.iteritems(ctxt):
81
74
        if v is None or v == '':
82
75
            _missing.append(k)
 
76
 
83
77
    if _missing:
84
 
        log('Missing required data: %s' % ' '.join(_missing), level='INFO')
 
78
        log('Missing required data: %s' % ' '.join(_missing), level=INFO)
85
79
        return False
 
80
 
86
81
    return True
87
82
 
88
83
 
89
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
    """
90
90
    if config_flags.find('==') >= 0:
91
 
        log("config_flags is not in expected format (key=value)",
92
 
            level=ERROR)
 
91
        log("config_flags is not in expected format (key=value)", level=ERROR)
93
92
        raise OSContextError
 
93
 
94
94
    # strip the following from each value.
95
95
    post_strippers = ' ,'
96
96
    # we strip any leading/trailing '=' or ' ' from the string then
98
98
    split = config_flags.strip(' =').split('=')
99
99
    limit = len(split)
100
100
    flags = {}
101
 
    for i in xrange(0, limit - 1):
 
101
    for i in range(0, limit - 1):
102
102
        current = split[i]
103
103
        next = split[i + 1]
104
104
        vindex = next.rfind(',')
113
113
            # if this not the first entry, expect an embedded key.
114
114
            index = current.rfind(',')
115
115
            if index < 0:
116
 
                log("invalid config value(s) at index %s" % (i),
117
 
                    level=ERROR)
 
116
                log("Invalid config value(s) at index %s" % (i), level=ERROR)
118
117
                raise OSContextError
119
118
            key = current[index + 1:]
120
119
 
121
120
        # Add to collection.
122
121
        flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
 
122
 
123
123
    return flags
124
124
 
125
125
 
126
126
class OSContextGenerator(object):
 
127
    """Base class for all context generators."""
127
128
    interfaces = []
128
129
 
129
130
    def __call__(self):
135
136
 
136
137
    def __init__(self,
137
138
                 database=None, user=None, relation_prefix=None, ssl_dir=None):
138
 
        '''
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)
142
 
        '''
 
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
        """
143
144
        self.relation_prefix = relation_prefix
144
145
        self.database = database
145
146
        self.user = user
149
150
        self.database = self.database or config('database')
150
151
        self.user = self.user or config('database-user')
151
152
        if None in [self.database, self.user]:
152
 
            log('Could not generate shared_db context. '
153
 
                'Missing required charm config options. '
154
 
                '(database name and user)')
 
153
            log("Could not generate shared_db context. Missing required charm "
 
154
                "config options. (database name and user)", level=ERROR)
155
155
            raise OSContextError
156
156
 
157
157
        ctxt = {}
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. '
209
 
                '(database name)')
 
207
            log('Could not generate postgresql_db context. Missing required '
 
208
                'charm config options. (database name)', level=ERROR)
210
209
            raise OSContextError
 
210
 
211
211
        ctxt = {}
212
 
 
213
212
        for rid in relation_ids(self.interfaces[0]):
214
213
            for unit in related_units(rid):
215
 
                ctxt = {
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',
221
 
                }
 
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):
223
223
                    return ctxt
 
224
 
224
225
        return {}
225
226
 
226
227
 
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']))
 
233
 
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)
235
237
        return ctxt
 
238
 
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)
241
244
            time.sleep(60)
 
245
 
242
246
        with open(cert_path, 'w') as fh:
243
247
            fh.write(b64decode(rdata['ssl_cert']))
 
248
 
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']))
 
253
 
248
254
        ctxt['database_ssl_key'] = key_path
 
255
 
249
256
    return ctxt
250
257
 
251
258
 
253
260
    interfaces = ['identity-service']
254
261
 
255
262
    def __call__(self):
256
 
        log('Generating template context for identity-service')
 
263
        log('Generating template context for identity-service', level=DEBUG)
257
264
        ctxt = {}
258
 
 
259
265
        for rid in relation_ids('identity-service'):
260
266
            for unit in related_units(rid):
261
267
                rdata = relation_get(rid=rid, unit=unit)
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
266
 
 
267
 
                ctxt = {
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'),
275
 
                    'service_protocol':
276
 
                    rdata.get('service_protocol') or 'http',
277
 
                    'auth_protocol':
278
 
                    rdata.get('auth_protocol') or 'http',
279
 
                }
 
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
283
286
                    # upgrading
284
287
                    ctxt['admin_tenant_id'] = rdata.get('service_tenant_id')
285
288
                    return ctxt
 
289
 
286
290
        return {}
287
291
 
288
292
 
295
299
        self.interfaces = [rel_name]
296
300
 
297
301
    def __call__(self):
298
 
        log('Generating template context for amqp')
 
302
        log('Generating template context for amqp', level=DEBUG)
299
303
        conf = config()
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)
 
307
        else:
 
308
            user_setting = 'rabbit-user'
 
309
            vhost_setting = 'rabbit-vhost'
305
310
 
306
311
        try:
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
 
318
 
313
319
        ctxt = {}
314
320
        for rid in relation_ids(self.rel_name):
315
321
            ha_vip_only = False
323
329
                    host = relation_get('private-address', rid=rid, unit=unit)
324
330
                    host = format_ipv6_addr(host) or host
325
331
                    ctxt['rabbitmq_host'] = host
 
332
 
326
333
                ctxt.update({
327
334
                    'rabbitmq_user': username,
328
335
                    'rabbitmq_password': relation_get('password', rid=rid,
333
340
                ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
334
341
                if ssl_port:
335
342
                    ctxt['rabbit_ssl_port'] = ssl_port
 
343
 
336
344
                ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
337
345
                if ssl_ca:
338
346
                    ctxt['rabbit_ssl_ca'] = ssl_ca
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 "
350
 
                                 "but ssl ca found"))
 
357
                            log("Charm not setup for ssl support but ssl ca "
 
358
                                "found", level=INFO)
351
359
                            break
 
360
 
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
 
366
 
357
367
                    # Sufficient information found = break out!
358
368
                    break
 
369
 
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)
 
378
 
 
379
                ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))
 
380
 
368
381
        if not context_complete(ctxt):
369
382
            return {}
370
 
        else:
371
 
            return ctxt
 
383
 
 
384
        return ctxt
372
385
 
373
386
 
374
387
class CephContext(OSContextGenerator):
 
388
    """Generates context for /etc/ceph/ceph.conf templates."""
375
389
    interfaces = ['ceph']
376
390
 
377
391
    def __call__(self):
378
 
        '''This generates context for /etc/ceph/ceph.conf templates'''
379
392
        if not relation_ids('ceph'):
380
393
            return {}
381
394
 
382
 
        log('Generating template context for ceph')
383
 
 
 
395
        log('Generating template context for ceph', level=DEBUG)
384
396
        mon_hosts = []
385
397
        auth = None
386
398
        key = None
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)
392
 
                ceph_addr = \
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,
 
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
395
409
                ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
396
410
                mon_hosts.append(ceph_addr)
397
411
 
398
 
        ctxt = {
399
 
            'mon_hosts': ' '.join(mon_hosts),
400
 
            'auth': auth,
401
 
            'key': key,
402
 
            'use_syslog': use_syslog
403
 
        }
 
412
        ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)),
 
413
                'auth': auth,
 
414
                'key': key,
 
415
                'use_syslog': use_syslog}
404
416
 
405
417
        if not os.path.isdir('/etc/ceph'):
406
418
            os.mkdir('/etc/ceph')
409
421
            return {}
410
422
 
411
423
        ensure_packages(['ceph-common'])
412
 
 
413
424
        return ctxt
414
425
 
415
426
 
416
 
ADDRESS_TYPES = ['admin', 'internal', 'public']
417
 
 
418
 
 
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.
 
431
    """
420
432
    interfaces = ['cluster']
421
433
 
 
434
    def __init__(self, singlenode_mode=False):
 
435
        self.singlenode_mode = singlenode_mode
 
436
 
422
437
    def __call__(self):
423
 
        '''
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.
427
 
        '''
428
 
        if not relation_ids('cluster'):
 
438
        if not relation_ids('cluster') and not self.singlenode_mode:
429
439
            return {}
430
440
 
431
 
        l_unit = local_unit().replace('/', '-')
432
 
 
433
441
        if config('prefer-ipv6'):
434
442
            addr = get_ipv6_addr(exc_list=[config('vip')])[0]
435
443
        else:
436
444
            addr = get_host_ip(unit_get('private-address'))
437
445
 
 
446
        l_unit = local_unit().replace('/', '-')
438
447
        cluster_hosts = {}
439
448
 
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))
445
454
            if laddr:
446
 
                cluster_hosts[laddr] = {}
447
 
                cluster_hosts[laddr]['network'] = "{}/{}".format(
448
 
                    laddr,
449
 
                    get_netmask_for_address(laddr)
450
 
                )
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,
 
457
                                                                  netmask),
 
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)
458
463
                        if _laddr:
 
464
                            _unit = unit.replace('/', '-')
459
465
                            cluster_hosts[laddr]['backends'][_unit] = _laddr
460
466
 
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(
466
 
                addr,
467
 
                get_netmask_for_address(addr)
468
 
            )
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)
476
477
                    if _laddr:
 
478
                        _unit = unit.replace('/', '-')
477
479
                        cluster_hosts[addr]['backends'][_unit] = _laddr
478
480
 
479
 
        ctxt = {
480
 
            'frontends': cluster_hosts,
481
 
        }
 
481
        ctxt = {'frontends': cluster_hosts}
482
482
 
483
483
        if config('haproxy-server-timeout'):
484
484
            ctxt['haproxy_server_timeout'] = config('haproxy-server-timeout')
 
485
 
485
486
        if config('haproxy-client-timeout'):
486
487
            ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')
487
488
 
495
496
            ctxt['stat_port'] = ':8888'
496
497
 
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.',
 
503
                    level=DEBUG)
501
504
                with open('/etc/default/haproxy', 'w') as out:
502
505
                    out.write('ENABLED=1\n')
 
506
 
503
507
                return ctxt
504
 
        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)
505
511
        return {}
506
512
 
507
513
 
509
515
    interfaces = ['image-service']
510
516
 
511
517
    def __call__(self):
512
 
        '''
513
 
        Obtains the glance API server from the image-service relation.  Useful
514
 
        in nova and cinder (currently).
515
 
        '''
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).
 
520
        """
 
521
        log('Generating template context for image-service.', level=DEBUG)
517
522
        rids = relation_ids('image-service')
518
523
        if not rids:
519
524
            return {}
 
525
 
520
526
        for rid in rids:
521
527
            for unit in related_units(rid):
522
528
                api_server = relation_get('glance-api-server',
523
529
                                          rid=rid, unit=unit)
524
530
                if api_server:
525
531
                    return {'glance_api_servers': api_server}
526
 
        log('ImageService context is incomplete. '
527
 
            'Missing required relation data.')
 
532
 
 
533
        log("ImageService context is incomplete. Missing required relation "
 
534
            "data.", level=INFO)
528
535
        return {}
529
536
 
530
537
 
531
538
class ApacheSSLContext(OSContextGenerator):
532
 
 
533
 
    """
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::
537
542
 
565
570
        else:
566
571
            cert_filename = 'cert'
567
572
            key_filename = 'key'
 
573
 
568
574
        write_file(path=os.path.join(ssl_dir, cert_filename),
569
575
                   content=b64decode(cert))
570
576
        write_file(path=os.path.join(ssl_dir, key_filename),
576
582
            install_ca_cert(b64decode(ca_cert))
577
583
 
578
584
    def canonical_names(self):
579
 
        '''Figure out which canonical names clients will access this service'''
 
585
        """Figure out which canonical names clients will access this service.
 
586
        """
580
587
        cns = []
581
588
        for r_id in relation_ids('identity-service'):
582
589
            for unit in related_units(r_id):
584
591
                for k in rdata:
585
592
                    if k.startswith('ssl_key_'):
586
593
                        cns.append(k.lstrip('ssl_key_'))
587
 
        return list(set(cns))
 
594
 
 
595
        return sorted(list(set(cns)))
588
596
 
589
597
    def get_network_addresses(self):
590
598
        """For each network configured, return corresponding address and vip
603
611
             ...]
604
612
        """
605
613
        addresses = []
606
 
        vips = []
607
614
        if config('vip'):
608
615
            vips = config('vip').split()
 
616
        else:
 
617
            vips = []
609
618
 
610
619
        for net_type in ['os-internal-network', 'os-admin-network',
611
620
                         'os-public-network']:
614
623
            if len(vips) > 1 and is_clustered():
615
624
                if not config(net_type):
616
625
                    log("Multiple networks configured but net_type "
617
 
                        "is None (%s)." % net_type, level='WARNING')
 
626
                        "is None (%s)." % net_type, level=WARNING)
618
627
                    continue
619
628
 
620
629
                for vip in vips:
627
636
            else:
628
637
                addresses.append((addr, addr))
629
638
 
630
 
        return addresses
 
639
        return sorted(addresses)
631
640
 
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()):
 
644
 
 
645
        if not self.external_ports or not https():
636
646
            return {}
637
647
 
638
648
        self.configure_ca()
639
649
        self.enable_modules()
640
650
 
641
 
        ctxt = {
642
 
            'namespace': self.service_namespace,
643
 
            'endpoints': [],
644
 
            'ext_ports': []
645
 
        }
 
651
        ctxt = {'namespace': self.service_namespace,
 
652
                'endpoints': [],
 
653
                'ext_ports': []}
646
654
 
647
655
        for cn in self.canonical_names():
648
656
            self.configure_cert(cn)
649
657
 
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']))
 
666
 
 
667
        ctxt['ext_ports'] = sorted(list(set(ctxt['ext_ports'])))
659
668
        return ctxt
660
669
 
661
670
 
672
681
 
673
682
    @property
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)
677
686
 
678
687
    @property
679
688
    def neutron_security_groups(self):
680
689
        return None
681
690
 
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)
684
694
 
685
695
    def _save_flag_file(self):
686
696
        if self.network_manager == 'quantum':
687
697
            _file = '/etc/nova/quantum_plugin.conf'
688
698
        else:
689
699
            _file = '/etc/nova/neutron_plugin.conf'
 
700
 
690
701
        with open(_file, 'wb') as out:
691
702
            out.write(self.plugin + '\n')
692
703
 
695
706
                                          self.network_manager)
696
707
        config = neutron_plugin_attribute(self.plugin, 'config',
697
708
                                          self.network_manager)
698
 
        ovs_ctxt = {
699
 
            'core_plugin': driver,
700
 
            'neutron_plugin': 'ovs',
701
 
            'neutron_security_groups': self.neutron_security_groups,
702
 
            'local_ip': unit_private_ip(),
703
 
            'config': config
704
 
        }
 
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}
705
714
 
706
715
        return ovs_ctxt
707
716
 
710
719
                                          self.network_manager)
711
720
        config = neutron_plugin_attribute(self.plugin, 'config',
712
721
                                          self.network_manager)
713
 
        nvp_ctxt = {
714
 
            'core_plugin': driver,
715
 
            'neutron_plugin': 'nvp',
716
 
            'neutron_security_groups': self.neutron_security_groups,
717
 
            'local_ip': unit_private_ip(),
718
 
            'config': config
719
 
        }
 
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}
720
727
 
721
728
        return nvp_ctxt
722
729
 
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')
729
 
        n1kv_ctxt = {
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'),
740
 
        }
 
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
 
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)
752
 
        calico_ctxt = {
753
 
            'core_plugin': driver,
754
 
            'neutron_plugin': 'Calico',
755
 
            'neutron_security_groups': self.neutron_security_groups,
756
 
            'local_ip': unit_private_ip(),
757
 
            'config': config
758
 
        }
 
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}
759
763
 
760
764
        return calico_ctxt
761
765
 
764
768
            proto = 'https'
765
769
        else:
766
770
            proto = 'http'
 
771
 
767
772
        if is_clustered():
768
773
            host = config('vip')
769
774
        else:
770
775
            host = unit_get('private-address')
771
 
        url = '%s://%s:%s' % (proto, host, '9696')
772
 
        ctxt = {
773
 
            'network_manager': self.network_manager,
774
 
            'neutron_url': url,
775
 
        }
 
776
 
 
777
        ctxt = {'network_manager': self.network_manager,
 
778
                'neutron_url': '%s://%s:%s' % (proto, host, '9696')}
776
779
        return ctxt
777
780
 
778
781
    def __call__(self):
805
808
 
806
809
 
807
810
class OSConfigFlagContext(OSContextGenerator):
808
 
 
809
 
    """
810
 
    Provides support for user-defined config flags.
 
811
    """Provides support for user-defined config flags.
811
812
 
812
813
    Users can define a comma-seperated list of key=value pairs
813
814
    in the charm configuration and apply them at any point in
826
827
    def __init__(self, charm_flag='config-flags',
827
828
                 template_flag='user_config_flags'):
828
829
        """
829
 
        charm_flag: config flags in charm configuration.
830
 
        template_flag: insert point for user-defined flags template file.
 
830
        :param charm_flag: config flags in charm configuration.
 
831
        :param template_flag: insert point for user-defined flags in template
 
832
                              file.
831
833
        """
832
834
        super(OSConfigFlagContext, self).__init__()
833
835
        self._charm_flag = charm_flag
883
885
                },
884
886
            }
885
887
        }
886
 
 
887
888
    """
888
889
 
889
890
    def __init__(self, service, config_file, interface):
913
914
 
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),
 
918
                            level=INFO)
917
919
                        continue
918
920
 
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
                            level=INFO)
923
926
                        continue
924
927
 
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),
 
933
                                    level=DEBUG)
930
934
                                ctxt[k][section] = config_dict
931
935
                        else:
932
936
                            ctxt[k] = v
933
937
 
934
938
        log("%d section(s) found" % (len(ctxt['sections'])), level=DEBUG)
935
 
 
936
939
        return ctxt
937
940
 
938
941
 
944
947
            False if config('debug') is None else config('debug')
945
948
        ctxt['verbose'] = \
946
949
            False if config('verbose') is None else config('verbose')
 
950
 
947
951
        return ctxt
948
952
 
949
953
 
950
954
class SyslogContext(OSContextGenerator):
951
955
 
952
956
    def __call__(self):
953
 
        ctxt = {
954
 
            'use_syslog': config('use-syslog')
955
 
        }
 
957
        ctxt = {'use_syslog': config('use-syslog')}
956
958
        return ctxt
957
959
 
958
960
 
960
962
 
961
963
    def __call__(self):
962
964
        if config('prefer-ipv6'):
963
 
            return {
964
 
                'bind_host': '::'
965
 
            }
 
965
            return {'bind_host': '::'}
966
966
        else:
967
 
            return {
968
 
                'bind_host': '0.0.0.0'
969
 
            }
 
967
            return {'bind_host': '0.0.0.0'}
970
968
 
971
969
 
972
970
class WorkerConfigContext(OSContextGenerator):
978
976
        except ImportError:
979
977
            apt_install('python-psutil', fatal=True)
980
978
            from psutil import NUM_CPUS
 
979
 
981
980
        return NUM_CPUS
982
981
 
983
982
    def __call__(self):
984
 
        multiplier = config('worker-multiplier') or 1
985
 
        ctxt = {
986
 
            "workers": self.num_cpus * multiplier
987
 
        }
 
983
        multiplier = config('worker-multiplier') or 0
 
984
        ctxt = {"workers": self.num_cpus * multiplier}
988
985
        return ctxt
989
986
 
990
987
 
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)
 
998
 
1001
999
        return ctxt
1002
1000
 
1003
1001
 
1004
1002
class NotificationDriverContext(OSContextGenerator):
1005
1003
 
1006
 
    def __init__(self, zmq_relation='zeromq-configuration', amqp_relation='amqp'):
 
1004
    def __init__(self, zmq_relation='zeromq-configuration',
 
1005
                 amqp_relation='amqp'):
1007
1006
        """
1008
 
        :param zmq_relation   : Name of Zeromq relation to check
 
1007
        :param zmq_relation: Name of Zeromq relation to check
1009
1008
        """
1010
1009
        self.zmq_relation = zmq_relation
1011
1010
        self.amqp_relation = amqp_relation
1012
1011
 
1013
1012
    def __call__(self):
1014
 
        ctxt = {
1015
 
            'notifications': 'False',
1016
 
        }
 
1013
        ctxt = {'notifications': 'False'}
1017
1014
        if is_relation_made(self.amqp_relation):
1018
1015
            ctxt['notifications'] = "True"
 
1016
 
1019
1017
        return ctxt