diff --git a/dragonflow/controller/l2_ml2_app.py b/dragonflow/controller/l2_ml2_app.py index dfa050d4f..f1a72f093 100644 --- a/dragonflow/controller/l2_ml2_app.py +++ b/dragonflow/controller/l2_ml2_app.py @@ -18,6 +18,7 @@ from neutron_lib import constants as common_const from oslo_config import cfg from oslo_log import log from ryu.lib.mac import haddr_to_bin +from ryu.ofproto import ether from dragonflow._i18n import _, _LI from dragonflow.controller.common import arp_responder @@ -225,8 +226,14 @@ class L2App(df_base_app.DFlowApp): del local_ports[lport_id] if len(local_ports) == 0: - del self.local_networks[local_network_id] + self._del_multicast_broadcast_flows_for_local(local_network_id) + + # delete local_networks + remote_ports = network.get('remote') + if not remote_ports: + del self.local_networks[local_network_id] + else: self._update_multicast_broadcast_flows_for_local(local_ports, topic, @@ -478,7 +485,10 @@ class L2App(df_base_app.DFlowApp): if network and network.get('local'): return - self._del_network_flows_for_tunnel(segmentation_id) + if network_type == 'vlan': + self._del_network_flows_for_vlan(segmentation_id) + else: + self._del_network_flows_for_tunnel(segmentation_id) def _del_network_flows_for_tunnel(self, segmentation_id): LOG.info(_LI("Delete network flows for tunnel.")) @@ -594,8 +604,13 @@ class L2App(df_base_app.DFlowApp): del remote_ports[lport_id] if len(remote_ports) == 0: - del self.local_networks[network_id] + self._del_multicast_broadcast_flows_for_remote(network_id) + + # delete local_networks + local_ports = network.get('local') + if not local_ports: + del self.local_networks[network_id] else: self._update_multicast_broadcast_flows_for_remote(network_id, segmentation_id, @@ -735,6 +750,7 @@ class L2App(df_base_app.DFlowApp): lport_id = lport.get_id() mac = lport.get_mac() network_id = lport.get_external_value('local_network_id') + network_type = lport.get_external_value('network_type') segmentation_id = lport.get_external_value('segmentation_id') ofport = lport.get_external_value('ofport') port_key = lport.get_tunnel_key() @@ -769,6 +785,9 @@ class L2App(df_base_app.DFlowApp): self._add_arp_responder(lport) + if network_type == 'vlan': + return + match = parser.OFPMatch(reg7=port_key) actions = [parser.OFPActionSetField(tunnel_id_nxm=segmentation_id), parser.OFPActionOutput(port=ofport)] @@ -797,7 +816,12 @@ class L2App(df_base_app.DFlowApp): local_ports = network.get('local') if local_ports: return - self._install_network_flows_for_tunnel(segmentation_id, + + if network_type == 'vlan': + self._install_network_flows_for_vlan(segmentation_id, + local_network_id) + else: + self._install_network_flows_for_tunnel(segmentation_id, local_network_id) """ @@ -833,6 +857,93 @@ class L2App(df_base_app.DFlowApp): priority=const.PRIORITY_MEDIUM, match=match) + """ + Install network flows for vlan + """ + def _install_network_flows_for_vlan(self, segmentation_id, + local_network_id): + LOG.info(_LI("Install network flows on first vlan up")) + + # L2_LOOKUP for Remote ports + datapath = self.get_datapath() + parser = datapath.ofproto_parser + ofproto = datapath.ofproto + match = parser.OFPMatch() + + addint = haddr_to_bin('00:00:00:00:00:00') + add_mask_int = haddr_to_bin('01:00:00:00:00:00') + match.set_dl_dst_masked(addint, add_mask_int) + match.set_metadata(local_network_id) + inst = [parser.OFPInstructionGotoTable(const.EGRESS_TABLE)] + self.mod_flow( + datapath=datapath, + inst=inst, + table_id=const.L2_LOOKUP_TABLE, + priority=const.PRIORITY_MEDIUM, + match=match) + + # EGRESS for Remote ports + # Table=Egress + # Match: metadata=network_id + # Actions: mod_vlan, output:patch + match = parser.OFPMatch(metadata=local_network_id) + actions = [parser.OFPActionPushVlan(ether.ETH_TYPE_8021Q), + parser.OFPActionSetField( + vlan_vid=(segmentation_id & 0x1fff) | 0x1000)] + + action_inst = parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions) + goto_inst = parser.OFPInstructionGotoTable(const.EGRESS_EXTERNAL_TABLE) + inst = [action_inst, goto_inst] + self.mod_flow( + datapath=datapath, + inst=inst, + table_id=const.EGRESS_TABLE, + priority=const.PRIORITY_LOW, + match=match) + + # Ingress + # Match: dl_vlan=vlan_id, + # Actions: metadata=network_id, + # goto 'Destination Port Classification' + match = parser.OFPMatch() + match.set_vlan_vid(segmentation_id) + actions = [parser.OFPActionSetField(metadata=local_network_id), + parser.OFPActionPopVlan()] + + action_inst = parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions) + + goto_inst = parser.OFPInstructionGotoTable( + const.INGRESS_DESTINATION_PORT_LOOKUP_TABLE) + + inst = [action_inst, goto_inst] + self.mod_flow( + datapath=datapath, + inst=inst, + table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE, + priority=const.PRIORITY_LOW, + match=match) + + def _del_network_flows_for_vlan(self, segmentation_id): + LOG.info(_LI("Delete network flows for vlan")) + if segmentation_id is None: + return + + datapath = self.get_datapath() + parser = datapath.ofproto_parser + ofproto = datapath.ofproto + match = parser.OFPMatch() + match.set_vlan_vid(segmentation_id) + self.mod_flow( + datapath=datapath, + table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE, + command=ofproto.OFPFC_DELETE, + priority=const.PRIORITY_LOW, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + match=match) + def _get_multicast_broadcast_match(self, network_id): match = self.get_datapath().\ ofproto_parser.OFPMatch(eth_dst='01:00:00:00:00:00') diff --git a/dragonflow/tests/fullstack/test_ml2_l2_flows.py b/dragonflow/tests/fullstack/test_ml2_l2_flows.py index c0b0c97d1..7ec8f1255 100644 --- a/dragonflow/tests/fullstack/test_ml2_l2_flows.py +++ b/dragonflow/tests/fullstack/test_ml2_l2_flows.py @@ -14,6 +14,7 @@ import re from oslo_config import cfg +import ConfigParser from dragonflow.controller.common import constants as const from dragonflow.tests.common import utils from dragonflow.tests.fullstack import test_base @@ -21,6 +22,7 @@ from dragonflow.tests.fullstack import test_objects as objects ML2_CONF_INI = '/etc/neutron/plugins/ml2/ml2_conf.ini' L2_ML2_APP_NAME = 'l2_ml2_app.L2App' +VLAN_MIN_DEFAULT = 2 class TestL2FLows(test_base.DFTestBase): @@ -86,6 +88,64 @@ class TestL2FLows(test_base.DFTestBase): vm.close() network.close() + def test_vlan_network_flows(self): + if self._check_l2_ml2_app_enable() is False: + return + + physical_network, vlan_min = self._parse_network_vlan_ranges() + if physical_network is None or vlan_min is None: + self.assertIsNotNone(None) + return + + # Create network + network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api)) + network_params = {"name": "vlan_1", + "provider:network_type": "vlan", + "provider:physical_network": physical_network, + "provider:segmentation_id": vlan_min} + network_id = network.create(network=network_params) + + # Create subnet + subnet_params = {'network_id': network_id, + 'cidr': '100.64.0.0/24', + 'gateway_ip': '10.64.0.1', + 'ip_version': 4, + 'name': 'private', + 'enable_dhcp': True} + subnet = self.neutron.create_subnet({'subnet': subnet_params}) + self.assertIsNotNone(subnet) + + # Create VM + ovs = utils.OvsFlowsParser() + vm = self.store(objects.VMTestObj(self, self.neutron)) + vm.create(network=network) + ip = vm.get_first_ipv4() + self.assertIsNotNone(ip) + mac = vm.get_first_mac() + self.assertIsNotNone(mac) + + metadataid = utils.wait_until_is_and_return( + lambda: self._get_metadata_id(ovs.dump(self.integration_bridge), + ip, mac), + exception=Exception('Metadata id was not found in OpenFlow rules') + ) + port = utils.wait_until_is_and_return( + lambda: self._get_vm_port(ip, mac), + exception=Exception('No port assigned to VM') + ) + port_key = port.get_tunnel_key() + port_key_hex = hex(port_key) + + r = self._check_vlan_flows(ovs.dump(self.integration_bridge), + metadataid, + vlan_min, + port_key_hex, + mac) + self.assertIsNotNone(r) + vm.server.stop() + vm.close() + network.close() + def _check_tunnel_flows(self, flows, metadtata, segmentation_id, port_key_hex, mac): l2_lookup_unicast_match = 'metadata=0x' + metadtata + \ @@ -132,8 +192,117 @@ class TestL2FLows(test_base.DFTestBase): return True + def _check_vlan_flows(self, flows, metadtata, segmentation_id, + port_key_hex, mac): + l2_lookup_unicast_match = 'metadata=0x' + metadtata + \ + ',dl_dst=' + mac + l2_lookup_unicast_action = 'goto_table:' + \ + str(const.EGRESS_TABLE) + l2_lookup_unknown_match = 'metadata=0x' + metadtata + \ + ',dl_dst=00:00:00:00:00:00/01:00:00:00:00:00' + l2_lookup_unkown_action = 'goto_table:' + \ + str(const.EGRESS_TABLE) + l2_lookup_multicast_match = 'metadata=0x' + metadtata + ',dl_dst=' + \ + '01:00:00:00:00:00/01:00:00:00:00:00' + l2_lookup_multicast_action = 'set_field:' + port_key_hex + \ + '->reg7,resubmit(,' + \ + str(const.EGRESS_TABLE) + ')' + \ + ',set_field:0' + \ + '->reg7,resubmit(,' + \ + str(const.EGRESS_TABLE) + ')' + + egress_match = 'metadata=0x' + metadtata + egress_action = 'push_vlan:0x8100,set_field:' + \ + str(int(segmentation_id) + 4096) + \ + "->vlan_vid,goto_table:" + \ + str(const.EGRESS_EXTERNAL_TABLE) + + ingress_match = 'dl_vlan=' + str(segmentation_id) + ingress_action = 'set_field:0x' + metadtata + '->metadata,' \ + 'pop_vlan,goto_table:' + \ + str(const.INGRESS_DESTINATION_PORT_LOOKUP_TABLE) + + l2_lookup_unicast_check = None + l2_lookup_multicast_check = None + l2_lookup_unkown_check = None + egress_check = None + ingress_check = None + + for flow in flows: + if flow['table'] == str(const.L2_LOOKUP_TABLE): + if (l2_lookup_multicast_match in flow['match']): + if l2_lookup_multicast_action in flow['actions']: + l2_lookup_multicast_check = True + if (l2_lookup_unicast_match in flow['match']): + if l2_lookup_unicast_action in flow['actions']: + l2_lookup_unicast_check = True + if (l2_lookup_unknown_match in flow['match']): + if l2_lookup_unkown_action in flow['actions']: + l2_lookup_unkown_check = True + if flow['table'] == str(const.EGRESS_TABLE): + if (egress_match in flow['match']): + if egress_action in flow['actions']: + egress_check = True + + if flow['table'] == str( + const.INGRESS_CLASSIFICATION_DISPATCH_TABLE): + if (ingress_match in flow['match']): + if ingress_action in flow['actions']: + ingress_check = True + + if l2_lookup_multicast_check is None or \ + l2_lookup_unicast_check is None or \ + l2_lookup_unkown_check is None or \ + egress_check is None or \ + ingress_check is None: + + return None + + return True + + def _get_vlan_ranges(self): + readhandle = None + vlan_ranges = None + try: + config = ConfigParser.ConfigParser() + readhandle = open(ML2_CONF_INI, 'r') + config.readfp(readhandle) + vlan_ranges = config.get("ml2_type_vlan", 'network_vlan_ranges') + except Exception: + vlan_ranges = None + + if readhandle is not None: + try: + readhandle.close() + except Exception: + return vlan_ranges + return vlan_ranges + def _check_l2_ml2_app_enable(self): apps_list = cfg.CONF.df.apps_list if L2_ML2_APP_NAME in apps_list: return True return False + + def _parse_network_vlan_ranges(self): + network_vlan_ranges = self._get_vlan_ranges() + + if network_vlan_ranges is None: + return None + + network_vlan_range_list = network_vlan_ranges.split(',') + if not network_vlan_range_list: + return None + + network_vlan_range = network_vlan_range_list[0] + if ':' in network_vlan_range: + try: + physical_network, vlan_min, vlan_max = \ + network_vlan_range.split(':') + except ValueError: + return None + else: + physical_network = network_vlan_range + vlan_min = VLAN_MIN_DEFAULT + + return physical_network, vlan_min