~corey.bryant/charms/trusty/keystone/python-six

« back to all changes in this revision

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

  • Committer: James Page
  • Date: 2014-03-27 10:54:38 UTC
  • mfrom: (55.1.22 keystone)
  • mto: (52.4.7 keystone)
  • mto: This revision was merged to the branch mainline in revision 60.
  • Revision ID: james.page@canonical.com-20140327105438-oid8czi9ud51iut1
Merge ssl-everywhere branch (may break stuff)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import json
 
2
import os
 
3
import time
 
4
 
 
5
from base64 import b64decode
 
6
 
 
7
from subprocess import (
 
8
    check_call
 
9
)
 
10
 
 
11
 
 
12
from charmhelpers.fetch import (
 
13
    apt_install,
 
14
    filter_installed_packages,
 
15
)
 
16
 
 
17
from charmhelpers.core.hookenv import (
 
18
    config,
 
19
    local_unit,
 
20
    log,
 
21
    relation_get,
 
22
    relation_ids,
 
23
    related_units,
 
24
    unit_get,
 
25
    unit_private_ip,
 
26
    ERROR,
 
27
)
 
28
 
 
29
from charmhelpers.contrib.hahelpers.cluster import (
 
30
    determine_apache_port,
 
31
    determine_api_port,
 
32
    https,
 
33
    is_clustered
 
34
)
 
35
 
 
36
from charmhelpers.contrib.hahelpers.apache import (
 
37
    get_cert,
 
38
    get_ca_cert,
 
39
)
 
40
 
 
41
from charmhelpers.contrib.openstack.neutron import (
 
42
    neutron_plugin_attribute,
 
43
)
 
44
 
 
45
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
 
46
 
 
47
 
 
48
class OSContextError(Exception):
 
49
    pass
 
50
 
 
51
 
 
52
def ensure_packages(packages):
 
53
    '''Install but do not upgrade required plugin packages'''
 
54
    required = filter_installed_packages(packages)
 
55
    if required:
 
56
        apt_install(required, fatal=True)
 
57
 
 
58
 
 
59
def context_complete(ctxt):
 
60
    _missing = []
 
61
    for k, v in ctxt.iteritems():
 
62
        if v is None or v == '':
 
63
            _missing.append(k)
 
64
    if _missing:
 
65
        log('Missing required data: %s' % ' '.join(_missing), level='INFO')
 
66
        return False
 
67
    return True
 
68
 
 
69
 
 
70
def config_flags_parser(config_flags):
 
71
    if config_flags.find('==') >= 0:
 
72
        log("config_flags is not in expected format (key=value)",
 
73
            level=ERROR)
 
74
        raise OSContextError
 
75
    # strip the following from each value.
 
76
    post_strippers = ' ,'
 
77
    # we strip any leading/trailing '=' or ' ' from the string then
 
78
    # split on '='.
 
79
    split = config_flags.strip(' =').split('=')
 
80
    limit = len(split)
 
81
    flags = {}
 
82
    for i in xrange(0, limit - 1):
 
83
        current = split[i]
 
84
        next = split[i + 1]
 
85
        vindex = next.rfind(',')
 
86
        if (i == limit - 2) or (vindex < 0):
 
87
            value = next
 
88
        else:
 
89
            value = next[:vindex]
 
90
 
 
91
        if i == 0:
 
92
            key = current
 
93
        else:
 
94
            # if this not the first entry, expect an embedded key.
 
95
            index = current.rfind(',')
 
96
            if index < 0:
 
97
                log("invalid config value(s) at index %s" % (i),
 
98
                    level=ERROR)
 
99
                raise OSContextError
 
100
            key = current[index + 1:]
 
101
 
 
102
        # Add to collection.
 
103
        flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
 
104
    return flags
 
105
 
 
106
 
 
107
class OSContextGenerator(object):
 
108
    interfaces = []
 
109
 
 
110
    def __call__(self):
 
111
        raise NotImplementedError
 
112
 
 
113
 
 
114
class SharedDBContext(OSContextGenerator):
 
115
    interfaces = ['shared-db']
 
116
 
 
117
    def __init__(self,
 
118
                 database=None, user=None, relation_prefix=None, ssl_dir=None):
 
119
        '''
 
120
        Allows inspecting relation for settings prefixed with relation_prefix.
 
121
        This is useful for parsing access for multiple databases returned via
 
122
        the shared-db interface (eg, nova_password, quantum_password)
 
123
        '''
 
