Merge "Delete network namespace on last port deletion"

This commit is contained in:
Zuul 2023-06-28 07:53:48 +00:00 committed by Gerrit Code Review
commit 41ee4cec69
2 changed files with 65 additions and 16 deletions

View File

@ -245,8 +245,9 @@ class DhcpBase(object, metaclass=abc.ABCMeta):
class DhcpLocalProcess(DhcpBase, metaclass=abc.ABCMeta):
PORTS = []
# Track running interfaces.
_interfaces = set()
# Track running interfaces, indexed by network ID, for example,
# {net-id-1: set(intf_1, intf_2), net-id-2: set(intf_3, intf_4), ...}
_interfaces = collections.defaultdict(set)
def __init__(self, conf, network, process_monitor, version=None,
plugin=None, segment=None):
@ -267,20 +268,24 @@ class DhcpLocalProcess(DhcpBase, metaclass=abc.ABCMeta):
fileutils.ensure_tree(self.network_conf_dir, mode=0o755)
@classmethod
def _add_running_interface(cls, interface):
"""Safe method that add running interface"""
cls._interfaces.add(interface)
def _add_running_interface(cls, interface, network_id):
"""Safe method that adds a given interface"""
cls._interfaces[network_id].add(interface)
@classmethod
def _del_running_interface(cls, interface):
"""Safe method that remove given interface"""
if interface in cls._interfaces:
cls._interfaces.remove(interface)
def _del_running_interface(cls, interface, network_id):
"""Safe method that removes a given interface"""
if cls._interfaces.get(network_id):
if interface in cls._interfaces[network_id]:
cls._interfaces[network_id].remove(interface)
# no entries, cleanup
if not cls._interfaces[network_id]:
del cls._interfaces[network_id]
@classmethod
def _has_running_interfaces(cls):
"""Safe method that remove given interface"""
return bool(cls._interfaces)
def _has_running_interfaces(cls, network_id):
"""Safe method that checks for interfaces"""
bool(cls._interfaces.get(network_id))
@staticmethod
def get_confs_dir(conf):
@ -332,7 +337,8 @@ class DhcpLocalProcess(DhcpBase, metaclass=abc.ABCMeta):
self.network, self.segment)
self.interface_name = interface_name
self.spawn_process()
self._add_running_interface(self.interface_name)
self._add_running_interface(self.interface_name,
self.network.id)
return True
except exceptions.ProcessExecutionError as error:
LOG.debug("Spawning DHCP process for network %s failed; "
@ -364,7 +370,7 @@ class DhcpLocalProcess(DhcpBase, metaclass=abc.ABCMeta):
pm.pid, SIGTERM_TIMEOUT)
pm.disable(sig=str(int(signal.SIGKILL)))
common_utils.wait_until_true(lambda: not self.active)
self._del_running_interface(self.interface_name)
self._del_running_interface(self.interface_name, self.network.id)
if not retain_port:
self._destroy_namespace_and_port()
self._remove_config_files()
@ -376,8 +382,9 @@ class DhcpLocalProcess(DhcpBase, metaclass=abc.ABCMeta):
except RuntimeError:
LOG.warning('Failed trying to delete interface: %s',
self.interface_name)
if not self._has_running_interfaces():
# Delete nm only if we don't serve different segmentation id.
# Delete namespace only if there are no running interfaces in it,
# which covers the case where a network has multiple segmentation ids.
if not self._has_running_interfaces(self.network.id):
try:
ip_lib.delete_network_namespace(self.network.namespace)
except RuntimeError:

View File

@ -719,6 +719,17 @@ class FakeDualNetwork(object):
FakeRouterPort(domain=domain)]
class FakeDualNetworkV2(object):
def __init__(self, domain='openstacklocal'):
self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
self.subnets = [FakeV4Subnet(), FakeV6SubnetDHCPStateful()]
self.namespace = 'qdhcp-ns-v2'
self.ports = [FakePort1(domain=domain), FakeV6Port(domain=domain),
FakeDualPort(domain=domain),
FakeRouterHAPort(),
FakeRouterPort(domain=domain)]
class FakeDeviceManagerNetwork(object):
def __init__(self):
self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
@ -1259,6 +1270,37 @@ class TestDhcpLocalProcess(TestBase):
delete_ns.assert_called_with('qdhcp-ns')
def test_enable_disable_two_networks(self):
attrs_to_mock = {'active': mock.DEFAULT}
with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks:
mocks['active'].__get__ = mock.Mock(return_value=False)
lp = LocalChild(self.conf, FakeDualNetwork())
lp2 = LocalChild(self.conf, FakeDualNetworkV2())
lp.enable()
lp2.enable()
with mock.patch('neutron.agent.linux.ip_lib.'
'delete_network_namespace') as delete_ns:
lp.disable()
self.rmtree.assert_called_once()
self._assert_disabled(lp)
delete_ns.assert_called_once()
delete_ns.assert_called_with('qdhcp-ns')
delete_ns.reset_mock()
self.rmtree.reset_mock()
with mock.patch('neutron.agent.linux.ip_lib.'
'delete_network_namespace') as delete_ns:
lp2.disable()
self.rmtree.assert_called_once()
self._assert_disabled(lp2)
delete_ns.assert_called_once()
delete_ns.assert_called_with('qdhcp-ns-v2')
def test_disable_config_dir_removed_after_destroy(self):
parent = mock.MagicMock()
parent.attach_mock(self.rmtree, 'rmtree')