Merge "Add IPv6 default route to DHCP namespace"

This commit is contained in:
Jenkins 2017-05-28 09:51:27 +00:00 committed by Gerrit Code Review
commit e4557a7793
3 changed files with 157 additions and 59 deletions

View File

@ -1114,31 +1114,39 @@ class DeviceManager(object):
return common_utils.get_dhcp_agent_device_id(network.id, return common_utils.get_dhcp_agent_device_id(network.id,
self.conf.host) self.conf.host)
def _set_default_route(self, network, device_name): def _set_default_route_ip_version(self, network, device_name, ip_version):
"""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.
"""
device = ip_lib.IPDevice(device_name, namespace=network.namespace) 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: if gateway:
gateway = gateway.get('gateway') gateway = gateway.get('gateway')
for subnet in network.subnets: for subnet in network.subnets:
skip_subnet = ( skip_subnet = (
subnet.ip_version != 4 subnet.ip_version != ip_version
or not subnet.enable_dhcp or not subnet.enable_dhcp
or subnet.gateway_ip is None) or subnet.gateway_ip is None)
if skip_subnet: if skip_subnet:
continue 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: if gateway != subnet.gateway_ip:
LOG.debug('Setting gateway for dhcp netns on net %(n)s to ' LOG.debug('Setting IPv%(version)s gateway for dhcp netns '
'%(ip)s', 'on net %(n)s to %(ip)s',
{'n': network.id, 'ip': subnet.gateway_ip}) {'n': network.id, 'ip': subnet.gateway_ip,
'version': ip_version})
# Check for and remove the on-link route for the old # Check for and remove the on-link route for the old
# gateway being replaced, if it is outside the subnet # gateway being replaced, if it is outside the subnet
@ -1146,12 +1154,8 @@ class DeviceManager(object):
not ipam_utils.check_subnet_ip( not ipam_utils.check_subnet_ip(
subnet.cidr, gateway)) subnet.cidr, gateway))
if is_old_gateway_not_in_subnet: if is_old_gateway_not_in_subnet:
v4_onlink = device.route.list_onlink_routes( onlink = device.route.list_onlink_routes(ip_version)
constants.IP_VERSION_4) existing_onlink_routes = set(r['cidr'] for r in onlink)
v6_onlink = device.route.list_onlink_routes(
constants.IP_VERSION_6)
existing_onlink_routes = set(
r['cidr'] for r in v4_onlink + v6_onlink)
if gateway in existing_onlink_routes: if gateway in existing_onlink_routes:
device.route.delete_route(gateway, scope='link') device.route.delete_route(gateway, scope='link')
@ -1168,10 +1172,23 @@ class DeviceManager(object):
# No subnets on the network have a valid gateway. Clean it up to avoid # No subnets on the network have a valid gateway. Clean it up to avoid
# confusion from seeing an invalid gateway here. # confusion from seeing an invalid gateway here.
if gateway is not None: 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) 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): def _setup_existing_dhcp_port(self, network, device_id, dhcp_subnets):
"""Set up the existing DHCP port, if there is one.""" """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', 4: {'addr': '192.168.10.11',
'cidr': '192.168.10.0/24', 'cidr': '192.168.10.0/24',
'gateway': '192.168.10.1'}, 'gateway': '192.168.10.1'},
6: {'addr': '0:0:0:0:0:ffff:c0a8:a0b', 6: {'addr': '2001:db8:0:1::c0a8:a0b',
'cidr': '0:0:0:0:0:ffff:c0a8:a00/120', 'cidr': '2001:db8:0:1::c0a8:a00/120',
'gateway': '0:0:0:0:0:ffff:c0a8:a01'}, } 'gateway': '2001:db8:0:1::c0a8:a01'}, }
def setUp(self): def setUp(self):
super(DHCPAgentOVSTestFramework, self).setUp() super(DHCPAgentOVSTestFramework, self).setUp()

View File

@ -1329,6 +1329,11 @@ class FakePort1(object):
self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
class FakePort2(object):
def __init__(self):
self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
class FakeV4Subnet(object): class FakeV4Subnet(object):
def __init__(self): def __init__(self):
self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
@ -1338,12 +1343,27 @@ class FakeV4Subnet(object):
self.enable_dhcp = True 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): class FakeV4SubnetOutsideGateway(FakeV4Subnet):
def __init__(self): def __init__(self):
super(FakeV4SubnetOutsideGateway, self).__init__() super(FakeV4SubnetOutsideGateway, self).__init__()
self.gateway_ip = '192.168.1.1' 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): class FakeV4SubnetNoGateway(object):
def __init__(self): def __init__(self):
self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
@ -1353,6 +1373,15 @@ class FakeV4SubnetNoGateway(object):
self.enable_dhcp = True 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): class FakeV4Network(object):
def __init__(self): def __init__(self):
self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
@ -1361,24 +1390,39 @@ class FakeV4Network(object):
self.namespace = 'qdhcp-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' 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): class FakeV4NetworkOutsideGateway(FakeV4Network):
def __init__(self): def __init__(self):
super(FakeV4NetworkOutsideGateway, self).__init__() super(FakeV4NetworkOutsideGateway, self).__init__()
self.subnets = [FakeV4SubnetOutsideGateway()] 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): def __init__(self):
self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' self.id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
self.subnets = [] self.subnets = []
self.ports = [] self.ports = []
class FakeV4NetworkNoGateway(object): class FakeDualNetworkNoGateway(object):
def __init__(self): def __init__(self):
self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc' self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
self.subnets = [FakeV4SubnetNoGateway()] self.subnets = [FakeV4SubnetNoGateway(), FakeV6SubnetNoGateway()]
self.ports = [FakePort1()] self.ports = [FakePort1(), FakePort2()]
class TestDeviceManager(base.BaseTestCase): class TestDeviceManager(base.BaseTestCase):
@ -1733,7 +1777,7 @@ class TestDeviceManager(base.BaseTestCase):
network = FakeV4Network() network = FakeV4Network()
dh._set_default_route(network, 'tap-name') 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.delete_gateway.called)
device.route.add_gateway.assert_called_once_with('192.168.0.1') device.route.add_gateway.assert_called_once_with('192.168.0.1')
@ -1747,7 +1791,7 @@ class TestDeviceManager(base.BaseTestCase):
network = FakeV4NetworkOutsideGateway() network = FakeV4NetworkOutsideGateway()
dh._set_default_route(network, 'tap-name') 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.delete_gateway.called)
device.route.add_route.assert_called_once_with('192.168.1.1', device.route.add_route.assert_called_once_with('192.168.1.1',
scope='link') scope='link')
@ -1759,104 +1803,141 @@ class TestDeviceManager(base.BaseTestCase):
device = mock.Mock() device = mock.Mock()
mock_IPDevice.return_value = device mock_IPDevice.return_value = device
device.route.get_gateway.return_value = None device.route.get_gateway.return_value = None
network = FakeV4NetworkNoSubnet() network = FakeDualNetworkNoSubnet()
network.namespace = 'qdhcp-1234' network.namespace = 'qdhcp-1234'
dh._set_default_route(network, 'tap-name') 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.delete_gateway.called)
self.assertFalse(device.route.add_gateway.called) self.assertFalse(device.route.add_gateway.called)
def test_set_default_route_no_subnet_delete_gateway(self): def test_set_default_route_no_subnet_delete_gateway(self):
dh = dhcp.DeviceManager(cfg.CONF, None) 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: with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock() device = mock.Mock()
mock_IPDevice.return_value = device mock_IPDevice.return_value = device
device.route.get_gateway.return_value = dict(gateway='192.168.0.1') device.route.get_gateway.side_effect = [
network = FakeV4NetworkNoSubnet() dict(gateway=v4_gateway), dict(gateway=v6_gateway)]
network = FakeDualNetworkNoSubnet()
network.namespace = 'qdhcp-1234' network.namespace = 'qdhcp-1234'
dh._set_default_route(network, 'tap-name') 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)
device.route.delete_gateway.assert_called_once_with('192.168.0.1') self.assertEqual(2, device.route.delete_gateway.call_count)
device.route.delete_gateway.assert_has_calls(expected)
self.assertFalse(device.route.add_gateway.called) self.assertFalse(device.route.add_gateway.called)
def test_set_default_route_no_gateway(self): def test_set_default_route_no_gateway(self):
dh = dhcp.DeviceManager(cfg.CONF, None) 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: with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock() device = mock.Mock()
mock_IPDevice.return_value = device mock_IPDevice.return_value = device
device.route.get_gateway.return_value = dict(gateway='192.168.0.1') device.route.get_gateway.side_effect = [
network = FakeV4NetworkNoGateway() dict(gateway=v4_gateway), dict(gateway=v6_gateway)]
network = FakeDualNetworkNoGateway()
network.namespace = 'qdhcp-1234' network.namespace = 'qdhcp-1234'
dh._set_default_route(network, 'tap-name') 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)
device.route.delete_gateway.assert_called_once_with('192.168.0.1') self.assertEqual(2, device.route.delete_gateway.call_count)
device.route.delete_gateway.assert_has_calls(expected)
self.assertFalse(device.route.add_gateway.called) self.assertFalse(device.route.add_gateway.called)
def test_set_default_route_do_nothing(self): def test_set_default_route_do_nothing(self):
dh = dhcp.DeviceManager(cfg.CONF, None) 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: with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock() device = mock.Mock()
mock_IPDevice.return_value = device mock_IPDevice.return_value = device
device.route.get_gateway.return_value = dict(gateway='192.168.0.1') device.route.get_gateway.side_effect = [
network = FakeV4Network() dict(gateway=v4_gateway), dict(gateway=v6_gateway)]
network = FakeDualNetwork()
dh._set_default_route(network, 'tap-name') 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.delete_gateway.called)
self.assertFalse(device.route.add_gateway.called) self.assertFalse(device.route.add_gateway.called)
def test_set_default_route_change_gateway(self): def test_set_default_route_change_gateway(self):
dh = dhcp.DeviceManager(cfg.CONF, None) 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: with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock() device = mock.Mock()
mock_IPDevice.return_value = device mock_IPDevice.return_value = device
device.route.get_gateway.return_value = dict(gateway='192.168.0.2') device.route.get_gateway.side_effect = [
network = FakeV4Network() dict(gateway=old_v4_gateway), dict(gateway=old_v6_gateway)]
network = FakeDualNetwork()
dh._set_default_route(network, 'tap-name') 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.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): def test_set_default_route_change_gateway_outside_subnet(self):
dh = dhcp.DeviceManager(cfg.CONF, None) 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: with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock() device = mock.Mock()
mock_IPDevice.return_value = device mock_IPDevice.return_value = device
device.route.list_onlink_routes.return_value = ( device.route.list_onlink_routes.side_effect = [
[{'cidr': '192.168.2.1'}]) [{'cidr': old_v4_gateway}], []]
device.route.get_gateway.return_value = dict(gateway='192.168.2.1') device.route.get_gateway.side_effect = [
network = FakeV4NetworkOutsideGateway() dict(gateway=old_v4_gateway), dict(gateway=old_v6_gateway)]
network = FakeDualNetworkOutsideGateway()
dh._set_default_route(network, 'tap-name') 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.assertEqual(2, device.route.list_onlink_routes.call_count)
self.assertFalse(device.route.delete_gateway.called) 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') scope='link')
device.route.add_route.assert_called_once_with('192.168.1.1', device.route.add_route.assert_has_calls(add_route_expected)
scope='link') device.route.add_gateway.assert_has_calls(add_gw_expected)
device.route.add_gateway.assert_called_once_with('192.168.1.1')
def test_set_default_route_two_subnets(self): def test_set_default_route_two_subnets(self):
# Try two subnets. Should set gateway from the first. # Try two subnets. Should set gateway from the first.
dh = dhcp.DeviceManager(cfg.CONF, None) 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: with mock.patch.object(dhcp.ip_lib, 'IPDevice') as mock_IPDevice:
device = mock.Mock() device = mock.Mock()
mock_IPDevice.return_value = device mock_IPDevice.return_value = device
device.route.get_gateway.return_value = None device.route.get_gateway.return_value = None
network = FakeV4Network() network = FakeDualNetwork()
subnet2 = FakeV4Subnet() subnet2 = FakeV4Subnet()
subnet2.gateway_ip = '192.168.1.1' subnet2.gateway_ip = v4_gateway
network.subnets = [subnet2, FakeV4Subnet()] subnet3 = FakeV6Subnet()
subnet3.gateway_ip = v6_gateway
network.subnets = [subnet2, FakeV4Subnet(),
subnet3, FakeV6Subnet()]
dh._set_default_route(network, 'tap-name') 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.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): class TestDictModel(base.BaseTestCase):