124
        self.relation_prefix = relation_prefix
 
125
        self.database = database
 
126
        self.user = user
 
127
        self.ssl_dir = ssl_dir
 
128
 
 
129
    def __call__(self):
 
130
        self.database = self.database or config('database')
 
131
        self.user = self.user or config('database-user')
 
132
        if None in [self.database, self.user]:
 
133
            log('Could not generate shared_db context. '
 
134
                'Missing required charm config options. '
 
135
                '(database name and user)')
 
136
            raise OSContextError
 
137
        ctxt = {}
 
138
 
 
139
        password_setting = 'password'
 
140
        if self.relation_prefix:
 
141
            password_setting = self.relation_prefix + '_password'
 
142
 
 
143
        for rid in relation_ids('shared-db'):
 
144
            for unit in related_units(rid):
 
145
                rdata = relation_get(rid=rid, unit=unit)
 
146
                ctxt = {
 
147
                    'database_host': rdata.get('db_host'),
 
148
                    'database': self.database,
 
149
                    'database_user': self.user,
 
150
                    'database_password': rdata.get(password_setting)
 
151
                }
 
152
                if context_complete(ctxt):
 
153
                    db_ssl(rdata, ctxt, self.ssl_dir)
 
154
                    return ctxt
 
155
        return {}
 
156
 
 
157
 
 
158
def db_ssl(rdata, ctxt, ssl_dir):
 
159
    if 'ssl_ca' in rdata and ssl_dir:
 
160
        ca_path = os.path.join(ssl_dir, 'db-client.ca')
 
161
        with open(ca_path, 'w') as fh:
 
162
            fh.write(b64decode(rdata['ssl_ca']))
 
163
        ctxt['database_ssl_ca'] = ca_path
 
164
    elif 'ssl_ca' in rdata:
 
165
        log("Charm not setup for ssl support but ssl ca found")
 
166
        return ctxt
 
167
    if 'ssl_cert' in rdata:
 
168
        cert_path = os.path.join(
 
169
            ssl_dir, 'db-client.cert')
 
170
        if not os.path.exists(cert_path):
 
171
            log("Waiting 1m for ssl client cert validity")
 
172
            time.sleep(60)
 
173
        with open(cert_path, 'w') as fh:
 
174
            fh.write(b64decode(rdata['ssl_cert']))
 
175
        ctxt['database_ssl_cert'] = cert_path
 
176
        key_path = os.path.join(ssl_dir, 'db-client.key')
 
177
        with open(key_path, 'w') as fh:
 
178
            fh.write(b64decode(rdata['ssl_key']))
 
179
        ctxt['database_ssl_key'] = key_path
 
180
    return ctxt
 
181
 
 
182
 
 
183
class IdentityServiceContext(OSContextGenerator):
 
184
    interfaces = ['identity-service']
 
185
 
 
186
    def __call__(self):
 
187
        log('Generating template context for identity-service')
 
188
        ctxt = {}
 
189
 
 
190
        for rid in relation_ids('identity-service'):
 
191
            for unit in related_units(rid):
 
192
                rdata = relation_get(rid=rid, unit=unit)
 
193
                ctxt = {
 
194
                    'service_port': rdata.get('service_port'),
 
195
                    'service_host': rdata.get('service_host'),
 
196
                    'auth_host': rdata.get('auth_host'),
 
197
                    'auth_port': rdata.get('auth_port'),
 
198
                    'admin_tenant_name': rdata.get('service_tenant'),
 
199
                    'admin_user': rdata.get('service_username'),
 
200
                    'admin_password': rdata.get('service_password'),
 
201
                    'service_protocol':
 
202
                    rdata.get('service_protocol') or 'http',
 
203
                    'auth_protocol':
 
204
                    rdata.get('auth_protocol') or 'http',
 
205
                }
 
206
                if context_complete(ctxt):
 
207
                    return ctxt
 
208
        return {}
 
209
 
 
210
 
 
211
class AMQPContext(OSContextGenerator):
 
212
    interfaces = ['amqp']
 
213
 
 
214
    def __init__(self, ssl_dir=None):
 
215
        self.ssl_dir = ssl_dir
 
216
 
 
217
    def __call__(self):
 
218
        log('Generating template context for amqp')
 
219
        conf = config()
 
220
        try:
 
221
            username = conf['rabbit-user']
 
