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 9d5bd529cd
commit 9666455383
9 changed files with 70 additions and 9 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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'])

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,
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:

View File

@ -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()

View File

@ -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'])

View File

@ -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()

View File

@ -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):