Merge "Correctly configure IPv6 addresses on upgrades" into stable/newton
This commit is contained in:
commit
7bdb6fbb07
@ -1364,15 +1364,6 @@ class DeviceManager(object):
|
|||||||
|
|
||||||
def plug(self, network, port, interface_name):
|
def plug(self, network, port, interface_name):
|
||||||
"""Plug device settings for the network's DHCP on this host."""
|
"""Plug device settings for the network's DHCP on this host."""
|
||||||
# Disable acceptance of RAs in the namespace so we don't
|
|
||||||
# auto-configure an IPv6 address since we explicitly configure
|
|
||||||
# them on the device. This must be done before any interfaces
|
|
||||||
# are plugged since it could receive an RA by the time
|
|
||||||
# plug() returns, so we have to create the namespace first.
|
|
||||||
if network.namespace:
|
|
||||||
ip_lib.IPWrapper().ensure_namespace(network.namespace)
|
|
||||||
self.driver.configure_ipv6_ra(network.namespace, 'default',
|
|
||||||
n_const.ACCEPT_RA_DISABLED)
|
|
||||||
self.driver.plug(network.id,
|
self.driver.plug(network.id,
|
||||||
port.id,
|
port.id,
|
||||||
interface_name,
|
interface_name,
|
||||||
@ -1392,6 +1383,19 @@ class DeviceManager(object):
|
|||||||
self._update_dhcp_port(network, port)
|
self._update_dhcp_port(network, port)
|
||||||
interface_name = self.get_interface_name(network, port)
|
interface_name = self.get_interface_name(network, port)
|
||||||
|
|
||||||
|
# Disable acceptance of RAs in the namespace so we don't
|
||||||
|
# auto-configure an IPv6 address since we explicitly configure
|
||||||
|
# them on the device. This must be done before any interfaces
|
||||||
|
# are plugged since it could receive an RA by the time
|
||||||
|
# plug() returns, so we have to create the namespace first.
|
||||||
|
# It must also be done in the case there is an existing IPv6
|
||||||
|
# address here created via SLAAC, since it will be deleted
|
||||||
|
# and added back statically in the call to init_l3() below.
|
||||||
|
if network.namespace:
|
||||||
|
ip_lib.IPWrapper().ensure_namespace(network.namespace)
|
||||||
|
self.driver.configure_ipv6_ra(network.namespace, 'default',
|
||||||
|
n_const.ACCEPT_RA_DISABLED)
|
||||||
|
|
||||||
if ip_lib.ensure_device_is_ready(interface_name,
|
if ip_lib.ensure_device_is_ready(interface_name,
|
||||||
namespace=network.namespace):
|
namespace=network.namespace):
|
||||||
LOG.debug('Reusing existing device: %s.', interface_name)
|
LOG.debug('Reusing existing device: %s.', interface_name)
|
||||||
|
@ -117,30 +117,50 @@ class LinuxInterfaceDriver(object):
|
|||||||
# Neutron, so it would be deleted if we added it to the 'previous'
|
# Neutron, so it would be deleted if we added it to the 'previous'
|
||||||
# list here
|
# list here
|
||||||
default_ipv6_lla = ip_lib.get_ipv6_lladdr(device.link.address)
|
default_ipv6_lla = ip_lib.get_ipv6_lladdr(device.link.address)
|
||||||
previous = {addr['cidr'] for addr in device.addr.list(
|
|
||||||
filters=['permanent'])} - {default_ipv6_lla}
|
|
||||||
|
|
||||||
# add new addresses
|
cidrs = set()
|
||||||
|
remove_ips = set()
|
||||||
|
|
||||||
|
# normalize all the IP addresses first
|
||||||
for ip_cidr in ip_cidrs:
|
for ip_cidr in ip_cidrs:
|
||||||
|
|
||||||
net = netaddr.IPNetwork(ip_cidr)
|
net = netaddr.IPNetwork(ip_cidr)
|
||||||
# Convert to compact IPv6 address because the return values of
|
# Convert to compact IPv6 address because the return values of
|
||||||
# "ip addr list" are compact.
|
# "ip addr list" are compact.
|
||||||
if net.version == 6:
|
if net.version == 6:
|
||||||
ip_cidr = str(net)
|
ip_cidr = str(net)
|
||||||
if ip_cidr in previous:
|
cidrs.add(ip_cidr)
|
||||||
previous.remove(ip_cidr)
|
|
||||||
|
# Determine the addresses that must be added and removed
|
||||||
|
for address in device.addr.list():
|
||||||
|
cidr = address['cidr']
|
||||||
|
dynamic = address['dynamic']
|
||||||
|
|
||||||
|
# skip the IPv6 link-local
|
||||||
|
if cidr == default_ipv6_lla:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
device.addr.add(ip_cidr)
|
if cidr in preserve_ips:
|
||||||
|
continue
|
||||||
|
|
||||||
# clean up any old addresses
|
# Statically created addresses are OK, dynamically created
|
||||||
for ip_cidr in previous:
|
# addresses must be removed and replaced
|
||||||
if ip_cidr not in preserve_ips:
|
if cidr in cidrs and not dynamic:
|
||||||
if clean_connections:
|
cidrs.remove(cidr)
|
||||||
device.delete_addr_and_conntrack_state(ip_cidr)
|
continue
|
||||||
else:
|
|
||||||
device.addr.delete(ip_cidr)
|
remove_ips.add(cidr)
|
||||||
|
|
||||||
|
# Clean up any old addresses. This must be done first since there
|
||||||
|
# could be a dynamic address being replaced with a static one.
|
||||||
|
for ip_cidr in remove_ips:
|
||||||
|
if clean_connections:
|
||||||
|
device.delete_addr_and_conntrack_state(ip_cidr)
|
||||||
|
else:
|
||||||
|
device.addr.delete(ip_cidr)
|
||||||
|
|
||||||
|
# add any new addresses
|
||||||
|
for ip_cidr in cidrs:
|
||||||
|
device.addr.add(ip_cidr)
|
||||||
|
|
||||||
def init_router_port(self,
|
def init_router_port(self,
|
||||||
device_name,
|
device_name,
|
||||||
|
@ -1506,15 +1506,13 @@ class TestDeviceManager(base.BaseTestCase):
|
|||||||
expected_ips = ['172.9.9.9/24', '169.254.169.254/16']
|
expected_ips = ['172.9.9.9/24', '169.254.169.254/16']
|
||||||
expected = [
|
expected = [
|
||||||
mock.call.get_device_name(port),
|
mock.call.get_device_name(port),
|
||||||
|
mock.call.configure_ipv6_ra(net.namespace, 'default', 0),
|
||||||
mock.call.init_l3(
|
mock.call.init_l3(
|
||||||
'tap12345678-12',
|
'tap12345678-12',
|
||||||
expected_ips,
|
expected_ips,
|
||||||
namespace=net.namespace)]
|
namespace=net.namespace)]
|
||||||
|
|
||||||
if not device_is_ready:
|
if not device_is_ready:
|
||||||
expected.insert(1,
|
|
||||||
mock.call.configure_ipv6_ra(net.namespace,
|
|
||||||
'default', 0))
|
|
||||||
expected.insert(2,
|
expected.insert(2,
|
||||||
mock.call.plug(net.id,
|
mock.call.plug(net.id,
|
||||||
port.id,
|
port.id,
|
||||||
|
@ -121,9 +121,9 @@ class TestABCDriver(TestBase):
|
|||||||
extra_subnets=[{'cidr': '172.20.0.0/24'}])
|
extra_subnets=[{'cidr': '172.20.0.0/24'}])
|
||||||
self.ip_dev.assert_has_calls(
|
self.ip_dev.assert_has_calls(
|
||||||
[mock.call('tap0', namespace=ns),
|
[mock.call('tap0', namespace=ns),
|
||||||
mock.call().addr.list(filters=['permanent']),
|
mock.call().addr.list(),
|
||||||
mock.call().addr.add('192.168.1.2/24'),
|
|
||||||
mock.call().addr.delete('172.16.77.240/24'),
|
mock.call().addr.delete('172.16.77.240/24'),
|
||||||
|
mock.call().addr.add('192.168.1.2/24'),
|
||||||
mock.call('tap0', namespace=ns),
|
mock.call('tap0', namespace=ns),
|
||||||
mock.call().route.list_onlink_routes(constants.IP_VERSION_4),
|
mock.call().route.list_onlink_routes(constants.IP_VERSION_4),
|
||||||
mock.call().route.list_onlink_routes(constants.IP_VERSION_6),
|
mock.call().route.list_onlink_routes(constants.IP_VERSION_6),
|
||||||
@ -155,7 +155,7 @@ class TestABCDriver(TestBase):
|
|||||||
preserve_ips=['192.168.1.3/32'])
|
preserve_ips=['192.168.1.3/32'])
|
||||||
self.ip_dev.assert_has_calls(
|
self.ip_dev.assert_has_calls(
|
||||||
[mock.call('tap0', namespace=ns),
|
[mock.call('tap0', namespace=ns),
|
||||||
mock.call().addr.list(filters=['permanent']),
|
mock.call().addr.list(),
|
||||||
mock.call().addr.add('192.168.1.2/24')])
|
mock.call().addr.add('192.168.1.2/24')])
|
||||||
self.assertFalse(self.ip_dev().addr.delete.called)
|
self.assertFalse(self.ip_dev().addr.delete.called)
|
||||||
self.assertFalse(self.ip_dev().delete_addr_and_conntrack_state.called)
|
self.assertFalse(self.ip_dev().delete_addr_and_conntrack_state.called)
|
||||||
@ -198,9 +198,9 @@ class TestABCDriver(TestBase):
|
|||||||
bc.init_router_port('tap0', [new_cidr], **kwargs)
|
bc.init_router_port('tap0', [new_cidr], **kwargs)
|
||||||
expected_calls = (
|
expected_calls = (
|
||||||
[mock.call('tap0', namespace=ns),
|
[mock.call('tap0', namespace=ns),
|
||||||
mock.call().addr.list(filters=['permanent']),
|
mock.call().addr.list(),
|
||||||
mock.call().addr.add('2001:db8:a::124/64'),
|
mock.call().addr.delete('2001:db8:a::123/64'),
|
||||||
mock.call().addr.delete('2001:db8:a::123/64')])
|
mock.call().addr.add('2001:db8:a::124/64')])
|
||||||
expected_calls += (
|
expected_calls += (
|
||||||
[mock.call('tap0', namespace=ns),
|
[mock.call('tap0', namespace=ns),
|
||||||
mock.call().route.list_onlink_routes(constants.IP_VERSION_4),
|
mock.call().route.list_onlink_routes(constants.IP_VERSION_4),
|
||||||
@ -222,7 +222,7 @@ class TestABCDriver(TestBase):
|
|||||||
extra_subnets=[{'cidr': '172.20.0.0/24'}])
|
extra_subnets=[{'cidr': '172.20.0.0/24'}])
|
||||||
self.ip_dev.assert_has_calls(
|
self.ip_dev.assert_has_calls(
|
||||||
[mock.call('tap0', namespace=ns),
|
[mock.call('tap0', namespace=ns),
|
||||||
mock.call().addr.list(filters=['permanent']),
|
mock.call().addr.list(),
|
||||||
mock.call().addr.add('192.168.1.2/24'),
|
mock.call().addr.add('192.168.1.2/24'),
|
||||||
mock.call().addr.add('2001:db8:a::124/64'),
|
mock.call().addr.add('2001:db8:a::124/64'),
|
||||||
mock.call().addr.delete('172.16.77.240/24'),
|
mock.call().addr.delete('172.16.77.240/24'),
|
||||||
@ -269,6 +269,22 @@ class TestABCDriver(TestBase):
|
|||||||
namespace=ns)
|
namespace=ns)
|
||||||
self.assertFalse(self.ip_dev().addr.add.called)
|
self.assertFalse(self.ip_dev().addr.add.called)
|
||||||
|
|
||||||
|
def test_l3_init_with_duplicated_ipv6_dynamic(self):
|
||||||
|
device_name = 'tap0'
|
||||||
|
cidr = '2001:db8:a::123/64'
|
||||||
|
ns = '12345678-1234-5678-90ab-ba0987654321'
|
||||||
|
addresses = [dict(scope='global',
|
||||||
|
dynamic=True,
|
||||||
|
cidr=cidr)]
|
||||||
|
self.ip_dev().addr.list = mock.Mock(return_value=addresses)
|
||||||
|
bc = BaseChild(self.conf)
|
||||||
|
bc.init_l3(device_name, [cidr], namespace=ns)
|
||||||
|
self.ip_dev.assert_has_calls(
|
||||||
|
[mock.call(device_name, namespace=ns),
|
||||||
|
mock.call().addr.list(),
|
||||||
|
mock.call().addr.delete(cidr),
|
||||||
|
mock.call().addr.add(cidr)])
|
||||||
|
|
||||||
def test_add_ipv6_addr(self):
|
def test_add_ipv6_addr(self):
|
||||||
device_name = 'tap0'
|
device_name = 'tap0'
|
||||||
cidr = '2001:db8::/64'
|
cidr = '2001:db8::/64'
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
IPv6 addresses in DHCP namespaces will now be
|
||||||
|
(correctly) statically configured by the DHCP agent.
|
||||||
|
fixes:
|
||||||
|
- There is a race condition when adding ports in
|
||||||
|
DHCP namespaces where an IPv6 address could be
|
||||||
|
dynamically created via SLAAC from a Router
|
||||||
|
Advertisement sent from the L3 agent, leading to
|
||||||
|
a failure to start the DHCP agent. This bug has
|
||||||
|
been fixed, but care must be taken on an upgrade
|
||||||
|
dealing with any possibly stale dynamic addresses.
|
||||||
|
For more information, see bug
|
||||||
|
`1627902 <https://launchpad.net/bugs/1627902>`_.
|
||||||
|
upgrade:
|
||||||
|
- On upgrade, IPv6 addresses in the DHCP namespaces
|
||||||
|
that have been created dynmically via SLAAC will be
|
||||||
|
removed, and a static IPv6 address will be added
|
||||||
|
instead.
|
Loading…
Reference in New Issue
Block a user