Skip to content

Commit 9a809bc

Browse files
author
Steve Keay
committed
Allocate network segment on bind, with VLAN ID unique within VLAN GROUP
1 parent 9d5fe7a commit 9a809bc

File tree

2 files changed

+82
-13
lines changed

2 files changed

+82
-13
lines changed

python/neutron-understack/neutron_understack/neutron_understack_mech.py

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@
1111

1212
from neutron_understack import config
1313
from neutron_understack import utils
14+
from neutron_understack import vlan_group_name_convention
1415
from neutron_understack.nautobot import Nautobot
1516
from neutron_understack.trunk import UnderStackTrunkDriver
1617
from neutron_understack.undersync import Undersync
1718
from neutron_understack.vlan_manager import VlanManager
1819

1920
LOG = logging.getLogger(__name__)
2021

22+
# We take interest in port binding events for Networks of these types:
23+
RELEVANT_NETWORK_TYPES = [
24+
p_const.TYPE_VXLAN,
25+
p_const.TYPE_VLAN,
26+
]
2127

2228
config.register_ml2_type_understack_opts(cfg.CONF)
2329
config.register_ml2_understack_opts(cfg.CONF)
@@ -284,22 +290,35 @@ def _configure_switchport_on_bind(self, context: PortContext) -> None:
284290
)
285291

286292
def bind_port(self, context: PortContext) -> None:
293+
"""Bind port hook.
294+
295+
Example segment: {
296+
'id': '308b7fd5-e3bc-4b64-b491-910480e4c3e4',
297+
'network_type': 'vxlan',
298+
'physical_network': None,
299+
'segmentation_id': 200025,
300+
'network_id': '6af047b2-65ff-48dc-aa57-9647119c4883'
301+
}
302+
"""
287303
for segment in context.network.network_segments:
288-
if self.check_segment(segment):
304+
if segment[api.NETWORK_TYPE] in RELEVANT_NETWORK_TYPES:
289305
context.set_binding(
290306
segment[api.ID],
291307
portbindings.VIF_TYPE_OTHER,
292308
{},
293309
status=p_const.PORT_STATUS_ACTIVE,
294310
)
295311
LOG.debug("Bound segment: %s", segment)
312+
self._find_or_create_network_segment(context)
296313
self._configure_switchport_on_bind(context)
297-
return
314+
315+
return # bind one segment only
298316
else:
299317
LOG.debug(
300-
"Refusing to bind port for segment ID %(id)s, "
318+
"Ignoring bind_port for segment ID %(id)s, "
301319
"segment %(seg)s, phys net %(physnet)s, and "
302-
"network type %(nettype)s",
320+
"network type %(nettype)s because network_type "
321+
"is not VXLAN or VLAN type",
303322
{
304323
"id": segment[api.ID],
305324
"seg": segment[api.SEGMENTATION_ID],
@@ -308,17 +327,40 @@ def bind_port(self, context: PortContext) -> None:
308327
},
309328
)
310329

311-
def check_segment(self, segment):
312-
"""Verify a segment is valid for the Understack MechanismDriver.
330+
def _find_or_create_network_segment(self, context: PortContext):
331+
"""Ensure that a VLAN-type network segment is present for this network.
313332
314-
Verify the requested segment is supported by Understack and return True or
315-
False to indicate this to callers.
333+
In the VXLAN fabric, a Network has a VLAN created on every switch where
334+
that network is in use. The VLAN is local to the VLAN GROUP (pair of
335+
switches) and must be assigned an arbitrary vlan number that is unique
336+
within that VLAN GROUP.
337+
338+
We use the switch name to figure out which VLAN GROUP this network is
339+
being bound to. We then look for existing network segments in that VLAN
340+
GROUP. If none are found then we create a new one with a dynamically
341+
allocated VLAN ID.
316342
"""
317-
network_type = segment[api.NETWORK_TYPE]
318-
return network_type in [
319-
p_const.TYPE_VXLAN,
320-
p_const.TYPE_VLAN,
321-
]
343+
switch_name = str(
344+
context.current["binding:profile"]["local_link_information"][0]["switch_info"]
345+
)
346+
347+
vlan_group_name = vlan_group_name_convention.for_switch(switch_name)
348+
349+
for segment in context.network.network_segments:
350+
if (
351+
segment.get(api.NETWORK_TYPE) == p_const.TYPE_VLAN
352+
and segment.get(api.PHYSICAL_NETWORK) == vlan_group_name
353+
):
354+
LOG.info("Using existing VLAN network segment %s", segment)
355+
return segment
356+
357+
segment_args = {
358+
api.NETWORK_TYPE: p_const.TYPE_VLAN,
359+
api.PHYSICAL_NETWORK: vlan_group_name,
360+
}
361+
segment = context.allocate_dynamic_segment(segment_args)
362+
LOG.info("Allocated new VLAN network segment %s", segment)
363+
return segment
322364

323365
def check_vlan_transparency(self, context):
324366
pass
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Please keep this in sync with data_center.py in this repo:
2+
VLAN_GROUP_SUFFIXES = {
3+
"-1": "network",
4+
"-2": "network",
5+
"-1f": "storage",
6+
"-2f": "storage",
7+
"-3f": "storage-appliance",
8+
"-4f": "storage-appliance",
9+
"-1d": "bmc",
10+
}
11+
12+
13+
def for_switch(switch_name: str) -> str | None:
14+
switch_name = switch_name.split(".")[0]
15+
16+
for switch_name_suffix, vlan_group_suffix in VLAN_GROUP_SUFFIXES:
17+
if switch_name.endswith(switch_name_suffix):
18+
cabinet_name = switch_name.removesuffix(switch_name_suffix)
19+
return f"{cabinet_name}-{vlan_group_suffix}"
20+
21+
raise ValueError(
22+
"Could not determine the VLAN GROUP name for Switch %s. We "
23+
"only have a convention to do this for switch names ending "
24+
"in one of the suffixes %s",
25+
switch_name,
26+
VLAN_GROUP_SUFFIXES.keys(),
27+
)

0 commit comments

Comments
 (0)