From: Francois Eleouet Date: Wed, 7 Aug 2013 09:19:46 +0000 (+0200) Subject: Enable GRE and VXLAN with the same ID X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=a369f9e39691c01a4e4f7f8668cb37fc17ba03b3;p=openstack-build%2Fneutron-build.git Enable GRE and VXLAN with the same ID Current packet processing in br-tun is based on tun-id, as a consequence, two networks using different tunnel types but sharing the same tun-id would not be properly isolated. To ensure proper isolation within a single bridge, NORMAL action can't be used any more as it floods unknown unicasts on all bridges ports. It is replaced by a learn action that dynamically sets-up flows when packets are recieved from tunnel ports. As mac address are learnt in explicit flows (in table 20), we can use a default action in that table to flood unknown unicasts to the right set of ports, like broadcasts and multicasts packets. See https://wiki.openstack.org/wiki/Ovs-flow-logic for a more detailled explanation of the flow logic Another alternative could have been to use distinct bridges for each tunnel type (whithout modifying the current flow logic), but previous alternative may be preferable as it paves the way for new tunneling optimisations (like RPC based mac learning and partial-mesh flooding proposed in bp/l2-population) Change-Id: I1dfe74f96680c2c6fe4d8d4aac4821c6b020c005 Closes-Bug: #1196963 --- diff --git a/neutron/agent/linux/ovs_lib.py b/neutron/agent/linux/ovs_lib.py index 9dbadb666..2c2bd9d8b 100644 --- a/neutron/agent/linux/ovs_lib.py +++ b/neutron/agent/linux/ovs_lib.py @@ -126,6 +126,8 @@ class OVSBridge: elif 'priority' in kwargs: raise Exception(_("Cannot match priority on flow deletion")) + table = ('table' in kwargs and ",table=%s" % + kwargs['table'] or '') in_port = ('in_port' in kwargs and ",in_port=%s" % kwargs['in_port'] or '') dl_type = ('dl_type' in kwargs and ",dl_type=%s" % @@ -139,14 +141,14 @@ class OVSBridge: tun_id = 'tun_id' in kwargs and ",tun_id=%s" % kwargs['tun_id'] or '' proto = 'proto' in kwargs and ",%s" % kwargs['proto'] or '' ip = ('nw_src' in kwargs or 'nw_dst' in kwargs) and ',ip' or '' - match = (in_port + dl_type + dl_vlan + dl_src + dl_dst + + match = (table + in_port + dl_type + dl_vlan + dl_src + dl_dst + (ip or proto) + nw_src + nw_dst + tun_id) if match: match = match[1:] # strip leading comma flow_expr_arr.append(match) return flow_expr_arr - def add_flow(self, **kwargs): + def add_or_mod_flow_str(self, **kwargs): if "actions" not in kwargs: raise Exception(_("Must specify one or more actions")) if "priority" not in kwargs: @@ -155,8 +157,16 @@ class OVSBridge: flow_expr_arr = self._build_flow_expr_arr(**kwargs) flow_expr_arr.append("actions=%s" % (kwargs["actions"])) flow_str = ",".join(flow_expr_arr) + return flow_str + + def add_flow(self, **kwargs): + flow_str = self.add_or_mod_flow_str(**kwargs) self.run_ofctl("add-flow", [flow_str]) + def mod_flow(self, **kwargs): + flow_str = self.add_or_mod_flow_str(**kwargs) + self.run_ofctl("mod-flows", [flow_str]) + def delete_flows(self, **kwargs): kwargs['delete'] = True flow_expr_arr = self._build_flow_expr_arr(**kwargs) diff --git a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py index 842dda911..db7b92b0c 100644 --- a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py @@ -172,6 +172,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): self.int_br = self.setup_integration_br(integ_br) self.setup_physical_bridges(bridge_mappings) self.local_vlan_map = {} + self.tun_br_ofports = {constants.TYPE_GRE: set(), + constants.TYPE_VXLAN: set()} self.polling_interval = polling_interval @@ -307,8 +309,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): if tunnel_ip == self.local_ip: return tun_name = '%s-%s' % (tunnel_type, tunnel_id) - self.tun_br.add_tunnel_port(tun_name, tunnel_ip, self.local_ip, - tunnel_type, self.vxlan_udp_port) + self.setup_tunnel_port(tun_name, tunnel_ip, tunnel_type) def create_rpc_dispatcher(self): '''Get the rpc dispatcher for this manager. @@ -342,18 +343,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): if network_type in constants.TUNNEL_NETWORK_TYPES: if self.enable_tunneling: - # outbound - self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport, + # outbound broadcast/multicast + ofports = ','.join(self.tun_br_ofports[network_type]) + self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN, + priority=1, dl_vlan=lvid, - actions="set_tunnel:%s,normal" % - segmentation_id) - # inbound bcast/mcast - self.tun_br.add_flow( - priority=3, - tun_id=segmentation_id, - dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", - actions="mod_vlan_vid:%s,output:%s" % - (lvid, self.patch_int_ofport)) + actions="strip_vlan,set_tunnel:%s," + "output:%s" % (segmentation_id, ofports)) + # inbound from tunnels: set lvid in the right table + # and resubmit to Table LEARN_FROM_TUN for mac learning + self.tun_br.add_flow(table=constants.TUN_TABLE[network_type], + priority=1, + tun_id=segmentation_id, + actions="mod_vlan_vid:%s,resubmit(,%s)" % + (lvid, constants.LEARN_FROM_TUN)) else: LOG.error(_("Cannot provision %(network_type)s network for " "net-id=%(net_uuid)s - tunneling disabled"), @@ -421,7 +424,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): if lvm.network_type in constants.TUNNEL_NETWORK_TYPES: if self.enable_tunneling: - self.tun_br.delete_flows(tun_id=lvm.segmentation_id) + self.tun_br.delete_flows( + table=constants.TUN_TABLE[lvm.network_type], + tun_id=lvm.segmentation_id) self.tun_br.delete_flows(dl_vlan=lvm.vlan) elif lvm.network_type == constants.TYPE_FLAT: if lvm.physical_network in self.phys_brs: @@ -474,14 +479,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): lvm = self.local_vlan_map[net_uuid] lvm.vif_ports[port.vif_id] = port - if network_type in constants.TUNNEL_NETWORK_TYPES: - if self.enable_tunneling: - # inbound unicast - self.tun_br.add_flow(priority=3, tun_id=segmentation_id, - dl_dst=port.vif_mac, - actions="mod_vlan_vid:%s,normal" % - lvm.vlan) - self.int_br.set_db_attribute("Port", port.port_name, "tag", str(lvm.vlan)) if int(port.ofport) != -1: @@ -503,18 +500,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): LOG.info(_('port_unbound() net_uuid %s not in local_vlan_map'), net_uuid) return - lvm = self.local_vlan_map[net_uuid] - vif_port = lvm.vif_ports.pop(vif_id, None) - if vif_port: - if self.enable_tunneling and lvm.network_type in ( - constants.TUNNEL_NETWORK_TYPES): - # remove inbound unicast flow - self.tun_br.delete_flows(tun_id=lvm.segmentation_id, - dl_dst=vif_port.vif_mac) - else: - LOG.info(_('port_unbound: vif_id %s not in local_vlan_map'), - vif_id) + lvm = self.local_vlan_map[net_uuid] + lvm.vif_ports.pop(vif_id, None) if not lvm.vif_ports: self.reclaim_local_vlan(net_uuid, lvm) @@ -590,7 +578,58 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): "Agent terminated!")) exit(1) self.tun_br.remove_all_flows() - self.tun_br.add_flow(priority=1, actions="drop") + + # Table 0 (default) will sort incoming traffic depending on in_port + self.tun_br.add_flow(priority=1, + in_port=self.patch_int_ofport, + actions="resubmit(,%s)" % + constants.PATCH_LV_TO_TUN) + self.tun_br.add_flow(priority=0, actions="drop") + # PATCH_LV_TO_TUN table will handle packets coming from patch_int + # unicasts go to table UCAST_TO_TUN where remote adresses are learnt + self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, + dl_dst="00:00:00:00:00:00/01:00:00:00:00:00", + actions="resubmit(,%s)" % constants.UCAST_TO_TUN) + # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding + self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, + dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", + actions="resubmit(,%s)" % constants.FLOOD_TO_TUN) + # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id + # for each tunnel type, and resubmit to table LEARN_FROM_TUN where + # remote mac adresses will be learnt + for tunnel_type in constants.TUNNEL_NETWORK_TYPES: + self.tun_br.add_flow(table=constants.TUN_TABLE[tunnel_type], + priority=0, + actions="drop") + # LEARN_FROM_TUN table will have a single flow using a learn action to + # dynamically set-up flows in UCAST_TO_TUN corresponding to remote mac + # adresses (assumes that lvid has already been set by a previous flow) + learned_flow = ("table=%s," + "priority=1," + "hard_timeout=300," + "NXM_OF_VLAN_TCI[0..11]," + "load:0->NXM_OF_VLAN_TCI[]," + "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[]," + "output:NXM_OF_IN_PORT[]" % + constants.UCAST_TO_TUN) + # Once remote mac adresses are learnt, packet is outputed to patch_int + self.tun_br.add_flow(table=constants.LEARN_FROM_TUN, + priority=1, + actions="learn(%s),output:%s" % + (learned_flow, self.patch_int_ofport)) + # Egress unicast will be handled in table UCAST_TO_TUN, where remote + # mac adresses will be learned. For now, just add a default flow that + # will resubmit unknown unicasts to table FLOOD_TO_TUN to treat them + # as broadcasts/multicasts + self.tun_br.add_flow(table=constants.UCAST_TO_TUN, + priority=0, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) + # FLOOD_TO_TUN will handle flooding in tunnels based on lvid, + # for now, add a default drop action + self.tun_br.add_flow(table=constants.FLOOD_TO_TUN, + priority=0, + actions="drop") def setup_physical_bridges(self, bridge_mappings): '''Setup the physical network bridges. @@ -685,6 +724,35 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): else: LOG.debug(_("No VIF port for port %s defined on agent."), port_id) + def setup_tunnel_port(self, port_name, remote_ip, tunnel_type): + ofport = self.tun_br.add_tunnel_port(port_name, + remote_ip, + self.local_ip, + tunnel_type, + self.vxlan_udp_port) + if ofport < 0: + LOG.error(_("Failed to set-up %(type)s tunnel port to %(ip)s"), + {'type': tunnel_type, 'ip': remote_ip}) + else: + self.tun_br_ofports[tunnel_type].add(ofport) + # Add flow in default table to resubmit to the right + # tunelling table (lvid will be set in the latter) + self.tun_br.add_flow(priority=1, + in_port=ofport, + actions="resubmit(,%s)" % + constants.TUN_TABLE[tunnel_type]) + # Update flooding flows to include the new tunnel + ofports = ','.join(self.tun_br_ofports[tunnel_type]) + for network_id, vlan_mapping in self.local_vlan_map.iteritems(): + if vlan_mapping.network_type == tunnel_type: + self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN, + priority=1, + dl_vlan=vlan_mapping.vlan, + actions="strip_vlan," + "set_tunnel:%s,output:%s" % + (vlan_mapping.segmentation_id, + ofports)) + def treat_devices_added(self, devices): resync = False self.sg_agent.prepare_devices_filter(devices) @@ -806,11 +874,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): if self.local_ip != tunnel['ip_address']: tunnel_id = tunnel.get('id', tunnel['ip_address']) tun_name = '%s-%s' % (tunnel_type, tunnel_id) - self.tun_br.add_tunnel_port(tun_name, - tunnel['ip_address'], - self.local_ip, - tunnel_type, - self.vxlan_udp_port) + self.setup_tunnel_port(tun_name, + tunnel['ip_address'], + tunnel_type) except Exception as e: LOG.debug(_("Unable to sync tunnel IP %(local_ip)s: %(e)s"), {'local_ip': self.local_ip, 'e': e}) diff --git a/neutron/plugins/openvswitch/common/constants.py b/neutron/plugins/openvswitch/common/constants.py index 7b42fb314..01488b447 100644 --- a/neutron/plugins/openvswitch/common/constants.py +++ b/neutron/plugins/openvswitch/common/constants.py @@ -38,3 +38,13 @@ MINIMUM_OVS_VXLAN_VERSION = "1.10" # The different types of tunnels TUNNEL_NETWORK_TYPES = [TYPE_GRE, TYPE_VXLAN] + +# Various tables for tunneling flows +PATCH_LV_TO_TUN = 1 +GRE_TUN_TO_LV = 2 +VXLAN_TUN_TO_LV = 3 +LEARN_FROM_TUN = 10 +UCAST_TO_TUN = 20 +FLOOD_TO_TUN = 21 +# Map tunnel types to tables number +TUN_TABLE = {TYPE_GRE: GRE_TUN_TO_LV, TYPE_VXLAN: VXLAN_TUN_TO_LV} diff --git a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py index d7c0deb98..5f6192a38 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py @@ -331,17 +331,13 @@ class TestOvsNeutronAgent(base.BaseTestCase): "int_ofport") def test_port_unbound(self): - with contextlib.nested( - mock.patch.object(self.agent.tun_br, "delete_flows"), - mock.patch.object(self.agent, "reclaim_local_vlan") - ) as (delfl_fn, reclvl_fn): + with mock.patch.object(self.agent, "reclaim_local_vlan") as reclvl_fn: self.agent.enable_tunneling = True lvm = mock.Mock() lvm.network_type = "gre" lvm.vif_ports = {"vif1": mock.Mock()} self.agent.local_vlan_map["netuid12345"] = lvm self.agent.port_unbound("vif1", "netuid12345") - self.assertTrue(delfl_fn.called) self.assertTrue(reclvl_fn.called) reclvl_fn.called = False diff --git a/neutron/tests/unit/openvswitch/test_ovs_tunnel.py b/neutron/tests/unit/openvswitch/test_ovs_tunnel.py index 83d585c4f..c9f0f5791 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_tunnel.py +++ b/neutron/tests/unit/openvswitch/test_ovs_tunnel.py @@ -37,13 +37,20 @@ VIF_MAC = '3c:09:24:1e:78:23' OFPORT_NUM = 1 VIF_PORT = ovs_lib.VifPort('port', OFPORT_NUM, VIF_ID, VIF_MAC, 'switch') -VIF_PORTS = {LV_ID: VIF_PORT} +VIF_PORTS = {VIF_ID: VIF_PORT} LVM = ovs_neutron_agent.LocalVLANMapping(LV_ID, 'gre', None, LS_ID, VIF_PORTS) LVM_FLAT = ovs_neutron_agent.LocalVLANMapping( LV_ID, 'flat', 'net1', LS_ID, VIF_PORTS) LVM_VLAN = ovs_neutron_agent.LocalVLANMapping( LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS) + +GRE_OFPORTS = set(['11', '12']) +VXLAN_OFPORTS = set(['13', '14']) +TUN_OFPORTS = {constants.TYPE_GRE: GRE_OFPORTS, + constants.TYPE_VXLAN: VXLAN_OFPORTS} + BCAST_MAC = "01:00:00:00:00:00/01:00:00:00:00:00" +UCAST_MAC = "00:00:00:00:00:00/01:00:00:00:00:00" class DummyPort: @@ -108,8 +115,45 @@ class TunnelTest(base.BaseTestCase): 'patch-tun', 'patch-int').AndReturn(self.TUN_OFPORT) self.mock_tun_bridge.add_patch_port( 'patch-int', 'patch-tun').AndReturn(self.INT_OFPORT) + self.mock_tun_bridge.remove_all_flows() - self.mock_tun_bridge.add_flow(priority=1, actions='drop') + self.mock_tun_bridge.add_flow(priority=1, + in_port=self.INT_OFPORT, + actions="resubmit(,%s)" % + constants.PATCH_LV_TO_TUN) + self.mock_tun_bridge.add_flow(priority=0, actions='drop') + self.mock_tun_bridge.add_flow(table=constants.PATCH_LV_TO_TUN, + dl_dst=UCAST_MAC, + actions="resubmit(,%s)" % + constants.UCAST_TO_TUN) + self.mock_tun_bridge.add_flow(table=constants.PATCH_LV_TO_TUN, + dl_dst=BCAST_MAC, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) + for tunnel_type in constants.TUNNEL_NETWORK_TYPES: + self.mock_tun_bridge.add_flow( + table=constants.TUN_TABLE[tunnel_type], + priority=0, + actions="drop") + learned_flow = ("table=%s," + "priority=1," + "hard_timeout=300," + "NXM_OF_VLAN_TCI[0..11]," + "load:0->NXM_OF_VLAN_TCI[]," + "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[]," + "output:NXM_OF_IN_PORT[]" % + constants.UCAST_TO_TUN) + self.mock_tun_bridge.add_flow(table=constants.LEARN_FROM_TUN, + priority=1, + actions="learn(%s),output:%s" % + (learned_flow, self.INT_OFPORT)) + self.mock_tun_bridge.add_flow(table=constants.UCAST_TO_TUN, + priority=0, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) + self.mock_tun_bridge.add_flow(table=constants.FLOOD_TO_TUN, + priority=0, + actions="drop") self.mox.StubOutWithMock(ip_lib, 'device_exists') ip_lib.device_exists('tunnel_bridge_mapping', 'sudo').AndReturn(True) @@ -153,14 +197,18 @@ class TunnelTest(base.BaseTestCase): self.mox.VerifyAll() def testProvisionLocalVlan(self): - action_string = 'set_tunnel:%s,normal' % LS_ID - self.mock_tun_bridge.add_flow(priority=4, in_port=self.INT_OFPORT, - dl_vlan=LV_ID, actions=action_string) - - action_string = 'mod_vlan_vid:%s,output:%s' % (LV_ID, self.INT_OFPORT) - self.mock_tun_bridge.add_flow(priority=3, tun_id=LS_ID, - dl_dst=BCAST_MAC, actions=action_string) - + self.mock_tun_bridge.mod_flow(table=constants.FLOOD_TO_TUN, + priority=1, + dl_vlan=LV_ID, + actions="strip_vlan," + "set_tunnel:%s,output:%s" % + (LS_ID, ','.join(GRE_OFPORTS))) + + self.mock_tun_bridge.add_flow(table=constants.TUN_TABLE['gre'], + priority=1, + tun_id=LS_ID, + actions="mod_vlan_vid:%s,resubmit(,%s)" % + (LV_ID, constants.LEARN_FROM_TUN)) self.mox.ReplayAll() a = ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE, @@ -169,6 +217,7 @@ class TunnelTest(base.BaseTestCase): 'sudo', 2, ['gre'], self.VETH_MTU) a.available_local_vlans = set([LV_ID]) + a.tun_br_ofports = TUN_OFPORTS a.provision_local_vlan(NET_UUID, constants.TYPE_GRE, None, LS_ID) self.mox.VerifyAll() @@ -240,8 +289,8 @@ class TunnelTest(base.BaseTestCase): self.mox.VerifyAll() def testReclaimLocalVlan(self): - self.mock_tun_bridge.delete_flows(tun_id=LVM.segmentation_id) - + self.mock_tun_bridge.delete_flows( + table=constants.TUN_TABLE['gre'], tun_id=LS_ID) self.mock_tun_bridge.delete_flows(dl_vlan=LVM.vlan) self.mox.ReplayAll() @@ -307,11 +356,6 @@ class TunnelTest(base.BaseTestCase): 'tag', str(LVM.vlan)) self.mock_int_bridge.delete_flows(in_port=VIF_PORT.ofport) - action_string = 'mod_vlan_vid:%s,normal' % LV_ID - self.mock_tun_bridge.add_flow(priority=3, tun_id=LS_ID, - dl_dst=VIF_PORT.vif_mac, - actions=action_string) - self.mox.ReplayAll() a = ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE, self.TUN_BRIDGE, @@ -323,15 +367,10 @@ class TunnelTest(base.BaseTestCase): self.mox.VerifyAll() def testPortUnbound(self): - self.mock_int_bridge.set_db_attribute('Port', VIF_PORT.port_name, - 'tag', str(LVM.vlan)) - self.mock_int_bridge.delete_flows(in_port=VIF_PORT.ofport) + self.mox.StubOutWithMock( + ovs_neutron_agent.OVSNeutronAgent, 'reclaim_local_vlan') + ovs_neutron_agent.OVSNeutronAgent.reclaim_local_vlan(NET_UUID, LVM) - action_string = 'mod_vlan_vid:%s,normal' % LV_ID - self.mock_tun_bridge.add_flow(priority=3, tun_id=LS_ID, - dl_dst=VIF_PORT.vif_mac, - actions=action_string) - self.mock_tun_bridge.delete_flows(dl_dst=VIF_MAC, tun_id=LS_ID) self.mox.ReplayAll() a = ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE, @@ -340,9 +379,6 @@ class TunnelTest(base.BaseTestCase): 'sudo', 2, ['gre'], self.VETH_MTU) a.local_vlan_map[NET_UUID] = LVM - a.port_bound(VIF_PORT, NET_UUID, 'gre', None, LS_ID) - a.available_local_vlans = set([LV_ID]) - a.local_vlan_map[NET_UUID] = LVM a.port_unbound(VIF_ID, NET_UUID) self.mox.VerifyAll()