diff --git a/components/neutron/values.yaml b/components/neutron/values.yaml index b8ed8b151..4552de42b 100644 --- a/components/neutron/values.yaml +++ b/components/neutron/values.yaml @@ -43,9 +43,10 @@ conf: ml2_conf: ml2: extension_drivers: 'port_security' - # set the default ml2 backend to our plugin, neutron_understack - # we'll need to use the ovn ML2 plugin to hook the routers to our network - mechanism_drivers: "understack,undersync,ovn" + # ovn is listed first so it creates virtual ports for routers before + # our baremetal drivers run, matching the recommended ordering from + # https://docs.openstack.org/networking-baremetal/latest/ + mechanism_drivers: "ovn,understack,baremetal,undersync" tenant_network_types: "vxlan" type_drivers: "vlan,vxlan" ml2_type_vxlan: diff --git a/docs/design-guide/neutron-networking.md b/docs/design-guide/neutron-networking.md index ea75f1bc5..63b17378f 100644 --- a/docs/design-guide/neutron-networking.md +++ b/docs/design-guide/neutron-networking.md @@ -7,10 +7,13 @@ many of these features can be achieved. To enable this we are using the following plugins/features of Neutron: -- [OVN driver][ovn-driver] for general [OVN][ovn] support +- [OVN driver][ovn-driver] for general [OVN][ovn] support — loaded first so it + creates virtual ports for routers before the baremetal drivers run, as + recommended by [networking-baremetal][networking-baremetal] - [networking-baremetal][networking-baremetal] to have Neutron aware of the physical networks of Ironic baremetal ports. -- our custom mechanism drivers `understack` and `undersync` (both must be loaded) +- our custom mechanism drivers `understack` and `undersync` (both must be loaded, + with `baremetal` from [networking-baremetal][networking-baremetal] loaded between them) - [ovn-router][ovn-admin] as the L3 router plugin - [trunk plugin][neutron-trunk] service plugin - [network segment range][neutron-network-segment-range] service plugin @@ -377,12 +380,15 @@ The names and the IDs all match, along with the VLAN ID of the segment where the ## ML2 Mechanism Operations Our ML2 mechanism is split across two drivers that must both be present in -`mechanism_drivers`: +`mechanism_drivers`, with the `baremetal` driver from +[networking-baremetal][networking-baremetal] loaded between them: - `understack` — the primary driver responsible for allocating dynamic VLAN segments on VXLAN networks (`bind_port()`), releasing them when ports are removed (`delete_port_postcommit()`), and triggering switch configuration updates (`update_port_postcommit()`) +- `baremetal` — the [networking-baremetal][networking-baremetal] driver that + makes Neutron aware of the physical networks of Ironic baremetal ports - `undersync` — handles level-1 binding by calling `set_binding()` on the VLAN segment that `understack` allocated via `continue_binding()`; without it port binding fails at level 1 diff --git a/docs/operator-guide/openstack-neutron.md b/docs/operator-guide/openstack-neutron.md index 6be849276..e05da6260 100644 --- a/docs/operator-guide/openstack-neutron.md +++ b/docs/operator-guide/openstack-neutron.md @@ -17,7 +17,7 @@ conf: # replacing so you'll need to pay attention # to any changes your environment might have # from the default - mechanism_drivers: "logger,understack,undersync,ovn" + mechanism_drivers: "logger,ovn,understack,baremetal,undersync" logging: loggers: # for 'keys' we are attempt to append 'mechanism_logger' diff --git a/python/neutron-understack/neutron_understack/tests/test_neutron_understack_mech.py b/python/neutron-understack/neutron_understack/tests/test_neutron_understack_mech.py index 7efd7fd11..017277242 100644 --- a/python/neutron-understack/neutron_understack/tests/test_neutron_understack_mech.py +++ b/python/neutron-understack/neutron_understack/tests/test_neutron_understack_mech.py @@ -16,6 +16,56 @@ def test_with_simple_port(self, understack_driver, port_context): @pytest.mark.usefixtures("_ironic_baremetal_port_physical_network") class TestBindPort: + def test_does_not_bind_vlan_only_segments( + self, + mocker, + port_context, + understack_driver, + vlan_network_segment, + ): + """At level 1 understack receives only the VLAN segment and should do nothing. + + undersync is responsible for that binding. + """ + port_context._prepare_to_bind([vlan_network_segment]) + mocker.patch.object(port_context, "continue_binding") + + understack_driver.bind_port(port_context) + + port_context.continue_binding.assert_not_called() + + def test_uses_existing_vlan_segment( + self, + mocker, + port_context, + understack_driver, + vlan_network_segment, + ): + """When a VLAN segment already exists for the physnet, reuse it. + + It should not allocate a new dynamic segment. + """ + mocker.patch.object(port_context, "allocate_dynamic_segment") + mocker.patch.object(port_context, "continue_binding") + mocker.patch( + "neutron_understack.neutron_understack_mech.utils.vlan_segment_for_physnet", + return_value=vlan_network_segment, + ) + port_context._prepare_to_bind(port_context.network.network_segments) + + understack_driver.bind_port(port_context) + + port_context.allocate_dynamic_segment.assert_not_called() + vxlan_segment = next( + s + for s in port_context.network.network_segments + if s[api.NETWORK_TYPE] == "vxlan" + ) + port_context.continue_binding.assert_called_once_with( + segment_id=vxlan_segment[api.ID], + next_segments_to_bind=[vlan_network_segment], + ) + def test_with_no_trunk( self, mocker, diff --git a/python/neutron-understack/neutron_understack/tests/test_undersync_mech.py b/python/neutron-understack/neutron_understack/tests/test_undersync_mech.py index e1b000448..42a229a3a 100644 --- a/python/neutron-understack/neutron_understack/tests/test_undersync_mech.py +++ b/python/neutron-understack/neutron_understack/tests/test_undersync_mech.py @@ -5,19 +5,19 @@ from neutron_lib.api.definitions import portbindings from neutron_lib.plugins.ml2 import api -from neutron_understack.undersync_mech import UnderstackUndersyncDriver +from neutron_understack.undersync_mech import UndersyncDriver @pytest.fixture def driver(): - d = UnderstackUndersyncDriver() + d = UndersyncDriver() d.initialize() return d def _make_context(vnic_type=portbindings.VNIC_BAREMETAL, segments=None): context = MagicMock() - context.current = {portbindings.VNIC_TYPE: vnic_type} + context.current = {"id": "port-1", portbindings.VNIC_TYPE: vnic_type} context.segments_to_bind = segments or [] return context @@ -50,7 +50,7 @@ def _make(segment_id="seg-vxlan-1"): return _make -class TestUnderstackUndersyncDriverBindPort: +class TestUndersyncDriverBindPort: def test_binds_vlan_segment(self, driver, vlan_segment): seg = vlan_segment() ctx = _make_context(segments=[seg]) @@ -100,6 +100,21 @@ def test_normal_vnic_type_is_supported(self, driver, vlan_segment): ctx.set_binding.assert_called_once() + def test_binds_vlan_when_preceded_by_vxlan( + self, driver, vxlan_segment, vlan_segment + ): + vlan = vlan_segment() + ctx = _make_context(segments=[vxlan_segment(), vlan]) + + driver.bind_port(ctx) + + ctx.set_binding.assert_called_once_with( + segment_id=vlan[api.ID], + vif_type=portbindings.VIF_TYPE_OTHER, + vif_details={}, + status=p_const.PORT_STATUS_ACTIVE, + ) + def test_empty_segments_to_bind(self, driver): ctx = _make_context(segments=[]) diff --git a/python/neutron-understack/neutron_understack/type_understack_vxlan.py b/python/neutron-understack/neutron_understack/type_understack_vxlan.py deleted file mode 100644 index 2e515f57d..000000000 --- a/python/neutron-understack/neutron_understack/type_understack_vxlan.py +++ /dev/null @@ -1,9 +0,0 @@ -from neutron.plugins.ml2.drivers import type_vxlan -from neutron_lib import constants as p_const - - -class UnderstackVxlanTypeDriver(type_vxlan.VxlanTypeDriver): - """Manage state for EVPN L2VNI networks with ML2.""" - - def get_type(self): - return p_const.TYPE_VXLAN diff --git a/python/neutron-understack/neutron_understack/undersync_mech.py b/python/neutron-understack/neutron_understack/undersync_mech.py index c2c9e34b4..f986661ff 100644 --- a/python/neutron-understack/neutron_understack/undersync_mech.py +++ b/python/neutron-understack/neutron_understack/undersync_mech.py @@ -1,3 +1,5 @@ +import logging + from neutron_lib import constants as p_const from neutron_lib.api.definitions import portbindings from neutron_lib.plugins.ml2 import api @@ -5,10 +7,12 @@ from .ml2_type_annotations import PortContext +LOG = logging.getLogger(__name__) + SUPPORTED_VNIC_TYPES = [portbindings.VNIC_BAREMETAL, portbindings.VNIC_NORMAL] -class UnderstackUndersyncDriver(MechanismDriver): +class UndersyncDriver(MechanismDriver): @property def connectivity(self): # type: ignore return portbindings.CONNECTIVITY_L2 @@ -17,14 +21,25 @@ def initialize(self): pass def bind_port(self, context: PortContext) -> None: - vnic_type = context.current.get( - portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL + port = context.current + vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL) + LOG.debug( + "bind_port called for port %s vnic_type %s segments %s", + port["id"], + vnic_type, + context.segments_to_bind, ) if vnic_type not in SUPPORTED_VNIC_TYPES: + LOG.debug("Skipping unsupported vnic_type %s", vnic_type) return for segment in context.segments_to_bind: if segment[api.NETWORK_TYPE] == p_const.TYPE_VLAN: + LOG.debug( + "bind_port: setting binding for port %s on VLAN segment %s", + port["id"], + segment, + ) context.set_binding( segment_id=segment[api.ID], vif_type=portbindings.VIF_TYPE_OTHER, @@ -32,3 +47,9 @@ def bind_port(self, context: PortContext) -> None: status=p_const.PORT_STATUS_ACTIVE, ) return + + LOG.warning( + "bind_port: no VLAN segment found for port %s in %s", + port["id"], + context.segments_to_bind, + ) diff --git a/python/neutron-understack/pyproject.toml b/python/neutron-understack/pyproject.toml index 2a8ce8ba2..453bb35b0 100644 --- a/python/neutron-understack/pyproject.toml +++ b/python/neutron-understack/pyproject.toml @@ -31,10 +31,7 @@ dependencies = [ [project.entry-points."neutron.ml2.mechanism_drivers"] understack = "neutron_understack.neutron_understack_mech:UnderstackDriver" -undersync = "neutron_understack.undersync_mech:UnderstackUndersyncDriver" - -[project.entry-points."neutron.ml2.type_drivers"] -understack_vxlan = "neutron_understack.type_understack_vxlan:UnderstackVxlanTypeDriver" +undersync = "neutron_understack.undersync_mech:UndersyncDriver" [project.entry-points."neutron.service_plugins"] l3_service_cisco_asa = "neutron_understack.l3_service_cisco_asa:CiscoAsa"