diff --git a/etc/neutron/rootwrap.d/l3.filters b/etc/neutron/rootwrap.d/l3.filters index 6563169827..9a3031822a 100644 --- a/etc/neutron/rootwrap.d/l3.filters +++ b/etc/neutron/rootwrap.d/l3.filters @@ -46,3 +46,6 @@ ip6tables-restore: CommandFilter, ip6tables-restore, root # Keepalived keepalived: CommandFilter, keepalived, root kill_keepalived: KillFilter, root, /usr/sbin/keepalived, -HUP, -15, -9 + +# l3 agent to delete floatingip's conntrack state +conntrack: CommandFilter, conntrack, root diff --git a/neutron/agent/l3_agent.py b/neutron/agent/l3_agent.py index 84953bc2e2..1dd673f3a3 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -1115,6 +1115,9 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, else: net = netaddr.IPNetwork(ip_cidr) device.addr.delete(net.version, ip_cidr) + self.driver.delete_conntrack_state(root_helper=self.root_helper, + namespace=ri.ns_name, + ip=ip_cidr) if ri.router['distributed']: self.floating_ip_removed_dist(ri, ip_cidr) diff --git a/neutron/agent/linux/interface.py b/neutron/agent/linux/interface.py index 77b8365c4b..7613db4105 100644 --- a/neutron/agent/linux/interface.py +++ b/neutron/agent/linux/interface.py @@ -25,6 +25,7 @@ from neutron.agent.linux import ovs_lib from neutron.agent.linux import utils from neutron.common import exceptions from neutron.extensions import flavor +from neutron.openstack.common.gettextutils import _LE from neutron.openstack.common import importutils from neutron.openstack.common import log as logging @@ -110,6 +111,9 @@ class LinuxInterfaceDriver(object): for ip_cidr, ip_version in previous.items(): if ip_cidr not in preserve_ips: device.addr.delete(ip_version, ip_cidr) + self.delete_conntrack_state(root_helper=self.root_helper, + namespace=namespace, + ip=ip_cidr) if gateway: device.route.add_gateway(gateway) @@ -121,6 +125,43 @@ class LinuxInterfaceDriver(object): for route in existing_onlink_routes - new_onlink_routes: device.route.delete_onlink_route(route) + def delete_conntrack_state(self, root_helper, namespace, ip): + """Delete conntrack state associated with an IP address. + + This terminates any active connections through an IP. Call this soon + after removing the IP address from an interface so that new connections + cannot be created before the IP address is gone. + + root_helper: root_helper to gain root access to call conntrack + namespace: the name of the namespace where the IP has been configured + ip: the IP address for which state should be removed. This can be + passed as a string with or without /NN. A netaddr.IPAddress or + netaddr.Network representing the IP address can also be passed. + """ + ip_str = str(netaddr.IPNetwork(ip).ip) + ip_wrapper = ip_lib.IPWrapper(root_helper, namespace=namespace) + + # Delete conntrack state for ingress traffic + # If 0 flow entries have been deleted + # conntrack -D will return 1 + try: + ip_wrapper.netns.execute(["conntrack", "-D", "-d", ip_str], + check_exit_code=True, + extra_ok_codes=[1]) + + except RuntimeError: + LOG.exception(_LE("Failed deleting ingress connection state of" + " floatingip %s"), ip_str) + + # Delete conntrack state for egress traffic + try: + ip_wrapper.netns.execute(["conntrack", "-D", "-q", ip_str], + check_exit_code=True, + extra_ok_codes=[1]) + except RuntimeError: + LOG.exception(_LE("Failed deleting egress connection state of" + " floatingip %s"), ip_str) + def check_bridge_exists(self, bridge): if not ip_lib.device_exists(bridge): raise exceptions.BridgeDoesNotExist(bridge=bridge) diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index f085b4ded8..1d32a5497f 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -532,7 +532,8 @@ class IpNetnsCommand(IpCommandBase): def delete(self, name): self._as_root('delete', name, use_root_namespace=True) - def execute(self, cmds, addl_env={}, check_exit_code=True): + def execute(self, cmds, addl_env={}, check_exit_code=True, + extra_ok_codes=None): ns_params = [] if self._parent.namespace: if not self._parent.root_helper: @@ -546,7 +547,7 @@ class IpNetnsCommand(IpCommandBase): return utils.execute( ns_params + env_params + list(cmds), root_helper=self._parent.root_helper, - check_exit_code=check_exit_code) + check_exit_code=check_exit_code, extra_ok_codes=extra_ok_codes) def exists(self, name): output = self._parent._execute('o', 'netns', ['list']) diff --git a/neutron/agent/linux/utils.py b/neutron/agent/linux/utils.py index e82a0d1496..e0a66f843c 100644 --- a/neutron/agent/linux/utils.py +++ b/neutron/agent/linux/utils.py @@ -58,7 +58,8 @@ def create_process(cmd, root_helper=None, addl_env=None): def execute(cmd, root_helper=None, process_input=None, addl_env=None, - check_exit_code=True, return_stderr=False, log_fail_as_error=True): + check_exit_code=True, return_stderr=False, log_fail_as_error=True, + extra_ok_codes=None): try: obj, cmd = create_process(cmd, root_helper=root_helper, addl_env=addl_env) @@ -70,6 +71,10 @@ def execute(cmd, root_helper=None, process_input=None, addl_env=None, "Stderr: %(stderr)r") % {'cmd': cmd, 'code': obj.returncode, 'stdout': _stdout, 'stderr': _stderr} + extra_ok_codes = extra_ok_codes or [] + if obj.returncode and obj.returncode in extra_ok_codes: + obj.returncode = None + if obj.returncode and log_fail_as_error: LOG.error(m) else: diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index 0be506e7ce..8c0d0256f9 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -1153,6 +1153,10 @@ vrrp_instance VR_1 { ri, {'id': _uuid()}) self.assertEqual({}, fip_statuses) device.addr.delete.assert_called_once_with(4, '15.1.2.3/32') + self.mock_driver.delete_conntrack_state.assert_called_once_with( + root_helper=self.conf.root_helper, + namespace=ri.ns_name, + ip='15.1.2.3/32') def test_process_router_floating_ip_nat_rules_remove(self): ri = mock.MagicMock() diff --git a/neutron/tests/unit/test_linux_dhcp.py b/neutron/tests/unit/test_linux_dhcp.py index f6f5fc6a23..b3090e1b57 100644 --- a/neutron/tests/unit/test_linux_dhcp.py +++ b/neutron/tests/unit/test_linux_dhcp.py @@ -798,7 +798,8 @@ class TestDnsmasq(TestBase): self.assertTrue(mocks['_output_opts_file'].called) self.execute.assert_called_once_with(expected, root_helper='sudo', - check_exit_code=True) + check_exit_code=True, + extra_ok_codes=None) def test_spawn(self): self._test_spawn(['--conf-file=', '--domain=openstacklocal']) diff --git a/neutron/tests/unit/test_linux_external_process.py b/neutron/tests/unit/test_linux_external_process.py index 85c24544a2..88f4bc075b 100644 --- a/neutron/tests/unit/test_linux_external_process.py +++ b/neutron/tests/unit/test_linux_external_process.py @@ -41,7 +41,8 @@ class TestProcessManager(base.BaseTestCase): name.assert_called_once_with(ensure_pids_dir=True) self.execute.assert_called_once_with(['the', 'cmd'], root_helper='sudo', - check_exit_code=True) + check_exit_code=True, + extra_ok_codes=None) def test_enable_with_namespace(self): callback = mock.Mock() diff --git a/neutron/tests/unit/test_linux_ip_lib.py b/neutron/tests/unit/test_linux_ip_lib.py index 6c20aa6d74..2b9486d3e7 100644 --- a/neutron/tests/unit/test_linux_ip_lib.py +++ b/neutron/tests/unit/test_linux_ip_lib.py @@ -792,7 +792,7 @@ class TestIpNetnsCommand(TestIPCmdBase): execute.assert_called_once_with( ['ip', 'netns', 'exec', 'ns', 'sysctl', '-w', 'net.ipv4.conf.all.promote_secondaries=1'], - root_helper='sudo', check_exit_code=True) + root_helper='sudo', check_exit_code=True, extra_ok_codes=None) def test_delete_namespace(self): with mock.patch('neutron.agent.linux.utils.execute'): @@ -830,7 +830,8 @@ class TestIpNetnsCommand(TestIPCmdBase): execute.assert_called_once_with(['ip', 'netns', 'exec', 'ns', 'ip', 'link', 'list'], root_helper='sudo', - check_exit_code=True) + check_exit_code=True, + extra_ok_codes=None) def test_execute_env_var_prepend(self): self.parent.namespace = 'ns' @@ -841,7 +842,7 @@ class TestIpNetnsCommand(TestIPCmdBase): ['ip', 'netns', 'exec', 'ns', 'env'] + ['%s=%s' % (k, v) for k, v in env.items()] + ['ip', 'link', 'list'], - root_helper='sudo', check_exit_code=True) + root_helper='sudo', check_exit_code=True, extra_ok_codes=None) def test_execute_nosudo_with_no_namespace(self): with mock.patch('neutron.agent.linux.utils.execute') as execute: @@ -850,7 +851,8 @@ class TestIpNetnsCommand(TestIPCmdBase): self.netns_cmd.execute(['test']) execute.assert_called_once_with(['test'], root_helper=None, - check_exit_code=True) + check_exit_code=True, + extra_ok_codes=None) class TestDeviceExists(base.BaseTestCase):