Fix support for network-spaces
Fix misc issues with use of Juju 2.0 network spaces: - Ensure that wsrep address for local unit is correctly set using the binding for the cluster peer relation. - Correctly set the DB access hostname for db and db-admin relation types. This includes a little refactoring to support reuse within the charm. Closes-Bug: 1657305 Change-Id: Id1a800e2ada6fd196422b003fd8e251cab5ad724
This commit is contained in:
parent
ae533965d6
commit
8815918715
@ -77,7 +77,7 @@ from charmhelpers.contrib.openstack.ha.utils import (
|
||||
from percona_utils import (
|
||||
determine_packages,
|
||||
setup_percona_repo,
|
||||
get_host_ip,
|
||||
resolve_hostname_to_ip,
|
||||
get_cluster_hosts,
|
||||
configure_sstuser,
|
||||
configure_mysql_root_password,
|
||||
@ -96,6 +96,7 @@ from percona_utils import (
|
||||
resolve_cnf_file,
|
||||
create_binlogs_directory,
|
||||
bootstrap_pxc,
|
||||
get_cluster_host_ip,
|
||||
)
|
||||
|
||||
|
||||
@ -138,7 +139,7 @@ def render_config(clustered=False, hosts=None):
|
||||
|
||||
context = {
|
||||
'cluster_name': 'juju_cluster',
|
||||
'private_address': get_host_ip(),
|
||||
'private_address': get_cluster_host_ip(),
|
||||
'clustered': clustered,
|
||||
'cluster_hosts': ",".join(hosts),
|
||||
'sst_method': config('sst-method'),
|
||||
@ -328,17 +329,7 @@ def cluster_joined():
|
||||
relation_settings = {'private-address': addr,
|
||||
'hostname': socket.gethostname()}
|
||||
|
||||
cluster_network = config('cluster-network')
|
||||
if cluster_network:
|
||||
cluster_addr = get_address_in_network(cluster_network, fatal=True)
|
||||
relation_settings['cluster-address'] = cluster_addr
|
||||
else:
|
||||
try:
|
||||
cluster_addr = network_get_primary_address('cluster')
|
||||
relation_settings['cluster-address'] = cluster_addr
|
||||
except NotImplementedError:
|
||||
# NOTE(jamespage): skip - fallback to previous behaviour
|
||||
pass
|
||||
relation_settings['cluster-address'] = get_cluster_host_ip()
|
||||
|
||||
log("Setting cluster relation: '%s'" % (relation_settings),
|
||||
level=INFO)
|
||||
@ -383,22 +374,17 @@ def db_changed(relation_id=None, unit=None, admin=None):
|
||||
relation_clear(relation_id)
|
||||
return
|
||||
|
||||
if is_clustered():
|
||||
db_host = config('vip')
|
||||
else:
|
||||
if config('prefer-ipv6'):
|
||||
db_host = get_ipv6_addr(exc_list=[config('vip')])[0]
|
||||
else:
|
||||
db_host = unit_get('private-address')
|
||||
|
||||
if admin not in [True, False]:
|
||||
admin = relation_type() == 'db-admin'
|
||||
|
||||
db_name, _ = remote_unit().split("/")
|
||||
username = db_name
|
||||
db_helper = get_db_helper()
|
||||
addr = relation_get('private-address', unit=unit, rid=relation_id)
|
||||
password = db_helper.configure_db(addr, db_name, username, admin=admin)
|
||||
|
||||
db_host = get_db_host(addr, interface=relation_type())
|
||||
|
||||
relation_set(relation_id=relation_id,
|
||||
relation_settings={
|
||||
'user': username,
|
||||
@ -409,7 +395,7 @@ def db_changed(relation_id=None, unit=None, admin=None):
|
||||
|
||||
|
||||
def get_db_host(client_hostname, interface='shared-db'):
|
||||
"""Get address of local database host.
|
||||
"""Get address of local database host for use by db clients
|
||||
|
||||
If an access-network has been configured, expect selected address to be
|
||||
on that network. If none can be found, revert to primary address.
|
||||
@ -417,16 +403,24 @@ def get_db_host(client_hostname, interface='shared-db'):
|
||||
If network spaces are supported (Juju >= 2.0), use network-get to
|
||||
retrieve the network binding for the interface.
|
||||
|
||||
If DNSHA is set pass os-access-hostname
|
||||
|
||||
If vip(s) are configured, chooses first available.
|
||||
|
||||
@param client_hostname: hostname of client side relation setting hostname.
|
||||
Only used if access-network is configured
|
||||
@param interface: Network space binding to check.
|
||||
Usually the relationship name.
|
||||
@returns IP for use with db clients
|
||||
"""
|
||||
vips = config('vip').split() if config('vip') else []
|
||||
dns_ha = config('dns-ha')
|
||||
access_network = config('access-network')
|
||||
client_ip = get_host_ip(client_hostname)
|
||||
if is_clustered() and dns_ha:
|
||||
log("Using DNS HA hostname: {}".format(config('os-access-hostname')))
|
||||
return config('os-access-hostname')
|
||||
elif access_network:
|
||||
client_ip = resolve_hostname_to_ip(client_hostname)
|
||||
if is_address_in_network(access_network, client_ip):
|
||||
if is_clustered():
|
||||
for vip in vips:
|
||||
@ -463,6 +457,7 @@ def get_db_host(client_hostname, interface='shared-db'):
|
||||
if config('prefer-ipv6'):
|
||||
return get_ipv6_addr(exc_list=vips)[0]
|
||||
|
||||
# Last resort
|
||||
return unit_get('private-address')
|
||||
|
||||
|
||||
@ -523,7 +518,7 @@ def shared_db_changed(relation_id=None, unit=None):
|
||||
database = settings['database']
|
||||
username = settings['username']
|
||||
|
||||
normalized_address = get_host_ip(hostname)
|
||||
normalized_address = resolve_hostname_to_ip(hostname)
|
||||
if access_network and not is_address_in_network(access_network,
|
||||
normalized_address):
|
||||
# NOTE: for configurations using access-network, only setup
|
||||
@ -583,7 +578,7 @@ def shared_db_changed(relation_id=None, unit=None):
|
||||
hostname = databases[db]['hostname']
|
||||
username = databases[db]['username']
|
||||
|
||||
normalized_address = get_host_ip(hostname)
|
||||
normalized_address = resolve_hostname_to_ip(hostname)
|
||||
if (access_network and
|
||||
not is_address_in_network(access_network,
|
||||
normalized_address)):
|
||||
|
@ -102,7 +102,12 @@ def setup_percona_repo():
|
||||
subprocess.check_call(['apt-key', 'add', KEY])
|
||||
|
||||
|
||||
def get_host_ip(hostname=None):
|
||||
def resolve_hostname_to_ip(hostname):
|
||||
"""Resolve hostname to IP
|
||||
|
||||
@param hostname: hostname to be resolved
|
||||
@returns IP address
|
||||
"""
|
||||
try:
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
@ -110,12 +115,6 @@ def get_host_ip(hostname=None):
|
||||
fatal=True)
|
||||
import dns.resolver
|
||||
|
||||
if config('prefer-ipv6'):
|
||||
# Ensure we have a valid ipv6 address configured
|
||||
get_ipv6_addr(exc_list=[config('vip')], fatal=True)[0]
|
||||
return socket.gethostname()
|
||||
|
||||
hostname = hostname or unit_get('private-address')
|
||||
try:
|
||||
# Test to see if already an IPv4 address
|
||||
socket.inet_aton(hostname)
|
||||
@ -154,23 +153,15 @@ def is_sufficient_peers():
|
||||
def get_cluster_hosts():
|
||||
hosts_map = {}
|
||||
|
||||
if config('cluster-network'):
|
||||
hostname = get_address_in_network(config('cluster-network'),
|
||||
fatal=True)
|
||||
else:
|
||||
try:
|
||||
hostname = network_get_primary_address('cluster')
|
||||
except NotImplementedError:
|
||||
# NOTE(jamespage): skip - fallback to previous behaviour
|
||||
hostname = get_host_ip()
|
||||
local_cluster_address = get_cluster_host_ip()
|
||||
|
||||
# We need to add this localhost dns name to /etc/hosts along with peer
|
||||
# hosts to ensure percona gets consistently resolved addresses.
|
||||
if config('prefer-ipv6'):
|
||||
addr = get_ipv6_addr(exc_list=[config('vip')], fatal=True)[0]
|
||||
hosts_map = {addr: hostname}
|
||||
hosts_map = {addr: socket.gethostname()}
|
||||
|
||||
hosts = [hostname]
|
||||
hosts = [local_cluster_address]
|
||||
for relid in relation_ids('cluster'):
|
||||
for unit in related_units(relid):
|
||||
rdata = relation_get(unit=unit, rid=relid)
|
||||
@ -192,7 +183,7 @@ def get_cluster_hosts():
|
||||
hosts_map[cluster_address] = hostname
|
||||
hosts.append(hostname)
|
||||
else:
|
||||
hosts.append(get_host_ip(cluster_address))
|
||||
hosts.append(resolve_hostname_to_ip(cluster_address))
|
||||
|
||||
if hosts_map:
|
||||
update_hosts_file(hosts_map)
|
||||
@ -570,3 +561,24 @@ def create_binlogs_directory():
|
||||
|
||||
if not os.path.isdir(binlogs_directory):
|
||||
mkdir(binlogs_directory, 'mysql', 'mysql', 0o750)
|
||||
|
||||
|
||||
def get_cluster_host_ip():
|
||||
"""Get the this host's IP address for use with percona cluster peers
|
||||
|
||||
@returns IP to pass to cluster peers
|
||||
"""
|
||||
|
||||
cluster_network = config('cluster-network')
|
||||
if cluster_network:
|
||||
cluster_addr = get_address_in_network(cluster_network, fatal=True)
|
||||
else:
|
||||
try:
|
||||
cluster_addr = network_get_primary_address('cluster')
|
||||
except NotImplementedError:
|
||||
# NOTE(jamespage): fallback to previous behaviour
|
||||
cluster_addr = resolve_hostname_to_ip(
|
||||
unit_get('private-address')
|
||||
)
|
||||
|
||||
return cluster_addr
|
||||
|
@ -26,7 +26,7 @@ TO_PATCH = ['log', 'config',
|
||||
'network_get_primary_address',
|
||||
'resolve_network_cidr',
|
||||
'unit_get',
|
||||
'get_host_ip',
|
||||
'resolve_hostname_to_ip',
|
||||
'is_clustered',
|
||||
'get_ipv6_addr',
|
||||
'get_hacluster_config',
|
||||
@ -165,7 +165,7 @@ class TestHostResolution(CharmTestCase):
|
||||
Ensure that with nothing other than defaults private-address is used
|
||||
'''
|
||||
self.unit_get.return_value = 'mydbhost'
|
||||
self.get_host_ip.return_value = '10.0.0.2'
|
||||
self.resolve_hostname_to_ip.return_value = '10.0.0.2'
|
||||
self.assertEqual(hooks.get_db_host('myclient'), 'mydbhost')
|
||||
|
||||
def test_get_db_host_network_spaces(self):
|
||||
@ -173,7 +173,7 @@ class TestHostResolution(CharmTestCase):
|
||||
Ensure that if the shared-db relation is bound, its bound address
|
||||
is used
|
||||
'''
|
||||
self.get_host_ip.return_value = '10.0.0.2'
|
||||
self.resolve_hostname_to_ip.return_value = '10.0.0.2'
|
||||
self.network_get_primary_address.side_effect = None
|
||||
self.network_get_primary_address.return_value = '192.168.20.2'
|
||||
self.assertEqual(hooks.get_db_host('myclient'), '192.168.20.2')
|
||||
@ -184,7 +184,7 @@ class TestHostResolution(CharmTestCase):
|
||||
Ensure that if the shared-db relation is bound and the unit is
|
||||
clustered, that the correct VIP is chosen
|
||||
'''
|
||||
self.get_host_ip.return_value = '10.0.0.2'
|
||||
self.resolve_hostname_to_ip.return_value = '10.0.0.2'
|
||||
self.is_clustered.return_value = True
|
||||
self.test_config.set('vip', '10.0.0.100 192.168.20.200')
|
||||
self.network_get_primary_address.side_effect = None
|
||||
|
@ -73,23 +73,24 @@ class UtilsTests(unittest.TestCase):
|
||||
self.assertEqual(lines[1], "%s %s\n" % (map.items()[0]))
|
||||
self.assertEqual(lines[4], "%s %s\n" % (map.items()[3]))
|
||||
|
||||
@mock.patch("percona_utils.get_cluster_host_ip")
|
||||
@mock.patch("percona_utils.log")
|
||||
@mock.patch("percona_utils.config")
|
||||
@mock.patch("percona_utils.update_hosts_file")
|
||||
@mock.patch("percona_utils.get_host_ip")
|
||||
@mock.patch("percona_utils.relation_get")
|
||||
@mock.patch("percona_utils.related_units")
|
||||
@mock.patch("percona_utils.relation_ids")
|
||||
def test_get_cluster_hosts(self, mock_rel_ids, mock_rel_units,
|
||||
mock_rel_get, mock_get_host_ip,
|
||||
mock_rel_get,
|
||||
mock_update_hosts_file, mock_config,
|
||||
mock_log):
|
||||
mock_log,
|
||||
mock_get_cluster_host_ip):
|
||||
mock_rel_ids.return_value = [1]
|
||||
mock_rel_units.return_value = [2]
|
||||
mock_get_host_ip.return_value = 'hostA'
|
||||
mock_get_cluster_host_ip.return_value = '10.2.0.1'
|
||||
|
||||
def _mock_rel_get(*args, **kwargs):
|
||||
return {'private-address': '0.0.0.0'}
|
||||
return {'private-address': '10.2.0.2'}
|
||||
|
||||
mock_rel_get.side_effect = _mock_rel_get
|
||||
mock_config.side_effect = lambda k: False
|
||||
@ -98,25 +99,29 @@ class UtilsTests(unittest.TestCase):
|
||||
|
||||
self.assertFalse(mock_update_hosts_file.called)
|
||||
mock_rel_get.assert_called_with(rid=1, unit=2)
|
||||
self.assertEqual(hosts, ['hostA', 'hostA'])
|
||||
self.assertEqual(hosts, ['10.2.0.1', '10.2.0.2'])
|
||||
|
||||
@mock.patch.object(percona_utils, 'socket')
|
||||
@mock.patch("percona_utils.get_cluster_host_ip")
|
||||
@mock.patch.object(percona_utils, 'get_ipv6_addr')
|
||||
@mock.patch.object(percona_utils, 'log')
|
||||
@mock.patch.object(percona_utils, 'config')
|
||||
@mock.patch.object(percona_utils, 'update_hosts_file')
|
||||
@mock.patch.object(percona_utils, 'get_host_ip')
|
||||
@mock.patch.object(percona_utils, 'relation_get')
|
||||
@mock.patch.object(percona_utils, 'related_units')
|
||||
@mock.patch.object(percona_utils, 'relation_ids')
|
||||
def test_get_cluster_hosts_ipv6(self, mock_rel_ids, mock_rel_units,
|
||||
mock_rel_get, mock_get_host_ip,
|
||||
mock_rel_get,
|
||||
mock_update_hosts_file, mock_config,
|
||||
mock_log, mock_get_ipv6_addr):
|
||||
mock_log, mock_get_ipv6_addr,
|
||||
mock_get_cluster_host_ip,
|
||||
mock_socket):
|
||||
ipv6addr = '2001:db8:1:0:f816:3eff:fe79:cd'
|
||||
mock_get_ipv6_addr.return_value = [ipv6addr]
|
||||
mock_rel_ids.return_value = [88]
|
||||
mock_rel_units.return_value = [1, 2]
|
||||
mock_get_host_ip.return_value = 'hostA'
|
||||
mock_get_cluster_host_ip.return_value = 'hostA'
|
||||
mock_socket.gethostname.return_value = 'hostA'
|
||||
|
||||
def _mock_rel_get(*args, **kwargs):
|
||||
host_suffix = 'BC'
|
||||
|
Loading…
Reference in New Issue
Block a user