[xianghui,dosaboy,r=james-page,t=gema] Add IPv6 support using prefer-ipv6 flag
This commit is contained in:
commit
5ae1136076
2
.bzrignore
Normal file
2
.bzrignore
Normal file
@ -0,0 +1,2 @@
|
||||
bin
|
||||
.coverage
|
12
config.yaml
12
config.yaml
@ -134,3 +134,15 @@ options:
|
||||
description: |
|
||||
The IP address and netmask of the cluster (back-side) network (e.g.,
|
||||
192.168.0.0/24)
|
||||
prefer-ipv6:
|
||||
type: boolean
|
||||
default: False
|
||||
description: |
|
||||
If True enables IPv6 support. The charm will expect network interfaces
|
||||
to be configured with an IPv6 address. If set to False (default) IPv4
|
||||
is expected.
|
||||
.
|
||||
NOTE: these charms do not currently support IPv6 privacy extension. In
|
||||
order for this charm to function correctly, the privacy extension must be
|
||||
disabled and a non-temporary address must be configured/available on
|
||||
your network interface.
|
||||
|
@ -1,11 +1,16 @@
|
||||
import glob
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from functools import partial
|
||||
|
||||
from charmhelpers.core.hookenv import unit_get
|
||||
from charmhelpers.fetch import apt_install
|
||||
from charmhelpers.core.hookenv import (
|
||||
ERROR, log,
|
||||
WARNING,
|
||||
ERROR,
|
||||
log
|
||||
)
|
||||
|
||||
try:
|
||||
@ -164,13 +169,14 @@ def format_ipv6_addr(address):
|
||||
if is_ipv6(address):
|
||||
address = "[%s]" % address
|
||||
else:
|
||||
log("Not an valid ipv6 address: %s" % address,
|
||||
level=ERROR)
|
||||
log("Not a valid ipv6 address: %s" % address, level=WARNING)
|
||||
address = None
|
||||
|
||||
return address
|
||||
|
||||
|
||||
def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, fatal=True, exc_list=None):
|
||||
def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
|
||||
fatal=True, exc_list=None):
|
||||
"""
|
||||
Return the assigned IP address for a given interface, if any, or [].
|
||||
"""
|
||||
@ -210,26 +216,105 @@ def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, fatal=T
|
||||
if 'addr' in entry and entry['addr'] not in exc_list:
|
||||
addresses.append(entry['addr'])
|
||||
if fatal and not addresses:
|
||||
raise Exception("Interface '%s' doesn't have any %s addresses." % (iface, inet_type))
|
||||
raise Exception("Interface '%s' doesn't have any %s addresses." %
|
||||
(iface, inet_type))
|
||||
return addresses
|
||||
|
||||
get_ipv4_addr = partial(get_iface_addr, inet_type='AF_INET')
|
||||
|
||||
|
||||
def get_ipv6_addr(iface='eth0', inc_aliases=False, fatal=True, exc_list=None):
|
||||
def get_iface_from_addr(addr):
|
||||
"""Work out on which interface the provided address is configured."""
|
||||
for iface in netifaces.interfaces():
|
||||
addresses = netifaces.ifaddresses(iface)
|
||||
for inet_type in addresses:
|
||||
for _addr in addresses[inet_type]:
|
||||
_addr = _addr['addr']
|
||||
# link local
|
||||
ll_key = re.compile("(.+)%.*")
|
||||
raw = re.match(ll_key, _addr)
|
||||
if raw:
|
||||
_addr = raw.group(1)
|
||||
if _addr == addr:
|
||||
log("Address '%s' is configured on iface '%s'" %
|
||||
(addr, iface))
|
||||
return iface
|
||||
|
||||
msg = "Unable to infer net iface on which '%s' is configured" % (addr)
|
||||
raise Exception(msg)
|
||||
|
||||
|
||||
def sniff_iface(f):
|
||||
"""If no iface provided, inject net iface inferred from unit private
|
||||
address.
|
||||
"""
|
||||
Return the assigned IPv6 address for a given interface, if any, or [].
|
||||
def iface_sniffer(*args, **kwargs):
|
||||
if not kwargs.get('iface', None):
|
||||
kwargs['iface'] = get_iface_from_addr(unit_get('private-address'))
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return iface_sniffer
|
||||
|
||||
|
||||
@sniff_iface
|
||||
def get_ipv6_addr(iface=None, inc_aliases=False, fatal=True, exc_list=None,
|
||||
dynamic_only=True):
|
||||
"""Get assigned IPv6 address for a given interface.
|
||||
|
||||
Returns list of addresses found. If no address found, returns empty list.
|
||||
|
||||
If iface is None, we infer the current primary interface by doing a reverse
|
||||
lookup on the unit private-address.
|
||||
|
||||
We currently only support scope global IPv6 addresses i.e. non-temporary
|
||||
addresses. If no global IPv6 address is found, return the first one found
|
||||
in the ipv6 address list.
|
||||
"""
|
||||
addresses = get_iface_addr(iface=iface, inet_type='AF_INET6',
|
||||
inc_aliases=inc_aliases, fatal=fatal,
|
||||
exc_list=exc_list)
|
||||
remotly_addressable = []
|
||||
for address in addresses:
|
||||
if not address.startswith('fe80'):
|
||||
remotly_addressable.append(address)
|
||||
if fatal and not remotly_addressable:
|
||||
raise Exception("Interface '%s' doesn't have global ipv6 address." % iface)
|
||||
return remotly_addressable
|
||||
|
||||
if addresses:
|
||||
global_addrs = []
|
||||
for addr in addresses:
|
||||
key_scope_link_local = re.compile("^fe80::..(.+)%(.+)")
|
||||
m = re.match(key_scope_link_local, addr)
|
||||
if m:
|
||||
eui_64_mac = m.group(1)
|
||||
iface = m.group(2)
|
||||
else:
|
||||
global_addrs.append(addr)
|
||||
|
||||
if global_addrs:
|
||||
# Make sure any found global addresses are not temporary
|
||||
cmd = ['ip', 'addr', 'show', iface]
|
||||
out = subprocess.check_output(cmd)
|
||||
if dynamic_only:
|
||||
key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")
|
||||
else:
|
||||
key = re.compile("inet6 (.+)/[0-9]+ scope global.*")
|
||||
|
||||
addrs = []
|
||||
for line in out.split('\n'):
|
||||
line = line.strip()
|
||||
m = re.match(key, line)
|
||||
if m and 'temporary' not in line:
|
||||
# Return the first valid address we find
|
||||
for addr in global_addrs:
|
||||
if m.group(1) == addr:
|
||||
if not dynamic_only or \
|
||||
m.group(1).endswith(eui_64_mac):
|
||||
addrs.append(addr)
|
||||
|
||||
if addrs:
|
||||
return addrs
|
||||
|
||||
if fatal:
|
||||
raise Exception("Interface '%s' doesn't have a scope global "
|
||||
"non-temporary ipv6 address." % iface)
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def get_bridges(vnic_dir='/sys/devices/virtual/net'):
|
||||
|
@ -40,11 +40,15 @@ from charmhelpers.fetch import (
|
||||
)
|
||||
from charmhelpers.payload.execd import execd_preinstall
|
||||
from charmhelpers.contrib.openstack.alternatives import install_alternative
|
||||
from charmhelpers.contrib.network.ip import is_ipv6
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
get_ipv6_addr,
|
||||
format_ipv6_addr
|
||||
)
|
||||
|
||||
from utils import (
|
||||
render_template,
|
||||
get_public_addr,
|
||||
assert_charm_supports_ipv6
|
||||
)
|
||||
|
||||
hooks = Hooks()
|
||||
@ -77,6 +81,14 @@ def emit_cephconf():
|
||||
'ceph_public_network': config('ceph-public-network'),
|
||||
'ceph_cluster_network': config('ceph-cluster-network'),
|
||||
}
|
||||
|
||||
if config('prefer-ipv6'):
|
||||
dynamic_ipv6_address = get_ipv6_addr()[0]
|
||||
if not config('ceph-public-network'):
|
||||
cephcontext['public_addr'] = dynamic_ipv6_address
|
||||
if not config('ceph-cluster-network'):
|
||||
cephcontext['cluster_addr'] = dynamic_ipv6_address
|
||||
|
||||
# Install ceph.conf as an alternative to support
|
||||
# co-existence with other charms that write this file
|
||||
charm_ceph_conf = "/var/lib/charm/{}/ceph.conf".format(service_name())
|
||||
@ -91,6 +103,9 @@ JOURNAL_ZAPPED = '/var/lib/ceph/journal_zapped'
|
||||
|
||||
@hooks.hook('config-changed')
|
||||
def config_changed():
|
||||
if config('prefer-ipv6'):
|
||||
assert_charm_supports_ipv6()
|
||||
|
||||
log('Monitor hosts are ' + repr(get_mon_hosts()))
|
||||
|
||||
# Pre-flight checks
|
||||
@ -132,19 +147,14 @@ def config_changed():
|
||||
def get_mon_hosts():
|
||||
hosts = []
|
||||
addr = get_public_addr()
|
||||
if is_ipv6(addr):
|
||||
hosts.append('[{}]:6789'.format(addr))
|
||||
else:
|
||||
hosts.append('{}:6789'.format(addr))
|
||||
hosts.append('{}:6789'.format(format_ipv6_addr(addr) or addr))
|
||||
|
||||
for relid in relation_ids('mon'):
|
||||
for unit in related_units(relid):
|
||||
addr = relation_get('ceph-public-address', unit, relid)
|
||||
if addr is not None:
|
||||
if is_ipv6(addr):
|
||||
hosts.append('[{}]:6789'.format(addr))
|
||||
else:
|
||||
hosts.append('{}:6789'.format(addr))
|
||||
hosts.append('{}:6789'.format(
|
||||
format_ipv6_addr(addr) or addr))
|
||||
|
||||
hosts.sort()
|
||||
return hosts
|
||||
|
@ -19,7 +19,14 @@ from charmhelpers.fetch import (
|
||||
filter_installed_packages
|
||||
)
|
||||
|
||||
from charmhelpers.core.host import (
|
||||
lsb_release
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.network import ip
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
get_ipv6_addr
|
||||
)
|
||||
|
||||
TEMPLATES_DIR = 'templates'
|
||||
|
||||
@ -64,6 +71,9 @@ def get_unit_hostname():
|
||||
|
||||
@cached
|
||||
def get_host_ip(hostname=None):
|
||||
if config('prefer-ipv6'):
|
||||
return get_ipv6_addr()[0]
|
||||
|
||||
hostname = hostname or unit_get('private-address')
|
||||
try:
|
||||
# Test to see if already an IPv4 address
|
||||
@ -81,3 +91,10 @@ def get_host_ip(hostname=None):
|
||||
def get_public_addr():
|
||||
return ip.get_address_in_network(config('ceph-public-network'),
|
||||
fallback=get_host_ip())
|
||||
|
||||
|
||||
def assert_charm_supports_ipv6():
|
||||
"""Check whether we are able to support charms ipv6."""
|
||||
if lsb_release()['DISTRIB_CODENAME'].lower() < "trusty":
|
||||
raise Exception("IPv6 is not supported in the charms for Ubuntu "
|
||||
"versions less than Trusty 14.04")
|
||||
|
@ -22,6 +22,13 @@ public network = {{ ceph_public_network }}
|
||||
cluster network = {{ ceph_cluster_network }}
|
||||
{%- endif %}
|
||||
|
||||
{% if public_addr %}
|
||||
public addr = {{ public_addr }}
|
||||
{% endif %}
|
||||
{% if cluster_addr %}
|
||||
cluster addr = {{ cluster_addr }}
|
||||
{%- endif %}
|
||||
|
||||
[mon]
|
||||
keyring = /var/lib/ceph/mon/$cluster-$id/keyring
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user