diff --git a/neutron/agent/linux/ovs_lib.py b/neutron/agent/linux/ovs_lib.py index 9dbadb6664..2c2bd9d8b5 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 842dda9118..db7b92b0cc 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 7b42fb314d..01488b447e 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 d7c0deb988..5f6192a382 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 83d585c4f4..c9f0f5791b 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()