~openstack-charmers-next/charms/wily/lxd/trunk

« back to all changes in this revision

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

  • Committer: James Page
  • Date: 2016-02-25 16:35:49 UTC
  • mfrom: (50.1.1 trunk)
  • Revision ID: james.page@ubuntu.com-20160225163549-9owa4y2x6hbj3ysa
Resync helpers

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# You should have received a copy of the GNU Lesser General Public License
15
15
# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
16
16
 
 
17
import glob
17
18
import json
18
19
import os
19
20
import re
56
57
    get_nic_hwaddr,
57
58
    mkdir,
58
59
    write_file,
 
60
    pwgen,
59
61
)
60
62
from charmhelpers.contrib.hahelpers.cluster import (
61
63
    determine_apache_port,
86
88
    is_bridge_member,
87
89
)
88
90
from charmhelpers.contrib.openstack.utils import get_host_ip
 
91
from charmhelpers.core.unitdata import kv
 
92
 
 
93
try:
 
94
    import psutil
 
95
except ImportError:
 
96
    apt_install('python-psutil', fatal=True)
 
97
    import psutil
 
98
 
89
99
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
90
100
ADDRESS_TYPES = ['admin', 'internal', 'public']
91
101
 
194
204
class OSContextGenerator(object):
195
205
    """Base class for all context generators."""
196
206
    interfaces = []
 
207
    related = False
 
208
    complete = False
 
209
    missing_data = []
197
210
 
198
211
    def __call__(self):
199
212
        raise NotImplementedError
200
213
 
 
214
    def context_complete(self, ctxt):
 
215
        """Check for missing data for the required context data.
 
216
        Set self.missing_data if it exists and return False.
 
217
        Set self.complete if no missing data and return True.
 
218
        """
 
219
        # Fresh start
 
220
        self.complete = False
 
221
        self.missing_data = []
 
222
        for k, v in six.iteritems(ctxt):
 
223
            if v is None or v == '':
 
224
                if k not in self.missing_data:
 
225
                    self.missing_data.append(k)
 
226
 
 
227
        if self.missing_data:
 
228
            self.complete = False
 
229
            log('Missing required data: %s' % ' '.join(self.missing_data), level=INFO)
 
230
        else:
 
231
            self.complete = True
 
232
        return self.complete
 
233
 
 
234
    def get_related(self):
 
235
        """Check if any of the context interfaces have relation ids.
 
236
        Set self.related and return True if one of the interfaces
 
237
        has relation ids.
 
238
        """
 
239
        # Fresh start
 
240
        self.related = False
 
241
        try:
 
242
            for interface in self.interfaces:
 
243
                if relation_ids(interface):
 
244
                    self.related = True
 
245
            return self.related
 
246
        except AttributeError as e:
 
247
            log("{} {}"
 
248
                "".format(self, e), 'INFO')
 
249
            return self.related
 
250
 
201
251
 
202
252
class SharedDBContext(OSContextGenerator):
203
253
    interfaces = ['shared-db']
213
263
        self.database = database
214
264
        self.user = user
215
265
        self.ssl_dir = ssl_dir
 
266
        self.rel_name = self.interfaces[0]
216
267
 
217
268
    def __call__(self):
218
269
        self.database = self.database or config('database')
246
297
            password_setting = self.relation_prefix + '_password'
247
298
 
248
299
        for rid in relation_ids(self.interfaces[0]):
 
300
            self.related = True
249
301
            for unit in related_units(rid):
250
302
                rdata = relation_get(rid=rid, unit=unit)
251
303
                host = rdata.get('db_host')
257
309
                    'database_password': rdata.get(password_setting),
258
310
                    'database_type': 'mysql'
259
311
                }
260
 
                if context_complete(ctxt):
 
312
                if self.context_complete(ctxt):
261
313
                    db_ssl(rdata, ctxt, self.ssl_dir)
262
314
                    return ctxt
263
315
        return {}
278
330
 
279
331
        ctxt = {}
280
332
        for rid in relation_ids(self.interfaces[0]):
 
333
            self.related = True
281
334
            for unit in related_units(rid):
282
335
                rel_host = relation_get('host', rid=rid, unit=unit)
