Skip to content

Commit a9df2b1

Browse files
authored
Merge pull request #922 from rackerlabs/PUC-925
fix: check static network segments on bind
2 parents 6cbc769 + 1a58174 commit a9df2b1

File tree

4 files changed

+62
-6
lines changed

4 files changed

+62
-6
lines changed

python/neutron-understack/neutron_understack/neutron_understack_mech.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from neutron_understack.undersync import Undersync
2121

2222
from .ml2_type_annotations import NetworkContext
23+
from .ml2_type_annotations import NetworkSegmentDict
2324
from .ml2_type_annotations import PortContext
2425

2526
LOG = logging.getLogger(__name__)
@@ -267,7 +268,9 @@ def _tenant_network_port_cleanup(self, context: PortContext):
267268
original_binding, self.nb
268269
)
269270

270-
if not utils.ports_bound_to_segment(segment_id):
271+
if not utils.ports_bound_to_segment(
272+
segment_id
273+
) and utils.is_dynamic_network_segment(segment_id):
271274
context.release_dynamic_segment(segment_id)
272275
self.nb.delete_vlan(segment_id)
273276

@@ -369,7 +372,8 @@ def _bind_port_segment(self, context: PortContext, segment):
369372
vlan_group_name,
370373
)
371374

372-
dynamic_segment = context.allocate_dynamic_segment(
375+
current_vlan_segment = self._vlan_segment_for_physnet(context, vlan_group_name)
376+
dynamic_segment = current_vlan_segment or context.allocate_dynamic_segment(
373377
segment={
374378
"network_type": p_const.TYPE_VLAN,
375379
"physical_network": vlan_group_name,
@@ -399,6 +403,21 @@ def _bind_port_segment(self, context: PortContext, segment):
399403
status=p_const.PORT_STATUS_ACTIVE,
400404
)
401405

406+
def _vlan_segment_for_physnet(
407+
self, context: PortContext, physnet: str
408+
) -> NetworkSegmentDict | None:
409+
for segment in context.network.network_segments:
410+
if (
411+
segment[api.NETWORK_TYPE] == p_const.TYPE_VLAN
412+
and segment[api.PHYSICAL_NETWORK] == physnet
413+
):
414+
LOG.info(
415+
"vlan segment: %(segment)s already preset for physnet: "
416+
"%(physnet)s",
417+
{"segment": segment, "physnet": physnet},
418+
)
419+
return segment
420+
402421
def invoke_undersync(self, vlan_group_name: str):
403422
self.undersync.sync_devices(
404423
vlan_group=vlan_group_name,

python/neutron-understack/neutron_understack/tests/test_trunk.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ def test_when_parent_port_is_bound(
108108
"neutron_understack.utils.allocate_dynamic_segment",
109109
return_value=vlan_network_segment,
110110
)
111+
mocker.patch(
112+
"neutron_understack.utils.network_segment_by_physnet", return_value=None
113+
)
111114
mocker.patch("neutron_understack.utils.create_binding_profile_level")
112115
mocker.patch.object(understack_trunk_driver.nb, "add_port_vlan_associations")
113116
understack_trunk_driver._handle_tenant_vlan_id_and_switchport_config(
@@ -220,7 +223,7 @@ def test_when_parent_port_is_bound(
220223
understack_trunk_driver._clean_parent_port_switchport_config(trunk, [subport])
221224

222225
understack_trunk_driver.nb.remove_port_network_associations.assert_called_once_with(
223-
interface_uuid=str(port_id), network_ids_to_remove={network_id}
226+
interface_uuid=str(port_id), vlan_ids_to_remove={network_id}
224227
)
225228
understack_trunk_driver.undersync.sync_devices.assert_called_once_with(
226229
vlan_group="physnet",
@@ -264,6 +267,9 @@ def test_when_segment_is_unused_by_other_ports(
264267
mocker.patch(
265268
"neutron_understack.utils.ports_bound_to_segment", return_value=False
266269
)
270+
mocker.patch(
271+
"neutron_understack.utils.is_dynamic_network_segment", return_value=True
272+
)
267273
mocker.patch.object(understack_trunk_driver.nb, "delete_vlan")
268274
mocker.patch("neutron_understack.utils.release_dynamic_segment")
269275

python/neutron-understack/neutron_understack/trunk.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,11 @@ def _handle_segment_allocation(
171171
subport_network_id = utils.fetch_subport_network_id(
172172
subport_id=subport["port_id"]
173173
)
174-
network_segment = utils.allocate_dynamic_segment(
174+
current_segment = utils.network_segment_by_physnet(
175+
network_id=subport_network_id,
176+
physnet=vlan_group_name,
177+
)
178+
network_segment = current_segment or utils.allocate_dynamic_segment(
175179
network_id=subport_network_id,
176180
physnet=vlan_group_name,
177181
)
@@ -246,7 +250,9 @@ def _delete_binding_level(self, port_id: str, host: str) -> PortBindingLevel:
246250

247251
def _delete_unused_segment(self, segment_id: str) -> NetworkSegment:
248252
network_segment = utils.network_segment_by_id(segment_id)
249-
if not utils.ports_bound_to_segment(segment_id):
253+
if not utils.ports_bound_to_segment(
254+
segment_id
255+
) and utils.is_dynamic_network_segment(segment_id):
250256
utils.release_dynamic_segment(segment_id)
251257
self.nb.delete_vlan(vlan_id=segment_id)
252258
return network_segment
@@ -282,7 +288,7 @@ def _handle_subports_removal(
282288
vlan_ids_to_remove = self._handle_segment_deallocation(subports, binding_host)
283289
self.nb.remove_port_network_associations(
284290
interface_uuid=connected_interface_id,
285-
network_ids_to_remove=vlan_ids_to_remove,
291+
vlan_ids_to_remove=vlan_ids_to_remove,
286292
)
287293

288294
if invoke_undersync and vlan_group_name:

python/neutron-understack/neutron_understack/utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@ def network_segment_by_id(id: str) -> NetworkSegment:
7777
return NetworkSegment.get_object(context, id=id)
7878

7979

80+
def network_segment_by_physnet(network_id: str, physnet: str) -> NetworkSegment | None:
81+
"""Fetches vlan network segments for network in particular physnet.
82+
83+
We return first segment on purpose, there shouldn't be more, but if
84+
that is the case, it may be intended for some reason and we don't want
85+
to halt the code.
86+
"""
87+
context = n_context.get_admin_context()
88+
89+
segments = NetworkSegment.get_objects(
90+
context,
91+
network_id=network_id,
92+
physical_network=physnet,
93+
network_type=constants.TYPE_VLAN,
94+
)
95+
if not segments:
96+
return
97+
return segments[0]
98+
99+
80100
def release_dynamic_segment(segment_id: str) -> None:
81101
context = n_context.get_admin_context()
82102
core_plugin = directory.get_plugin() # Get the core plugin
@@ -85,6 +105,11 @@ def release_dynamic_segment(segment_id: str) -> None:
85105
core_plugin.type_manager.release_dynamic_segment(context, segment_id)
86106

87107

108+
def is_dynamic_network_segment(segment_id: str) -> bool:
109+
segment = network_segment_by_id(segment_id)
110+
return segment.is_dynamic
111+
112+
88113
def fetch_connected_interface_uuid(binding_profile: dict, nautobot: Nautobot) -> str:
89114
"""Fetches the connected interface UUID from the port's binding profile.
90115

0 commit comments

Comments
 (0)