Add Juju Network Space support
Juju 2.0 provides support for network spaces, allowing charm authors to support direct binding of relations and extra-bindings onto underlying network spaces. Resync charm-helpers to pickup support for new charm hook tools. Update get_db_host function to attempt to use the network space support with appropriate fallback if on an older Juju version. Add some unit tests to cover the new support. Change-Id: I28ae4beab5329eb69baddce5715b7f049af65b06
This commit is contained in:
20
README.md
20
README.md
@@ -61,6 +61,26 @@ related services:
|
||||
|
||||
juju add-relation keystone percona-cluster
|
||||
|
||||
Network Space support
|
||||
---------------------
|
||||
|
||||
This charm supports the use of Juju Network Spaces, allowing the charm to be bound to network space configurations managed directly by Juju. This is only supported with Juju 2.0 and above.
|
||||
|
||||
You can ensure that database connections are bound to a specific network space by binding the appropriate interfaces:
|
||||
|
||||
juju deploy percona-cluster --bind "shared-db=internal-space"
|
||||
|
||||
alternatively these can also be provided as part of a juju native bundle configuration:
|
||||
|
||||
percona-cluster:
|
||||
charm: cs:xenial/percona-cluster
|
||||
num_units: 1
|
||||
bindings:
|
||||
shared-db: internal-space
|
||||
|
||||
**NOTE:** Spaces must be configured in the underlying provider prior to attempting to use them.
|
||||
|
||||
**NOTE:** Existing deployments using the access-network configuration option will continue to function; this option is preferred over any network space binding provided if set.
|
||||
|
||||
Limitiations
|
||||
============
|
||||
|
1
actions/backup
Symbolic link
1
actions/backup
Symbolic link
@@ -0,0 +1 @@
|
||||
actions.py
|
@@ -191,6 +191,15 @@ get_iface_for_address = partial(_get_for_address, key='iface')
|
||||
get_netmask_for_address = partial(_get_for_address, key='netmask')
|
||||
|
||||
|
||||
def resolve_network_cidr(ip_address):
|
||||
'''
|
||||
Resolves the full address cidr of an ip_address based on
|
||||
configured network interfaces
|
||||
'''
|
||||
netmask = get_netmask_for_address(ip_address)
|
||||
return str(netaddr.IPNetwork("%s/%s" % (ip_address, netmask)).cidr)
|
||||
|
||||
|
||||
def format_ipv6_addr(address):
|
||||
"""If address is IPv6, wrap it in '[]' otherwise return None.
|
||||
|
||||
|
@@ -24,6 +24,7 @@ from charmhelpers.core.hookenv import (
|
||||
INFO,
|
||||
WARNING,
|
||||
is_leader,
|
||||
network_get_primary_address,
|
||||
)
|
||||
from charmhelpers.core.host import (
|
||||
service,
|
||||
@@ -80,6 +81,7 @@ from charmhelpers.contrib.network.ip import (
|
||||
get_netmask_for_address,
|
||||
get_ipv6_addr,
|
||||
is_address_in_network,
|
||||
resolve_network_cidr,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.charmsupport import nrpe
|
||||
@@ -365,18 +367,21 @@ def db_changed(relation_id=None, unit=None, admin=None):
|
||||
})
|
||||
|
||||
|
||||
def get_db_host(client_hostname):
|
||||
def get_db_host(client_hostname, interface='shared-db'):
|
||||
"""Get address of local database host.
|
||||
|
||||
If an access-network has been configured, expect selected address to be
|
||||
on that network. If none can be found, revert to primary address.
|
||||
|
||||
If network spaces are supported (Juju >= 2.0), use network-get to
|
||||
retrieve the network binding for the interface.
|
||||
|
||||
If vip(s) are configured, chooses first available.
|
||||
"""
|
||||
vips = config('vip').split() if config('vip') else []
|
||||
access_network = config('access-network')
|
||||
client_ip = get_host_ip(client_hostname)
|
||||
if access_network:
|
||||
client_ip = get_host_ip(client_hostname)
|
||||
if is_address_in_network(access_network, client_ip):
|
||||
if is_clustered():
|
||||
for vip in vips:
|
||||
@@ -390,6 +395,22 @@ def get_db_host(client_hostname):
|
||||
else:
|
||||
log("Client address '%s' not in access-network '%s'" %
|
||||
(client_ip, access_network), level=WARNING)
|
||||
else:
|
||||
try:
|
||||
# NOTE(jamespage)
|
||||
# Try to use network spaces to resolve binding for
|
||||
# interface, and to resolve the VIP associated with
|
||||
# the binding if provided.
|
||||
interface_binding = network_get_primary_address(interface)
|
||||
if is_clustered() and vips:
|
||||
interface_cidr = resolve_network_cidr(interface_binding)
|
||||
for vip in vips:
|
||||
if is_address_in_network(interface_cidr, vip):
|
||||
return vip
|
||||
return interface_binding
|
||||
except NotImplementedError:
|
||||
# NOTE(jamespage): skip - fallback to previous behaviour
|
||||
pass
|
||||
|
||||
if is_clustered() and vips:
|
||||
return vips[0] # NOTE on private network
|
||||
|
@@ -126,7 +126,9 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
# Charms which can not use openstack-origin, ie. many subordinates
|
||||
no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe',
|
||||
'openvswitch-odl', 'neutron-api-odl', 'odl-controller',
|
||||
'cinder-backup']
|
||||
'cinder-backup', 'nexentaedge-data',
|
||||
'nexentaedge-iscsi-gw', 'nexentaedge-swift-gw',
|
||||
'cinder-nexentaedge', 'nexentaedge-mgmt']
|
||||
|
||||
if self.openstack:
|
||||
for svc in services:
|
||||
|
@@ -22,12 +22,19 @@ TO_PATCH = ['log', 'config',
|
||||
'get_iface_for_address',
|
||||
'get_netmask_for_address',
|
||||
'is_bootstrapped',
|
||||
'is_sufficient_peers']
|
||||
'is_sufficient_peers',
|
||||
'network_get_primary_address',
|
||||
'resolve_network_cidr',
|
||||
'unit_get',
|
||||
'get_host_ip',
|
||||
'is_clustered',
|
||||
'get_ipv6_addr']
|
||||
|
||||
|
||||
class TestHaRelation(CharmTestCase):
|
||||
class TestHARelation(CharmTestCase):
|
||||
def setUp(self):
|
||||
CharmTestCase.setUp(self, hooks, TO_PATCH)
|
||||
self.network_get_primary_address.side_effect = NotImplementedError
|
||||
|
||||
@mock.patch('sys.exit')
|
||||
def test_relation_not_configured(self, exit_):
|
||||
@@ -138,3 +145,45 @@ class TestHaRelation(CharmTestCase):
|
||||
|
||||
call_args, call_kwargs = self.relation_set.call_args
|
||||
self.assertEqual(resource_params, call_kwargs['resource_params'])
|
||||
|
||||
|
||||
class TestHostResolution(CharmTestCase):
|
||||
def setUp(self):
|
||||
CharmTestCase.setUp(self, hooks, TO_PATCH)
|
||||
self.network_get_primary_address.side_effect = NotImplementedError
|
||||
self.is_clustered.return_value = False
|
||||
self.config.side_effect = self.test_config.get
|
||||
self.test_config.set('prefer-ipv6', False)
|
||||
|
||||
def test_get_db_host_defaults(self):
|
||||
'''
|
||||
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.assertEqual(hooks.get_db_host('myclient'), 'mydbhost')
|
||||
|
||||
def test_get_db_host_network_spaces(self):
|
||||
'''
|
||||
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.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')
|
||||
self.network_get_primary_address.assert_called_with('shared-db')
|
||||
|
||||
def test_get_db_host_network_spaces_clustered(self):
|
||||
'''
|
||||
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.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
|
||||
self.network_get_primary_address.return_value = '192.168.20.2'
|
||||
self.resolve_network_cidr.return_value = '192.168.20.2/24'
|
||||
self.assertEqual(hooks.get_db_host('myclient'), '192.168.20.200')
|
||||
self.network_get_primary_address.assert_called_with('shared-db')
|
||||
|
Reference in New Issue
Block a user