# 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
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)
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
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)
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)
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:
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'])
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)
"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:
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()
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'])
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()
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'):
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'
['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:
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):