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
This commit is contained in:
Yong Sheng Gong 2014-06-30 15:01:17 +08:00 committed by Carl Baldwin
parent ec3d40cba1
commit a786806064
9 changed files with 70 additions and 9 deletions

View File

@ -46,3 +46,6 @@ ip6tables-restore: CommandFilter, ip6tables-restore, root
# Keepalived # Keepalived
keepalived: CommandFilter, keepalived, root keepalived: CommandFilter, keepalived, root
kill_keepalived: KillFilter, root, /usr/sbin/keepalived, -HUP, -15, -9 kill_keepalived: KillFilter, root, /usr/sbin/keepalived, -HUP, -15, -9
# l3 agent to delete floatingip's conntrack state
conntrack: CommandFilter, conntrack, root

View File

@ -1115,6 +1115,9 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
else: else:
net = netaddr.IPNetwork(ip_cidr) net = netaddr.IPNetwork(ip_cidr)
device.addr.delete(net.version, 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']: if ri.router['distributed']:
self.floating_ip_removed_dist(ri, ip_cidr) self.floating_ip_removed_dist(ri, ip_cidr)

View File

@ -25,6 +25,7 @@ from neutron.agent.linux import ovs_lib
from neutron.agent.linux import utils from neutron.agent.linux import utils
from neutron.common import exceptions from neutron.common import exceptions
from neutron.extensions import flavor from neutron.extensions import flavor
from neutron.openstack.common.gettextutils import _LE
from neutron.openstack.common import importutils from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging from neutron.openstack.common import log as logging
@ -110,6 +111,9 @@ class LinuxInterfaceDriver(object):
for ip_cidr, ip_version in previous.items(): for ip_cidr, ip_version in previous.items():
if ip_cidr not in preserve_ips: if ip_cidr not in preserve_ips:
device.addr.delete(ip_version, ip_cidr) device.addr.delete(ip_version, ip_cidr)
self.delete_conntrack_state(root_helper=self.root_helper,
namespace=namespace,
ip=ip_cidr)
if gateway: if gateway:
device.route.add_gateway(gateway) device.route.add_gateway(gateway)
@ -121,6 +125,43 @@ class LinuxInterfaceDriver(object):
for route in existing_onlink_routes - new_onlink_routes: for route in existing_onlink_routes - new_onlink_routes:
device.route.delete_onlink_route(route) 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): def check_bridge_exists(self, bridge):
if not ip_lib.device_exists(bridge): if not ip_lib.device_exists(bridge):
raise exceptions.BridgeDoesNotExist(bridge=bridge) raise exceptions.BridgeDoesNotExist(bridge=bridge)

View File

@ -532,7 +532,8 @@ class IpNetnsCommand(IpCommandBase):
def delete(self, name): def delete(self, name):
self._as_root('delete', name, use_root_namespace=True) 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 = [] ns_params = []
if self._parent.namespace: if self._parent.namespace:
if not self._parent.root_helper: if not self._parent.root_helper:
@ -546,7 +547,7 @@ class IpNetnsCommand(IpCommandBase):
return utils.execute( return utils.execute(
ns_params + env_params + list(cmds), ns_params + env_params + list(cmds),
root_helper=self._parent.root_helper, 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): def exists(self, name):
output = self._parent._execute('o', 'netns', ['list']) output = self._parent._execute('o', 'netns', ['list'])

View File

@ -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, 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: try:
obj, cmd = create_process(cmd, root_helper=root_helper, obj, cmd = create_process(cmd, root_helper=root_helper,
addl_env=addl_env) 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, "Stderr: %(stderr)r") % {'cmd': cmd, 'code': obj.returncode,
'stdout': _stdout, 'stderr': _stderr} '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: if obj.returncode and log_fail_as_error:
LOG.error(m) LOG.error(m)
else: else:

View File

@ -1153,6 +1153,10 @@ vrrp_instance VR_1 {
ri, {'id': _uuid()}) ri, {'id': _uuid()})
self.assertEqual({}, fip_statuses) self.assertEqual({}, fip_statuses)
device.addr.delete.assert_called_once_with(4, '15.1.2.3/32') 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): def test_process_router_floating_ip_nat_rules_remove(self):
ri = mock.MagicMock() ri = mock.MagicMock()

View File

@ -798,7 +798,8 @@ class TestDnsmasq(TestBase):
self.assertTrue(mocks['_output_opts_file'].called) self.assertTrue(mocks['_output_opts_file'].called)
self.execute.assert_called_once_with(expected, self.execute.assert_called_once_with(expected,
root_helper='sudo', root_helper='sudo',
check_exit_code=True) check_exit_code=True,
extra_ok_codes=None)
def test_spawn(self): def test_spawn(self):
self._test_spawn(['--conf-file=', '--domain=openstacklocal']) self._test_spawn(['--conf-file=', '--domain=openstacklocal'])

View File

@ -41,7 +41,8 @@ class TestProcessManager(base.BaseTestCase):
name.assert_called_once_with(ensure_pids_dir=True) name.assert_called_once_with(ensure_pids_dir=True)
self.execute.assert_called_once_with(['the', 'cmd'], self.execute.assert_called_once_with(['the', 'cmd'],
root_helper='sudo', root_helper='sudo',
check_exit_code=True) check_exit_code=True,
extra_ok_codes=None)
def test_enable_with_namespace(self): def test_enable_with_namespace(self):
callback = mock.Mock() callback = mock.Mock()

View File

@ -792,7 +792,7 @@ class TestIpNetnsCommand(TestIPCmdBase):
execute.assert_called_once_with( execute.assert_called_once_with(
['ip', 'netns', 'exec', 'ns', ['ip', 'netns', 'exec', 'ns',
'sysctl', '-w', 'net.ipv4.conf.all.promote_secondaries=1'], '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): def test_delete_namespace(self):
with mock.patch('neutron.agent.linux.utils.execute'): 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', execute.assert_called_once_with(['ip', 'netns', 'exec', 'ns', 'ip',
'link', 'list'], 'link', 'list'],
root_helper='sudo', root_helper='sudo',
check_exit_code=True) check_exit_code=True,
extra_ok_codes=None)
def test_execute_env_var_prepend(self): def test_execute_env_var_prepend(self):
self.parent.namespace = 'ns' self.parent.namespace = 'ns'
@ -841,7 +842,7 @@ class TestIpNetnsCommand(TestIPCmdBase):
['ip', 'netns', 'exec', 'ns', 'env'] + ['ip', 'netns', 'exec', 'ns', 'env'] +
['%s=%s' % (k, v) for k, v in env.items()] + ['%s=%s' % (k, v) for k, v in env.items()] +
['ip', 'link', 'list'], ['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): def test_execute_nosudo_with_no_namespace(self):
with mock.patch('neutron.agent.linux.utils.execute') as execute: with mock.patch('neutron.agent.linux.utils.execute') as execute:
@ -850,7 +851,8 @@ class TestIpNetnsCommand(TestIPCmdBase):
self.netns_cmd.execute(['test']) self.netns_cmd.execute(['test'])
execute.assert_called_once_with(['test'], execute.assert_called_once_with(['test'],
root_helper=None, root_helper=None,
check_exit_code=True) check_exit_code=True,
extra_ok_codes=None)
class TestDeviceExists(base.BaseTestCase): class TestDeviceExists(base.BaseTestCase):