222
            vhost = conf['rabbit-vhost']
 
223
        except KeyError as e:
 
224
            log('Could not generate shared_db context. '
 
225
                'Missing required charm config options: %s.' % e)
 
226
            raise OSContextError
 
227
        ctxt = {}
 
228
        for rid in relation_ids('amqp'):
 
229
            ha_vip_only = False
 
230
            for unit in related_units(rid):
 
231
                if relation_get('clustered', rid=rid, unit=unit):
 
232
                    ctxt['clustered'] = True
 
233
                    ctxt['rabbitmq_host'] = relation_get('vip', rid=rid,
 
234
                                                         unit=unit)
 
235
                else:
 
236
                    ctxt['rabbitmq_host'] = relation_get('private-address',
 
237
                                                         rid=rid, unit=unit)
 
238
                ctxt.update({
 
239
                    'rabbitmq_user': username,
 
240
                    'rabbitmq_password': relation_get('password', rid=rid,
 
241
                                                      unit=unit),
 
242
                    'rabbitmq_virtual_host': vhost,
 
243
                })
 
244
 
 
245
                ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
 
246
                if ssl_port:
 
247
                    ctxt['rabbit_ssl_port'] = ssl_port
 
248
                ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
 
249
                if ssl_ca:
 
250
                    ctxt['rabbit_ssl_ca'] = ssl_ca
 
251
 
 
252
                if relation_get('ha_queues', rid=rid, unit=unit) is not None:
 
253
                    ctxt['rabbitmq_ha_queues'] = True
 
254
 
 
255
                ha_vip_only = relation_get('ha-vip-only',
 
256
                                           rid=rid, unit=unit) is not None
 
257
 
 
258
                if context_complete(ctxt):
 
259
                    if 'rabbit_ssl_ca' in ctxt:
 
260
                        if not self.ssl_dir:
 
261
                            log(("Charm not setup for ssl support "
 
262
                                 "but ssl ca found"))
 
263
                            break
 
264
                        ca_path = os.path.join(
 
265
                            self.ssl_dir, 'rabbit-client-ca.pem')
 
266
                        with open(ca_path, 'w') as fh:
 
267
                            fh.write(b64decode(ctxt['rabbit_ssl_ca']))
 
268
                            ctxt['rabbit_ssl_ca'] = ca_path
 
269
                    # Sufficient information found = break out!
 
270
                    break
 
271
            # Used for active/active rabbitmq >= grizzly
 
272
            if ('clustered' not in ctxt or ha_vip_only) \
 
273
                    and len(related_units(rid)) > 1:
 
274
                rabbitmq_hosts = []
 
275
                for unit in related_units(rid):
 
276
                    rabbitmq_hosts.append(relation_get('private-address',
 
277
                                                       rid=rid, unit=unit))
 
278
                ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts)
 
279
        if not context_complete(ctxt):
 
280
            return {}
 
281
        else:
 
282
            return ctxt
 
283
 
 
284
 
 
285
class CephContext(OSContextGenerator):
 
286
    interfaces = ['ceph']
 
287
 
 
288
    def __call__(self):
 
289
        '''This generates context for /etc/ceph/ceph.conf templates'''
 
290
        if not relation_ids('ceph'):
 
291
            return {}
 
292
 
 
293
        log('Generating template context for ceph')
 
294
 
 
295
        mon_hosts = []
 
296
        auth = None
 
297
        key = None
 
298
        use_syslog = str(config('use-syslog')).lower()
 
299
        for rid in relation_ids('ceph'):
 
300
            for unit in related_units(rid):
 
301
                mon_hosts.append(relation_get('private-address', rid=rid,
 
302
                                              unit=unit))
 
303
                auth = relation_get('auth', rid=rid, unit=unit)
 
304
                key = relation_get('key', rid=rid, unit=unit)
 
305
 
 
306
        ctxt = {
 
307
            'mon_hosts': ' '.join(mon_hosts),
 
308
            'auth': auth,
 
309
            'key': key,
 
310
            'use_syslog': use_syslog
 
311
        }
 
312
 
 
313
        if not os.path.isdir('/etc/ceph'):
 
314
            os.mkdir('/etc/ceph')
 
315
 
 
316
        if not context_complete(ctxt):
 
317
            return {}
 
318
 
 
319
        ensure_packages(['ceph-common'])
 
320
 
 
321
        return ctxt
 
322
 
 
323
 
 
324
class HAProxyContext(OSContextGenerator):
 
325
    interfaces = ['cluster']
 
326
 
 
327
    def __call__(self):
 
328
        '''
 
329
        Builds half a context for the haproxy template, which describes
 
330
        all peers to be included in the cluster.  Each charm needs to include
 
331
        its own context generator that describes the port mapping.
 
332
        '''
 
333
        if not relation_ids('cluster'):
 
334
            return {}
 
335
 
 
336
        cluster_hosts = {}
 
337
        l_unit = local_unit().replace('/', '-')
 
338
        cluster_hosts[l_unit] = unit_get('private-address')
 
339
 
 
340
        for rid in relation_ids('cluster'):
 
341
            for unit in related_units(rid):
 
342
                _unit = unit.replace('/', '-')
 
343
                addr = relation_get('private-address', rid=rid, unit=unit)
 
344
                cluster_hosts[_unit] = addr
 
345
 
 
346
        ctxt = {
 
347
            'units': cluster_hosts,
 
348
        }
 
349
        if len(cluster_hosts.keys()) > 1:
 
350
            # Enable haproxy when we have enough peers.
 
351
            log('Ensuring haproxy enabled in /etc/default/haproxy.')
 
352
            with open('/etc/default/haproxy', 'w') as out:
 
353
                out.write('ENABLED=1\n')
 
354
            return ctxt
 
355
        log('HAProxy context is incomplete, this unit has no peers.')
 
356
        return {}
 
357
 
 
358
 
 
359
class ImageServiceContext(OSContextGenerator):
 
360
    interfaces = ['image-service']
 
361
 
 
362
    def __call__(self):
 
363
        '''
 
364
        Obtains the glance API server from the image-service relation.  Useful
 
365
        in nova and cinder (currently).
 
366
        '''
 
367
        log('Generating template context for image-service.')
 
368
        rids = relation_ids('image-service')
 
369
        if not rids:
 
370
            return {}
 
371
        for rid in rids:
 
372
            for unit in related_units(rid):
 
373
                api_server = relation_get('glance-api-server',
 
374
                                          rid=rid, unit=unit)
 
375
                if api_server:
 
376
                    return {'glance_api_servers': api_server}
 
377
        log('ImageService context is incomplete. '
 
378
            'Missing required relation data.')
 
379
        return {}
 
380
 
 
381
 
 
382
class ApacheSSLContext(OSContextGenerator):
 
383
 
 
384
    """
 
385
    Generates a context for an apache vhost configuration that configures
 
386
    HTTPS reverse proxying for one or many endpoints.  Generated context
 
387
    looks something like:
 
388
    {
 
389
        'namespace': 'cinder',
 
390
        'private_address': 'iscsi.mycinderhost.com',
 
391
        'endpoints': [(8776, 8766), (8777, 8767)]
 
392
    }
 
393
 
 
394
    The endpoints list consists of a tuples mapping external ports
 
395
    to internal ports.
 
396
    """
 
397
    interfaces = ['https']
 
398
 
 
399
    # charms should inherit this context and set external ports
 
400
    # and service namespace accordingly.
 
401
    external_ports = []
 
402
    service_namespace = None
 
403
 
 
404
    def enable_modules(self):
 
405
        cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']
 
406
        check_call(cmd)
 
407
 
 
408
    def configure_cert(self):
 
409
        if not os.path.isdir('/etc/apache2/ssl'):
 
410
            os.mkdir('/etc/apache2/ssl')
 
411
        ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
 
412
        if not os.path.isdir(ssl_dir):
 
413
            os.mkdir(ssl_dir)
 
414
        cert, key = get_cert()
 
415
        with open(os.path.join(ssl_dir, 'cert'), 'w') as cert_out:
 
416
            cert_out.write(b64decode(cert))
 
417
        with open(os.path.join(ssl_dir, 'key'), 'w') as key_out:
 
418
            key_out.write(b64decode(key))
 
419
        ca_cert = get_ca_cert()
 
420
        if ca_cert:
 
421
            with open(CA_CERT_PATH, 'w') as ca_out:
 
422
                ca_out.write(b64decode(ca_cert))
 
423
            check_call(['update-ca-certificates'])
 
424
 
 
425
    def __call__(self):
 
426
        if isinstance(self.external_ports, basestring):
 
427
            self.external_ports = [self.external_ports]
 
428
        if (not self.external_ports or not https()):
 
429
            return {}
 
430
 
 
431
        self.configure_cert()
 
432
        self.enable_modules()
 
433
 
 
434
        ctxt = {
 
435
            'namespace': self.service_namespace,
 
436
            'private_address': unit_get('private-address'),
 
437
            'endpoints': []
 
438
        }
 
439
        if is_clustered():
 
440
            ctxt['private_address'] = config('vip')
 
441
        for api_port in self.external_ports:
 
442
            ext_port = determine_apache_port(api_port)
 
443
            int_port = determine_api_port(api_port)
 
444
            portmap = (int(ext_port), int(int_port))
 
445
            ctxt['endpoints'].append(portmap)
 
446
        return ctxt
 
447
 
 
448
 
 
449
class NeutronContext(OSContextGenerator):
 
450
    interfaces = []
 
451
 
 
452
    @property
 
453
    def plugin(self):
 
454
        return None
 
455
 
 
456
    @property
 
457
    def network_manager(self):
 
458
        return None
 
459
 
 
460
    @property
 
461
    def packages(self):
 
462
        return neutron_plugin_attribute(
 
463
            self.plugin, 'packages', self.network_manager)
 
464
 
 
465
    @property
 
466
    def neutron_security_groups(self):
 
467
        return None
 
468
 
 
469
    def _ensure_packages(self):
 
470
        [ensure_packages(pkgs) for pkgs in self.packages]
 
471
 
 
472
    def _save_flag_file(self):
 
473
        if self.network_manager == 'quantum':
 
474
            _file = '/etc/nova/quantum_plugin.conf'
 
475
        else:
 
476
            _file = '/etc/nova/neutron_plugin.conf'
 
477
        with open(_file, 'wb') as out:
 
478
            out.write(self.plugin + '\n')
 
479
 
 
480
    def ovs_ctxt(self):
 
481
        driver = neutron_plugin_attribute(self.plugin, 'driver',
 
482
                                          self.network_manager)
 
483
        config = neutron_plugin_attribute(self.plugin, 'config',
 
484
                                          self.network_manager)
 
485
        ovs_ctxt = {
 
486
            'core_plugin': driver,
 
487
            'neutron_plugin': 'ovs',
 
488
            'neutron_security_groups': self.neutron_security_groups,
 
489
            'local_ip': unit_private_ip(),
 
490
            'config': config
 
491
        }
 
492
 
 
493
        return ovs_ctxt
 
494
 
 
495
    def nvp_ctxt(self):
 
496
        driver = neutron_plugin_attribute(self.plugin, 'driver',
 
497
                                          self.network_manager)
 
498
        config = neutron_plugin_attribute(self.plugin, 'config',
 
499
                                          self.network_manager)
 
500
        nvp_ctxt = {
 
501
            'core_plugin': driver,
 
502
            'neutron_plugin': 'nvp',
 
503
            'neutron_security_groups': self.neutron_security_groups,
 
504
            'local_ip': unit_private_ip(),
 
505
            'config': config
 
506
        }
 
507
 
 
508
        return nvp_ctxt
 
509
 
 
510
    def neutron_ctxt(self):
 
511
        if https():
 
512
            proto = 'https'
 
513
        else:
 
514
            proto = 'http'
 
515
        if is_clustered():
 
516
            host = config('vip')
 
517
        else:
 
518
            host = unit_get('private-address')
 
519
        url = '%s://%s:%s' % (proto, host, '9696')
 
520
        ctxt = {
 
521
            'network_manager': self.network_manager,
 
522
            'neutron_url': url,
 
523
        }
 
524
        return ctxt
 
525
 
 
526
    def __call__(self):
 
527
        self._ensure_packages()
 
528
 
 
529
        if self.network_manager not in ['quantum', 'neutron']:
 
530
            return {}
 
531
 
 
532
        if not self.plugin:
 
533
            return {}
 
534
 
 
535
        ctxt = self.neutron_ctxt()
 
536
 
 
537
        if self.plugin == 'ovs':
 
538
            ctxt.update(self.ovs_ctxt())
 
539
        elif self.plugin == 'nvp':
 
540
            ctxt.update(self.nvp_ctxt())
 
541
 
 
542
        alchemy_flags = config('neutron-alchemy-flags')
 
543
        if alchemy_flags:
 
544
            flags = config_flags_parser(alchemy_flags)
 
