Merge "Implement L2 Communication for vlan network."
This commit is contained in:
commit
15f2751804
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue