1
# Copyright 2014-2015 Canonical Limited.
3
# This file is part of charm-helpers.
5
# charm-helpers is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU Lesser General Public License version 3 as
7
# published by the Free Software Foundation.
9
# charm-helpers is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU Lesser General Public License for more details.
14
# You should have received a copy of the GNU Lesser General Public License
15
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
20
from base64 import b64decode
21
from subprocess import check_call
25
from charmhelpers.fetch import (
27
filter_installed_packages,
29
from charmhelpers.core.hookenv import (
47
from charmhelpers.core.sysctl import create as sysctl_create
49
from charmhelpers.core.host import (
53
from charmhelpers.contrib.hahelpers.cluster import (
54
determine_apache_port,
59
from charmhelpers.contrib.hahelpers.apache import (
64
from charmhelpers.contrib.openstack.neutron import (
65
neutron_plugin_attribute,
67
from charmhelpers.contrib.network.ip import (
68
get_address_in_network,
70
get_netmask_for_address,
72
is_address_in_network,
74
from charmhelpers.contrib.openstack.utils import get_host_ip
76
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
77
ADDRESS_TYPES = ['admin', 'internal', 'public']
80
class OSContextError(Exception):
84
def ensure_packages(packages):
85
"""Install but do not upgrade required plugin packages."""
86
required = filter_installed_packages(packages)
88
apt_install(required, fatal=True)
91
def context_complete(ctxt):
93
for k, v in six.iteritems(ctxt):
94
if v is None or v == '':
98
log('Missing required data: %s' % ' '.join(_missing), level=INFO)
104
def config_flags_parser(config_flags):
105
"""Parses config flags string into dict.
107
The provided config_flags string may be a list of comma-separated values
108
which themselves may be comma-separated list of values.
110
if config_flags.find('==') >= 0:
111
log("config_flags is not in expected format (key=value)", level=ERROR)
114
# strip the following from each value.
115
post_strippers = ' ,'
116
# we strip any leading/trailing '=' or ' ' from the string then
118
split = config_flags.strip(' =').split('=')
121
for i in range(0, limit - 1):
124
vindex = next.rfind(',')
125
if (i == limit - 2) or (vindex < 0):
128
value = next[:vindex]
133
# if this not the first entry, expect an embedded key.
134
index = current.rfind(',')
136
log("Invalid config value(s) at index %s" % (i), level=ERROR)
138
key = current[index + 1:]
141
flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
146
class OSContextGenerator(object):
147
"""Base class for all context generators."""
151
raise NotImplementedError
154
class SharedDBContext(OSContextGenerator):
155
interfaces = ['shared-db']
158
database=None, user=None, relation_prefix=None, ssl_dir=None):
159
"""Allows inspecting relation for settings prefixed with
160
relation_prefix. This is useful for parsing access for multiple
161
databases returned via the shared-db interface (eg, nova_password,
164
self.relation_prefix = relation_prefix
165
self.database = database
167
self.ssl_dir = ssl_dir
170
self.database = self.database or config('database')
171
self.user = self.user or config('database-user')
172
if None in [self.database, self.user]:
173
log("Could not generate shared_db context. Missing required charm "
174
"config options. (database name and user)", level=ERROR)
179
# NOTE(jamespage) if mysql charm provides a network upon which
180
# access to the database should be made, reconfigure relation
181
# with the service units local address and defer execution
182
access_network = relation_get('access-network')
183
if access_network is not None:
184
if self.relation_prefix is not None:
185
hostname_key = "{}_hostname".format(self.relation_prefix)
187
hostname_key = "hostname"
188
access_hostname = get_address_in_network(access_network,
189
unit_get('private-address'))
190
set_hostname = relation_get(attribute=hostname_key,
192
if set_hostname != access_hostname:
193
relation_set(relation_settings={hostname_key: access_hostname})
194
return ctxt # Defer any further hook execution for now....
196
password_setting = 'password'
197
if self.relation_prefix:
198
password_setting = self.relation_prefix + '_password'
200
for rid in relation_ids('shared-db'):
201
for unit in related_units(rid):
202
rdata = relation_get(rid=rid, unit=unit)
203
host = rdata.get('db_host')
204
host = format_ipv6_addr(host) or host
206
'database_host': host,
207
'database': self.database,
208
'database_user': self.user,
209
'database_password': rdata.get(password_setting),
210
'database_type': 'mysql'
212
if context_complete(ctxt):
213
db_ssl(rdata, ctxt, self.ssl_dir)
218
class PostgresqlDBContext(OSContextGenerator):
219
interfaces = ['pgsql-db']
221
def __init__(self, database=None):
222
self.database = database
225
self.database = self.database or config('database')
226
if self.database is None:
227
log('Could not generate postgresql_db context. Missing required '
228
'charm config options. (database name)', level=ERROR)
232
for rid in relation_ids(self.interfaces[0]):
233
for unit in related_units(rid):
234
rel_host = relation_get('host', rid=rid, unit=unit)
235
rel_user = relation_get('user', rid=rid, unit=unit)
236
rel_passwd = relation_get('password', rid=rid, unit=unit)
237
ctxt = {'database_host': rel_host,
238
'database': self.database,
239
'database_user': rel_user,
240
'database_password': rel_passwd,
241
'database_type': 'postgresql'}
242
if context_complete(ctxt):
248
def db_ssl(rdata, ctxt, ssl_dir):
249
if 'ssl_ca' in rdata and ssl_dir:
250
ca_path = os.path.join(ssl_dir, 'db-client.ca')
251
with open(ca_path, 'w') as fh:
252
fh.write(b64decode(rdata['ssl_ca']))
254
ctxt['database_ssl_ca'] = ca_path
255
elif 'ssl_ca' in rdata:
256
log("Charm not setup for ssl support but ssl ca found", level=INFO)
259
if 'ssl_cert' in rdata:
260
cert_path = os.path.join(
261
ssl_dir, 'db-client.cert')
262
if not os.path.exists(cert_path):
263
log("Waiting 1m for ssl client cert validity", level=INFO)
266
with open(cert_path, 'w') as fh:
267
fh.write(b64decode(rdata['ssl_cert']))
269
ctxt['database_ssl_cert'] = cert_path
270
key_path = os.path.join(ssl_dir, 'db-client.key')
271
with open(key_path, 'w') as fh:
272
fh.write(b64decode(rdata['ssl_key']))
274
ctxt['database_ssl_key'] = key_path
279
class IdentityServiceContext(OSContextGenerator):
280
interfaces = ['identity-service']
283
log('Generating template context for identity-service', level=DEBUG)
285
for rid in relation_ids('identity-service'):
286
for unit in related_units(rid):
287
rdata = relation_get(rid=rid, unit=unit)
288
serv_host = rdata.get('service_host')
289
serv_host = format_ipv6_addr(serv_host) or serv_host
290
auth_host = rdata.get('auth_host')
291
auth_host = format_ipv6_addr(auth_host) or auth_host
292
svc_protocol = rdata.get('service_protocol') or 'http'
293
auth_protocol = rdata.get('auth_protocol') or 'http'
294
ctxt = {'service_port': rdata.get('service_port'),
295
'service_host': serv_host,
296
'auth_host': auth_host,
297
'auth_port': rdata.get('auth_port'),
298
'admin_tenant_name': rdata.get('service_tenant'),
299
'admin_user': rdata.get('service_username'),
300
'admin_password': rdata.get('service_password'),
301
'service_protocol': svc_protocol,
302
'auth_protocol': auth_protocol}
303
if context_complete(ctxt):
304
# NOTE(jamespage) this is required for >= icehouse
305
# so a missing value just indicates keystone needs
307
ctxt['admin_tenant_id'] = rdata.get('service_tenant_id')
313
class AMQPContext(OSContextGenerator):
315
def __init__(self, ssl_dir=None, rel_name='amqp', relation_prefix=None):
316
self.ssl_dir = ssl_dir
317
self.rel_name = rel_name
318
self.relation_prefix = relation_prefix
319
self.interfaces = [rel_name]
322
log('Generating template context for amqp', level=DEBUG)
324
if self.relation_prefix:
325
user_setting = '%s-rabbit-user' % (self.relation_prefix)
326
vhost_setting = '%s-rabbit-vhost' % (self.relation_prefix)
328
user_setting = 'rabbit-user'
329
vhost_setting = 'rabbit-vhost'
332
username = conf[user_setting]
333
vhost = conf[vhost_setting]
334
except KeyError as e:
335
log('Could not generate shared_db context. Missing required charm '
336
'config options: %s.' % e, level=ERROR)
340
for rid in relation_ids(self.rel_name):
342
for unit in related_units(rid):
343
if relation_get('clustered', rid=rid, unit=unit):
344
ctxt['clustered'] = True
345
vip = relation_get('vip', rid=rid, unit=unit)
346
vip = format_ipv6_addr(vip) or vip
347
ctxt['rabbitmq_host'] = vip
349
host = relation_get('private-address', rid=rid, unit=unit)
350
host = format_ipv6_addr(host) or host
351
ctxt['rabbitmq_host'] = host
354
'rabbitmq_user': username,
355
'rabbitmq_password': relation_get('password', rid=rid,
357
'rabbitmq_virtual_host': vhost,
360
ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
362
ctxt['rabbit_ssl_port'] = ssl_port
364
ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
366
ctxt['rabbit_ssl_ca'] = ssl_ca
368
if relation_get('ha_queues', rid=rid, unit=unit) is not None:
369
ctxt['rabbitmq_ha_queues'] = True
371
ha_vip_only = relation_get('ha-vip-only',
372
rid=rid, unit=unit) is not None
374
if context_complete(ctxt):
375
if 'rabbit_ssl_ca' in ctxt:
377
log("Charm not setup for ssl support but ssl ca "
381
ca_path = os.path.join(
382
self.ssl_dir, 'rabbit-client-ca.pem')
383
with open(ca_path, 'w') as fh:
384
fh.write(b64decode(ctxt['rabbit_ssl_ca']))
385
ctxt['rabbit_ssl_ca'] = ca_path
387
# Sufficient information found = break out!
390
# Used for active/active rabbitmq >= grizzly
391
if (('clustered' not in ctxt or ha_vip_only) and
392
len(related_units(rid)) > 1):
394
for unit in related_units(rid):
395
host = relation_get('private-address', rid=rid, unit=unit)
396
host = format_ipv6_addr(host) or host
397
rabbitmq_hosts.append(host)
399
ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))
401
if not context_complete(ctxt):
407
class CephContext(OSContextGenerator):
408
"""Generates context for /etc/ceph/ceph.conf templates."""
409
interfaces = ['ceph']
412
if not relation_ids('ceph'):
415
log('Generating template context for ceph', level=DEBUG)
419
use_syslog = str(config('use-syslog')).lower()
420
for rid in relation_ids('ceph'):
421
for unit in related_units(rid):
422
auth = relation_get('auth', rid=rid, unit=unit)
423
key = relation_get('key', rid=rid, unit=unit)
424
ceph_pub_addr = relation_get('ceph-public-address', rid=rid,
426
unit_priv_addr = relation_get('private-address', rid=rid,
428
ceph_addr = ceph_pub_addr or unit_priv_addr
429
ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
430
mon_hosts.append(ceph_addr)
432
ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)),
435
'use_syslog': use_syslog}
437
if not os.path.isdir('/etc/ceph'):
438
os.mkdir('/etc/ceph')
440
if not context_complete(ctxt):
443
ensure_packages(['ceph-common'])
447
class HAProxyContext(OSContextGenerator):
448
"""Provides half a context for the haproxy template, which describes
449
all peers to be included in the cluster. Each charm needs to include
450
its own context generator that describes the port mapping.
452
interfaces = ['cluster']
454
def __init__(self, singlenode_mode=False):
455
self.singlenode_mode = singlenode_mode
458
if not relation_ids('cluster') and not self.singlenode_mode:
461
if config('prefer-ipv6'):
462
addr = get_ipv6_addr(exc_list=[config('vip')])[0]
464
addr = get_host_ip(unit_get('private-address'))
466
l_unit = local_unit().replace('/', '-')
469
# NOTE(jamespage): build out map of configured network endpoints
470
# and associated backends
471
for addr_type in ADDRESS_TYPES:
472
cfg_opt = 'os-{}-network'.format(addr_type)
473
laddr = get_address_in_network(config(cfg_opt))
475
netmask = get_netmask_for_address(laddr)
476
cluster_hosts[laddr] = {'network': "{}/{}".format(laddr,
478
'backends': {l_unit: laddr}}
479
for rid in relation_ids('cluster'):
480
for unit in related_units(rid):
481
_laddr = relation_get('{}-address'.format(addr_type),
484
_unit = unit.replace('/', '-')
485
cluster_hosts[laddr]['backends'][_unit] = _laddr
487
# NOTE(jamespage) add backend based on private address - this
488
# with either be the only backend or the fallback if no acls
489
# match in the frontend
490
cluster_hosts[addr] = {}
491
netmask = get_netmask_for_address(addr)
492
cluster_hosts[addr] = {'network': "{}/{}".format(addr, netmask),
493
'backends': {l_unit: addr}}
494
for rid in relation_ids('cluster'):
495
for unit in related_units(rid):
496
_laddr = relation_get('private-address',
499
_unit = unit.replace('/', '-')
500
cluster_hosts[addr]['backends'][_unit] = _laddr
503
'frontends': cluster_hosts,
504
'default_backend': addr
507
if config('haproxy-server-timeout'):
508
ctxt['haproxy_server_timeout'] = config('haproxy-server-timeout')
510
if config('haproxy-client-timeout'):
511
ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')
513
if config('prefer-ipv6'):
515
ctxt['local_host'] = 'ip6-localhost'
516
ctxt['haproxy_host'] = '::'
517
ctxt['stat_port'] = ':::8888'
519
ctxt['local_host'] = '127.0.0.1'
520
ctxt['haproxy_host'] = '0.0.0.0'
521
ctxt['stat_port'] = ':8888'
523
for frontend in cluster_hosts:
524
if (len(cluster_hosts[frontend]['backends']) > 1 or
525
self.singlenode_mode):
526
# Enable haproxy when we have enough peers.
527
log('Ensuring haproxy enabled in /etc/default/haproxy.',
529
with open('/etc/default/haproxy', 'w') as out:
530
out.write('ENABLED=1\n')
534
log('HAProxy context is incomplete, this unit has no peers.',
539
class ImageServiceContext(OSContextGenerator):
540
interfaces = ['image-service']
543
"""Obtains the glance API server from the image-service relation.
544
Useful in nova and cinder (currently).
546
log('Generating template context for image-service.', level=DEBUG)
547
rids = relation_ids('image-service')
552
for unit in related_units(rid):
553
api_server = relation_get('glance-api-server',
556
return {'glance_api_servers': api_server}
558
log("ImageService context is incomplete. Missing required relation "
563
class ApacheSSLContext(OSContextGenerator):
564
"""Generates a context for an apache vhost configuration that configures
565
HTTPS reverse proxying for one or many endpoints. Generated context
566
looks something like::
569
'namespace': 'cinder',
570
'private_address': 'iscsi.mycinderhost.com',
571
'endpoints': [(8776, 8766), (8777, 8767)]
574
The endpoints list consists of a tuples mapping external ports
577
interfaces = ['https']
579
# charms should inherit this context and set external ports
580
# and service namespace accordingly.
582
service_namespace = None
584
def enable_modules(self):
585
cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']
588
def configure_cert(self, cn=None):
589
ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
591
cert, key = get_cert(cn)
593
cert_filename = 'cert_{}'.format(cn)
594
key_filename = 'key_{}'.format(cn)
596
cert_filename = 'cert'
599
write_file(path=os.path.join(ssl_dir, cert_filename),
600
content=b64decode(cert))
601
write_file(path=os.path.join(ssl_dir, key_filename),
602
content=b64decode(key))
604
def configure_ca(self):
605
ca_cert = get_ca_cert()
607
install_ca_cert(b64decode(ca_cert))
609
def canonical_names(self):
610
"""Figure out which canonical names clients will access this service.
613
for r_id in relation_ids('identity-service'):
614
for unit in related_units(r_id):
615
rdata = relation_get(rid=r_id, unit=unit)
617
if k.startswith('ssl_key_'):
618
cns.append(k.lstrip('ssl_key_'))
620
return sorted(list(set(cns)))
622
def get_network_addresses(self):
623
"""For each network configured, return corresponding address and vip
626
Returns a list of tuples of the form:
628
[(address_in_net_a, vip_in_net_a),
629
(address_in_net_b, vip_in_net_b),
632
or, if no vip(s) available:
634
[(address_in_net_a, address_in_net_a),
635
(address_in_net_b, address_in_net_b),
640
vips = config('vip').split()
644
for net_type in ['os-internal-network', 'os-admin-network',
645
'os-public-network']:
646
addr = get_address_in_network(config(net_type),
647
unit_get('private-address'))
648
if len(vips) > 1 and is_clustered():
649
if not config(net_type):
650
log("Multiple networks configured but net_type "
651
"is None (%s)." % net_type, level=WARNING)
655
if is_address_in_network(config(net_type), vip):
656
addresses.append((addr, vip))
659
elif is_clustered() and config('vip'):
660
addresses.append((addr, config('vip')))
662
addresses.append((addr, addr))
664
return sorted(addresses)
667
if isinstance(self.external_ports, six.string_types):
668
self.external_ports = [self.external_ports]
670
if not self.external_ports or not https():
674
self.enable_modules()
676
ctxt = {'namespace': self.service_namespace,
680
for cn in self.canonical_names():
681
self.configure_cert(cn)
683
addresses = self.get_network_addresses()
684
for address, endpoint in sorted(set(addresses)):
685
for api_port in self.external_ports:
686
ext_port = determine_apache_port(api_port,
687
singlenode_mode=True)
688
int_port = determine_api_port(api_port, singlenode_mode=True)
689
portmap = (address, endpoint, int(ext_port), int(int_port))
690
ctxt['endpoints'].append(portmap)
691
ctxt['ext_ports'].append(int(ext_port))
693
ctxt['ext_ports'] = sorted(list(set(ctxt['ext_ports'])))
697
class NeutronContext(OSContextGenerator):
705
def network_manager(self):
710
return neutron_plugin_attribute(self.plugin, 'packages',
711
self.network_manager)
714
def neutron_security_groups(self):
717
def _ensure_packages(self):
718
for pkgs in self.packages:
719
ensure_packages(pkgs)
721
def _save_flag_file(self):
722
if self.network_manager == 'quantum':
723
_file = '/etc/nova/quantum_plugin.conf'
725
_file = '/etc/nova/neutron_plugin.conf'
727
with open(_file, 'wb') as out:
728
out.write(self.plugin + '\n')
731
driver = neutron_plugin_attribute(self.plugin, 'driver',
732
self.network_manager)
733
config = neutron_plugin_attribute(self.plugin, 'config',
734
self.network_manager)
735
ovs_ctxt = {'core_plugin': driver,
736
'neutron_plugin': 'ovs',
737
'neutron_security_groups': self.neutron_security_groups,
738
'local_ip': unit_private_ip(),
744
driver = neutron_plugin_attribute(self.plugin, 'driver',
745
self.network_manager)
746
config = neutron_plugin_attribute(self.plugin, 'config',
747
self.network_manager)
748
nvp_ctxt = {'core_plugin': driver,
749
'neutron_plugin': 'nvp',
750
'neutron_security_groups': self.neutron_security_groups,
751
'local_ip': unit_private_ip(),
757
driver = neutron_plugin_attribute(self.plugin, 'driver',
758
self.network_manager)
759
n1kv_config = neutron_plugin_attribute(self.plugin, 'config',
760
self.network_manager)
761
n1kv_user_config_flags = config('n1kv-config-flags')
762
restrict_policy_profiles = config('n1kv-restrict-policy-profiles')
763
n1kv_ctxt = {'core_plugin': driver,
764
'neutron_plugin': 'n1kv',
765
'neutron_security_groups': self.neutron_security_groups,
766
'local_ip': unit_private_ip(),
767
'config': n1kv_config,
768
'vsm_ip': config('n1kv-vsm-ip'),
769
'vsm_username': config('n1kv-vsm-username'),
770
'vsm_password': config('n1kv-vsm-password'),
771
'restrict_policy_profiles': restrict_policy_profiles}
773
if n1kv_user_config_flags:
774
flags = config_flags_parser(n1kv_user_config_flags)
775
n1kv_ctxt['user_config_flags'] = flags
779
def calico_ctxt(self):
780
driver = neutron_plugin_attribute(self.plugin, 'driver',
781
self.network_manager)
782
config = neutron_plugin_attribute(self.plugin, 'config',
783
self.network_manager)
784
calico_ctxt = {'core_plugin': driver,
785
'neutron_plugin': 'Calico',
786
'neutron_security_groups': self.neutron_security_groups,
787
'local_ip': unit_private_ip(),
792
def neutron_ctxt(self):
801
host = unit_get('private-address')
803
ctxt = {'network_manager': self.network_manager,
804
'neutron_url': '%s://%s:%s' % (proto, host, '9696')}
808
self._ensure_packages()
810
if self.network_manager not in ['quantum', 'neutron']:
816
ctxt = self.neutron_ctxt()
818
if self.plugin == 'ovs':
819
ctxt.update(self.ovs_ctxt())
820
elif self.plugin in ['nvp', 'nsx']:
821
ctxt.update(self.nvp_ctxt())
822
elif self.plugin == 'n1kv':
823
ctxt.update(self.n1kv_ctxt())
824
elif self.plugin == 'Calico':
825
ctxt.update(self.calico_ctxt())
827
alchemy_flags = config('neutron-alchemy-flags')
829
flags = config_flags_parser(alchemy_flags)
830
ctxt['neutron_alchemy_flags'] = flags
832
self._save_flag_file()
836
class OSConfigFlagContext(OSContextGenerator):
837
"""Provides support for user-defined config flags.
839
Users can define a comma-seperated list of key=value pairs
840
in the charm configuration and apply them at any point in
841
any file by using a template flag.
843
Sometimes users might want config flags inserted within a
844
specific section so this class allows users to specify the
845
template flag name, allowing for multiple template flags
846
(sections) within the same context.
848
NOTE: the value of config-flags may be a comma-separated list of
849
key=value pairs and some Openstack config files support
850
comma-separated lists as values.
853
def __init__(self, charm_flag='config-flags',
854
template_flag='user_config_flags'):
856
:param charm_flag: config flags in charm configuration.
857
:param template_flag: insert point for user-defined flags in template
860
super(OSConfigFlagContext, self).__init__()
861
self._charm_flag = charm_flag
862
self._template_flag = template_flag
865
config_flags = config(self._charm_flag)
869
return {self._template_flag:
870
config_flags_parser(config_flags)}
873
class SubordinateConfigContext(OSContextGenerator):
876
Responsible for inspecting relations to subordinates that
877
may be exporting required config via a json blob.
879
The subordinate interface allows subordinates to export their
880
configuration requirements to the principle for multiple config
881
files and multiple serivces. Ie, a subordinate that has interfaces
882
to both glance and nova may export to following yaml blob as json::
885
/etc/glance/glance-api.conf:
889
/etc/glance/glance-registry.conf:
899
It is then up to the principle charms to subscribe this context to
900
the service+config file it is interestd in. Configuration data will
901
be available in the template context, in glance's case, as::
904
... other context ...
905
'subordinate_config': {
916
def __init__(self, service, config_file, interface):
918
:param service : Service name key to query in any subordinate
920
:param config_file : Service's config file to query sections
921
:param interface : Subordinate interface to inspect
923
self.service = service
924
self.config_file = config_file
925
self.interface = interface
928
ctxt = {'sections': {}}
929
for rid in relation_ids(self.interface):
930
for unit in related_units(rid):
931
sub_config = relation_get('subordinate_configuration',
933
if sub_config and sub_config != '':
935
sub_config = json.loads(sub_config)
937
log('Could not parse JSON from subordinate_config '
938
'setting from %s' % rid, level=ERROR)
941
if self.service not in sub_config:
942
log('Found subordinate_config on %s but it contained'
943
'nothing for %s service' % (rid, self.service),
947
sub_config = sub_config[self.service]
948
if self.config_file not in sub_config:
949
log('Found subordinate_config on %s but it contained'
950
'nothing for %s' % (rid, self.config_file),
954
sub_config = sub_config[self.config_file]
955
for k, v in six.iteritems(sub_config):
957
for section, config_dict in six.iteritems(v):
958
log("adding section '%s'" % (section),
960
ctxt[k][section] = config_dict
964
log("%d section(s) found" % (len(ctxt['sections'])), level=DEBUG)
968
class LogLevelContext(OSContextGenerator):
973
False if config('debug') is None else config('debug')
975
False if config('verbose') is None else config('verbose')
980
class SyslogContext(OSContextGenerator):
983
ctxt = {'use_syslog': config('use-syslog')}
987
class BindHostContext(OSContextGenerator):
990
if config('prefer-ipv6'):
991
return {'bind_host': '::'}
993
return {'bind_host': '0.0.0.0'}
996
class WorkerConfigContext(OSContextGenerator):
1001
from psutil import NUM_CPUS
1003
apt_install('python-psutil', fatal=True)
1004
from psutil import NUM_CPUS
1009
multiplier = config('worker-multiplier') or 0
1010
ctxt = {"workers": self.num_cpus * multiplier}
1014
class ZeroMQContext(OSContextGenerator):
1015
interfaces = ['zeromq-configuration']
1019
if is_relation_made('zeromq-configuration', 'host'):
1020
for rid in relation_ids('zeromq-configuration'):
1021
for unit in related_units(rid):
1022
ctxt['zmq_nonce'] = relation_get('nonce', unit, rid)
1023
ctxt['zmq_host'] = relation_get('host', unit, rid)
1028
class NotificationDriverContext(OSContextGenerator):
1030
def __init__(self, zmq_relation='zeromq-configuration',
1031
amqp_relation='amqp'):
1033
:param zmq_relation: Name of Zeromq relation to check
1035
self.zmq_relation = zmq_relation
1036
self.amqp_relation = amqp_relation
1039
ctxt = {'notifications': 'False'}
1040
if is_relation_made(self.amqp_relation):
1041
ctxt['notifications'] = "True"
1046
class SysctlContext(OSContextGenerator):
1047
"""This context check if the 'sysctl' option exists on configuration
1048
then creates a file with the loaded contents"""
1050
sysctl_dict = config('sysctl')
1052
sysctl_create(sysctl_dict,
1053
'/etc/sysctl.d/50-{0}.conf'.format(charm_name()))
1054
return {'sysctl': sysctl_dict}