283
336
                rel_user = relation_get('user', rid=rid, unit=unit)
287
340
                        'database_user': rel_user,
288
341
                        'database_password': rel_passwd,
289
342
                        'database_type': 'postgresql'}
290
 
                if context_complete(ctxt):
 
343
                if self.context_complete(ctxt):
291
344
                    return ctxt
292
345
 
293
346
        return {}
348
401
            ctxt['signing_dir'] = cachedir
349
402
 
350
403
        for rid in relation_ids(self.rel_name):
 
404
            self.related = True
351
405
            for unit in related_units(rid):
352
406
                rdata = relation_get(rid=rid, unit=unit)
353
407
                serv_host = rdata.get('service_host')
356
410
                auth_host = format_ipv6_addr(auth_host) or auth_host
357
411
                svc_protocol = rdata.get('service_protocol') or 'http'
358
412
                auth_protocol = rdata.get('auth_protocol') or 'http'
 
413
                api_version = rdata.get('api_version') or '2.0'
359
414
                ctxt.update({'service_port': rdata.get('service_port'),
360
415
                             'service_host': serv_host,
361
416
                             'auth_host': auth_host,
364
419
                             'admin_user': rdata.get('service_username'),
365
420
                             'admin_password': rdata.get('service_password'),
366
421
                             'service_protocol': svc_protocol,
367
 
                             'auth_protocol': auth_protocol})
 
422
                             'auth_protocol': auth_protocol,
 
423
                             'api_version': api_version})
368
424
 
369
 
                if context_complete(ctxt):
 
425
                if self.context_complete(ctxt):
370
426
                    # NOTE(jamespage) this is required for >= icehouse
371
427
                    # so a missing value just indicates keystone needs
372
428
                    # upgrading
405
461
        ctxt = {}
406
462
        for rid in relation_ids(self.rel_name):
407
463
            ha_vip_only = False
 
464
            self.related = True
408
465
            for unit in related_units(rid):
409
466
                if relation_get('clustered', rid=rid, unit=unit):
410
467
                    ctxt['clustered'] = True
437
494
                ha_vip_only = relation_get('ha-vip-only',
438
495
                                           rid=rid, unit=unit) is not None
439
496
 
440
 
                if context_complete(ctxt):
 
497
                if self.context_complete(ctxt):
441
498
                    if 'rabbit_ssl_ca' in ctxt:
442
499
                        if not self.ssl_dir:
443
500
                            log("Charm not setup for ssl support but ssl ca "
469
526
            ctxt['oslo_messaging_flags'] = config_flags_parser(
470
527
                oslo_messaging_flags)
471
528
 
472
 
        if not context_complete(ctxt):
 
529
        if not self.complete:
473
530
            return {}
474
531
 
475
532
        return ctxt
485
542
 
486
543
        log('Generating template context for ceph', level=DEBUG)
487
544
        mon_hosts = []
488
 
        auth = None
489
 
        key = None
490
 
        use_syslog = str(config('use-syslog')).lower()
 
545
        ctxt = {
 
546
            'use_syslog': str(config('use-syslog')).lower()
 
547
        }
491
548
        for rid in relation_ids('ceph'):
492
549
            for unit in related_units(rid):
493
 
                auth = relation_get('auth', rid=rid, unit=unit)
494
 
                key = relation_get('key', rid=rid, unit=unit)
 
550
                if not ctxt.get('auth'):
 
551
                    ctxt['auth'] = relation_get('auth', rid=rid, unit=unit)
 
552
                if not ctxt.get('key'):
 
553
                    ctxt['key'] = relation_get('key', rid=rid, unit=unit)
495
554
                ceph_pub_addr = relation_get('ceph-public-address', rid=rid,
496
555
                                             unit=unit)
497
556
                unit_priv_addr = relation_get('private-address', rid=rid,
500
559
                ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
501
560
                mon_hosts.append(ceph_addr)
502
561
 
503
 
        ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)),
504
 
                'auth': auth,
505
 
                'key': key,
506
 
                'use_syslog': use_syslog}
 
562
        ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts))
507
563
 
508
564
        if not os.path.isdir('/etc/ceph'):
