diff --git a/neutron/agent/l3_agent.py b/neutron/agent/l3_agent.py index 5ae6dbc526d..72a1aa36729 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -534,8 +534,17 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): bridge=self.conf.external_network_bridge, namespace=ri.ns_name(), prefix=EXTERNAL_DEV_PREFIX) + + # Compute a list of addresses this router is supposed to have. + # This avoids unnecessarily removing those addresses and + # causing a momentarily network outage. + floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, []) + preserve_ips = [ip['floating_ip_address'] + FLOATING_IP_CIDR_SUFFIX + for ip in floating_ips] + self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']], - namespace=ri.ns_name()) + namespace=ri.ns_name(), + preserve_ips=preserve_ips) ip_address = ex_gw_port['ip_cidr'].split('/')[0] self._send_gratuitous_arp_packet(ri, interface_name, ip_address) diff --git a/neutron/agent/linux/interface.py b/neutron/agent/linux/interface.py index 1c9bddaed02..fc32efc1cb9 100644 --- a/neutron/agent/linux/interface.py +++ b/neutron/agent/linux/interface.py @@ -71,10 +71,12 @@ class LinuxInterfaceDriver(object): self.conf = conf self.root_helper = config.get_root_helper(conf) - def init_l3(self, device_name, ip_cidrs, namespace=None): + def init_l3(self, device_name, ip_cidrs, namespace=None, + preserve_ips=[]): """Set the L3 settings for the interface using data from the port. ip_cidrs: list of 'X.X.X.X/YY' strings + preserve_ips: list of ip cidrs that should not be removed from device """ device = ip_lib.IPDevice(device_name, self.root_helper, @@ -96,7 +98,8 @@ class LinuxInterfaceDriver(object): # clean up any old addresses for ip_cidr, ip_version in previous.items(): - device.addr.delete(ip_version, ip_cidr) + if ip_cidr not in preserve_ips: + device.addr.delete(ip_version, ip_cidr) def check_bridge_exists(self, bridge): if not ip_lib.device_exists(bridge): diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index a23ece46600..9aa545ff7e1 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -167,12 +167,20 @@ class TestBasicRouterOperations(base.BaseTestCase): if action == 'add': self.device_exists.return_value = False + ri.router = mock.Mock() + ri.router.get.return_value = [{'floating_ip_address': + '192.168.1.34'}] agent.external_gateway_added(ri, ex_gw_port, interface_name, internal_cidrs) self.assertEqual(self.mock_driver.plug.call_count, 1) self.assertEqual(self.mock_driver.init_l3.call_count, 1) self.send_arp.assert_called_once_with(ri, interface_name, '20.0.0.30') + kwargs = {'preserve_ips': ['192.168.1.34/32'], + 'namespace': 'qrouter-' + router_id} + self.mock_driver.init_l3.assert_called_with(interface_name, + ['20.0.0.30/24'], + **kwargs) elif action == 'remove': self.device_exists.return_value = True diff --git a/neutron/tests/unit/test_linux_interface.py b/neutron/tests/unit/test_linux_interface.py index 7ffc8276086..48ecb4c31d0 100644 --- a/neutron/tests/unit/test_linux_interface.py +++ b/neutron/tests/unit/test_linux_interface.py @@ -94,6 +94,21 @@ class TestABCDriver(TestBase): mock.call().addr.add(4, '192.168.1.2/24', '192.168.1.255'), mock.call().addr.delete(4, '172.16.77.240/24')]) + def test_l3_init_with_preserve(self): + addresses = [dict(ip_version=4, scope='global', + dynamic=False, cidr='192.168.1.3/32')] + self.ip_dev().addr.list = mock.Mock(return_value=addresses) + + bc = BaseChild(self.conf) + ns = '12345678-1234-5678-90ab-ba0987654321' + bc.init_l3('tap0', ['192.168.1.2/24'], namespace=ns, + preserve_ips=['192.168.1.3/32']) + self.ip_dev.assert_has_calls( + [mock.call('tap0', 'sudo', namespace=ns), + mock.call().addr.list(scope='global', filters=['permanent']), + mock.call().addr.add(4, '192.168.1.2/24', '192.168.1.255')]) + self.assertFalse(self.ip_dev().addr.delete.called) + class TestOVSInterfaceDriver(TestBase):