diff --git a/doc/source/devref/openvswitch_agent.rst b/doc/source/devref/openvswitch_agent.rst index 2d709a18ff1..9c85ed569b3 100644 --- a/doc/source/devref/openvswitch_agent.rst +++ b/doc/source/devref/openvswitch_agent.rst @@ -467,7 +467,7 @@ Implementation Trunk Bridge (Option C) This implementation is based on this `etherpad `_. Credits to Bence Romsics. The option use_veth_interconnection=true won't be supported, it will probably be deprecated soon, -see [1]. +see [1]. The IDs used for bridge and port names are truncated. :: @@ -479,24 +479,25 @@ see [1]. | +-----+--------------------------+ | tap1 | - | br-trunk-1 | + | tbr-trunk-id | | | - | tp-patch-trunk sp-patch-trunk | + | tpt-parent-id spt-subport-id | | (tag 100) | +-----+-----------------+--------+ | | | | | | +-----+-----------------+---------+ - | tp-patch-int sp-patch-int | + | tpi-parent-id spi-subport-id | | (tag 3) (tag 5) | + | | | br-int | +---------------------------------+ -tp-patch-trunk: trunk bridge side of the patch port that implements a trunk -tp-patch-int: int bridge side of the patch port that implements a trunk -sp-patch-trunk: trunk bridge side of the patch port that implements a subport -sp-patch-int: int bridge side of the patch port that implements a subport +tpt-parent-id: trunk bridge side of the patch port that implements a trunk. +tpi-parent-id: int bridge side of the patch port that implements a trunk. +spt-subport-id: trunk bridge side of the patch port that implements a subport. +spi-subport-id: int bridge side of the patch port that implements a subport. [1] https://bugs.launchpad.net/neutron/+bug/1587296 @@ -505,23 +506,23 @@ Trunk creation A VM is spawned passing to Nova the port-id of a parent port associated with a trunk. Neutron will pass to Nova the bridge where to plug the vif as part of the vif details. -The os-vif driver creates the trunk bridge br-trunk-1 if it does not exist in plug(). -It will create the tap interface tap1 and plug it into br-trunk-1 setting the parent port ID in the external-ids. +The os-vif driver creates the trunk bridge tbr-trunk-id if it does not exist in plug(). +It will create the tap interface tap1 and plug it into tbr-trunk-id setting the parent port ID in the external-ids. The OVS agent will be monitoring the creation of ports on the trunk bridges. When it detects that a new port has been created on the trunk bridge, it will do the following: :: - ovs-vsctl add-port br-trunk-1 tp-patch-trunk -- set Interface tp-patch-trunk type=patch options:peer=tp-patch-int - ovs-vsctl add-port br-int tp-patch-int tag=3 -- set Interface tp-patch-int type=patch options:peer=tp-patch-trunk + ovs-vsctl add-port tbr-trunk-id tpt-parent-id -- set Interface tpt-parent-id type=patch options:peer=tpi-parent-id + ovs-vsctl add-port br-int tpi-parent-id tag=3 -- set Interface tpi-parent-id type=patch options:peer=tpt-parent-id A patch port is created to connect the trunk bridge to the integration bridge. -tp-patch-trunk, the trunk bridge side of the patch is not associated to any +tpt-parent-id, the trunk bridge side of the patch is not associated to any tag. It will carry untagged traffic. -tp-patch-int, the br-int side the patch port is tagged with VLAN 3. We assume that the +tpi-parent-id, the br-int side the patch port is tagged with VLAN 3. We assume that the trunk is on network1 that on this host is associated with VLAN 3. -The OVS agent will set the trunk ID in the external-ids of tp-patch-trunk and tp-patch-int. +The OVS agent will set the trunk ID in the external-ids of tpt-parent-id and tpi-parent-id. If the parent port is associated with one or more subports the agent will process them as described in the next paragraph. @@ -536,32 +537,32 @@ create a new patch port: :: - ovs-vsctl add-port br-trunk-1 sp-patch-trunk tag=100 -- set Interface sp-patch-trunk type=patch options:peer=sp-patch-int - ovs-vsctl add-port br-int sp-patch-int tag=5 -- set Interface sp-patch-int type=patch options:peer=sp-patch-trunk + ovs-vsctl add-port tbr-trunk-id spt-subport-id tag=100 -- set Interface spt-subport-id type=patch options:peer=spi-subport-id + ovs-vsctl add-port br-int spi-subport-id tag=5 -- set Interface spi-subport-id type=patch options:peer=spt-subport-id This patch port connects the trunk bridge to the integration bridge. -sp-patch-trunk, the trunk bridge side of the patch is tagged using VLAN 100. +spt-subport-id, the trunk bridge side of the patch is tagged using VLAN 100. We assume that the segmentation ID of the subport is 100. -sp-patch-int, the br-int side of the patch port is tagged with VLAN 5. We +spi-subport-id, the br-int side of the patch port is tagged with VLAN 5. We assume that the subport is on network2 that on this host uses VLAN 5. -The OVS agent will set the subport ID in the external-ids of sp-patch-trunk and sp-patch-int. +The OVS agent will set the subport ID in the external-ids of spt-subport-id and spi-subport-id. *Inbound traffic from the VM point of view* -The traffic coming out of tp-patch-int will be stripped by br-int of VLAN 3. -It will reach tp-patch-trunk untagged and from there tap1. -The traffic coming out of sp-patch-int will be stripped by br-int of VLAN 5. -It will reach sp-patch-trunk where it will be tagged with VLAN 100 and it will +The traffic coming out of tpi-parent-id will be stripped by br-int of VLAN 3. +It will reach tpt-parent-id untagged and from there tap1. +The traffic coming out of spi-subport-id will be stripped by br-int of VLAN 5. +It will reach spt-subport-id where it will be tagged with VLAN 100 and it will then get to tap1 tagged. *Outbound traffic from the VM point of view* -The untagged traffic coming from tap1 will reach tp-patch-trunk and from there -tp-patch-int where it will be tagged using VLAN 3. -The traffic tagged with VLAN 100 from tap1 will reach sp-patch-trunk. -VLAN 100 will be stripped since sp-patch-trunk is a tagged port and the packet -will reach sp-patch-int, where it's tagged using VLAN 5. +The untagged traffic coming from tap1 will reach tpt-parent-id and from there +tpi-parent-id where it will be tagged using VLAN 3. +The traffic tagged with VLAN 100 from tap1 will reach spt-subport-id. +VLAN 100 will be stripped since spt-subport-id is a tagged port and the packet +will reach spi-subport-id, where it's tagged using VLAN 5. Parent port deletion ++++++++++++++++++++ diff --git a/neutron/services/trunk/drivers/openvswitch/agent/trunk_manager.py b/neutron/services/trunk/drivers/openvswitch/agent/trunk_manager.py index ad8794c43ff..b604165f44d 100644 --- a/neutron/services/trunk/drivers/openvswitch/agent/trunk_manager.py +++ b/neutron/services/trunk/drivers/openvswitch/agent/trunk_manager.py @@ -59,7 +59,10 @@ def get_patch_peer_attrs(peer_name, port_mac=None, port_id=None): class TrunkBridge(ovs_lib.OVSBridge): + """An OVS trunk bridge. + A trunk bridge has a name that follows a specific naming convention. + """ def __init__(self, trunk_id): name = utils.gen_trunk_br_name(trunk_id) super(TrunkBridge, self).__init__(name) @@ -69,6 +72,14 @@ class TrunkBridge(ovs_lib.OVSBridge): class TrunkParentPort(object): + """An OVS trunk parent port. + + A trunk parent port is represented in OVS with two patch ports that + connect a trunk bridge and the integration bridge respectively. + These patch ports follow strict naming conventions: tpi- for + the patch port that goes into the integration bridge, and tpt- + for the patch port that goes into the trunk bridge. + """ DEV_PREFIX = 'tp' def __init__(self, trunk_id, port_id, port_mac=None): @@ -76,10 +87,8 @@ class TrunkParentPort(object): self.port_id = port_id self.port_mac = port_mac self.bridge = TrunkBridge(self.trunk_id) - # The name has form of tpi- self.patch_port_int_name = get_br_int_port_name( self.DEV_PREFIX, port_id) - # The name has form of tpt- self.patch_port_trunk_name = get_br_trunk_port_name( self.DEV_PREFIX, port_id) self._transaction = None @@ -104,20 +113,24 @@ class TrunkParentPort(object): self._transaction = None def plug(self, br_int): - """Create patch ports between trunk bridge and given bridge. + """Plug patch ports between trunk bridge and given bridge. - The method creates one patch port on the given bridge side using - port mac and id as external ids. The other endpoint of patch port is + The method plugs one patch port on the given bridge side using + port MAC and ID as external IDs. The other endpoint of patch port is attached to the trunk bridge. Everything is done in a single - ovsdb transaction so either all operations succeed or fail. + OVSDB transaction so either all operations succeed or fail. - :param br_int: An integration bridge where peer endpoint of patch port + :param br_int: an integration bridge where peer endpoint of patch port will be created. - """ - # NOTE(jlibosva): osvdb is an api so it doesn't matter whether we + # NOTE(jlibosva): OVSDB is an api so it doesn't matter whether we # use self.bridge or br_int ovsdb = self.bridge.ovsdb + # Once the bridges are connected with the following patch ports, + # the ovs agent will recognize the ports for processing and it will + # take over the wiring process and everything that entails. + # REVISIT(rossella_s): revisit this integration part, should tighter + # control over the wiring logic for trunk ports be required. patch_int_attrs = get_patch_peer_attrs( self.patch_port_trunk_name, self.port_mac, self.port_id) patch_trunk_attrs = get_patch_peer_attrs(self.patch_port_int_name) @@ -135,10 +148,10 @@ class TrunkParentPort(object): def unplug(self, bridge): """Unplug the trunk from bridge. - Method deletes in single ovsdb transaction the trunk bridge and patch + Method unplugs in single OVSDB transaction the trunk bridge and patch port on provided bridge. - :param bridge: Bridge that has peer side of patch port for this + :param bridge: bridge that has peer side of patch port for this subport. """ ovsdb = self.bridge.ovsdb @@ -149,7 +162,14 @@ class TrunkParentPort(object): class SubPort(TrunkParentPort): - # Patch port names have form of spi- or spt- respectively. + """An OVS trunk subport. + + A subport is represented in OVS with two patch ports that + connect a trunk bridge and the integration bridge respectively. + These patch ports follow strict naming conventions: spi- for + the patch port that goes into the integration bridge, and spt- + for the patch port that goes into the trunk bridge. + """ DEV_PREFIX = 'sp' def __init__(self, trunk_id, port_id, port_mac=None, segmentation_id=None): @@ -157,17 +177,16 @@ class SubPort(TrunkParentPort): self.segmentation_id = segmentation_id def plug(self, br_int): - """Create patch ports between trunk bridge and given bridge. + """Unplug patch ports between trunk bridge and given bridge. - The method creates one patch port on the given bridge side using - port mac and id as external ids. The other endpoint of patch port is + The method unplugs one patch port on the given bridge side using + port MAC and ID as external IDs. The other endpoint of patch port is attached to the trunk bridge. Then it sets vlan tag represented by - segmentation_id. Everything is done in a single ovsdb transaction so + segmentation_id. Everything is done in a single OVSDB transaction so either all operations succeed or fail. - :param br_int: An integration bridge where peer endpoint of patch port + :param br_int: an integration bridge where peer endpoint of patch port will be created. - """ ovsdb = self.bridge.ovsdb with self.ovsdb_transaction() as txn: @@ -179,10 +198,10 @@ class SubPort(TrunkParentPort): def unplug(self, bridge): """Unplug the sub port from the bridge. - Method deletes in single ovsdb transaction both endpoints of patch + Method unplugs in single OVSDB transaction both endpoints of patch ports that represents the subport. - :param bridge: Bridge that has peer side of patch port for this + :param bridge: bridge that has peer side of patch port for this subport. """ ovsdb = self.bridge.ovsdb @@ -211,19 +230,13 @@ class TrunkManager(object): :param trunk_id: ID of the trunk. :param port_id: ID of the parent port. :param port_mac: the MAC address of the parent port. - :raises: TrunkBridgeNotFound -- In case trunk bridge doesn't exist. - + :raises: + TrunkBridgeNotFound: in case trunk bridge does not exist. """ trunk = TrunkParentPort(trunk_id, port_id, port_mac) try: if not trunk.bridge.exists(): raise exc.TrunkBridgeNotFound(bridge=trunk.bridge.br_name) - # Once the bridges are connected with the following patch ports, - # the ovs agent will recognize the ports for processing and it will - # take over the wiring process and everything that entails. - # REVISIT(rossella_s): revisit this integration part, should - # tighter control over the wiring logic for trunk ports be - # required. trunk.plug(self.br_int) except RuntimeError as e: raise TrunkManagerError(error=e) @@ -235,18 +248,17 @@ class TrunkManager(object): if trunk.bridge.exists(): trunk.unplug(self.br_int) else: - LOG.debug("Trunk bridge with ID %s doesn't exist.", trunk_id) + LOG.debug("Trunk bridge with ID %s does not exist.", trunk_id) except RuntimeError as e: raise TrunkManagerError(error=e) def add_sub_port(self, trunk_id, port_id, port_mac, segmentation_id): """Create a sub_port. - :param trunk_id: ID of the trunk - :param port_id: ID of the child port - :param segmentation_id: segmentation ID associated with this sub-port - :param port_mac: MAC address of the child port - + :param trunk_id: ID of the trunk. + :param port_id: ID of the subport. + :param segmentation_id: segmentation ID associated with this subport. + :param port_mac: MAC address of the subport. """ sub_port = SubPort(trunk_id, port_id, port_mac, segmentation_id) # If creating of parent trunk bridge takes longer than API call for @@ -261,8 +273,8 @@ class TrunkManager(object): def remove_sub_port(self, trunk_id, port_id): """Remove a sub_port. - :param trunk_id: ID of the trunk - :param port_id: ID of the child port + :param trunk_id: ID of the trunk. + :param port_id: ID of the subport. """ sub_port = SubPort(trunk_id, port_id) @@ -272,7 +284,7 @@ class TrunkManager(object): if sub_port.bridge.exists(): sub_port.unplug(self.br_int) else: - LOG.debug("Trunk bridge with ID %s doesn't exist.", trunk_id) + LOG.debug("Trunk bridge with ID %s does not exist.", trunk_id) except RuntimeError as e: raise TrunkManagerError(error=e) diff --git a/releasenotes/notes/vlan-aware-vms-aka-trunk-3341cc75ba1bf5b4.yaml b/releasenotes/notes/vlan-aware-vms-aka-trunk-3341cc75ba1bf5b4.yaml new file mode 100644 index 00000000000..5f3ab6c6adb --- /dev/null +++ b/releasenotes/notes/vlan-aware-vms-aka-trunk-3341cc75ba1bf5b4.yaml @@ -0,0 +1,28 @@ +--- +prelude: > + The "vlan-aware-vms" feature allows Nova users to launch VMs on a single + port (trunk parent port) that connects multiple Neutron logical networks + together. +features: + - The feature "vlan-aware-vms" is available. To enable it, a service plugin + named 'trunk' must be added to the option ``service_plugins`` in your + neutron.conf. The plugin exposes two new extensions ``trunk`` and + ``trunk_details``. The plugin can work with multiple backends and in + particular Neutron has support for `ML2/openvswitch `_ + and ML2/linuxbridge. + Even though Neutron API compatibility should be preserved for ports + associated to trunks, since this is the first release where the feature + is available, it is reasonable to expect possible functionality gaps for + one or both drivers. These will be filled over time as being reported. + The CLI is available via openstackclient, and python-neutronclient 5.1.0 + or above. For more details, please check the networking guide. +security: + - | + When working with the ML2/openvswitch driver, the "vlan-aware-vms" feature + has the following limitations: + + * security groups do not work in conjunction with the iptables-based + firewall driver. + * if security groups are desired, the use of the stateful OVS firewall is + required, however that prevents the use of the DPDK datapath for OVS + versions 2.5 or lower.