Sync charm-helpers
Notable issues resolved: openstack_upgrade_available() broken for swift https://bugs.launchpad.net/charm-swift-proxy/+bug/1743847 haproxy context doesn't consider bindings https://bugs.launchpad.net/charm-helpers/+bug/1735421 regression in haproxy check https://bugs.launchpad.net/charm-helpers/+bug/1743287 This change also breaks the inheritance between HorizonHAProxyContext and HAProxyContext (as this was not needed) and aligns the code in HorizonHAProxyContext to the behaviour in HAProxyContext of using get_relation_ip instead of unit_get. Change-Id: I72600b01744a07e140a29255efc2823ea39ce730
This commit is contained in:
parent
b4019e45bf
commit
f36b718162
|
@ -27,6 +27,7 @@ from charmhelpers.core.hookenv import (
|
||||||
network_get_primary_address,
|
network_get_primary_address,
|
||||||
unit_get,
|
unit_get,
|
||||||
WARNING,
|
WARNING,
|
||||||
|
NoNetworkBinding,
|
||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.core.host import (
|
from charmhelpers.core.host import (
|
||||||
|
@ -578,6 +579,9 @@ def get_relation_ip(interface, cidr_network=None):
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
# If network-get is not available
|
# If network-get is not available
|
||||||
address = get_host_ip(unit_get('private-address'))
|
address = get_host_ip(unit_get('private-address'))
|
||||||
|
except NoNetworkBinding:
|
||||||
|
log("No network binding for {}".format(interface), WARNING)
|
||||||
|
address = get_host_ip(unit_get('private-address'))
|
||||||
|
|
||||||
if config('prefer-ipv6'):
|
if config('prefer-ipv6'):
|
||||||
# Currently IPv6 has priority, eventually we want IPv6 to just be
|
# Currently IPv6 has priority, eventually we want IPv6 to just be
|
||||||
|
|
|
@ -93,10 +93,10 @@ from charmhelpers.contrib.network.ip import (
|
||||||
format_ipv6_addr,
|
format_ipv6_addr,
|
||||||
is_bridge_member,
|
is_bridge_member,
|
||||||
is_ipv6_disabled,
|
is_ipv6_disabled,
|
||||||
|
get_relation_ip,
|
||||||
)
|
)
|
||||||
from charmhelpers.contrib.openstack.utils import (
|
from charmhelpers.contrib.openstack.utils import (
|
||||||
config_flags_parser,
|
config_flags_parser,
|
||||||
get_host_ip,
|
|
||||||
enable_memcache,
|
enable_memcache,
|
||||||
snap_install_requested,
|
snap_install_requested,
|
||||||
CompareOpenStackReleases,
|
CompareOpenStackReleases,
|
||||||
|
@ -617,7 +617,9 @@ class HAProxyContext(OSContextGenerator):
|
||||||
"""
|
"""
|
||||||
interfaces = ['cluster']
|
interfaces = ['cluster']
|
||||||
|
|
||||||
def __init__(self, singlenode_mode=False):
|
def __init__(self, singlenode_mode=False,
|
||||||
|
address_types=ADDRESS_TYPES):
|
||||||
|
self.address_types = address_types
|
||||||
self.singlenode_mode = singlenode_mode
|
self.singlenode_mode = singlenode_mode
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
|
@ -626,19 +628,22 @@ class HAProxyContext(OSContextGenerator):
|
||||||
if not relation_ids('cluster') and not self.singlenode_mode:
|
if not relation_ids('cluster') and not self.singlenode_mode:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
if config('prefer-ipv6'):
|
|
||||||
addr = get_ipv6_addr(exc_list=[config('vip')])[0]
|
|
||||||
else:
|
|
||||||
addr = get_host_ip(unit_get('private-address'))
|
|
||||||
|
|
||||||
l_unit = local_unit().replace('/', '-')
|
l_unit = local_unit().replace('/', '-')
|
||||||
cluster_hosts = {}
|
cluster_hosts = {}
|
||||||
|
|
||||||
# NOTE(jamespage): build out map of configured network endpoints
|
# NOTE(jamespage): build out map of configured network endpoints
|
||||||
# and associated backends
|
# and associated backends
|
||||||
for addr_type in ADDRESS_TYPES:
|
for addr_type in self.address_types:
|
||||||
cfg_opt = 'os-{}-network'.format(addr_type)
|
cfg_opt = 'os-{}-network'.format(addr_type)
|
||||||
laddr = get_address_in_network(config(cfg_opt))
|
# NOTE(thedac) For some reason the ADDRESS_MAP uses 'int' rather
|
||||||
|
# than 'internal'
|
||||||
|
if addr_type == 'internal':
|
||||||
|
_addr_map_type = INTERNAL
|
||||||
|
else:
|
||||||
|
_addr_map_type = addr_type
|
||||||
|
# Network spaces aware
|
||||||
|
laddr = get_relation_ip(ADDRESS_MAP[_addr_map_type]['binding'],
|
||||||
|
config(cfg_opt))
|
||||||
if laddr:
|
if laddr:
|
||||||
netmask = get_netmask_for_address(laddr)
|
netmask = get_netmask_for_address(laddr)
|
||||||
cluster_hosts[laddr] = {
|
cluster_hosts[laddr] = {
|
||||||
|
@ -649,15 +654,19 @@ class HAProxyContext(OSContextGenerator):
|
||||||
}
|
}
|
||||||
for rid in relation_ids('cluster'):
|
for rid in relation_ids('cluster'):
|
||||||
for unit in sorted(related_units(rid)):
|
for unit in sorted(related_units(rid)):
|
||||||
|
# API Charms will need to set {addr_type}-address with
|
||||||
|
# get_relation_ip(addr_type)
|
||||||
_laddr = relation_get('{}-address'.format(addr_type),
|
_laddr = relation_get('{}-address'.format(addr_type),
|
||||||
rid=rid, unit=unit)
|
rid=rid, unit=unit)
|
||||||
if _laddr:
|
if _laddr:
|
||||||
_unit = unit.replace('/', '-')
|
_unit = unit.replace('/', '-')
|
||||||
cluster_hosts[laddr]['backends'][_unit] = _laddr
|
cluster_hosts[laddr]['backends'][_unit] = _laddr
|
||||||
|
|
||||||
# NOTE(jamespage) add backend based on private address - this
|
# NOTE(jamespage) add backend based on get_relation_ip - this
|
||||||
# with either be the only backend or the fallback if no acls
|
# will either be the only backend or the fallback if no acls
|
||||||
# match in the frontend
|
# match in the frontend
|
||||||
|
# Network spaces aware
|
||||||
|
addr = get_relation_ip('cluster')
|
||||||
cluster_hosts[addr] = {}
|
cluster_hosts[addr] = {}
|
||||||
netmask = get_netmask_for_address(addr)
|
netmask = get_netmask_for_address(addr)
|
||||||
cluster_hosts[addr] = {
|
cluster_hosts[addr] = {
|
||||||
|
@ -667,6 +676,8 @@ class HAProxyContext(OSContextGenerator):
|
||||||
}
|
}
|
||||||
for rid in relation_ids('cluster'):
|
for rid in relation_ids('cluster'):
|
||||||
for unit in sorted(related_units(rid)):
|
for unit in sorted(related_units(rid)):
|
||||||
|
# API Charms will need to set their private-address with
|
||||||
|
# get_relation_ip('cluster')
|
||||||
_laddr = relation_get('private-address',
|
_laddr = relation_get('private-address',
|
||||||
rid=rid, unit=unit)
|
rid=rid, unit=unit)
|
||||||
if _laddr:
|
if _laddr:
|
||||||
|
@ -1775,3 +1786,30 @@ class MemcacheContext(OSContextGenerator):
|
||||||
ctxt['memcache_server_formatted'],
|
ctxt['memcache_server_formatted'],
|
||||||
ctxt['memcache_port'])
|
ctxt['memcache_port'])
|
||||||
return ctxt
|
return ctxt
|
||||||
|
|
||||||
|
|
||||||
|
class EnsureDirContext(OSContextGenerator):
|
||||||
|
'''
|
||||||
|
Serves as a generic context to create a directory as a side-effect.
|
||||||
|
|
||||||
|
Useful for software that supports drop-in files (.d) in conjunction
|
||||||
|
with config option-based templates. Examples include:
|
||||||
|
* OpenStack oslo.policy drop-in files;
|
||||||
|
* systemd drop-in config files;
|
||||||
|
* other software that supports overriding defaults with .d files
|
||||||
|
|
||||||
|
Another use-case is when a subordinate generates a configuration for
|
||||||
|
primary to render in a separate directory.
|
||||||
|
|
||||||
|
Some software requires a user to create a target directory to be
|
||||||
|
scanned for drop-in files with a specific format. This is why this
|
||||||
|
context is needed to do that before rendering a template.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, dirname):
|
||||||
|
'''Used merely to ensure that a given directory exists.'''
|
||||||
|
self.dirname = dirname
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
mkdir(self.dirname)
|
||||||
|
return {}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
CRITICAL=0
|
CRITICAL=0
|
||||||
NOTACTIVE=''
|
NOTACTIVE=''
|
||||||
LOGFILE=/var/log/nagios/check_haproxy.log
|
LOGFILE=/var/log/nagios/check_haproxy.log
|
||||||
AUTH=$(grep -r "stats auth" /etc/haproxy/haproxy.cfg | awk 'NR=1{print $4}')
|
AUTH=$(grep -r "stats auth" /etc/haproxy/haproxy.cfg | awk 'NR=1{print $3}')
|
||||||
|
|
||||||
typeset -i N_INSTANCES=0
|
typeset -i N_INSTANCES=0
|
||||||
for appserver in $(awk '/^\s+server/{print $2}' /etc/haproxy/haproxy.cfg)
|
for appserver in $(awk '/^\s+server/{print $2}' /etc/haproxy/haproxy.cfg)
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
CURRQthrsh=0
|
CURRQthrsh=0
|
||||||
MAXQthrsh=100
|
MAXQthrsh=100
|
||||||
|
|
||||||
AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}')
|
AUTH=$(grep -r "stats auth" /etc/haproxy/haproxy.cfg | awk 'NR=1{print $3}')
|
||||||
|
|
||||||
HAPROXYSTATS=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v)
|
HAPROXYSTATS=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v)
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
Helpers for high availability.
|
Helpers for high availability.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
|
@ -32,6 +34,7 @@ from charmhelpers.core.hookenv import (
|
||||||
config,
|
config,
|
||||||
status_set,
|
status_set,
|
||||||
DEBUG,
|
DEBUG,
|
||||||
|
WARNING,
|
||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.core.host import (
|
from charmhelpers.core.host import (
|
||||||
|
@ -40,6 +43,23 @@ from charmhelpers.core.host import (
|
||||||
|
|
||||||
from charmhelpers.contrib.openstack.ip import (
|
from charmhelpers.contrib.openstack.ip import (
|
||||||
resolve_address,
|
resolve_address,
|
||||||
|
is_ipv6,
|
||||||
|
)
|
||||||
|
|
||||||
|
from charmhelpers.contrib.network.ip import (
|
||||||
|
get_iface_for_address,
|
||||||
|
get_netmask_for_address,
|
||||||
|
)
|
||||||
|
|
||||||
|
from charmhelpers.contrib.hahelpers.cluster import (
|
||||||
|
get_hacluster_config
|
||||||
|
)
|
||||||
|
|
||||||
|
JSON_ENCODE_OPTIONS = dict(
|
||||||
|
sort_keys=True,
|
||||||
|
allow_nan=False,
|
||||||
|
indent=None,
|
||||||
|
separators=(',', ':'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,8 +73,8 @@ class DNSHAException(Exception):
|
||||||
def update_dns_ha_resource_params(resources, resource_params,
|
def update_dns_ha_resource_params(resources, resource_params,
|
||||||
relation_id=None,
|
relation_id=None,
|
||||||
crm_ocf='ocf:maas:dns'):
|
crm_ocf='ocf:maas:dns'):
|
||||||
""" Check for os-*-hostname settings and update resource dictionaries for
|
""" Configure DNS-HA resources based on provided configuration and
|
||||||
the HA relation.
|
update resource dictionaries for the HA relation.
|
||||||
|
|
||||||
@param resources: Pointer to dictionary of resources.
|
@param resources: Pointer to dictionary of resources.
|
||||||
Usually instantiated in ha_joined().
|
Usually instantiated in ha_joined().
|
||||||
|
@ -64,7 +84,85 @@ def update_dns_ha_resource_params(resources, resource_params,
|
||||||
@param crm_ocf: Corosync Open Cluster Framework resource agent to use for
|
@param crm_ocf: Corosync Open Cluster Framework resource agent to use for
|
||||||
DNS HA
|
DNS HA
|
||||||
"""
|
"""
|
||||||
|
_relation_data = {'resources': {}, 'resource_params': {}}
|
||||||
|
update_hacluster_dns_ha(charm_name(),
|
||||||
|
_relation_data,
|
||||||
|
crm_ocf)
|
||||||
|
resources.update(_relation_data['resources'])
|
||||||
|
resource_params.update(_relation_data['resource_params'])
|
||||||
|
relation_set(relation_id=relation_id, groups=_relation_data['groups'])
|
||||||
|
|
||||||
|
|
||||||
|
def assert_charm_supports_dns_ha():
|
||||||
|
"""Validate prerequisites for DNS HA
|
||||||
|
The MAAS client is only available on Xenial or greater
|
||||||
|
|
||||||
|
:raises DNSHAException: if release is < 16.04
|
||||||
|
"""
|
||||||
|
if lsb_release().get('DISTRIB_RELEASE') < '16.04':
|
||||||
|
msg = ('DNS HA is only supported on 16.04 and greater '
|
||||||
|
'versions of Ubuntu.')
|
||||||
|
status_set('blocked', msg)
|
||||||
|
raise DNSHAException(msg)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def expect_ha():
|
||||||
|
""" Determine if the unit expects to be in HA
|
||||||
|
|
||||||
|
Check for VIP or dns-ha settings which indicate the unit should expect to
|
||||||
|
be related to hacluster.
|
||||||
|
|
||||||
|
@returns boolean
|
||||||
|
"""
|
||||||
|
return config('vip') or config('dns-ha')
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ha_relation_data(service):
|
||||||
|
""" Generate relation data for ha relation
|
||||||
|
|
||||||
|
Based on configuration options and unit interfaces, generate a json
|
||||||
|
encoded dict of relation data items for the hacluster relation,
|
||||||
|
providing configuration for DNS HA or VIP's + haproxy clone sets.
|
||||||
|
|
||||||
|
@returns dict: json encoded data for use with relation_set
|
||||||
|
"""
|
||||||
|
_haproxy_res = 'res_{}_haproxy'.format(service)
|
||||||
|
_relation_data = {
|
||||||
|
'resources': {
|
||||||
|
_haproxy_res: 'lsb:haproxy',
|
||||||
|
},
|
||||||
|
'resource_params': {
|
||||||
|
_haproxy_res: 'op monitor interval="5s"'
|
||||||
|
},
|
||||||
|
'init_services': {
|
||||||
|
_haproxy_res: 'haproxy'
|
||||||
|
},
|
||||||
|
'clones': {
|
||||||
|
'cl_{}_haproxy'.format(service): _haproxy_res
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if config('dns-ha'):
|
||||||
|
update_hacluster_dns_ha(service, _relation_data)
|
||||||
|
else:
|
||||||
|
update_hacluster_vip(service, _relation_data)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'json_{}'.format(k): json.dumps(v, **JSON_ENCODE_OPTIONS)
|
||||||
|
for k, v in _relation_data.items() if v
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def update_hacluster_dns_ha(service, relation_data,
|
||||||
|
crm_ocf='ocf:maas:dns'):
|
||||||
|
""" Configure DNS-HA resources based on provided configuration
|
||||||
|
|
||||||
|
@param service: Name of the service being configured
|
||||||
|
@param relation_data: Pointer to dictionary of relation data.
|
||||||
|
@param crm_ocf: Corosync Open Cluster Framework resource agent to use for
|
||||||
|
DNS HA
|
||||||
|
"""
|
||||||
# Validate the charm environment for DNS HA
|
# Validate the charm environment for DNS HA
|
||||||
assert_charm_supports_dns_ha()
|
assert_charm_supports_dns_ha()
|
||||||
|
|
||||||
|
@ -93,7 +191,7 @@ def update_dns_ha_resource_params(resources, resource_params,
|
||||||
status_set('blocked', msg)
|
status_set('blocked', msg)
|
||||||
raise DNSHAException(msg)
|
raise DNSHAException(msg)
|
||||||
|
|
||||||
hostname_key = 'res_{}_{}_hostname'.format(charm_name(), endpoint_type)
|
hostname_key = 'res_{}_{}_hostname'.format(service, endpoint_type)
|
||||||
if hostname_key in hostname_group:
|
if hostname_key in hostname_group:
|
||||||
log('DNS HA: Resource {}: {} already exists in '
|
log('DNS HA: Resource {}: {} already exists in '
|
||||||
'hostname group - skipping'.format(hostname_key, hostname),
|
'hostname group - skipping'.format(hostname_key, hostname),
|
||||||
|
@ -101,42 +199,67 @@ def update_dns_ha_resource_params(resources, resource_params,
|
||||||
continue
|
continue
|
||||||
|
|
||||||
hostname_group.append(hostname_key)
|
hostname_group.append(hostname_key)
|
||||||
resources[hostname_key] = crm_ocf
|
relation_data['resources'][hostname_key] = crm_ocf
|
||||||
resource_params[hostname_key] = (
|
relation_data['resource_params'][hostname_key] = (
|
||||||
'params fqdn="{}" ip_address="{}" '
|
'params fqdn="{}" ip_address="{}"'
|
||||||
''.format(hostname, resolve_address(endpoint_type=endpoint_type,
|
.format(hostname, resolve_address(endpoint_type=endpoint_type,
|
||||||
override=False)))
|
override=False)))
|
||||||
|
|
||||||
if len(hostname_group) >= 1:
|
if len(hostname_group) >= 1:
|
||||||
log('DNS HA: Hostname group is set with {} as members. '
|
log('DNS HA: Hostname group is set with {} as members. '
|
||||||
'Informing the ha relation'.format(' '.join(hostname_group)),
|
'Informing the ha relation'.format(' '.join(hostname_group)),
|
||||||
DEBUG)
|
DEBUG)
|
||||||
relation_set(relation_id=relation_id, groups={
|
relation_data['groups'] = {
|
||||||
'grp_{}_hostnames'.format(charm_name()): ' '.join(hostname_group)})
|
'grp_{}_hostnames'.format(service): ' '.join(hostname_group)
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
msg = 'DNS HA: Hostname group has no members.'
|
msg = 'DNS HA: Hostname group has no members.'
|
||||||
status_set('blocked', msg)
|
status_set('blocked', msg)
|
||||||
raise DNSHAException(msg)
|
raise DNSHAException(msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_charm_supports_dns_ha():
|
def update_hacluster_vip(service, relation_data):
|
||||||
"""Validate prerequisites for DNS HA
|
""" Configure VIP resources based on provided configuration
|
||||||
The MAAS client is only available on Xenial or greater
|
|
||||||
|
@param service: Name of the service being configured
|
||||||
|
@param relation_data: Pointer to dictionary of relation data.
|
||||||
"""
|
"""
|
||||||
if lsb_release().get('DISTRIB_RELEASE') < '16.04':
|
cluster_config = get_hacluster_config()
|
||||||
msg = ('DNS HA is only supported on 16.04 and greater '
|
vip_group = []
|
||||||
'versions of Ubuntu.')
|
for vip in cluster_config['vip'].split():
|
||||||
status_set('blocked', msg)
|
if is_ipv6(vip):
|
||||||
raise DNSHAException(msg)
|
res_neutron_vip = 'ocf:heartbeat:IPv6addr'
|
||||||
return True
|
vip_params = 'ipv6addr'
|
||||||
|
else:
|
||||||
|
res_neutron_vip = 'ocf:heartbeat:IPaddr2'
|
||||||
|
vip_params = 'ip'
|
||||||
|
|
||||||
|
iface = (get_iface_for_address(vip) or
|
||||||
|
config('vip_iface'))
|
||||||
|
netmask = (get_netmask_for_address(vip) or
|
||||||
|
config('vip_cidr'))
|
||||||
|
|
||||||
def expect_ha():
|
if iface is not None:
|
||||||
""" Determine if the unit expects to be in HA
|
vip_key = 'res_{}_{}_vip'.format(service, iface)
|
||||||
|
if vip_key in vip_group:
|
||||||
|
if vip not in relation_data['resource_params'][vip_key]:
|
||||||
|
vip_key = '{}_{}'.format(vip_key, vip_params)
|
||||||
|
else:
|
||||||
|
log("Resource '%s' (vip='%s') already exists in "
|
||||||
|
"vip group - skipping" % (vip_key, vip), WARNING)
|
||||||
|
continue
|
||||||
|
|
||||||
Check for VIP or dns-ha settings which indicate the unit should expect to
|
relation_data['resources'][vip_key] = res_neutron_vip
|
||||||
be related to hacluster.
|
relation_data['resource_params'][vip_key] = (
|
||||||
|
'params {ip}="{vip}" cidr_netmask="{netmask}" '
|
||||||
|
'nic="{iface}"'.format(ip=vip_params,
|
||||||
|
vip=vip,
|
||||||
|
iface=iface,
|
||||||
|
netmask=netmask)
|
||||||
|
)
|
||||||
|
vip_group.append(vip_key)
|
||||||
|
|
||||||
@returns boolean
|
if len(vip_group) >= 1:
|
||||||
"""
|
relation_data['groups'] = {
|
||||||
return config('vip') or config('dns-ha')
|
'grp_{}_vips'.format(service): ' '.join(vip_group)
|
||||||
|
}
|
||||||
|
|
|
@ -15,9 +15,6 @@ Listen {{ public_port }}
|
||||||
{% if port -%}
|
{% if port -%}
|
||||||
<VirtualHost *:{{ port }}>
|
<VirtualHost *:{{ port }}>
|
||||||
WSGIDaemonProcess {{ service_name }} processes={{ processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
|
WSGIDaemonProcess {{ service_name }} processes={{ processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
|
||||||
{% if python_path -%}
|
|
||||||
python-path={{ python_path }} \
|
|
||||||
{% endif -%}
|
|
||||||
display-name=%{GROUP}
|
display-name=%{GROUP}
|
||||||
WSGIProcessGroup {{ service_name }}
|
WSGIProcessGroup {{ service_name }}
|
||||||
WSGIScriptAlias / {{ script }}
|
WSGIScriptAlias / {{ script }}
|
||||||
|
@ -29,7 +26,7 @@ Listen {{ public_port }}
|
||||||
ErrorLog /var/log/apache2/{{ service_name }}_error.log
|
ErrorLog /var/log/apache2/{{ service_name }}_error.log
|
||||||
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
|
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
|
||||||
|
|
||||||
<Directory {{ usr_bin }}>
|
<Directory /usr/bin>
|
||||||
<IfVersion >= 2.4>
|
<IfVersion >= 2.4>
|
||||||
Require all granted
|
Require all granted
|
||||||
</IfVersion>
|
</IfVersion>
|
||||||
|
@ -44,9 +41,6 @@ Listen {{ public_port }}
|
||||||
{% if admin_port -%}
|
{% if admin_port -%}
|
||||||
<VirtualHost *:{{ admin_port }}>
|
<VirtualHost *:{{ admin_port }}>
|
||||||
WSGIDaemonProcess {{ service_name }}-admin processes={{ admin_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
|
WSGIDaemonProcess {{ service_name }}-admin processes={{ admin_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
|
||||||
{% if python_path -%}
|
|
||||||
python-path={{ python_path }} \
|
|
||||||
{% endif -%}
|
|
||||||
display-name=%{GROUP}
|
display-name=%{GROUP}
|
||||||
WSGIProcessGroup {{ service_name }}-admin
|
WSGIProcessGroup {{ service_name }}-admin
|
||||||
WSGIScriptAlias / {{ admin_script }}
|
WSGIScriptAlias / {{ admin_script }}
|
||||||
|
@ -58,7 +52,7 @@ Listen {{ public_port }}
|
||||||
ErrorLog /var/log/apache2/{{ service_name }}_error.log
|
ErrorLog /var/log/apache2/{{ service_name }}_error.log
|
||||||
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
|
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
|
||||||
|
|
||||||
<Directory {{ usr_bin }}>
|
<Directory /usr/bin>
|
||||||
<IfVersion >= 2.4>
|
<IfVersion >= 2.4>
|
||||||
Require all granted
|
Require all granted
|
||||||
</IfVersion>
|
</IfVersion>
|
||||||
|
@ -73,9 +67,6 @@ Listen {{ public_port }}
|
||||||
{% if public_port -%}
|
{% if public_port -%}
|
||||||
<VirtualHost *:{{ public_port }}>
|
<VirtualHost *:{{ public_port }}>
|
||||||
WSGIDaemonProcess {{ service_name }}-public processes={{ public_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
|
WSGIDaemonProcess {{ service_name }}-public processes={{ public_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
|
||||||
{% if python_path -%}
|
|
||||||
python-path={{ python_path }} \
|
|
||||||
{% endif -%}
|
|
||||||
display-name=%{GROUP}
|
display-name=%{GROUP}
|
||||||
WSGIProcessGroup {{ service_name }}-public
|
WSGIProcessGroup {{ service_name }}-public
|
||||||
WSGIScriptAlias / {{ public_script }}
|
WSGIScriptAlias / {{ public_script }}
|
||||||
|
@ -87,7 +78,7 @@ Listen {{ public_port }}
|
||||||
ErrorLog /var/log/apache2/{{ service_name }}_error.log
|
ErrorLog /var/log/apache2/{{ service_name }}_error.log
|
||||||
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
|
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
|
||||||
|
|
||||||
<Directory {{ usr_bin }}>
|
<Directory /usr/bin>
|
||||||
<IfVersion >= 2.4>
|
<IfVersion >= 2.4>
|
||||||
Require all granted
|
Require all granted
|
||||||
</IfVersion>
|
</IfVersion>
|
||||||
|
|
|
@ -93,7 +93,8 @@ class OSConfigTemplate(object):
|
||||||
Associates a config file template with a list of context generators.
|
Associates a config file template with a list of context generators.
|
||||||
Responsible for constructing a template context based on those generators.
|
Responsible for constructing a template context based on those generators.
|
||||||
"""
|
"""
|
||||||
def __init__(self, config_file, contexts):
|
|
||||||
|
def __init__(self, config_file, contexts, config_template=None):
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
|
|
||||||
if hasattr(contexts, '__call__'):
|
if hasattr(contexts, '__call__'):
|
||||||
|
@ -103,6 +104,8 @@ class OSConfigTemplate(object):
|
||||||
|
|
||||||
self._complete_contexts = []
|
self._complete_contexts = []
|
||||||
|
|
||||||
|
self.config_template = config_template
|
||||||
|
|
||||||
def context(self):
|
def context(self):
|
||||||
ctxt = {}
|
ctxt = {}
|
||||||
for context in self.contexts:
|
for context in self.contexts:
|
||||||
|
@ -124,6 +127,11 @@ class OSConfigTemplate(object):
|
||||||
self.context()
|
self.context()
|
||||||
return self._complete_contexts
|
return self._complete_contexts
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_string_template(self):
|
||||||
|
""":returns: Boolean if this instance is a template initialised with a string"""
|
||||||
|
return self.config_template is not None
|
||||||
|
|
||||||
|
|
||||||
class OSConfigRenderer(object):
|
class OSConfigRenderer(object):
|
||||||
"""
|
"""
|
||||||
|
@ -148,6 +156,10 @@ class OSConfigRenderer(object):
|
||||||
contexts=[context.IdentityServiceContext()])
|
contexts=[context.IdentityServiceContext()])
|
||||||
configs.register(config_file='/etc/haproxy/haproxy.conf',
|
configs.register(config_file='/etc/haproxy/haproxy.conf',
|
||||||
contexts=[context.HAProxyContext()])
|
contexts=[context.HAProxyContext()])
|
||||||
|
configs.register(config_file='/etc/keystone/policy.d/extra.cfg',
|
||||||
|
contexts=[context.ExtraPolicyContext()
|
||||||
|
context.KeystoneContext()],
|
||||||
|
config_template=hookenv.config('extra-policy'))
|
||||||
# write out a single config
|
# write out a single config
|
||||||
configs.write('/etc/nova/nova.conf')
|
configs.write('/etc/nova/nova.conf')
|
||||||
# write out all registered configs
|
# write out all registered configs
|
||||||
|
@ -218,14 +230,23 @@ class OSConfigRenderer(object):
|
||||||
else:
|
else:
|
||||||
apt_install('python3-jinja2')
|
apt_install('python3-jinja2')
|
||||||
|
|
||||||
def register(self, config_file, contexts):
|
def register(self, config_file, contexts, config_template=None):
|
||||||
"""
|
"""
|
||||||
Register a config file with a list of context generators to be called
|
Register a config file with a list of context generators to be called
|
||||||
during rendering.
|
during rendering.
|
||||||
|
config_template can be used to load a template from a string instead of
|
||||||
|
using template loaders and template files.
|
||||||
|
:param config_file (str): a path where a config file will be rendered
|
||||||
|
:param contexts (list): a list of context dictionaries with kv pairs
|
||||||
|
:param config_template (str): an optional template string to use
|
||||||
"""
|
"""
|
||||||
self.templates[config_file] = OSConfigTemplate(config_file=config_file,
|
self.templates[config_file] = OSConfigTemplate(
|
||||||
contexts=contexts)
|
config_file=config_file,
|
||||||
log('Registered config file: %s' % config_file, level=INFO)
|
contexts=contexts,
|
||||||
|
config_template=config_template
|
||||||
|
)
|
||||||
|
log('Registered config file: {}'.format(config_file),
|
||||||
|
level=INFO)
|
||||||
|
|
||||||
def _get_tmpl_env(self):
|
def _get_tmpl_env(self):
|
||||||
if not self._tmpl_env:
|
if not self._tmpl_env:
|
||||||
|
@ -235,32 +256,58 @@ class OSConfigRenderer(object):
|
||||||
def _get_template(self, template):
|
def _get_template(self, template):
|
||||||
self._get_tmpl_env()
|
self._get_tmpl_env()
|
||||||
template = self._tmpl_env.get_template(template)
|
template = self._tmpl_env.get_template(template)
|
||||||
log('Loaded template from %s' % template.filename, level=INFO)
|
log('Loaded template from {}'.format(template.filename),
|
||||||
|
level=INFO)
|
||||||
|
return template
|
||||||
|
|
||||||
|
def _get_template_from_string(self, ostmpl):
|
||||||
|
'''
|
||||||
|
Get a jinja2 template object from a string.
|
||||||
|
:param ostmpl: OSConfigTemplate to use as a data source.
|
||||||
|
'''
|
||||||
|
self._get_tmpl_env()
|
||||||
|
template = self._tmpl_env.from_string(ostmpl.config_template)
|
||||||
|
log('Loaded a template from a string for {}'.format(
|
||||||
|
ostmpl.config_file),
|
||||||
|
level=INFO)
|
||||||
return template
|
return template
|
||||||
|
|
||||||
def render(self, config_file):
|
def render(self, config_file):
|
||||||
if config_file not in self.templates:
|
if config_file not in self.templates:
|
||||||
log('Config not registered: %s' % config_file, level=ERROR)
|
log('Config not registered: {}'.format(config_file), level=ERROR)
|
||||||
raise OSConfigException
|
raise OSConfigException
|
||||||
ctxt = self.templates[config_file].context()
|
|
||||||
|
|
||||||
_tmpl = os.path.basename(config_file)
|
ostmpl = self.templates[config_file]
|
||||||
try:
|
ctxt = ostmpl.context()
|
||||||
template = self._get_template(_tmpl)
|
|
||||||
except exceptions.TemplateNotFound:
|
if ostmpl.is_string_template:
|
||||||
# if no template is found with basename, try looking for it
|
template = self._get_template_from_string(ostmpl)
|
||||||
# using a munged full path, eg:
|
log('Rendering from a string template: '
|
||||||
# /etc/apache2/apache2.conf -> etc_apache2_apache2.conf
|
'{}'.format(config_file),
|
||||||
_tmpl = '_'.join(config_file.split('/')[1:])
|
level=INFO)
|
||||||
|
else:
|
||||||
|
_tmpl = os.path.basename(config_file)
|
||||||
try:
|
try:
|
||||||
template = self._get_template(_tmpl)
|
template = self._get_template(_tmpl)
|
||||||
except exceptions.TemplateNotFound as e:
|
except exceptions.TemplateNotFound:
|
||||||
log('Could not load template from %s by %s or %s.' %
|
# if no template is found with basename, try looking
|
||||||
(self.templates_dir, os.path.basename(config_file), _tmpl),
|
# for it using a munged full path, eg:
|
||||||
level=ERROR)
|
# /etc/apache2/apache2.conf -> etc_apache2_apache2.conf
|
||||||
raise e
|
_tmpl = '_'.join(config_file.split('/')[1:])
|
||||||
|
try:
|
||||||
|
template = self._get_template(_tmpl)
|
||||||
|
except exceptions.TemplateNotFound as e:
|
||||||
|
log('Could not load template from {} by {} or {}.'
|
||||||
|
''.format(
|
||||||
|
self.templates_dir,
|
||||||
|
os.path.basename(config_file),
|
||||||
|
_tmpl
|
||||||
|
),
|
||||||
|
level=ERROR)
|
||||||
|
raise e
|
||||||
|
|
||||||
log('Rendering from template: %s' % _tmpl, level=INFO)
|
log('Rendering from template: {}'.format(config_file),
|
||||||
|
level=INFO)
|
||||||
return template.render(ctxt)
|
return template.render(ctxt)
|
||||||
|
|
||||||
def write(self, config_file):
|
def write(self, config_file):
|
||||||
|
|
|
@ -626,11 +626,6 @@ def openstack_upgrade_available(package):
|
||||||
else:
|
else:
|
||||||
avail_vers = get_os_version_install_source(src)
|
avail_vers = get_os_version_install_source(src)
|
||||||
apt.init()
|
apt.init()
|
||||||
if "swift" in package:
|
|
||||||
major_cur_vers = cur_vers.split('.', 1)[0]
|
|
||||||
major_avail_vers = avail_vers.split('.', 1)[0]
|
|
||||||
major_diff = apt.version_compare(major_avail_vers, major_cur_vers)
|
|
||||||
return avail_vers > cur_vers and (major_diff == 1 or major_diff == 0)
|
|
||||||
return apt.version_compare(avail_vers, cur_vers) == 1
|
return apt.version_compare(avail_vers, cur_vers) == 1
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ if not six.PY3:
|
||||||
else:
|
else:
|
||||||
from collections import UserDict
|
from collections import UserDict
|
||||||
|
|
||||||
|
|
||||||
CRITICAL = "CRITICAL"
|
CRITICAL = "CRITICAL"
|
||||||
ERROR = "ERROR"
|
ERROR = "ERROR"
|
||||||
WARNING = "WARNING"
|
WARNING = "WARNING"
|
||||||
|
@ -344,6 +345,7 @@ class Config(dict):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
with open(self.path, 'w') as f:
|
with open(self.path, 'w') as f:
|
||||||
|
os.fchmod(f.fileno(), 0o600)
|
||||||
json.dump(self, f)
|
json.dump(self, f)
|
||||||
|
|
||||||
def _implicit_save(self):
|
def _implicit_save(self):
|
||||||
|
@ -818,6 +820,10 @@ class Hooks(object):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class NoNetworkBinding(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def charm_dir():
|
def charm_dir():
|
||||||
"""Return the root directory of the current charm"""
|
"""Return the root directory of the current charm"""
|
||||||
d = os.environ.get('JUJU_CHARM_DIR')
|
d = os.environ.get('JUJU_CHARM_DIR')
|
||||||
|
@ -1104,7 +1110,17 @@ def network_get_primary_address(binding):
|
||||||
:raise: NotImplementedError if run on Juju < 2.0
|
:raise: NotImplementedError if run on Juju < 2.0
|
||||||
'''
|
'''
|
||||||
cmd = ['network-get', '--primary-address', binding]
|
cmd = ['network-get', '--primary-address', binding]
|
||||||
return subprocess.check_output(cmd).decode('UTF-8').strip()
|
try:
|
||||||
|
response = subprocess.check_output(
|
||||||
|
cmd,
|
||||||
|
stderr=subprocess.STDOUT).decode('UTF-8').strip()
|
||||||
|
except CalledProcessError as e:
|
||||||
|
if 'no network config found for binding' in e.output.decode('UTF-8'):
|
||||||
|
raise NoNetworkBinding("No network binding for {}"
|
||||||
|
.format(binding))
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
||||||
|
|
|
@ -175,6 +175,8 @@ class Storage(object):
|
||||||
else:
|
else:
|
||||||
self.db_path = os.path.join(
|
self.db_path = os.path.join(
|
||||||
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
||||||
|
with open(self.db_path, 'a') as f:
|
||||||
|
os.fchmod(f.fileno(), 0o600)
|
||||||
self.conn = sqlite3.connect('%s' % self.db_path)
|
self.conn = sqlite3.connect('%s' % self.db_path)
|
||||||
self.cursor = self.conn.cursor()
|
self.cursor = self.conn.cursor()
|
||||||
self.revision = None
|
self.revision = None
|
||||||
|
|
|
@ -20,7 +20,6 @@ from charmhelpers.core.hookenv import (
|
||||||
related_units,
|
related_units,
|
||||||
relation_get,
|
relation_get,
|
||||||
local_unit,
|
local_unit,
|
||||||
unit_get,
|
|
||||||
log,
|
log,
|
||||||
WARNING,
|
WARNING,
|
||||||
ERROR,
|
ERROR,
|
||||||
|
@ -28,7 +27,6 @@ from charmhelpers.core.hookenv import (
|
||||||
from charmhelpers.core.strutils import bool_from_string
|
from charmhelpers.core.strutils import bool_from_string
|
||||||
from charmhelpers.contrib.openstack.context import (
|
from charmhelpers.contrib.openstack.context import (
|
||||||
OSContextGenerator,
|
OSContextGenerator,
|
||||||
HAProxyContext,
|
|
||||||
context_complete
|
context_complete
|
||||||
)
|
)
|
||||||
from charmhelpers.contrib.hahelpers.apache import (
|
from charmhelpers.contrib.hahelpers.apache import (
|
||||||
|
@ -39,6 +37,7 @@ from charmhelpers.contrib.hahelpers.apache import (
|
||||||
from charmhelpers.contrib.network.ip import (
|
from charmhelpers.contrib.network.ip import (
|
||||||
get_ipv6_addr,
|
get_ipv6_addr,
|
||||||
format_ipv6_addr,
|
format_ipv6_addr,
|
||||||
|
get_relation_ip,
|
||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.core.host import pwgen
|
from charmhelpers.core.host import pwgen
|
||||||
|
@ -53,7 +52,7 @@ VALID_ENDPOINT_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class HorizonHAProxyContext(HAProxyContext):
|
class HorizonHAProxyContext(OSContextGenerator):
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
'''
|
'''
|
||||||
Horizon specific HAProxy context; haproxy is used all the time
|
Horizon specific HAProxy context; haproxy is used all the time
|
||||||
|
@ -65,7 +64,7 @@ class HorizonHAProxyContext(HAProxyContext):
|
||||||
if config('prefer-ipv6'):
|
if config('prefer-ipv6'):
|
||||||
cluster_hosts[l_unit] = get_ipv6_addr(exc_list=[config('vip')])[0]
|
cluster_hosts[l_unit] = get_ipv6_addr(exc_list=[config('vip')])[0]
|
||||||
else:
|
else:
|
||||||
cluster_hosts[l_unit] = unit_get('private-address')
|
cluster_hosts[l_unit] = get_relation_ip('cluster')
|
||||||
|
|
||||||
for rid in relation_ids('cluster'):
|
for rid in relation_ids('cluster'):
|
||||||
for unit in related_units(rid):
|
for unit in related_units(rid):
|
||||||
|
|
|
@ -131,7 +131,8 @@ CONFIG_FILES = OrderedDict([
|
||||||
(HAPROXY_CONF, {
|
(HAPROXY_CONF, {
|
||||||
'hook_contexts': [
|
'hook_contexts': [
|
||||||
horizon_contexts.HorizonHAProxyContext(),
|
horizon_contexts.HorizonHAProxyContext(),
|
||||||
context.HAProxyContext(singlenode_mode=True),
|
context.HAProxyContext(singlenode_mode=True,
|
||||||
|
address_types=[]),
|
||||||
],
|
],
|
||||||
'services': ['haproxy'],
|
'services': ['haproxy'],
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -39,6 +39,7 @@ if not six.PY3:
|
||||||
else:
|
else:
|
||||||
from collections import UserDict
|
from collections import UserDict
|
||||||
|
|
||||||
|
|
||||||
CRITICAL = "CRITICAL"
|
CRITICAL = "CRITICAL"
|
||||||
ERROR = "ERROR"
|
ERROR = "ERROR"
|
||||||
WARNING = "WARNING"
|
WARNING = "WARNING"
|
||||||
|
@ -344,6 +345,7 @@ class Config(dict):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
with open(self.path, 'w') as f:
|
with open(self.path, 'w') as f:
|
||||||
|
os.fchmod(f.fileno(), 0o600)
|
||||||
json.dump(self, f)
|
json.dump(self, f)
|
||||||
|
|
||||||
def _implicit_save(self):
|
def _implicit_save(self):
|
||||||
|
@ -818,6 +820,10 @@ class Hooks(object):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class NoNetworkBinding(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def charm_dir():
|
def charm_dir():
|
||||||
"""Return the root directory of the current charm"""
|
"""Return the root directory of the current charm"""
|
||||||
d = os.environ.get('JUJU_CHARM_DIR')
|
d = os.environ.get('JUJU_CHARM_DIR')
|
||||||
|
@ -1104,7 +1110,17 @@ def network_get_primary_address(binding):
|
||||||
:raise: NotImplementedError if run on Juju < 2.0
|
:raise: NotImplementedError if run on Juju < 2.0
|
||||||
'''
|
'''
|
||||||
cmd = ['network-get', '--primary-address', binding]
|
cmd = ['network-get', '--primary-address', binding]
|
||||||
return subprocess.check_output(cmd).decode('UTF-8').strip()
|
try:
|
||||||
|
response = subprocess.check_output(
|
||||||
|
cmd,
|
||||||
|
stderr=subprocess.STDOUT).decode('UTF-8').strip()
|
||||||
|
except CalledProcessError as e:
|
||||||
|
if 'no network config found for binding' in e.output.decode('UTF-8'):
|
||||||
|
raise NoNetworkBinding("No network binding for {}"
|
||||||
|
.format(binding))
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
||||||
|
|
|
@ -175,6 +175,8 @@ class Storage(object):
|
||||||
else:
|
else:
|
||||||
self.db_path = os.path.join(
|
self.db_path = os.path.join(
|
||||||
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
||||||
|
with open(self.db_path, 'a') as f:
|
||||||
|
os.fchmod(f.fileno(), 0o600)
|
||||||
self.conn = sqlite3.connect('%s' % self.db_path)
|
self.conn = sqlite3.connect('%s' % self.db_path)
|
||||||
self.cursor = self.conn.cursor()
|
self.cursor = self.conn.cursor()
|
||||||
self.revision = None
|
self.revision = None
|
||||||
|
|
|
@ -30,7 +30,7 @@ TO_PATCH = [
|
||||||
'b64decode',
|
'b64decode',
|
||||||
'context_complete',
|
'context_complete',
|
||||||
'local_unit',
|
'local_unit',
|
||||||
'unit_get',
|
'get_relation_ip',
|
||||||
'pwgen',
|
'pwgen',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -597,7 +597,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||||
def test_HorizonHAProxyContext_no_cluster(self):
|
def test_HorizonHAProxyContext_no_cluster(self):
|
||||||
self.relation_ids.return_value = []
|
self.relation_ids.return_value = []
|
||||||
self.local_unit.return_value = 'openstack-dashboard/0'
|
self.local_unit.return_value = 'openstack-dashboard/0'
|
||||||
self.unit_get.return_value = "10.5.0.1"
|
self.get_relation_ip.return_value = "10.5.0.1"
|
||||||
with patch_open() as (_open, _file):
|
with patch_open() as (_open, _file):
|
||||||
self.assertEqual(horizon_contexts.HorizonHAProxyContext()(),
|
self.assertEqual(horizon_contexts.HorizonHAProxyContext()(),
|
||||||
{'units': {'openstack-dashboard-0': '10.5.0.1'},
|
{'units': {'openstack-dashboard-0': '10.5.0.1'},
|
||||||
|
@ -606,6 +606,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||||
'prefer_ipv6': False})
|
'prefer_ipv6': False})
|
||||||
_open.assert_called_with('/etc/default/haproxy', 'w')
|
_open.assert_called_with('/etc/default/haproxy', 'w')
|
||||||
self.assertTrue(_file.write.called)
|
self.assertTrue(_file.write.called)
|
||||||
|
self.get_relation_ip.assert_called_with('cluster')
|
||||||
|
|
||||||
def test_HorizonHAProxyContext_clustered(self):
|
def test_HorizonHAProxyContext_clustered(self):
|
||||||
self.relation_ids.return_value = ['cluster:0']
|
self.relation_ids.return_value = ['cluster:0']
|
||||||
|
@ -614,7 +615,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||||
]
|
]
|
||||||
self.relation_get.side_effect = ['10.5.0.2', '10.5.0.3']
|
self.relation_get.side_effect = ['10.5.0.2', '10.5.0.3']
|
||||||
self.local_unit.return_value = 'openstack-dashboard/0'
|
self.local_unit.return_value = 'openstack-dashboard/0'
|
||||||
self.unit_get.return_value = "10.5.0.1"
|
self.get_relation_ip.return_value = "10.5.0.1"
|
||||||
with patch_open() as (_open, _file):
|
with patch_open() as (_open, _file):
|
||||||
self.assertEqual(horizon_contexts.HorizonHAProxyContext()(),
|
self.assertEqual(horizon_contexts.HorizonHAProxyContext()(),
|
||||||
{'units': {'openstack-dashboard-0': '10.5.0.1',
|
{'units': {'openstack-dashboard-0': '10.5.0.1',
|
||||||
|
@ -625,6 +626,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||||
'prefer_ipv6': False})
|
'prefer_ipv6': False})
|
||||||
_open.assert_called_with('/etc/default/haproxy', 'w')
|
_open.assert_called_with('/etc/default/haproxy', 'w')
|
||||||
self.assertTrue(_file.write.called)
|
self.assertTrue(_file.write.called)
|
||||||
|
self.get_relation_ip.assert_called_with('cluster')
|
||||||
|
|
||||||
def test_RouterSettingContext(self):
|
def test_RouterSettingContext(self):
|
||||||
self.test_config.set('profile', 'cisco')
|
self.test_config.set('profile', 'cisco')
|
||||||
|
|
Loading…
Reference in New Issue