Switch to new canonical_url helper

This commit is contained in:
James Page
2014-07-04 12:49:08 +01:00
parent a3fe5c2d13
commit c201dcf343
8 changed files with 180 additions and 50 deletions

View File

@@ -28,7 +28,7 @@ def _validate_cidr(network):
def get_address_in_network(network, fallback=None, fatal=False):
"""
Get an IPv4 address within the network from the host.
Get an IPv4 or IPv6 address within the network from the host.
:param network (str): CIDR presentation format. For example,
'192.168.1.0/24'.
@@ -51,14 +51,23 @@ def get_address_in_network(network, fallback=None, fatal=False):
not_found_error_out()
_validate_cidr(network)
network = netaddr.IPNetwork(network)
for iface in netifaces.interfaces():
addresses = netifaces.ifaddresses(iface)
if netifaces.AF_INET in addresses:
if network.version == 4 and netifaces.AF_INET in addresses:
addr = addresses[netifaces.AF_INET][0]['addr']
netmask = addresses[netifaces.AF_INET][0]['netmask']
cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
if cidr in netaddr.IPNetwork(network):
if cidr in network:
return str(cidr.ip)
if network.version == 6 and netifaces.AF_INET6 in addresses:
for addr in addresses[netifaces.AF_INET6]:
if 'fe80' not in addr['addr']:
netmask = addr['netmask']
cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
netmask))
if cidr in network:
return str(cidr.ip)
if fallback is not None:
return fallback
@@ -69,6 +78,17 @@ def get_address_in_network(network, fallback=None, fatal=False):
return None
def is_ipv6(address):
'''Determine whether provided address is IPv6 or not'''
try:
address = netaddr.IPAddress(address)
except netaddr.AddrFormatError:
# probably a hostname - so not an address at all!
return False
else:
return address.version == 6
def is_address_in_network(network, address):
"""
Determine whether the provided address is within a network range.
@@ -93,3 +113,60 @@ def is_address_in_network(network, address):
return True
else:
return False
def _get_for_address(address, key):
"""Retrieve an attribute of or the physical interface that
the IP address provided could be bound to.
:param address (str): An individual IPv4 or IPv6 address without a net
mask or subnet prefix. For example, '192.168.1.1'.
:param key: 'iface' for the physical interface name or an attribute
of the configured interface, for example 'netmask'.
:returns str: Requested attribute or None if address is not bindable.
"""
address = netaddr.IPAddress(address)
for iface in netifaces.interfaces():
addresses = netifaces.ifaddresses(iface)
if address.version == 4 and netifaces.AF_INET in addresses:
addr = addresses[netifaces.AF_INET][0]['addr']
netmask = addresses[netifaces.AF_INET][0]['netmask']
cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
if address in cidr:
if key == 'iface':
return iface
else:
return addresses[netifaces.AF_INET][0][key]
if address.version == 6 and netifaces.AF_INET6 in addresses:
for addr in addresses[netifaces.AF_INET6]:
if 'fe80' not in addr['addr']:
cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
addr['netmask']))
if address in cidr:
if key == 'iface':
return iface
else:
return addr[key]
return None
def get_iface_for_address(address):
"""Determine the physical interface to which an IP address could be bound
:param address (str): An individual IPv4 or IPv6 address without a net
mask or subnet prefix. For example, '192.168.1.1'.
:returns str: Interface name or None if address is not bindable.
"""
return _get_for_address(address, 'iface')
def get_netmask_for_address(address):
"""Determine the netmask of the physical interface to which and IP address
could be bound
:param address (str): An individual IPv4 or IPv6 address without a net
mask or subnet prefix. For example, '192.168.1.1'.
:returns str: Netmask of configured interface or None if address is
not bindable.
"""
return _get_for_address(address, 'netmask')

View File

