DNS HA
Implement DNS high availability. Pass the correct information to hacluster to register a DNS entry with MAAS 2.0 or greater rather than using a virtual IP. Charm-helpers sync to bring in DNS HA helpers Change-Id: I2d9e2b92b0ebf33dc2f62327df6edbfd1e81dcca
This commit is contained in:
parent
dd9f4e7b71
commit
7f3cfab538
36
README.md
36
README.md
|
@ -49,17 +49,37 @@ deployment:
|
||||||
|
|
||||||
A minimium cluster size of three units is recommended.
|
A minimium cluster size of three units is recommended.
|
||||||
|
|
||||||
In order to access the cluster, use the hacluster charm to provide a single IP
|
|
||||||
address:
|
|
||||||
|
|
||||||
juju set percona-cluster vip=10.0.3.200
|
HA/Clustering
|
||||||
juju deploy hacluster
|
-------------
|
||||||
juju add-relation hacluster percona-cluster
|
|
||||||
|
|
||||||
Clients can then access using the vip provided. This vip will be passed to
|
There are two mutually exclusive high availability options: using virtual
|
||||||
related services:
|
IP(s) or DNS. In both cases, a relationship to hacluster is required which
|
||||||
|
provides the corosync back end HA functionality.
|
||||||
|
|
||||||
juju add-relation keystone percona-cluster
|
To use virtual IP(s) the clustered nodes must be on the same subnet such that
|
||||||
|
the VIP is a valid IP on the subnet for one of the node's interfaces and each
|
||||||
|
node has an interface in said subnet. The VIP becomes a highly-available API
|
||||||
|
endpoint.
|
||||||
|
|
||||||
|
At a minimum, the config option 'vip' must be set in order to use virtual IP
|
||||||
|
HA. If multiple networks are being used, a VIP should be provided for each
|
||||||
|
network, separated by spaces. Optionally, vip_iface or vip_cidr may be
|
||||||
|
specified.
|
||||||
|
|
||||||
|
To use DNS high availability there are several prerequisites. However, DNS HA
|
||||||
|
does not require the clustered nodes to be on the same subnet.
|
||||||
|
Currently the DNS HA feature is only available for MAAS 2.0 or greater
|
||||||
|
environments. MAAS 2.0 requires Juju 2.0 or greater. The clustered nodes must
|
||||||
|
have static or "reserved" IP addresses registered in MAAS. The DNS hostname(s)
|
||||||
|
must be pre-registered in MAAS before use with DNS HA.
|
||||||
|
|
||||||
|
At a minimum, the config option 'dns-ha' must be set to true and
|
||||||
|
'os-access-hostname' must be set in order to use DNS HA.
|
||||||
|
The charm will throw an exception in the following circumstances:
|
||||||
|
If neither 'vip' nor 'dns-ha' is set and the charm is related to hacluster
|
||||||
|
If both 'vip' and 'dns-ha' are set as they are mutually exclusive
|
||||||
|
If 'dns-ha' is set and os-access-hostname is not set
|
||||||
|
|
||||||
Network Space support
|
Network Space support
|
||||||
---------------------
|
---------------------
|
||||||
|
|
|
@ -6,6 +6,8 @@ include:
|
||||||
- fetch
|
- fetch
|
||||||
- contrib.hahelpers.cluster
|
- contrib.hahelpers.cluster
|
||||||
- contrib.openstack.utils
|
- contrib.openstack.utils
|
||||||
|
- contrib.openstack.ip
|
||||||
|
- contrib.openstack.ha.utils
|
||||||
- contrib.openstack.exceptions
|
- contrib.openstack.exceptions
|
||||||
- contrib.storage.linux
|
- contrib.storage.linux
|
||||||
- contrib.python.packages
|
- contrib.python.packages
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
# Copyright 2014-2016 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2016 Canonical Ltd.
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# Openstack Charmers <
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Helpers for high availability.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from charmhelpers.core.hookenv import (
|
||||||
|
log,
|
||||||
|
relation_set,
|
||||||
|
charm_name,
|
||||||
|
config,
|
||||||
|
status_set,
|
||||||
|
DEBUG,
|
||||||
|
)
|
||||||
|
|
||||||
|
from charmhelpers.contrib.openstack.ip import (
|
||||||
|
resolve_address,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DNSHAException(Exception):
|
||||||
|
"""Raised when an error occurs setting up DNS HA
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def update_dns_ha_resource_params(resources, resource_params,
|
||||||
|
relation_id=None,
|
||||||
|
crm_ocf='ocf:maas:dns'):
|
||||||
|
""" Check for os-*-hostname settings and update resource dictionaries for
|
||||||
|
the HA relation.
|
||||||
|
|
||||||
|
@param resources: Pointer to dictionary of resources.
|
||||||
|
Usually instantiated in ha_joined().
|
||||||
|
@param resource_params: Pointer to dictionary of resource parameters.
|
||||||
|
Usually instantiated in ha_joined()
|
||||||
|
@param relation_id: Relation ID of the ha relation
|
||||||
|
@param crm_ocf: Corosync Open Cluster Framework resource agent to use for
|
||||||
|
DNS HA
|
||||||
|
"""
|
||||||
|
|
||||||
|
settings = ['os-admin-hostname', 'os-internal-hostname',
|
||||||
|
'os-public-hostname']
|
||||||
|
|
||||||
|
# Check which DNS settings are set and update dictionaries
|
||||||
|
hostname_group = []
|
||||||
|
for setting in settings:
|
||||||
|
hostname = config(setting)
|
||||||
|
if hostname is None:
|
||||||
|
log('DNS HA: Hostname setting {} is None. Ignoring.'
|
||||||
|
''.format(setting),
|
||||||
|
DEBUG)
|
||||||
|
continue
|
||||||
|
m = re.search('os-(.+?)-hostname', setting)
|
||||||
|
if m:
|
||||||
|
networkspace = m.group(1)
|
||||||
|
else:
|
||||||
|
msg = ('Unexpected DNS hostname setting: {}. '
|
||||||
|
'Cannot determine network space name'
|
||||||
|
''.format(setting))
|
||||||
|
status_set('blocked', msg)
|
||||||
|
raise DNSHAException(msg)
|
||||||
|
|
||||||
|
hostname_key = 'res_{}_{}_hostname'.format(charm_name(), networkspace)
|
||||||
|
if hostname_key in hostname_group:
|
||||||
|
log('DNS HA: Resource {}: {} already exists in '
|
||||||
|
'hostname group - skipping'.format(hostname_key, hostname),
|
||||||
|
DEBUG)
|
||||||
|
continue
|
||||||
|
|
||||||
|
hostname_group.append(hostname_key)
|
||||||
|
resources[hostname_key] = crm_ocf
|
||||||
|
resource_params[hostname_key] = (
|
||||||
|
'params fqdn="{}" ip_address="{}" '
|
||||||
|
''.format(hostname, resolve_address(endpoint_type=networkspace,
|
||||||
|
override=False)))
|
||||||
|
|
||||||
|
if len(hostname_group) >= 1:
|
||||||
|
log('DNS HA: Hostname group is set with {} as members. '
|
||||||
|
'Informing the ha relation'.format(' '.join(hostname_group)),
|
||||||
|
DEBUG)
|
||||||
|
relation_set(relation_id=relation_id, groups={
|
||||||
|
'grp_{}_hostnames'.format(charm_name()): ' '.join(hostname_group)})
|
||||||
|
else:
|
||||||
|
msg = 'DNS HA: Hostname group has no members.'
|
||||||
|
status_set('blocked', msg)
|
||||||
|
raise DNSHAException(msg)
|
|
@ -0,0 +1,182 @@
|
||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
from charmhelpers.core.hookenv import (
|
||||||
|
config,
|
||||||
|
unit_get,
|
||||||
|
service_name,
|
||||||
|
network_get_primary_address,
|
||||||
|
)
|
||||||
|
from charmhelpers.contrib.network.ip import (
|
||||||
|
get_address_in_network,
|
||||||
|
is_address_in_network,
|
||||||
|
is_ipv6,
|
||||||
|
get_ipv6_addr,
|
||||||
|
resolve_network_cidr,
|
||||||
|
)
|
||||||
|
from charmhelpers.contrib.hahelpers.cluster import is_clustered
|
||||||
|
|
||||||
|
PUBLIC = 'public'
|
||||||
|
INTERNAL = 'int'
|
||||||
|
ADMIN = 'admin'
|
||||||
|
|
||||||
|
ADDRESS_MAP = {
|
||||||
|
PUBLIC: {
|
||||||
|
'binding': 'public',
|
||||||
|
'config': 'os-public-network',
|
||||||
|
'fallback': 'public-address',
|
||||||
|
'override': 'os-public-hostname',
|
||||||
|
},
|
||||||
|
INTERNAL: {
|
||||||
|
'binding': 'internal',
|
||||||
|
'config': 'os-internal-network',
|
||||||
|
'fallback': 'private-address',
|
||||||
|
'override': 'os-internal-hostname',
|
||||||
|
},
|
||||||
|
ADMIN: {
|
||||||
|
'binding': 'admin',
|
||||||
|
'config': 'os-admin-network',
|
||||||
|
'fallback': 'private-address',
|
||||||
|
'override': 'os-admin-hostname',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def canonical_url(configs, endpoint_type=PUBLIC):
|
||||||
|
"""Returns the correct HTTP URL to this host given the state of HTTPS
|
||||||
|
configuration, hacluster and charm configuration.
|
||||||
|
|
||||||
|
:param configs: OSTemplateRenderer config templating object to inspect
|
||||||
|
for a complete https context.
|
||||||
|
:param endpoint_type: str endpoint type to resolve.
|
||||||
|
:param returns: str base URL for services on the current service unit.
|
||||||
|
"""
|
||||||
|
scheme = _get_scheme(configs)
|
||||||
|
|
||||||
|
address = resolve_address(endpoint_type)
|
||||||
|
if is_ipv6(address):
|
||||||
|
address = "[{}]".format(address)
|
||||||
|
|
||||||
|
return '%s://%s' % (scheme, address)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_scheme(configs):
|
||||||
|
"""Returns the scheme to use for the url (either http or https)
|
||||||
|
depending upon whether https is in the configs value.
|
||||||
|
|
||||||
|
:param configs: OSTemplateRenderer config templating object to inspect
|
||||||
|
for a complete https context.
|
||||||
|
:returns: either 'http' or 'https' depending on whether https is
|
||||||
|
configured within the configs context.
|
||||||
|
"""
|
||||||
|
scheme = 'http'
|
||||||
|
if configs and 'https' in configs.complete_contexts():
|
||||||
|
scheme = 'https'
|
||||||
|
return scheme
|
||||||
|
|
||||||
|
|
||||||
|
def _get_address_override(endpoint_type=PUBLIC):
|
||||||
|
"""Returns any address overrides that the user has defined based on the
|
||||||
|
endpoint type.
|
||||||
|
|
||||||
|
Note: this function allows for the service name to be inserted into the
|
||||||
|
address if the user specifies {service_name}.somehost.org.
|
||||||
|
|
||||||
|
:param endpoint_type: the type of endpoint to retrieve the override
|
||||||
|
value for.
|
||||||
|
:returns: any endpoint address or hostname that the user has overridden
|
||||||
|
or None if an override is not present.
|
||||||
|
"""
|
||||||
|
override_key = ADDRESS_MAP[endpoint_type]['override']
|
||||||
|
addr_override = config(override_key)
|
||||||
|
if not addr_override:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return addr_override.format(service_name=service_name())
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_address(endpoint_type=PUBLIC, override=True):
|
||||||
|
"""Return unit address depending on net config.
|
||||||
|
|
||||||
|
If unit is clustered with vip(s) and has net splits defined, return vip on
|
||||||
|
correct network. If clustered with no nets defined, return primary vip.
|
||||||
|
|
||||||
|
If not clustered, return unit address ensuring address is on configured net
|
||||||
|
split if one is configured, or a Juju 2.0 extra-binding has been used.
|
||||||
|
|
||||||
|
:param endpoint_type: Network endpoing type
|
||||||
|
:param override: Accept hostname overrides or not
|
||||||
|
"""
|
||||||
|
resolved_address = None
|
||||||
|
if override:
|
||||||
|
resolved_address = _get_address_override(endpoint_type)
|
||||||
|
if resolved_address:
|
||||||
|
return resolved_address
|
||||||
|
|
||||||
|
vips = config('vip')
|
||||||
|
if vips:
|
||||||
|
vips = vips.split()
|
||||||
|
|
||||||
|
net_type = ADDRESS_MAP[endpoint_type]['config']
|
||||||
|
net_addr = config(net_type)
|
||||||
|
net_fallback = ADDRESS_MAP[endpoint_type]['fallback']
|
||||||
|
binding = ADDRESS_MAP[endpoint_type]['binding']
|
||||||
|
clustered = is_clustered()
|
||||||
|
|
||||||
|
if clustered and vips:
|
||||||
|
if net_addr:
|
||||||
|
for vip in vips:
|
||||||
|
if is_address_in_network(net_addr, vip):
|
||||||
|
resolved_address = vip
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# NOTE: endeavour to check vips against network space
|
||||||
|
# bindings
|
||||||
|
try:
|
||||||
|
bound_cidr = resolve_network_cidr(
|
||||||
|
network_get_primary_address(binding)
|
||||||
|
)
|
||||||
|
for vip in vips:
|
||||||
|
if is_address_in_network(bound_cidr, vip):
|
||||||
|
resolved_address = vip
|
||||||
|
break
|
||||||
|
except NotImplementedError:
|
||||||
|
# If no net-splits configured and no support for extra
|
||||||
|
# bindings/network spaces so we expect a single vip
|
||||||
|
resolved_address = vips[0]
|
||||||
|
else:
|
||||||
|
if config('prefer-ipv6'):
|
||||||
|
fallback_addr = get_ipv6_addr(exc_list=vips)[0]
|
||||||
|
else:
|
||||||
|
fallback_addr = unit_get(net_fallback)
|
||||||
|
|
||||||
|
if net_addr:
|
||||||
|
resolved_address = get_address_in_network(net_addr, fallback_addr)
|
||||||
|
else:
|
||||||
|
# NOTE: only try to use extra bindings if legacy network
|
||||||
|
# configuration is not in use
|
||||||
|
try:
|
||||||
|
resolved_address = network_get_primary_address(binding)
|
||||||
|
except NotImplementedError:
|
||||||
|
resolved_address = fallback_addr
|
||||||
|
|
||||||
|
if resolved_address is None:
|
||||||
|
raise ValueError("Unable to resolve a suitable IP address based on "
|
||||||
|
"charm state and configuration. (net_type=%s, "
|
||||||
|
"clustered=%s)" % (net_type, clustered))
|
||||||
|
|
||||||
|
return resolved_address
|
11
config.yaml
11
config.yaml
|
@ -79,6 +79,12 @@ options:
|
||||||
Minimum number of units expected to exist before charm will attempt to
|
Minimum number of units expected to exist before charm will attempt to
|
||||||
bootstrap percona cluster. If no value is provided this setting is
|
bootstrap percona cluster. If no value is provided this setting is
|
||||||
ignored.
|
ignored.
|
||||||
|
dns-ha:
|
||||||
|
type: boolean
|
||||||
|
default: False
|
||||||
|
description: |
|
||||||
|
Use DNS HA with MAAS 2.0. Note if this is set do not set vip
|
||||||
|
settings below.
|
||||||
vip:
|
vip:
|
||||||
type: string
|
type: string
|
||||||
default:
|
default:
|
||||||
|
@ -114,6 +120,11 @@ options:
|
||||||
The IP address and netmask of the 'access' network (e.g., 192.168.0.0/24)
|
The IP address and netmask of the 'access' network (e.g., 192.168.0.0/24)
|
||||||
.
|
.
|
||||||
This network will be used for access to database services.
|
This network will be used for access to database services.
|
||||||
|
os-access-hostname:
|
||||||
|
type: string
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
The hostname or address of the access endpoint for percona-cluster.
|
||||||
prefer-ipv6:
|
prefer-ipv6:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: False
|
default: False
|
||||||
|
|
|
@ -25,6 +25,7 @@ from charmhelpers.core.hookenv import (
|
||||||
WARNING,
|
WARNING,
|
||||||
is_leader,
|
is_leader,
|
||||||
network_get_primary_address,
|
network_get_primary_address,
|
||||||
|
charm_name,
|
||||||
)
|
)
|
||||||
from charmhelpers.core.host import (
|
from charmhelpers.core.host import (
|
||||||
service_restart,
|
service_restart,
|
||||||
|
@ -52,6 +53,7 @@ from charmhelpers.contrib.hahelpers.cluster import (
|
||||||
oldest_peer,
|
oldest_peer,
|
||||||
DC_RESOURCE_NAME,
|
DC_RESOURCE_NAME,
|
||||||
peer_units,
|
peer_units,
|
||||||
|
get_hacluster_config,
|
||||||
)
|
)
|
||||||
from charmhelpers.payload.execd import execd_preinstall
|
from charmhelpers.payload.execd import execd_preinstall
|
||||||
from charmhelpers.contrib.network.ip import (
|
from charmhelpers.contrib.network.ip import (
|
||||||
|
@ -68,6 +70,9 @@ from charmhelpers.contrib.hardening.mysql.checks import run_mysql_checks
|
||||||
from charmhelpers.contrib.openstack.utils import (
|
from charmhelpers.contrib.openstack.utils import (
|
||||||
is_unit_paused_set,
|
is_unit_paused_set,
|
||||||
)
|
)
|
||||||
|
from charmhelpers.contrib.openstack.ha.utils import (
|
||||||
|
update_dns_ha_resource_params,
|
||||||
|
)
|
||||||
|
|
||||||
from percona_utils import (
|
from percona_utils import (
|
||||||
determine_packages,
|
determine_packages,
|
||||||
|
@ -586,46 +591,55 @@ def shared_db_changed(relation_id=None, unit=None):
|
||||||
|
|
||||||
|
|
||||||
@hooks.hook('ha-relation-joined')
|
@hooks.hook('ha-relation-joined')
|
||||||
def ha_relation_joined():
|
def ha_relation_joined(relation_id=None):
|
||||||
vip = config('vip')
|
cluster_config = get_hacluster_config()
|
||||||
vip_iface = get_iface_for_address(vip) or config('vip_iface')
|
|
||||||
vip_cidr = get_netmask_for_address(vip) or config('vip_cidr')
|
|
||||||
corosync_bindiface = config('ha-bindiface')
|
|
||||||
corosync_mcastport = config('ha-mcastport')
|
|
||||||
|
|
||||||
if None in [vip, vip_cidr, vip_iface]:
|
|
||||||
log('Insufficient VIP information to configure cluster')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if config('prefer-ipv6'):
|
|
||||||
res_mysql_vip = 'ocf:heartbeat:IPv6addr'
|
|
||||||
vip_params = 'params ipv6addr="%s" cidr_netmask="%s" nic="%s"' % \
|
|
||||||
(vip, vip_cidr, vip_iface)
|
|
||||||
else:
|
|
||||||
res_mysql_vip = 'ocf:heartbeat:IPaddr2'
|
|
||||||
vip_params = 'params ip="%s" cidr_netmask="%s" nic="%s"' % \
|
|
||||||
(vip, vip_cidr, vip_iface)
|
|
||||||
|
|
||||||
resources = {'res_mysql_vip': res_mysql_vip,
|
|
||||||
'res_mysql_monitor': 'ocf:percona:mysql_monitor'}
|
|
||||||
|
|
||||||
sstpsswd = config('sst-password')
|
sstpsswd = config('sst-password')
|
||||||
resource_params = {'res_mysql_vip': vip_params,
|
resources = {'res_mysql_monitor': 'ocf:percona:mysql_monitor'}
|
||||||
'res_mysql_monitor':
|
resource_params = {'res_mysql_monitor':
|
||||||
RES_MONITOR_PARAMS % {'sstpass': sstpsswd}}
|
RES_MONITOR_PARAMS % {'sstpass': sstpsswd}}
|
||||||
groups = {'grp_percona_cluster': 'res_mysql_vip'}
|
|
||||||
|
if config('dns-ha'):
|
||||||
|
update_dns_ha_resource_params(relation_id=relation_id,
|
||||||
|
resources=resources,
|
||||||
|
resource_params=resource_params)
|
||||||
|
group_name = 'grp_{}_hostnames'.format(charm_name())
|
||||||
|
groups = {group_name: 'res_{}_access_hostname'.format(charm_name())}
|
||||||
|
|
||||||
|
else:
|
||||||
|
vip_iface = (get_iface_for_address(cluster_config['vip']) or
|
||||||
|
config('vip_iface'))
|
||||||
|
vip_cidr = (get_netmask_for_address(cluster_config['vip']) or
|
||||||
|
config('vip_cidr'))
|
||||||
|
|
||||||
|
if config('prefer-ipv6'):
|
||||||
|
res_mysql_vip = 'ocf:heartbeat:IPv6addr'
|
||||||
|
vip_params = 'params ipv6addr="%s" cidr_netmask="%s" nic="%s"' % \
|
||||||
|
(cluster_config['vip'], vip_cidr, vip_iface)
|
||||||
|
else:
|
||||||
|
res_mysql_vip = 'ocf:heartbeat:IPaddr2'
|
||||||
|
vip_params = 'params ip="%s" cidr_netmask="%s" nic="%s"' % \
|
||||||
|
(cluster_config['vip'], vip_cidr, vip_iface)
|
||||||
|
|
||||||
|
resources['res_mysql_vip'] = res_mysql_vip
|
||||||
|
|
||||||
|
resource_params['res_mysql_vip'] = vip_params
|
||||||
|
|
||||||
|
group_name = 'grp_percona_cluster'
|
||||||
|
groups = {group_name: 'res_mysql_vip'}
|
||||||
|
|
||||||
clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'}
|
clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'}
|
||||||
|
|
||||||
colocations = {'vip_mysqld': 'inf: grp_percona_cluster cl_mysql_monitor'}
|
colocations = {'colo_percona_cluster': 'inf: {} cl_mysql_monitor'
|
||||||
|
''.format(group_name)}
|
||||||
|
|
||||||
locations = {'loc_percona_cluster':
|
locations = {'loc_percona_cluster':
|
||||||
'grp_percona_cluster rule inf: writable eq 1'}
|
'{} rule inf: writable eq 1'
|
||||||
|
''.format(group_name)}
|
||||||
|
|
||||||
for rel_id in relation_ids('ha'):
|
for rel_id in relation_ids('ha'):
|
||||||
relation_set(relation_id=rel_id,
|
relation_set(relation_id=rel_id,
|
||||||
corosync_bindiface=corosync_bindiface,
|
corosync_bindiface=cluster_config['ha-bindiface'],
|
||||||
corosync_mcastport=corosync_mcastport,
|
corosync_mcastport=cluster_config['ha-mcastport'],
|
||||||
resources=resources,
|
resources=resources,
|
||||||
resource_params=resource_params,
|
resource_params=resource_params,
|
||||||
groups=groups,
|
groups=groups,
|
||||||
|
|
|
@ -28,7 +28,9 @@ TO_PATCH = ['log', 'config',
|
||||||
'unit_get',
|
'unit_get',
|
||||||
'get_host_ip',
|
'get_host_ip',
|
||||||
'is_clustered',
|
'is_clustered',
|
||||||
'get_ipv6_addr']
|
'get_ipv6_addr',
|
||||||
|
'get_hacluster_config',
|
||||||
|
'update_dns_ha_resource_params']
|
||||||
|
|
||||||
|
|
||||||
class TestHARelation(CharmTestCase):
|
class TestHARelation(CharmTestCase):
|
||||||
|
@ -36,18 +38,6 @@ class TestHARelation(CharmTestCase):
|
||||||
CharmTestCase.setUp(self, hooks, TO_PATCH)
|
CharmTestCase.setUp(self, hooks, TO_PATCH)
|
||||||
self.network_get_primary_address.side_effect = NotImplementedError
|
self.network_get_primary_address.side_effect = NotImplementedError
|
||||||
|
|
||||||
@mock.patch('sys.exit')
|
|
||||||
def test_relation_not_configured(self, exit_):
|
|
||||||
self.config.return_value = None
|
|
||||||
|
|
||||||
class MyError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def f(x):
|
|
||||||
raise MyError(x)
|
|
||||||
exit_.side_effect = f
|
|
||||||
self.assertRaises(MyError, hooks.ha_relation_joined)
|
|
||||||
|
|
||||||
def test_resources(self):
|
def test_resources(self):
|
||||||
self.relation_ids.return_value = ['ha:1']
|
self.relation_ids.return_value = ['ha:1']
|
||||||
password = 'ubuntu'
|
password = 'ubuntu'
|
||||||
|
@ -59,6 +49,11 @@ class TestHARelation(CharmTestCase):
|
||||||
self.get_iface_for_address.return_value = None
|
self.get_iface_for_address.return_value = None
|
||||||
self.test_config.set('vip', '10.0.3.3')
|
self.test_config.set('vip', '10.0.3.3')
|
||||||
self.test_config.set('sst-password', password)
|
self.test_config.set('sst-password', password)
|
||||||
|
self.get_hacluster_config.return_value = {
|
||||||
|
'vip': '10.0.3.3',
|
||||||
|
'ha-bindiface': 'eth0',
|
||||||
|
'ha-mcastport': 5490,
|
||||||
|
}
|
||||||
|
|
||||||
def f(k):
|
def f(k):
|
||||||
return self.test_config.get(k)
|
return self.test_config.get(k)
|
||||||
|
@ -77,7 +72,7 @@ class TestHARelation(CharmTestCase):
|
||||||
|
|
||||||
clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'}
|
clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'}
|
||||||
|
|
||||||
colocations = {'vip_mysqld': 'inf: grp_percona_cluster cl_mysql_monitor'} # noqa
|
colocations = {'colo_percona_cluster': 'inf: grp_percona_cluster cl_mysql_monitor'} # noqa
|
||||||
|
|
||||||
locations = {'loc_percona_cluster':
|
locations = {'loc_percona_cluster':
|
||||||
'grp_percona_cluster rule inf: writable eq 1'}
|
'grp_percona_cluster rule inf: writable eq 1'}
|
||||||
|
@ -101,6 +96,11 @@ class TestHARelation(CharmTestCase):
|
||||||
self.test_config.set('vip', '10.0.3.3')
|
self.test_config.set('vip', '10.0.3.3')
|
||||||
self.test_config.set('vip_cidr', '16')
|
self.test_config.set('vip_cidr', '16')
|
||||||
self.test_config.set('vip_iface', 'eth0')
|
self.test_config.set('vip_iface', 'eth0')
|
||||||
|
self.get_hacluster_config.return_value = {
|
||||||
|
'vip': '10.0.3.3',
|
||||||
|
'ha-bindiface': 'eth0',
|
||||||
|
'ha-mcastport': 5490,
|
||||||
|
}
|
||||||
|
|
||||||
def f(k):
|
def f(k):
|
||||||
return self.test_config.get(k)
|
return self.test_config.get(k)
|
||||||
|
@ -130,6 +130,11 @@ class TestHARelation(CharmTestCase):
|
||||||
self.test_config.set('vip', '10.0.3.3')
|
self.test_config.set('vip', '10.0.3.3')
|
||||||
self.test_config.set('vip_cidr', '16')
|
self.test_config.set('vip_cidr', '16')
|
||||||
self.test_config.set('vip_iface', 'eth1')
|
self.test_config.set('vip_iface', 'eth1')
|
||||||
|
self.get_hacluster_config.return_value = {
|
||||||
|
'vip': '10.0.3.3',
|
||||||
|
'ha-bindiface': 'eth1',
|
||||||
|
'ha-mcastport': 5490,
|
||||||
|
}
|
||||||
|
|
||||||
def f(k):
|
def f(k):
|
||||||
return self.test_config.get(k)
|
return self.test_config.get(k)
|
||||||
|
|
Loading…
Reference in New Issue