545
            ctxt['neutron_alchemy_flags'] = flags
 
546
 
 
547
        self._save_flag_file()
 
548
        return ctxt
 
549
 
 
550
 
 
551
class OSConfigFlagContext(OSContextGenerator):
 
552
 
 
553
        """
 
554
        Responsible for adding user-defined config-flags in charm config to a
 
555
        template context.
 
556
 
 
557
        NOTE: the value of config-flags may be a comma-separated list of
 
558
              key=value pairs and some Openstack config files support
 
559
              comma-separated lists as values.
 
560
        """
 
561
 
 
562
        def __call__(self):
 
563
            config_flags = config('config-flags')
 
564
            if not config_flags:
 
565
                return {}
 
566
 
 
567
            flags = config_flags_parser(config_flags)
 
568
            return {'user_config_flags': flags}
 
569
 
 
570
 
 
571
class SubordinateConfigContext(OSContextGenerator):
 
572
 
 
573
    """
 
574
    Responsible for inspecting relations to subordinates that
 
575
    may be exporting required config via a json blob.
 
576
 
 
577
    The subordinate interface allows subordinates to export their
 
578
    configuration requirements to the principle for multiple config
 
579
    files and multiple serivces.  Ie, a subordinate that has interfaces
 
580
    to both glance and nova may export to following yaml blob as json:
 
581
 
 
582
        glance:
 
583
            /etc/glance/glance-api.conf:
 
584
                sections:
 
585
                    DEFAULT:
 
586
                        - [key1, value1]
 
587
            /etc/glance/glance-registry.conf:
 
588
                    MYSECTION:
 
589
                        - [key2, value2]
 
590
        nova:
 
591
            /etc/nova/nova.conf:
 
592
                sections:
 
593
                    DEFAULT:
 
594
                        - [key3, value3]
 
595
 
 
596
 
 
597
    It is then up to the principle charms to subscribe this context to
 
598
    the service+config file it is interestd in.  Configuration data will
 
599
    be available in the template context, in glance's case, as:
 
600
        ctxt = {
 
601
            ... other context ...
 
602
            'subordinate_config': {
 
603
                'DEFAULT': {
 
604
                    'key1': 'value1',
 
605
                },
 
606
                'MYSECTION': {
 
607
                    'key2': 'value2',
 
608
                },
 
609
            }
 
610
        }
 
611
 
 
612
    """
 
613
 
 
614
    def __init__(self, service, config_file, interface):
 
615
        """
 
616
        :param service     : Service name key to query in any subordinate
 
617
                             data found
 
618
        :param config_file : Service's config file to query sections
 
619
        :param interface   : Subordinate interface to inspect
 
620
        """
 
621
        self.service = service
 
622
        self.config_file = config_file
 
623
        self.interface = interface
 
624
 
 
625
    def __call__(self):
 
626
        ctxt = {}
 
627
        for rid in relation_ids(self.interface):
 
628
            for unit in related_units(rid):
 
629
                sub_config = relation_get('subordinate_configuration',
 
630
                                          rid=rid, unit=unit)
 
631
                if sub_config and sub_config != '':
 
632
                    try:
 
633
                        sub_config = json.loads(sub_config)
 
634
                    except:
 
635
                        log('Could not parse JSON from subordinate_config '
 
636
                            'setting from %s' % rid, level=ERROR)
 
637
                        continue
 
638
 
 
639
                    if self.service not in sub_config:
 
640
                        log('Found subordinate_config on %s but it contained'
 
641
                            'nothing for %s service' % (rid, self.service))
 
642
                        continue
 
643
 
 
644
                    sub_config = sub_config[self.service]
 
645
                    if self.config_file not in sub_config:
 
646
                        log('Found subordinate_config on %s but it contained'
 
647
                            'nothing for %s' % (rid, self.config_file))
 
648
                        continue
 
649
 
 
650
                    sub_config = sub_config[self.config_file]
 
651
                    for k, v in sub_config.iteritems():
 
652
                        ctxt[k] = v
 
653
 
 
654
        if not ctxt:
 
655
            ctxt['sections'] = {}
 
656
 
 
657
        return ctxt
 
658
 
 
659
 
 
660
class SyslogContext(OSContextGenerator):
 
661
 
 
662
    def __call__(self):
 
663
        ctxt = {
 
664
            'use_syslog': config('use-syslog')
 
665
        }
 
666
        return ctxt