|
4 | 4 | from neutron.objects.trunk import SubPort
|
5 | 5 | from neutron.services.trunk.drivers import base as trunk_base
|
6 | 6 | from neutron.services.trunk.models import Trunk
|
| 7 | +from neutron_lib import exceptions as exc |
7 | 8 | from neutron_lib.api.definitions import portbindings
|
8 | 9 | from neutron_lib.callbacks import events
|
9 | 10 | from neutron_lib.callbacks import registry
|
|
21 | 22 | SUPPORTED_SEGMENTATION_TYPES = (trunk_consts.SEGMENTATION_TYPE_VLAN,)
|
22 | 23 |
|
23 | 24 |
|
| 25 | +class SubportSegmentationIDError(exc.NeutronException): |
| 26 | + message = ( |
| 27 | + "Segmentation ID: %(seg_id)s cannot be set to the Subport: " |
| 28 | + "%(subport_id)s as it falls outside of allowed ranges: " |
| 29 | + "%(network_segment_ranges)s. Please use different Segmentation ID." |
| 30 | + ) |
| 31 | + |
| 32 | + |
24 | 33 | class UnderStackTrunkDriver(trunk_base.DriverBase):
|
25 | 34 | def __init__(
|
26 | 35 | self,
|
@@ -96,13 +105,43 @@ def register(self, resource, event, trigger, payload=None):
|
96 | 105 | def _handle_tenant_vlan_id_and_switchport_config(
|
97 | 106 | self, subports: list[SubPort], trunk: Trunk
|
98 | 107 | ) -> None:
|
| 108 | + self._check_subports_segmentation_id(subports, trunk.id) |
99 | 109 | parent_port_obj = utils.fetch_port_object(trunk.port_id)
|
100 | 110 |
|
101 | 111 | if utils.parent_port_is_bound(parent_port_obj):
|
102 | 112 | self._add_subports_networks_to_parent_port_switchport(
|
103 |
| - parent_port_obj, subports |
| 113 | + parent_port_obj, subports, trunk.id |
104 | 114 | )
|
105 | 115 |
|
| 116 | + def _check_subports_segmentation_id( |
| 117 | + self, subports: list[SubPort], trunk_id: str |
| 118 | + ) -> None: |
| 119 | + """Checks if a subport's segmentation_id is within the allowed range. |
| 120 | +
|
| 121 | + A switchport cannot have a mapped VLAN ID equal to the native VLAN ID. |
| 122 | + Since the user specifies the VLAN ID (segmentation_id) when adding a |
| 123 | + subport, an error is raised if it falls within any VLAN network segment |
| 124 | + range, as these ranges are used to allocate VLAN tags for all VLAN |
| 125 | + segments, including native VLANs. |
| 126 | +
|
| 127 | + The only case where this check is not required is for a network node |
| 128 | + trunk, since its subport segmentation_ids are the same as the network |
| 129 | + segment VLAN tags allocated to the subports. Therefore, there is no |
| 130 | + possibility of conflict with the native VLAN. |
| 131 | + """ |
| 132 | + if trunk_id == cfg.CONF.ml2_understack.network_node_trunk_uuid: |
| 133 | + return |
| 134 | + |
| 135 | + ns_ranges = utils.allowed_tenant_vlan_id_ranges() |
| 136 | + for subport in subports: |
| 137 | + seg_id = subport.segmentation_id |
| 138 | + if not utils.segmentation_id_in_ranges(seg_id, ns_ranges): |
| 139 | + raise SubportSegmentationIDError( |
| 140 | + seg_id=seg_id, |
| 141 | + subport_id=subport.port_id, |
| 142 | + network_segment_ranges=utils.printable_ranges(ns_ranges), |
| 143 | + ) |
| 144 | + |
106 | 145 | def configure_trunk(self, trunk_details: dict, port_id: str) -> None:
|
107 | 146 | parent_port_obj = utils.fetch_port_object(port_id)
|
108 | 147 | subports = trunk_details.get("sub_ports", [])
|
@@ -146,6 +185,7 @@ def _add_subports_networks_to_parent_port_switchport(
|
146 | 185 | vlan_group_name = self.ironic_client.baremetal_port_physical_network(
|
147 | 186 | local_link_info
|
148 | 187 | )
|
| 188 | + |
149 | 189 | self._handle_segment_allocation(subports, vlan_group_name, binding_host)
|
150 | 190 |
|
151 | 191 | def clean_trunk(
|
|
0 commit comments