diff --git a/charm-helpers.yaml b/charm-helpers.yaml index 884ee2ba..81810093 100644 --- a/charm-helpers.yaml +++ b/charm-helpers.yaml @@ -1,4 +1,4 @@ -branch: lp:~james-page/charm-helpers/network-splits +branch: lp:charm-helpers destination: hooks/charmhelpers include: - core diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py index dd89f347..505de6b2 100644 --- a/hooks/charmhelpers/contrib/hahelpers/cluster.py +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -146,12 +146,12 @@ def get_hacluster_config(): Obtains all relevant configuration from charm configuration required for initiating a relation to hacluster: - ha-bindiface, ha-mcastport, vip, vip_iface, vip_cidr + ha-bindiface, ha-mcastport, vip returns: dict: A dict containing settings keyed by setting name. raises: HAIncompleteConfig if settings are missing. ''' - settings = ['ha-bindiface', 'ha-mcastport', 'vip', 'vip_iface', 'vip_cidr'] + settings = ['ha-bindiface', 'ha-mcastport', 'vip'] conf = {} for setting in settings: conf[setting] = config_get(setting) @@ -163,7 +163,7 @@ def get_hacluster_config(): return conf -def canonical_url(configs, vip_setting='vip', address=None): +def canonical_url(configs, vip_setting='vip'): ''' Returns the correct HTTP URL to this host given the state of HTTPS configuration and hacluster. @@ -180,5 +180,5 @@ def canonical_url(configs, vip_setting='vip', address=None): if is_clustered(): addr = config_get(vip_setting) else: - addr = address or unit_get('private-address') + addr = unit_get('private-address') return '%s://%s' % (scheme, addr) diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py index f2fa263f..0972e91a 100644 --- a/hooks/charmhelpers/contrib/network/ip.py +++ b/hooks/charmhelpers/contrib/network/ip.py @@ -1,5 +1,7 @@ import sys +from functools import partial + from charmhelpers.fetch import apt_install from charmhelpers.core.hookenv import ( ERROR, log, @@ -28,7 +30,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 +53,22 @@ 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 not addr['addr'].startswith('fe80'): + cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'], + addr['netmask'])) + if cidr in network: + return str(cidr.ip) if fallback is not None: return fallback @@ -69,6 +79,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 +114,43 @@ 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 not addr['addr'].startswith('fe80'): + cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'], + addr['netmask'])) + if address in cidr: + if key == 'iface': + return iface + else: + return addr[key] + return None + + +get_iface_for_address = partial(_get_for_address, key='iface') + +get_netmask_for_address = partial(_get_for_address, key='netmask') diff --git a/hooks/charmhelpers/contrib/network/ovs/__init__.py b/hooks/charmhelpers/contrib/network/ovs/__init__.py index 5eba8376..8f8a5230 100644 --- a/hooks/charmhelpers/contrib/network/ovs/__init__.py +++ b/hooks/charmhelpers/contrib/network/ovs/__init__.py @@ -21,12 +21,16 @@ def del_bridge(name): subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-br", name]) -def add_bridge_port(name, port): +def add_bridge_port(name, port, promisc=False): ''' Add a port to the named openvswitch bridge ''' log('Adding port {} to bridge {}'.format(port, name)) subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-port", name, port]) subprocess.check_call(["ip", "link", "set", port, "up"]) + if promisc: + subprocess.check_call(["ip", "link", "set", port, "promisc", "on"]) + else: + subprocess.check_call(["ip", "link", "set", port, "promisc", "off"]) def del_bridge_port(name, port): @@ -35,6 +39,7 @@ def del_bridge_port(name, port): subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-port", name, port]) subprocess.check_call(["ip", "link", "set", port, "down"]) + subprocess.check_call(["ip", "link", "set", port, "promisc", "off"]) def set_manager(manager): diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 9da9c1ff..92c41b23 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -25,6 +25,7 @@ from charmhelpers.core.hookenv import ( unit_get, unit_private_ip, ERROR, + INFO ) from charmhelpers.contrib.hahelpers.cluster import ( @@ -148,7 +149,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 +401,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): @@ -712,7 +715,7 @@ class SubordinateConfigContext(OSContextGenerator): self.interface = interface def __call__(self): - ctxt = {} + ctxt = {'sections': {}} for rid in relation_ids(self.interface): for unit in related_units(rid): sub_config = relation_get('subordinate_configuration', @@ -738,10 +741,14 @@ class SubordinateConfigContext(OSContextGenerator): sub_config = sub_config[self.config_file] for k, v in sub_config.iteritems(): - ctxt[k] = v + if k == 'sections': + for section, config_dict in v.iteritems(): + log("adding section '%s'" % (section)) + ctxt[k][section] = config_dict + else: + ctxt[k] = v - if not ctxt: - ctxt['sections'] = {} + log("%d section(s) found" % (len(ctxt['sections'])), level=INFO) return ctxt diff --git a/hooks/charmhelpers/contrib/openstack/vip.py b/hooks/charmhelpers/contrib/openstack/vip.py deleted file mode 100644 index d8c42f90..00000000 --- a/hooks/charmhelpers/contrib/openstack/vip.py +++ /dev/null @@ -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''' - diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index 8b617a42..d934f940 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -322,6 +322,10 @@ def cmp_pkgrevno(package, revno, pkgcache=None): import apt_pkg if not pkgcache: apt_pkg.init() + # Force Apt to build its cache in memory. That way we avoid race + # conditions with other applications building the cache in the same + # place. + apt_pkg.config.set("Dir::Cache::pkgcache", "") pkgcache = apt_pkg.Cache() pkg = pkgcache[package] return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)