Merge "Adds default route to DHCP namespace for upstream name resolution."
This commit is contained in:
commit
cc260d5d64
@ -230,6 +230,9 @@ class DhcpAgent(manager.Manager):
|
|||||||
else:
|
else:
|
||||||
self.disable_dhcp_helper(network.id)
|
self.disable_dhcp_helper(network.id)
|
||||||
|
|
||||||
|
if new_cidrs:
|
||||||
|
self.device_manager.update(network)
|
||||||
|
|
||||||
@utils.synchronized('dhcp-agent')
|
@utils.synchronized('dhcp-agent')
|
||||||
def network_create_end(self, context, payload):
|
def network_create_end(self, context, payload):
|
||||||
"""Handle the network.create.end notification event."""
|
"""Handle the network.create.end notification event."""
|
||||||
@ -525,6 +528,51 @@ class DeviceManager(object):
|
|||||||
host_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname())
|
host_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname())
|
||||||
return 'dhcp%s-%s' % (host_uuid, network.id)
|
return 'dhcp%s-%s' % (host_uuid, network.id)
|
||||||
|
|
||||||
|
def _get_device(self, network):
|
||||||
|
"""Return DHCP ip_lib device for this host on the network."""
|
||||||
|
device_id = self.get_device_id(network)
|
||||||
|
port = self.plugin.get_dhcp_port(network.id, device_id)
|
||||||
|
interface_name = self.get_interface_name(network, port)
|
||||||
|
namespace = NS_PREFIX + network.id
|
||||||
|
return ip_lib.IPDevice(interface_name,
|
||||||
|
self.root_helper,
|
||||||
|
namespace)
|
||||||
|
|
||||||
|
def _set_default_route(self, network):
|
||||||
|
"""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 = self._get_device(network)
|
||||||
|
gateway = device.route.get_gateway()
|
||||||
|
|
||||||
|
for subnet in network.subnets:
|
||||||
|
skip_subnet = (
|
||||||
|
subnet.ip_version != 4
|
||||||
|
or not subnet.enable_dhcp
|
||||||
|
or subnet.gateway_ip is None)
|
||||||
|
|
||||||
|
if skip_subnet:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if gateway != subnet.gateway_ip:
|
||||||
|
m = _('Setting gateway for dhcp netns on net %(n)s to %(ip)s')
|
||||||
|
LOG.debug(m, {'n': network.id, 'ip': subnet.gateway_ip})
|
||||||
|
|
||||||
|
device.route.add_gateway(subnet.gateway_ip)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
msg = _('Removing gateway for dhcp netns on net %s')
|
||||||
|
LOG.debug(msg, network.id)
|
||||||
|
|
||||||
|
device.route.delete_gateway(gateway)
|
||||||
|
|
||||||
def setup(self, network, reuse_existing=False):
|
def setup(self, network, reuse_existing=False):
|
||||||
"""Create and initialize a device for network's DHCP on this host."""
|
"""Create and initialize a device for network's DHCP on this host."""
|
||||||
device_id = self.get_device_id(network)
|
device_id = self.get_device_id(network)
|
||||||
@ -583,9 +631,16 @@ class DeviceManager(object):
|
|||||||
# Only 1 subnet on metadata access network
|
# Only 1 subnet on metadata access network
|
||||||
gateway_ip = metadata_subnets[0].gateway_ip
|
gateway_ip = metadata_subnets[0].gateway_ip
|
||||||
device.route.add_gateway(gateway_ip)
|
device.route.add_gateway(gateway_ip)
|
||||||
|
elif self.conf.use_namespaces:
|
||||||
|
self._set_default_route(network)
|
||||||
|
|
||||||
return interface_name
|
return interface_name
|
||||||
|
|
||||||
|
def update(self, network):
|
||||||
|
"""Update device settings for the network's DHCP on this host."""
|
||||||
|
if self.conf.use_namespaces and not self.conf.enable_metadata_network:
|
||||||
|
self._set_default_route(network)
|
||||||
|
|
||||||
def destroy(self, network, device_name):
|
def destroy(self, network, device_name):
|
||||||
"""Destroy the device used for the network's DHCP on this host."""
|
"""Destroy the device used for the network's DHCP on this host."""
|
||||||
if self.conf.use_namespaces:
|
if self.conf.use_namespaces:
|
||||||
|
@ -678,12 +678,14 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
|||||||
payload = dict(subnet=dict(network_id=fake_network.id))
|
payload = dict(subnet=dict(network_id=fake_network.id))
|
||||||
self.cache.get_network_by_id.return_value = fake_network
|
self.cache.get_network_by_id.return_value = fake_network
|
||||||
self.plugin.get_network_info.return_value = fake_network
|
self.plugin.get_network_info.return_value = fake_network
|
||||||
|
self.dhcp.device_manager.update = mock.Mock()
|
||||||
|
|
||||||
self.dhcp.subnet_update_end(None, payload)
|
self.dhcp.subnet_update_end(None, payload)
|
||||||
|
|
||||||
self.cache.assert_has_calls([mock.call.put(fake_network)])
|
self.cache.assert_has_calls([mock.call.put(fake_network)])
|
||||||
self.call_driver.assert_called_once_with('reload_allocations',
|
self.call_driver.assert_called_once_with('reload_allocations',
|
||||||
fake_network)
|
fake_network)
|
||||||
|
self.dhcp.device_manager.update.assert_called_once_with(fake_network)
|
||||||
|
|
||||||
def test_subnet_update_end_restart(self):
|
def test_subnet_update_end_restart(self):
|
||||||
new_state = FakeModel(fake_network.id,
|
new_state = FakeModel(fake_network.id,
|
||||||
@ -695,12 +697,14 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
|||||||
payload = dict(subnet=dict(network_id=fake_network.id))
|
payload = dict(subnet=dict(network_id=fake_network.id))
|
||||||
self.cache.get_network_by_id.return_value = fake_network
|
self.cache.get_network_by_id.return_value = fake_network
|
||||||
self.plugin.get_network_info.return_value = new_state
|
self.plugin.get_network_info.return_value = new_state
|
||||||
|
self.dhcp.device_manager.update = mock.Mock()
|
||||||
|
|
||||||
self.dhcp.subnet_update_end(None, payload)
|
self.dhcp.subnet_update_end(None, payload)
|
||||||
|
|
||||||
self.cache.assert_has_calls([mock.call.put(new_state)])
|
self.cache.assert_has_calls([mock.call.put(new_state)])
|
||||||
self.call_driver.assert_called_once_with('restart',
|
self.call_driver.assert_called_once_with('restart',
|
||||||
new_state)
|
new_state)
|
||||||
|
self.dhcp.device_manager.update.assert_called_once_with(new_state)
|
||||||
|
|
||||||
def test_subnet_update_end_delete_payload(self):
|
def test_subnet_update_end_delete_payload(self):
|
||||||
prev_state = FakeModel(fake_network.id,
|
prev_state = FakeModel(fake_network.id,
|
||||||
@ -713,6 +717,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
|||||||
self.cache.get_network_by_subnet_id.return_value = prev_state
|
self.cache.get_network_by_subnet_id.return_value = prev_state
|
||||||
self.cache.get_network_by_id.return_value = prev_state
|
self.cache.get_network_by_id.return_value = prev_state
|
||||||
self.plugin.get_network_info.return_value = fake_network
|
self.plugin.get_network_info.return_value = fake_network
|
||||||
|
self.dhcp.device_manager.update = mock.Mock()
|
||||||
|
|
||||||
self.dhcp.subnet_delete_end(None, payload)
|
self.dhcp.subnet_delete_end(None, payload)
|
||||||
|
|
||||||
@ -723,6 +728,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
|||||||
mock.call.put(fake_network)])
|
mock.call.put(fake_network)])
|
||||||
self.call_driver.assert_called_once_with('restart',
|
self.call_driver.assert_called_once_with('restart',
|
||||||
fake_network)
|
fake_network)
|
||||||
|
self.dhcp.device_manager.update.assert_called_once_with(fake_network)
|
||||||
|
|
||||||
def test_port_update_end(self):
|
def test_port_update_end(self):
|
||||||
payload = dict(port=vars(fake_port2))
|
payload = dict(port=vars(fake_port2))
|
||||||
@ -935,6 +941,44 @@ class TestNetworkCache(base.BaseTestCase):
|
|||||||
self.assertEqual(nc.get_port_by_id(fake_port1.id), fake_port1)
|
self.assertEqual(nc.get_port_by_id(fake_port1.id), fake_port1)
|
||||||
|
|
||||||
|
|
||||||
|
class FakePort1:
|
||||||
|
id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||||
|
|
||||||
|
|
||||||
|
class FakeV4Subnet:
|
||||||
|
id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
|
||||||
|
ip_version = 4
|
||||||
|
cidr = '192.168.0.0/24'
|
||||||
|
gateway_ip = '192.168.0.1'
|
||||||
|
enable_dhcp = True
|
||||||
|
|
||||||
|
|
||||||
|
class FakeV4SubnetNoGateway:
|
||||||
|
id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||||
|
ip_version = 4
|
||||||
|
cidr = '192.168.1.0/24'
|
||||||
|
gateway_ip = None
|
||||||
|
enable_dhcp = True
|
||||||
|
|
||||||
|
|
||||||
|
class FakeV4Network:
|
||||||
|
id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||||
|
subnets = [FakeV4Subnet()]
|
||||||
|
ports = [FakePort1()]
|
||||||
|
|
||||||
|
|
||||||
|
class FakeV4NetworkNoSubnet:
|
||||||
|
id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||||
|
subnets = []
|
||||||
|
ports = []
|
||||||
|
|
||||||
|
|
||||||
|
class FakeV4NetworkNoGateway:
|
||||||
|
id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
|
||||||
|
subnets = [FakeV4SubnetNoGateway()]
|
||||||
|
ports = [FakePort1()]
|
||||||
|
|
||||||
|
|
||||||
class TestDeviceManager(base.BaseTestCase):
|
class TestDeviceManager(base.BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDeviceManager, self).setUp()
|
super(TestDeviceManager, self).setUp()
|
||||||
@ -979,6 +1023,7 @@ class TestDeviceManager(base.BaseTestCase):
|
|||||||
self.mock_driver.get_device_name.return_value = 'tap12345678-12'
|
self.mock_driver.get_device_name.return_value = 'tap12345678-12'
|
||||||
|
|
||||||
dh = dhcp_agent.DeviceManager(cfg.CONF, plugin)
|
dh = dhcp_agent.DeviceManager(cfg.CONF, plugin)
|
||||||
|
dh._set_default_route = mock.Mock()
|
||||||
interface_name = dh.setup(net, reuse_existing)
|
interface_name = dh.setup(net, reuse_existing)
|
||||||
|
|
||||||
self.assertEqual(interface_name, 'tap12345678-12')
|
self.assertEqual(interface_name, 'tap12345678-12')
|
||||||
@ -1002,6 +1047,8 @@ class TestDeviceManager(base.BaseTestCase):
|
|||||||
namespace=namespace))
|
namespace=namespace))
|
||||||
self.mock_driver.assert_has_calls(expected)
|
self.mock_driver.assert_has_calls(expected)
|
||||||
|
|
||||||
|
dh._set_default_route.assert_called_once_with(net)
|
||||||
|
|
||||||
def test_setup(self):
|
def test_setup(self):
|
||||||
self._test_setup_helper(False)
|
self._test_setup_helper(False)
|
||||||
|
|
||||||
@ -1104,6 +1151,153 @@ class TestDeviceManager(base.BaseTestCase):
|
|||||||
uuid5.called_once_with(uuid.NAMESPACE_DNS, 'localhost')
|
uuid5.called_once_with(uuid.NAMESPACE_DNS, 'localhost')
|
||||||
self.assertEqual(dh.get_device_id(fake_network), expected)
|
self.assertEqual(dh.get_device_id(fake_network), expected)
|
||||||
|
|
||||||
|
def _get_device_manager_with_mock_device(self, conf, device):
|
||||||
|
dh = dhcp_agent.DeviceManager(conf, None)
|
||||||
|
dh._get_device = mock.Mock(return_value=device)
|
||||||
|
return dh
|
||||||
|
|
||||||
|
def test_update(self):
|
||||||
|
# Try with namespaces and no metadata network
|
||||||
|
cfg.CONF.set_override('use_namespaces', True)
|
||||||
|
cfg.CONF.set_override('enable_metadata_network', False)
|
||||||
|
dh = dhcp_agent.DeviceManager(cfg.CONF, None)
|
||||||
|
dh._set_default_route = mock.Mock()
|
||||||
|
|
||||||
|
dh.update(True)
|
||||||
|
|
||||||
|
dh._set_default_route.assert_called_once_with(True)
|
||||||
|
|
||||||
|
# No namespaces, shouldn't set default route.
|
||||||
|
cfg.CONF.set_override('use_namespaces', False)
|
||||||
|
cfg.CONF.set_override('enable_metadata_network', False)
|
||||||
|
dh = dhcp_agent.DeviceManager(cfg.CONF, None)
|
||||||
|
dh._set_default_route = mock.Mock()
|
||||||
|
|
||||||
|
dh.update(FakeV4Network())
|
||||||
|
|
||||||
|
self.assertFalse(dh._set_default_route.called)
|
||||||
|
|
||||||
|
# Meta data network enabled, don't interfere with its gateway.
|
||||||
|
cfg.CONF.set_override('use_namespaces', True)
|
||||||
|
cfg.CONF.set_override('enable_metadata_network', True)
|
||||||
|
dh = dhcp_agent.DeviceManager(cfg.CONF, None)
|
||||||
|
dh._set_default_route = mock.Mock()
|
||||||
|
|
||||||
|
dh.update(FakeV4Network())
|
||||||
|
|
||||||
|
self.assertFalse(dh._set_default_route.called)
|
||||||
|
|
||||||
|
# For completeness
|
||||||
|
cfg.CONF.set_override('use_namespaces', False)
|
||||||
|
cfg.CONF.set_override('enable_metadata_network', True)
|
||||||
|
dh = dhcp_agent.DeviceManager(cfg.CONF, None)
|
||||||
|
dh._set_default_route = mock.Mock()
|
||||||
|
|
||||||
|
dh.update(FakeV4Network())
|
||||||
|
|
||||||
|
self.assertFalse(dh._set_default_route.called)
|
||||||
|
|
||||||
|
def test_set_default_route(self):
|
||||||
|
device = mock.Mock()
|
||||||
|
device.route.get_gateway.return_value = None
|
||||||
|
|
||||||
|
# Basic one subnet with gateway.
|
||||||
|
dh = self._get_device_manager_with_mock_device(cfg.CONF, device)
|
||||||
|
network = FakeV4Network()
|
||||||
|
|
||||||
|
dh._set_default_route(network)
|
||||||
|
|
||||||
|
device.route.get_gateway.assert_called_once()
|
||||||
|
self.assertFalse(device.route.delete_gateway.called)
|
||||||
|
device.route.add_gateway.assert_called_once_with('192.168.0.1')
|
||||||
|
|
||||||
|
def test_set_default_route_no_subnet(self):
|
||||||
|
device = mock.Mock()
|
||||||
|
device.route.get_gateway.return_value = None
|
||||||
|
|
||||||
|
# Try a namespace but no subnet.
|
||||||
|
dh = self._get_device_manager_with_mock_device(cfg.CONF, device)
|
||||||
|
network = FakeV4NetworkNoSubnet()
|
||||||
|
|
||||||
|
dh._set_default_route(network)
|
||||||
|
|
||||||
|
device.route.get_gateway.assert_called_once()
|
||||||
|
self.assertFalse(device.route.delete_gateway.called)
|
||||||
|
self.assertFalse(device.route.add_gateway.called)
|
||||||
|
|
||||||
|
def test_set_default_route_no_subnet_delete_gateway(self):
|
||||||
|
device = mock.Mock()
|
||||||
|
device.route.get_gateway.return_value = '192.168.0.1'
|
||||||
|
|
||||||
|
# Try a namespace but no subnet where a gateway needs to be deleted.
|
||||||
|
dh = self._get_device_manager_with_mock_device(cfg.CONF, device)
|
||||||
|
network = FakeV4NetworkNoSubnet()
|
||||||
|
|
||||||
|
dh._set_default_route(network)
|
||||||
|
|
||||||
|
device.route.get_gateway.assert_called_once()
|
||||||
|
device.route.delete_gateway.assert_called_once_with('192.168.0.1')
|
||||||
|
self.assertFalse(device.route.add_gateway.called)
|
||||||
|
|
||||||
|
def test_set_default_route_no_gateway(self):
|
||||||
|
device = mock.Mock()
|
||||||
|
device.route.get_gateway.return_value = '192.168.0.1'
|
||||||
|
|
||||||
|
# Try a subnet with no gateway
|
||||||
|
dh = self._get_device_manager_with_mock_device(cfg.CONF, device)
|
||||||
|
network = FakeV4NetworkNoGateway()
|
||||||
|
|
||||||
|
dh._set_default_route(network)
|
||||||
|
|
||||||
|
device.route.get_gateway.assert_called_once()
|
||||||
|
device.route.delete_gateway.assert_called_once_with('192.168.0.1')
|
||||||
|
self.assertFalse(device.route.add_gateway.called)
|
||||||
|
|
||||||
|
def test_set_default_route_do_nothing(self):
|
||||||
|
device = mock.Mock()
|
||||||
|
device.route.get_gateway.return_value = '192.168.0.1'
|
||||||
|
|
||||||
|
# Try a subnet where the gateway doesn't change. Should do nothing.
|
||||||
|
dh = self._get_device_manager_with_mock_device(cfg.CONF, device)
|
||||||
|
network = FakeV4Network()
|
||||||
|
|
||||||
|
dh._set_default_route(network)
|
||||||
|
|
||||||
|
device.route.get_gateway.assert_called_once()
|
||||||
|
self.assertFalse(device.route.delete_gateway.called)
|
||||||
|
self.assertFalse(device.route.add_gateway.called)
|
||||||
|
|
||||||
|
def test_set_default_route_change_gateway(self):
|
||||||
|
device = mock.Mock()
|
||||||
|
device.route.get_gateway.return_value = '192.168.0.2'
|
||||||
|
|
||||||
|
# Try a subnet with a gateway this is different than the current.
|
||||||
|
dh = self._get_device_manager_with_mock_device(cfg.CONF, device)
|
||||||
|
network = FakeV4Network()
|
||||||
|
|
||||||
|
dh._set_default_route(network)
|
||||||
|
|
||||||
|
device.route.get_gateway.assert_called_once()
|
||||||
|
self.assertFalse(device.route.delete_gateway.called)
|
||||||
|
device.route.add_gateway.assert_called_once_with('192.168.0.1')
|
||||||
|
|
||||||
|
def test_set_default_route_two_subnets(self):
|
||||||
|
device = mock.Mock()
|
||||||
|
device.route.get_gateway.return_value = None
|
||||||
|
|
||||||
|
# Try two subnets. Should set gateway from the first.
|
||||||
|
dh = self._get_device_manager_with_mock_device(cfg.CONF, device)
|
||||||
|
network = FakeV4Network()
|
||||||
|
subnet2 = FakeV4Subnet()
|
||||||
|
subnet2.gateway_ip = '192.168.1.1'
|
||||||
|
network.subnets = [subnet2, FakeV4Subnet()]
|
||||||
|
|
||||||
|
dh._set_default_route(network)
|
||||||
|
|
||||||
|
device.route.get_gateway.assert_called_once()
|
||||||
|
self.assertFalse(device.route.delete_gateway.called)
|
||||||
|
device.route.add_gateway.assert_called_once_with('192.168.1.1')
|
||||||
|
|
||||||
|
|
||||||
class TestDhcpLeaseRelay(base.BaseTestCase):
|
class TestDhcpLeaseRelay(base.BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user