509
565
            os.mkdir('/etc/ceph')
510
566
 
511
 
        if not context_complete(ctxt):
 
567
        if not self.context_complete(ctxt):
512
568
            return {}
513
569
 
514
570
        ensure_packages(['ceph-common'])
581
637
        if config('haproxy-client-timeout'):
582
638
            ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')
583
639
 
 
640
        if config('haproxy-queue-timeout'):
 
641
            ctxt['haproxy_queue_timeout'] = config('haproxy-queue-timeout')
 
642
 
 
643
        if config('haproxy-connect-timeout'):
 
644
            ctxt['haproxy_connect_timeout'] = config('haproxy-connect-timeout')
 
645
 
584
646
        if config('prefer-ipv6'):
585
647
            ctxt['ipv6'] = True
586
648
            ctxt['local_host'] = 'ip6-localhost'
587
649
            ctxt['haproxy_host'] = '::'
588
 
            ctxt['stat_port'] = ':::8888'
589
650
        else:
590
651
            ctxt['local_host'] = '127.0.0.1'
591
652
            ctxt['haproxy_host'] = '0.0.0.0'
592
 
            ctxt['stat_port'] = ':8888'
 
653
 
 
654
        ctxt['stat_port'] = '8888'
 
655
 
 
656
        db = kv()
 
657
        ctxt['stat_password'] = db.get('stat-password')
 
658
        if not ctxt['stat_password']:
 
659
            ctxt['stat_password'] = db.set('stat-password',
 
660
                                           pwgen(32))
 
661
            db.flush()
593
662
 
594
663
        for frontend in cluster_hosts:
595
664
            if (len(cluster_hosts[frontend]['backends']) > 1 or
907
976
                    'config': config}
908
977
        return ovs_ctxt
909
978
 
 
979
    def midonet_ctxt(self):
 
980
        driver = neutron_plugin_attribute(self.plugin, 'driver',
 
981
                                          self.network_manager)
 
982
        midonet_config = neutron_plugin_attribute(self.plugin, 'config',
 
983
                                                  self.network_manager)
 
984
        mido_ctxt = {'core_plugin': driver,
 
985
                     'neutron_plugin': 'midonet',
 
986
                     'neutron_security_groups': self.neutron_security_groups,
 
987
                     'local_ip': unit_private_ip(),
 
988
                     'config': midonet_config}
 
989
 
 
990
        return mido_ctxt
 
991
 
910
992
    def __call__(self):
911
993
        if self.network_manager not in ['quantum', 'neutron']:
912
994
            return {}
928
1010
            ctxt.update(self.nuage_ctxt())
929
1011
        elif self.plugin == 'plumgrid':
930
1012
            ctxt.update(self.pg_ctxt())
 
1013
        elif self.plugin == 'midonet':
 
1014
            ctxt.update(self.midonet_ctxt())
931
1015
 
932
1016
        alchemy_flags = config('neutron-alchemy-flags')
933
1017
        if alchemy_flags:
1028
1112
                config_flags_parser(config_flags)}
1029
1113
 
1030
1114
 
 
1115
class LibvirtConfigFlagsContext(OSContextGenerator):
 
1116
    """
 
1117
    This context provides support for extending
 
1118
    the libvirt section through user-defined flags.
 
1119
    """
 
1120
    def __call__(self):
 
1121
        ctxt = {}
 
1122
        libvirt_flags = config('libvirt-flags')
 
1123
        if libvirt_flags:
 
1124
            ctxt['libvirt_flags'] = config_flags_parser(
 
1125
                libvirt_flags)
 
1126
        return ctxt
 
1127
 
 
1128
 
1031
1129
class SubordinateConfigContext(OSContextGenerator):
1032
1130
 
