Skip to content

Commit 1e568e9

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Delete network namespace on last port deletion" into stable/2023.1
2 parents 9003ffa + 31a79f1 commit 1e568e9

File tree

2 files changed

+65
-16
lines changed

2 files changed

+65
-16
lines changed

neutron/agent/linux/dhcp.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,9 @@ def clean_devices(self):
245245
class DhcpLocalProcess(DhcpBase, metaclass=abc.ABCMeta):
246246
PORTS = []
247247

248-
# Track running interfaces.
249-
_interfaces = set()
248+
# Track running interfaces, indexed by network ID, for example,
249+
# {net-id-1: set(intf_1, intf_2), net-id-2: set(intf_3, intf_4), ...}
250+
_interfaces = collections.defaultdict(set)
250251

251252
def __init__(self, conf, network, process_monitor, version=None,
252253
plugin=None, segment=None):
@@ -267,20 +268,24 @@ def __init__(self, conf, network, process_monitor, version=None,
267268
fileutils.ensure_tree(self.network_conf_dir, mode=0o755)
268269

269270
@classmethod
270-
def _add_running_interface(cls, interface):
271-
"""Safe method that add running interface"""
272-
cls._interfaces.add(interface)
271+
def _add_running_interface(cls, interface, network_id):
272+
"""Safe method that adds a given interface"""
273+
cls._interfaces[network_id].add(interface)
273274

274275
@classmethod
275-
def _del_running_interface(cls, interface):
276-
"""Safe method that remove given interface"""
277-
if interface in cls._interfaces:
278-
cls._interfaces.remove(interface)
276+
def _del_running_interface(cls, interface, network_id):
277+
"""Safe method that removes a given interface"""
278+
if cls._interfaces.get(network_id):
279+
if interface in cls._interfaces[network_id]:
280+
cls._interfaces[network_id].remove(interface)
281+
# no entries, cleanup
282+
if not cls._interfaces[network_id]:
283+
del cls._interfaces[network_id]
279284

280285
@classmethod
281-
def _has_running_interfaces(cls):
282-
"""Safe method that remove given interface"""
283-
return bool(cls._interfaces)
286+
def _has_running_interfaces(cls, network_id):
287+
"""Safe method that checks for interfaces"""
288+
bool(cls._interfaces.get(network_id))
284289

285290
@staticmethod
286291
def get_confs_dir(conf):
@@ -332,7 +337,8 @@ def _enable(self):
332337
self.network, self.segment)
333338
self.interface_name = interface_name
334339
self.spawn_process()
335-
self._add_running_interface(self.interface_name)
340+
self._add_running_interface(self.interface_name,
341+
self.network.id)
336342
return True
337343
except exceptions.ProcessExecutionError as error:
338344
LOG.debug("Spawning DHCP process for network %s failed; "
@@ -364,7 +370,7 @@ def disable(self, retain_port=False, block=False, **kwargs):
364370
pm.pid, SIGTERM_TIMEOUT)
365371
pm.disable(sig=str(int(signal.SIGKILL)))
366372
common_utils.wait_until_true(lambda: not self.active)
367-
self._del_running_interface(self.interface_name)
373+
self._del_running_interface(self.interface_name, self.network.id)
368374
if not retain_port:
369375
self._destroy_namespace_and_port()
370376
self._remove_config_files()
@@ -376,8 +382,9 @@ def _destroy_namespace_and_port(self):
376382
except RuntimeError:
377383
LOG.warning('Failed trying to delete interface: %s',
378384
self.interface_name)
379-
if not self._has_running_interfaces():
380-
# Delete nm only if we don't serve different segmentation id.
385+
# Delete namespace only if there are no running interfaces in it,
386+
# which covers the case where a network has multiple segmentation ids.
387+
if not self._has_running_interfaces(self.network.id):
381388
try:
382389
ip_lib.delete_network_namespace(self.network.namespace)
383390
except RuntimeError:

neutron/tests/unit/agent/linux/test_dhcp.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,17 @@ def __init__(self, domain='openstacklocal'):
719719
FakeRouterPort(domain=domain)]
720720

721721

722+
class FakeDualNetworkV2(object):
723+
def __init__(self, domain='openstacklocal'):
724+
self.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
725+
self.subnets = [FakeV4Subnet(), FakeV6SubnetDHCPStateful()]
726+
self.namespace = 'qdhcp-ns-v2'
727+
self.ports = [FakePort1(domain=domain), FakeV6Port(domain=domain),
728+
FakeDualPort(domain=domain),
729+
FakeRouterHAPort(),
730+
FakeRouterPort(domain=domain)]
731+
732+
722733
class FakeDeviceManagerNetwork(object):
723734
def __init__(self):
724735
self.id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
@@ -1259,6 +1270,37 @@ def test_disable(self):
12591270

12601271
delete_ns.assert_called_with('qdhcp-ns')
12611272

1273+
def test_enable_disable_two_networks(self):
1274+
attrs_to_mock = {'active': mock.DEFAULT}
1275+
1276+
with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks:
1277+
mocks['active'].__get__ = mock.Mock(return_value=False)
1278+
lp = LocalChild(self.conf, FakeDualNetwork())
1279+
lp2 = LocalChild(self.conf, FakeDualNetworkV2())
1280+
lp.enable()
1281+
lp2.enable()
1282+
with mock.patch('neutron.agent.linux.ip_lib.'
1283+
'delete_network_namespace') as delete_ns:
1284+
lp.disable()
1285+
self.rmtree.assert_called_once()
1286+
1287+
self._assert_disabled(lp)
1288+
1289+
delete_ns.assert_called_once()
1290+
delete_ns.assert_called_with('qdhcp-ns')
1291+
1292+
delete_ns.reset_mock()
1293+
self.rmtree.reset_mock()
1294+
with mock.patch('neutron.agent.linux.ip_lib.'
1295+
'delete_network_namespace') as delete_ns:
1296+
lp2.disable()
1297+
self.rmtree.assert_called_once()
1298+
1299+
self._assert_disabled(lp2)
1300+
1301+
delete_ns.assert_called_once()
1302+
delete_ns.assert_called_with('qdhcp-ns-v2')
1303+
12621304
def test_disable_config_dir_removed_after_destroy(self):
12631305
parent = mock.MagicMock()
12641306
parent.attach_mock(self.rmtree, 'rmtree')

0 commit comments

Comments
 (0)