Merge "Add IPv6 default route to DHCP namespace" into stable/ocata

This commit is contained in:
Jenkins 2017-06-15 07:16:07 +00:00 committed by Gerrit Code Review
commit e7e7a1d39f
3 changed files with 157 additions and 59 deletions

View File

@ -1111,31 +1111,39 @@ class DeviceManager(object):
return common_utils.get_dhcp_agent_device_id(network.id,
self.conf.host)
def _set_default_route(self, network, device_name):
"""Sets the default gateway for this dhcp namespace.
This method is idempotent and will only adjust the route if adjusting
it would change it from what it already is. This makes it safe to call
and avoids unnecessary perturbation of the system.
"""
def _set_default_route_ip_version(self, network, device_name, ip_version):
device = ip_lib.IPDevice(device_name, namespace=network.namespace)
gateway = device.route.get_gateway()
gateway = device.route.get_gateway(ip_version=ip_version)
if gateway:
gateway = gateway.get('gateway')
for subnet in network.subnets:
skip_subnet = (
subnet.ip_version != 4
subnet.ip_version != ip_version
or not subnet.enable_dhcp
or subnet.gateway_ip is None)
if skip_subnet:
continue
if subnet.ip_version == constants.IP_VERSION_6:
# This is duplicating some of the API checks already done,
# but some of the functional tests call directly
prefixlen = netaddr.IPNetwork(subnet.cidr).prefixlen
if prefixlen == 0 or prefixlen > 126:
continue
modes = [constants.IPV6_SLAAC, constants.DHCPV6_STATELESS]
addr_mode = getattr(subnet, 'ipv6_address_mode', None)
ra_mode = getattr(subnet, 'ipv6_ra_mode', None)
if (prefixlen != 64 and
(addr_mode in modes or ra_mode in modes)):
continue
if gateway != subnet.gateway_ip:
LOG.debug('Setting gateway for dhcp netns on net %(n)s to '
'%(ip)s',
{'n': network.id, 'ip': subnet.gateway_ip})
LOG.debug('Setting IPv%(version)s gateway for dhcp netns '
'on net %(n)s to %(ip)s',
{'n': network.id, 'ip': subnet.gateway_ip,
'version': ip_version})
# Check for and remove the on-link route for the old
# gateway being replaced, if it is outside the subnet
@ -1143,12 +1151,8 @@ class DeviceManager(object):
not ipam_utils.check_subnet_ip(
subnet.cidr, gateway))
if is_old_gateway_not_in_subnet:
v4_onlink = device.route.list_onlink_routes(
constants.IP_VERSION_4)
v6_onlink = device.route.list_onlink_routes(
constants.IP_VERSION_6)
existing_onlink_routes = set(
r['cidr'] for r in v4_onlink + v6_onlink)
onlink = device.route.list_onlink_routes(ip_version)
existing_onlink_routes = set(r['cidr'] for r in onlink)
if gateway in existing_onlink_routes:
device.route.delete_route(gateway, scope='link')
@ -1165,10 +1169,23 @@ class DeviceManager(object):
# No subnets on the network have a valid gateway. Clean it up to avoid
# confusion from seeing an invalid gateway here.
if gateway is not None:
LOG.debug('Removing gateway for dhcp netns on net %s', network.id)
LOG.debug('Removing IPv%(version)s gateway for dhcp netns on '
'net %(n)s',
{'n': network.id, 'version': ip_version})
device.route.delete_gateway(gateway)
def _set_default_route(self, network, device_name):
"""Sets the default gateway for this dhcp namespace.
This method is idempotent and will only adjust the route if adjusting
it would change it from what it already is. This makes it safe to call
and avoids unnecessary perturbation of the system.
"""
for ip_version in (constants.IP_VERSION_4, constants.IP_VERSION_6):
self._set_default_route_ip_version(network, device_name,
ip_version)
def _setup_existing_dhcp_port(self, network, device_id, dhcp_subnets):
"""Set up the existing DHCP port, if there is one."""

View File

@ -50,9 +50,9 @@ class DHCPAgentOVSTestFramework(base.BaseSudoTestCase):
4: {'addr': '192.168.10.11',
'cidr': '192.168.10.0/24',
'gateway': '192.168.10.1'},
6: {'addr': '0:0:0:0:0:ffff:c0a8:a0b',
'cidr': '0:0:0:0:0:ffff:c0a8:a00/120',
'gateway': '0:0:0:0:0:ffff:c0a8:a01'}, }
6: {'addr': '2001:db8:0:1::c0a8:a0b',
'cidr': '2001:db8:0:1::c0a8:a00/120',
'gateway': '2001:db8:0:1::c0a8:a01'}, }
def setUp(self):
super(DHCPAgentOVSTestFramework, self).setUp()

View File

