From 3a30d19e4e2e5bf4f54045561158661b43beecb3 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Fri, 26 Sep 2014 12:00:33 +0200 Subject: [PATCH] Deletes floating ip related connection states When a floating ip is dissociated with a port, the current connection with the floating ip is still working. This patch will clear the connection state and cut off the connection immediately. Since conntrack -D will return 1, which is not an error code, so add extra_ok_codes argument to execute methods. Change-Id: Ia9bd7ae243a0859dcb97e2fa939f7d16f9c2456c Closes-Bug: #1334926 (cherry picked from commit 966645538395079b5337b5ed30d597112279283c) --- etc/neutron/rootwrap.d/l3.filters | 3 ++ neutron/agent/l3_agent.py | 4 ++ neutron/agent/linux/interface.py | 40 +++++++++++++++++++ neutron/agent/linux/ip_lib.py | 5 ++- neutron/agent/linux/utils.py | 8 +++- neutron/tests/unit/test_l3_agent.py | 4 ++ neutron/tests/unit/test_linux_dhcp.py | 3 +- .../tests/unit/test_linux_external_process.py | 3 +- neutron/tests/unit/test_linux_ip_lib.py | 6 ++- 9 files changed, 69 insertions(+), 7 deletions(-) diff --git a/etc/neutron/rootwrap.d/l3.filters b/etc/neutron/rootwrap.d/l3.filters index 2031d779ec4..41138efb704 100644 --- a/etc/neutron/rootwrap.d/l3.filters +++ b/etc/neutron/rootwrap.d/l3.filters @@ -39,3 +39,6 @@ iptables-save: CommandFilter, iptables-save, root iptables-restore: CommandFilter, iptables-restore, root ip6tables-save: CommandFilter, ip6tables-save, root ip6tables-restore: CommandFilter, ip6tables-restore, root + +# 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 d90af43f3f8..b155b7c3a85 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -612,6 +612,10 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX): 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) return fip_statuses def _get_ex_gw_port(self, ri): diff --git a/neutron/agent/linux/interface.py b/neutron/agent/linux/interface.py index c3a97ecb209..89d30a5f79e 100644 --- a/neutron/agent/linux/interface.py +++ b/neutron/agent/linux/interface.py @@ -100,6 +100,46 @@ 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) + + 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(_("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(_("Failed deleting egress connection state of" + " floatingip %s"), ip_str) def check_bridge_exists(self, bridge): if not ip_lib.device_exists(bridge): diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index 3c8e3b07848..ef91a717f71 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -450,7 +450,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): if not self._parent.root_helper: raise exceptions.SudoRequired() ns_params = [] @@ -464,7 +465,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 72b17b42520..62813393c1d 100644 --- a/neutron/agent/linux/utils.py +++ b/neutron/agent/linux/utils.py @@ -60,7 +60,7 @@ 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): + check_exit_code=True, return_stderr=False, extra_ok_codes=None): try: obj, cmd = create_process(cmd, root_helper=root_helper, addl_env=addl_env) @@ -71,7 +71,13 @@ def execute(cmd, root_helper=None, process_input=None, addl_env=None, m = _("\nCommand: %(cmd)s\nExit code: %(code)s\nStdout: %(stdout)r\n" "Stderr: %(stderr)r") % {'cmd': cmd, 'code': obj.returncode, 'stdout': _stdout, 'stderr': _stderr} + LOG.debug(m) + + extra_ok_codes = extra_ok_codes or [] + if obj.returncode and obj.returncode in extra_ok_codes: + obj.returncode = None + if obj.returncode and check_exit_code: raise RuntimeError(m) finally: diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index ec272ef6a93..702f44e1c81 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -495,6 +495,10 @@ class TestBasicRouterOperations(base.BaseTestCase): 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 47912f27e88..23e7955d230 100644 --- a/neutron/tests/unit/test_linux_dhcp.py +++ b/neutron/tests/unit/test_linux_dhcp.py @@ -706,7 +706,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 36146a2ba9e..96c1959ab20 100644 --- a/neutron/tests/unit/test_linux_external_process.py +++ b/neutron/tests/unit/test_linux_external_process.py @@ -46,7 +46,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 7eb58ad7ba5..4e533ad6b8d 100644 --- a/neutron/tests/unit/test_linux_ip_lib.py +++ b/neutron/tests/unit/test_linux_ip_lib.py @@ -766,7 +766,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' @@ -776,7 +777,8 @@ class TestIpNetnsCommand(TestIPCmdBase): execute.assert_called_once_with( ['ip', 'netns', 'exec', 'ns', 'env', 'FOO=1', 'BAR=2', 'ip', 'link', 'list'], - root_helper='sudo', check_exit_code=True) + root_helper='sudo', check_exit_code=True, + extra_ok_codes=None) class TestDeviceExists(base.BaseTestCase):