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:
parent
ec3d40cba1
commit
a786806064
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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'])
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
@ -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'])
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user