@ -1343,6 +1343,11 @@ class FakePort1(object):
self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
class FakePort2(object):
def __init__(self):
self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
class FakeV4Subnet(object):
def __init__(self):
self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
@ -1352,12 +1357,27 @@ class FakeV4Subnet(object):
self.enable_dhcp = True
class FakeV6Subnet(object):
def __init__(self):
self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
self.ip_version = 6
self.cidr = '2001:db8:0:1::/64'
self.gateway_ip = '2001:db8:0:1::1'
self.enable_dhcp = True
class FakeV4SubnetOutsideGateway(FakeV4Subnet):
def __init__(self):
super(FakeV4SubnetOutsideGateway, self).__init__()
self.gateway_ip = '192.168.1.1'
class FakeV6SubnetOutsideGateway(FakeV6Subnet):
def __init__(self):
super(FakeV6SubnetOutsideGateway, self).__init__()
self.gateway_ip = '2001:db8:1:1::1'
class FakeV4SubnetNoGateway(object):
def __init__(self):
self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
@ -1367,6 +1387,15 @@ class FakeV4SubnetNoGateway(object):
self.enable_dhcp = True
class FakeV6SubnetNoGateway(object):
def __init__(self):
self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
self.ip_version = 6
self.cidr = '2001:db8:1:0::/64'
self.gateway_ip = None
self.enable_dhcp = True
class FakeV4Network(object):
def __init__(self):
self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
@ -1375,24 +1404,39 @@ class FakeV4Network(object):
self.namespace = 'qdhcp-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
class FakeDualNetwork(object):
def __init__(self):
self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
self.subnets = [FakeV4Subnet(), FakeV6Subnet()]
self.ports = [FakePort1(), FakePort2()]
self.namespace = 'qdhcp-dddddddd-dddd-dddd-dddd-dddddddddddd'
class FakeV4NetworkOutsideGateway(FakeV4Network):
def __init__(self):
super(FakeV4NetworkOutsideGateway, self).__init__()
self.subnets = [FakeV4SubnetOutsideGateway()]
class FakeV4NetworkNoSubnet(object):
class FakeDualNetworkOutsideGateway(FakeDualNetwork):
def __init__(self):
super(FakeDualNetworkOutsideGateway, self).__init__()
self.subnets = [FakeV4SubnetOutsideGateway(),
FakeV6SubnetOutsideGateway()]
class FakeDualNetworkNoSubnet(object):
def __init__(self):
self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
self.subnets = []
self.ports = []
class FakeV4NetworkNoGateway(object):
class FakeDualNetworkNoGateway(object):
def __init__(self):
self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
self.subnets = [FakeV4SubnetNoGateway()]
self.ports = [FakePort1()]
self.subnets = [FakeV4SubnetNoGateway(), FakeV6SubnetNoGateway()]
self.ports = [FakePort1(), FakePort2()]
class TestDeviceManager(base.BaseTestCase):
@ -1747,7 +1791,7 @@ class TestDeviceManager(base.BaseTestCase):
network = FakeV4Network()
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertFalse(device.route.delete_gateway.called)
device.route.add_gateway.assert_called_once_with('192.168.0.1')
@ -1761,7 +1805,7 @@ class TestDeviceManager(base.BaseTestCase):
network = FakeV4NetworkOutsideGateway()
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertFalse(device.route.delete_gateway.called)
device.route.add_route.assert_called_once_with('192.168.1.1',
scope='link')
@ -1773,104 +1817,141 @@ class TestDeviceManager(base.BaseTestCase):
device = mock.Mock()
mock_IPDevice.return_value = device
device.route.get_gateway.return_value = None
network = FakeV4NetworkNoSubnet()
network = FakeDualNetworkNoSubnet()
network.namespace = 'qdhcp-1234'
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertFalse(device.route.delete_gateway.called)
self.assertFalse(device.route.add_gateway.called)
def test_set_default_route_no_subnet_delete_gateway(self):
dh = dhcp.DeviceManager(cfg.CONF, None)
v4_gateway = '192.168.0.1'
v6_gateway = '2001:db8:0:1::1'
expected = [mock.call(v4_gateway),
mock.call(v6_gateway)]
with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock()
mock_IPDevice.return_value = device
device.route.get_gateway.return_value = dict(gateway='192.168.0.1')
network = FakeV4NetworkNoSubnet()
device.route.get_gateway.side_effect = [
dict(gateway=v4_gateway), dict(gateway=v6_gateway)]
network = FakeDualNetworkNoSubnet()
network.namespace = 'qdhcp-1234'
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
device.route.delete_gateway.assert_called_once_with('192.168.0.1')
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.delete_gateway.call_count)
device.route.delete_gateway.assert_has_calls(expected)
self.assertFalse(device.route.add_gateway.called)
def test_set_default_route_no_gateway(self):
dh = dhcp.DeviceManager(cfg.CONF, None)
v4_gateway = '192.168.0.1'
v6_gateway = '2001:db8:0:1::1'
expected = [mock.call(v4_gateway),
mock.call(v6_gateway)]
with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock()
mock_IPDevice.return_value = device
device.route.get_gateway.return_value = dict(gateway='192.168.0.1')
network = FakeV4NetworkNoGateway()
device.route.get_gateway.side_effect = [
dict(gateway=v4_gateway), dict(gateway=v6_gateway)]
network = FakeDualNetworkNoGateway()
network.namespace = 'qdhcp-1234'
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
device.route.delete_gateway.assert_called_once_with('192.168.0.1')
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.delete_gateway.call_count)
device.route.delete_gateway.assert_has_calls(expected)
self.assertFalse(device.route.add_gateway.called)
def test_set_default_route_do_nothing(self):
dh = dhcp.DeviceManager(cfg.CONF, None)
v4_gateway = '192.168.0.1'
v6_gateway = '2001:db8:0:1::1'
with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock()
mock_IPDevice.return_value = device
device.route.get_gateway.return_value = dict(gateway='192.168.0.1')
network = FakeV4Network()
device.route.get_gateway.side_effect = [
dict(gateway=v4_gateway), dict(gateway=v6_gateway)]
network = FakeDualNetwork()
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertFalse(device.route.delete_gateway.called)
self.assertFalse(device.route.add_gateway.called)
def test_set_default_route_change_gateway(self):
dh = dhcp.DeviceManager(cfg.CONF, None)
v4_gateway = '192.168.0.1'
old_v4_gateway = '192.168.0.2'
v6_gateway = '2001:db8:0:1::1'
old_v6_gateway = '2001:db8:0:1::2'
expected = [mock.call(v4_gateway),
mock.call(v6_gateway)]
with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock()
mock_IPDevice.return_value = device
device.route.get_gateway.return_value = dict(gateway='192.168.0.2')
network = FakeV4Network()
device.route.get_gateway.side_effect = [
dict(gateway=old_v4_gateway), dict(gateway=old_v6_gateway)]
network = FakeDualNetwork()
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertFalse(device.route.delete_gateway.called)
device.route.add_gateway.assert_called_once_with('192.168.0.1')
device.route.add_gateway.assert_has_calls(expected)
def test_set_default_route_change_gateway_outside_subnet(self):
dh = dhcp.DeviceManager(cfg.CONF, None)
v4_gateway = '192.168.1.1'
old_v4_gateway = '192.168.2.1'
v6_gateway = '2001:db8:1:1::1'
old_v6_gateway = '2001:db8:2:0::1'
add_route_expected = [mock.call(v4_gateway, scope='link'),
mock.call(v6_gateway, scope='link')]
add_gw_expected = [mock.call(v4_gateway),
mock.call(v6_gateway)]
with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock()
mock_IPDevice.return_value = device
device.route.list_onlink_routes.return_value = (
[{'cidr': '192.168.2.1'}])
device.route.get_gateway.return_value = dict(gateway='192.168.2.1')
network = FakeV4NetworkOutsideGateway()
device.route.list_onlink_routes.side_effect = [
[{'cidr': old_v4_gateway}], []]
device.route.get_gateway.side_effect = [
dict(gateway=old_v4_gateway), dict(gateway=old_v6_gateway)]
network = FakeDualNetworkOutsideGateway()
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.list_onlink_routes.call_count)
self.assertFalse(device.route.delete_gateway.called)
device.route.delete_route.assert_called_once_with('192.168.2.1',
device.route.delete_route.assert_called_once_with(old_v4_gateway,
scope='link')
device.route.add_route.assert_called_once_with('192.168.1.1',
scope='link')
device.route.add_gateway.assert_called_once_with('192.168.1.1')
device.route.add_route.assert_has_calls(add_route_expected)
device.route.add_gateway.assert_has_calls(add_gw_expected)
def test_set_default_route_two_subnets(self):
# Try two subnets. Should set gateway from the first.
dh = dhcp.DeviceManager(cfg.CONF, None)
v4_gateway = '192.168.1.1'
v6_gateway = '2001:db8:1:1::1'
expected = [mock.call(v4_gateway),
mock.call(v6_gateway)]
with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock()
mock_IPDevice.return_value = device
device.route.get_gateway.return_value = None
network = FakeV4Network()
network = FakeDualNetwork()
subnet2 = FakeV4Subnet()
subnet2.gateway_ip = '192.168.1.1'
network.subnets = [subnet2, FakeV4Subnet()]
subnet2.gateway_ip = v4_gateway
subnet3 = FakeV6Subnet()
subnet3.gateway_ip = v6_gateway
network.subnets = [subnet2, FakeV4Subnet(),
subnet3, FakeV6Subnet()]
dh._set_default_route(network, 'tap-name')
self.assertEqual(1, device.route.get_gateway.call_count)
self.assertEqual(2, device.route.get_gateway.call_count)
self.assertFalse(device.route.delete_gateway.called)
device.route.add_gateway.assert_called_once_with('192.168.1.1')
device.route.add_gateway.assert_has_calls(expected)
class TestDictModel(base.BaseTestCase):