1033
1131
    """
1060
1158
 
1061
1159
        ctxt = {
1062
1160
            ... other context ...
1063
 
            'subordinate_config': {
 
1161
            'subordinate_configuration': {
1064
1162
                'DEFAULT': {
1065
1163
                    'key1': 'value1',
1066
1164
                },
1101
1199
                    try:
1102
1200
                        sub_config = json.loads(sub_config)
1103
1201
                    except:
1104
 
                        log('Could not parse JSON from subordinate_config '
1105
 
                            'setting from %s' % rid, level=ERROR)
 
1202
                        log('Could not parse JSON from '
 
1203
                            'subordinate_configuration setting from %s'
 
1204
                            % rid, level=ERROR)
1106
1205
                        continue
1107
1206
 
1108
1207
                    for service in self.services:
1109
1208
                        if service not in sub_config:
1110
 
                            log('Found subordinate_config on %s but it contained'
1111
 
                                'nothing for %s service' % (rid, service),
1112
 
                                level=INFO)
 
1209
                            log('Found subordinate_configuration on %s but it '
 
1210
                                'contained nothing for %s service'
 
1211
                                % (rid, service), level=INFO)
1113
1212
                            continue
1114
1213
 
1115
1214
                        sub_config = sub_config[service]
1116
1215
                        if self.config_file not in sub_config:
1117
 
                            log('Found subordinate_config on %s but it contained'
1118
 
                                'nothing for %s' % (rid, self.config_file),
1119
 
                                level=INFO)
 
1216
                            log('Found subordinate_configuration on %s but it '
 
1217
                                'contained nothing for %s'
 
1218
                                % (rid, self.config_file), level=INFO)
1120
1219
                            continue
1121
1220
 
1122
1221
                        sub_config = sub_config[self.config_file]
1167
1266
 
1168
1267
    @property
1169
1268
    def num_cpus(self):
1170
 
        try:
1171
 
            from psutil import NUM_CPUS
1172
 
        except ImportError:
1173
 
            apt_install('python-psutil', fatal=True)
1174
 
            from psutil import NUM_CPUS
1175
 
 
1176
 
        return NUM_CPUS
 
1269
        # NOTE: use cpu_count if present (16.04 support)
 
1270
        if hasattr(psutil, 'cpu_count'):
 
1271
            return psutil.cpu_count()
 
1272
        else:
 
1273
            return psutil.NUM_CPUS
1177
1274
 
1178
1275
    def __call__(self):
1179
1276
        multiplier = config('worker-multiplier') or 0
1319
1416
            normalized.update({port: port for port in resolved
1320
1417
                               if port in ports})
1321
1418
            if resolved:
1322
 
                return {bridge: normalized[port] for port, bridge in
 
1419
                return {normalized[port]: bridge for port, bridge in
1323
1420
                        six.iteritems(portmap) if port in normalized.keys()}
1324
1421
 
1325
1422
        return None
1330
1427
    def __call__(self):
1331
1428
        ctxt = {}
1332
1429
        mappings = super(PhyNICMTUContext, self).__call__()
1333
 
        if mappings and mappings.values():
1334
 
            ports = mappings.values()
 
1430
        if mappings and mappings.keys():
 
1431
            ports = sorted(mappings.keys())
1335
1432
            napi_settings = NeutronAPIContext()()
1336
1433
            mtu = napi_settings.get('network_device_mtu')
 
1434
            all_ports = set()
 
1435
            # If any of ports is a vlan device, its underlying device must have
 
1436
            # mtu applied first.
 
1437
            for port in ports:
 
1438
                for lport in glob.glob("/sys/class/net/%s/lower_*" % port):
 
1439
                    lport = os.path.basename(lport)
 
1440
                    all_ports.add(lport.split('_')[1])
 
1441
 
 
1442
            all_ports = list(all_ports)
 
1443
            all_ports.extend(ports)
1337
1444
            if mtu:
1338
 
                ctxt["devs"] = '\\n'.join(ports)
 
1445
                ctxt["devs"] = '\\n'.join(all_ports)
1339
1446
                ctxt['mtu'] = mtu
1340
1447
 
1341
1448
        return ctxt
1366
1473
                    rdata.get('service_protocol') or 'http',
1367
1474
                    'auth_protocol':
1368
1475
                    rdata.get('auth_protocol') or 'http',
 
1476
                    'api_version':
 
1477
                    rdata.get('api_version') or '2.0',
1369
1478
                }
1370
 
                if context_complete(ctxt):
 
1479
                if self.context_complete(ctxt):
1371
1480
                    return ctxt
1372
1481
        return {}