@@ -148,7 +148,7 @@ class SharedDBContext(OSContextGenerator):
if self.relation_prefix is not None:
hostname_key = "{}_hostname".format(self.relation_prefix)
else:
hostname_key = "hostname"
hostname_key = "hostname"
access_hostname = get_address_in_network(access_network,
unit_get('private-address'))
set_hostname = relation_get(attribute=hostname_key,
@@ -400,7 +400,9 @@ class HAProxyContext(OSContextGenerator):
cluster_hosts = {}
l_unit = local_unit().replace('/', '-')
cluster_hosts[l_unit] = unit_get('private-address')
cluster_hosts[l_unit] = \
get_address_in_network(config('os-internal-network'),
unit_get('private-address'))
for rid in relation_ids('cluster'):
for unit in related_units(rid):

View File

@@ -0,0 +1,75 @@
from charmhelpers.core.hookenv import (
config,
unit_get,
)
from charmhelpers.contrib.network.ip import (
get_address_in_network,
is_address_in_network,
is_ipv6,
)
from charmhelpers.contrib.hahelpers.cluster import is_clustered
PUBLIC = 'public'
INTERNAL = 'int'
ADMIN = 'admin'
_address_map = {
PUBLIC: {
'config': 'os-public-network',
'fallback': 'public-address'
},
INTERNAL: {
'config': 'os-internal-network',
'fallback': 'private-address'
},
ADMIN: {
'config': 'os-admin-network',
'fallback': 'private-address'
}
}
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.
:configs OSTemplateRenderer: A config tempating object to inspect for
a complete https context.
:endpoint_type str: The endpoint type to resolve.
:returns str: Base URL for services on the current service unit.
'''
scheme = 'http'
if 'https' in configs.complete_contexts():
scheme = 'https'
address = resolve_address(endpoint_type)
if is_ipv6(address):
address = "[{}]".format(address)
return '%s://%s' % (scheme, address)
def resolve_address(endpoint_type=PUBLIC):
resolved_address = None
if is_clustered():
if config(_address_map[endpoint_type]['config']) is None:
# Assume vip is simple and pass back directly
resolved_address = config('vip')
else:
for vip in config('vip').split():
if is_address_in_network(
config(_address_map[endpoint_type]['config']),
vip):
resolved_address = vip
else:
resolved_address = get_address_in_network(
config(_address_map[endpoint_type]['config']),
unit_get(_address_map[endpoint_type]['fallback'])
)
if resolved_address is None:
raise ValueError('Unable to resolve a suitable IP address'
' based on charm state and configuration')
else:
return resolved_address

View File

@@ -27,7 +27,12 @@ listen stats :8888
{% if units -%}
{% for service, ports in service_ports.iteritems() -%}
listen {{ service }} 0.0.0.0:{{ ports[0] }}
listen {{ service }}_ipv4 0.0.0.0:{{ ports[0] }}
balance roundrobin
{% for unit, address in units.iteritems() -%}
server {{ unit }} {{ address }}:{{ ports[1] }} check
{% endfor %}
listen {{ service }}_ipv6 :::{{ ports[0] }}
balance roundrobin
{% for unit, address in units.iteritems() -%}
server {{ unit }} {{ address }}:{{ ports[1] }} check

View File

@@ -1,25 +0,0 @@
from netaddr import IPAddress, IPNetwork
class VIPConfiguration():
def __init__(self, configuration):
self.vip = []
for vip in configuration.split():
self.vips.append(IPAddress(vip))
def getVIP(self, network):
''' Determine the VIP for the provided network
:network str: CIDR presented network, e.g. 192.168.1.1/24
:returns str: IP address of VIP in provided network or None
'''
network = IPNetwork(network)
for vip in self.vips:
if vip in network:
return str(vip)
return None
def getNIC(self, network):
''' Determine the physical network interface in use
for the specified network'''

View File

@@ -73,14 +73,17 @@ from nova_cc_utils import (
)
from charmhelpers.contrib.hahelpers.cluster import (
canonical_url,
eligible_leader,
get_hacluster_config,
is_leader,
)
from charmhelpers.payload.execd import execd_preinstall
from charmhelpers.contrib.network.ip import get_address_in_network
from charmhelpers.contrib.openstack.ip import (
canonical_url,
PUBLIC, INTERNAL, ADMIN
)
hooks = Hooks()
CONFIGS = register_configs()
@@ -233,15 +236,9 @@ def image_service_changed():
def identity_joined(rid=None):
if not eligible_leader(CLUSTER_RES):
return
public_url = canonical_url(CONFIGS,
address=get_address_in_network(config('os-public-network'),
unit_get('public-address')))
internal_url = canonical_url(CONFIGS,
address=get_address_in_network(config('os-internal-network'),
unit_get('private-address')))
admin_url = canonical_url(CONFIGS,
address=get_address_in_network(config('os-admin-network'),
unit_get('private-address')))
public_url = canonical_url(CONFIGS, PUBLIC)
internal_url = canonical_url(CONFIGS, INTERNAL)
admin_url = canonical_url(CONFIGS, ADMIN)
relation_set(relation_id=rid, **determine_endpoints(public_url,
internal_url,
admin_url))
@@ -332,9 +329,7 @@ def neutron_settings():
'quantum_plugin': neutron_plugin(),
'region': config('region'),
'quantum_security_groups': config('quantum-security-groups'),
'quantum_url': "{}:{}".format(canonical_url(CONFIGS,
get_address_in_network(config('os-internal-network'),
unit_get('private-address'))),
'quantum_url': "{}:{}".format(canonical_url(CONFIGS, INTERNAL),
str(api_port('neutron-server'))),
})
neutron_url = urlparse(neutron_settings['quantum_url'])
@@ -510,9 +505,7 @@ def nova_vmware_relation_joined(rid=None):
rel_settings.update({
'quantum_plugin': neutron_plugin(),
'quantum_security_groups': config('quantum-security-groups'),
'quantum_url': "{}:{}".format(canonical_url(CONFIGS,
get_address_in_network(config('os-internal-network'),
unit_get('private-address'))),
'quantum_url': "{}:{}".format(canonical_url(CONFIGS, INTERNAL),
str(api_port('neutron-server')))})
relation_set(relation_id=rid, **rel_settings)
@@ -542,7 +535,7 @@ def neutron_api_relation_joined(rid=None):
service_stop('neutron-server')
for id_rid in relation_ids('identity-service'):
identity_joined(rid=id_rid)
nova_url = canonical_url(CONFIGS) + ":8774/v2"
nova_url = canonical_url(CONFIGS, INTERNAL) + ":8774/v2"
relation_set(relation_id=rid, nova_url=nova_url)

View File

@@ -42,7 +42,6 @@ from charmhelpers.core.host import (
service_start,
)
import nova_cc_context
TEMPLATES = 'templates/'

View File

@@ -441,6 +441,7 @@ class NovaCCUtilsTests(CharmTestCase):
self.relation_ids.return_value = []
self.assertEquals(
BASE_ENDPOINTS, utils.determine_endpoints('http://foohost.com',
'http://foohost.com',
'http://foohost.com'))
def test_determine_endpoints_nova_volume(self):
@@ -458,6 +459,7 @@ class NovaCCUtilsTests(CharmTestCase):
'nova-volume_service': 'nova-volume'})
self.assertEquals(
endpoints, utils.determine_endpoints('http://foohost.com',
'http://foohost.com',
'http://foohost.com'))
def test_determine_endpoints_quantum_neutron(self):
@@ -473,6 +475,7 @@ class NovaCCUtilsTests(CharmTestCase):
'quantum_service': 'quantum'})
self.assertEquals(
endpoints, utils.determine_endpoints('http://foohost.com',
'http://foohost.com',
'http://foohost.com'))
def test_determine_endpoints_neutron_api_rel(self):
@@ -488,6 +491,7 @@ class NovaCCUtilsTests(CharmTestCase):
'quantum_service': None})
self.assertEquals(
endpoints, utils.determine_endpoints('http://foohost.com',
'http://foohost.com',
'http://foohost.com'))
@patch.object(utils, 'known_hosts')