provider_networks_app
patch include: L2 app modifcation as a result of having provider_networks app Unit test for provider-networks app and the provider_networks application. Change-Id: I390402cde57106c7076ab3d6cc932744d3c40bca Partially-implements: bp Provider-networks-app
This commit is contained in:
parent
ccdcbdc109
commit
46d53e1fa6
@ -16,7 +16,8 @@ OVS_BRANCH=${OVS_BRANCH:-branch-2.6}
|
||||
DEFAULT_TUNNEL_TYPE="geneve"
|
||||
DEFAULT_APPS_LIST="l2_app.L2App,l3_proactive_app.L3ProactiveApp,"\
|
||||
"dhcp_app.DHCPApp,dnat_app.DNATApp,sg_app.SGApp,portsec_app.PortSecApp,"\
|
||||
"portqos_app.PortQosApp,classifier_app.ClassifierApp,tunneling_app.TunnelingApp"
|
||||
"portqos_app.PortQosApp,classifier_app.ClassifierApp,tunneling_app.TunnelingApp,"\
|
||||
"provider_networks_app.ProviderNetworksApp"
|
||||
|
||||
if is_service_enabled df-metadata ; then
|
||||
DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,metadata_service_app.MetadataServiceApp"
|
||||
|
@ -43,6 +43,10 @@ def get_vhu_sockpath(sock_dir, port_id):
|
||||
(n_const.VHOST_USER_DEVICE_PREFIX + port_id)[:14])
|
||||
|
||||
|
||||
def get_bitmask(width):
|
||||
return ~((-1 >> width) << width)
|
||||
|
||||
|
||||
def is_valid_version(old_obj, new_obj):
|
||||
if not old_obj:
|
||||
return True
|
||||
|
@ -20,6 +20,7 @@ from dragonflow.conf import df_dnat
|
||||
from dragonflow.conf import df_l2
|
||||
from dragonflow.conf import df_l3
|
||||
from dragonflow.conf import df_metadata_service
|
||||
from dragonflow.conf import df_provider_networks
|
||||
from dragonflow.conf import df_ryu
|
||||
|
||||
|
||||
@ -35,3 +36,4 @@ df_l2.register_opts()
|
||||
df_l3.register_opts()
|
||||
df_dnat.register_opts()
|
||||
df_ryu.register_opts()
|
||||
df_provider_networks.register_opts()
|
||||
|
@ -22,18 +22,7 @@ df_l2_app_opts = [
|
||||
cfg.BoolOpt(
|
||||
'l2_responder',
|
||||
default=True,
|
||||
help=_('Install OVS flows to respond to ARP and ND requests.')),
|
||||
cfg.ListOpt('bridge_mappings',
|
||||
default=[],
|
||||
help=_("Comma-separated list of <physical_network>:<bridge> "
|
||||
"tuples mapping physical network names to the "
|
||||
"dragonflow's node-specific Open vSwitch bridge names "
|
||||
"to be used for flat and VLAN networks. Each bridge "
|
||||
"must exist, and should have a physical network "
|
||||
"interface configured as a port. All physical "
|
||||
"networks configured on the server should have "
|
||||
"mappings to appropriate bridges on each dragonflow "
|
||||
"node.")),
|
||||
help=_('Install OVS flows to respond to ARP and ND requests.'))
|
||||
]
|
||||
|
||||
|
||||
|
42
dragonflow/conf/df_provider_networks.py
Normal file
42
dragonflow/conf/df_provider_networks.py
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright (c) 2017 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from dragonflow._i18n import _
|
||||
|
||||
|
||||
df_provider_networks_opts = [
|
||||
cfg.ListOpt('bridge_mappings',
|
||||
default=['public:br-ex'],
|
||||
help=_("Comma-separated list of <physical_network>:<bridge> "
|
||||
"tuples mapping physical network names to the "
|
||||
"dragonflow's node-specific Open vSwitch bridge names "
|
||||
"to be used for flat and VLAN networks. Each bridge "
|
||||
"must exist, and should have a physical network "
|
||||
"interface configured as a port. All physical "
|
||||
"networks configured on the server should have "
|
||||
"mappings to appropriate bridges on each dragonflow "
|
||||
"node.")),
|
||||
]
|
||||
|
||||
|
||||
def register_opts():
|
||||
cfg.CONF.register_opts(df_provider_networks_opts,
|
||||
group='df_provider_networks')
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {'df_provider_networks_app': df_provider_networks_opts}
|
@ -17,13 +17,11 @@ import collections
|
||||
|
||||
import netaddr
|
||||
from neutron_lib import constants as common_const
|
||||
from neutron_lib.utils import helpers
|
||||
from oslo_log import log
|
||||
from ryu.lib.mac import haddr_to_bin
|
||||
from ryu.lib.packet import in_proto
|
||||
from ryu.ofproto import ether
|
||||
|
||||
from dragonflow._i18n import _, _LI, _LE
|
||||
from dragonflow import conf as cfg
|
||||
from dragonflow.controller.common import arp_responder
|
||||
from dragonflow.controller.common import constants as const
|
||||
@ -56,42 +54,8 @@ class L2App(df_base_app.DFlowApp):
|
||||
self.local_networks = collections.defaultdict(_LocalNetwork)
|
||||
self.integration_bridge = cfg.CONF.df.integration_bridge
|
||||
self.is_install_l2_responder = cfg.CONF.df_l2_app.l2_responder
|
||||
self.bridge_mappings = self._parse_bridge_mappings(
|
||||
cfg.CONF.df_l2_app.bridge_mappings)
|
||||
self.int_ofports = {}
|
||||
|
||||
def _parse_bridge_mappings(self, bridge_mappings):
|
||||
try:
|
||||
return helpers.parse_mappings(bridge_mappings)
|
||||
except ValueError as e:
|
||||
raise ValueError(_("Parsing bridge_mappings failed: %s.") % e)
|
||||
|
||||
def setup_physical_bridges(self, bridge_mappings):
|
||||
'''Setup the physical network bridges.
|
||||
|
||||
Creates physical network bridges and links them to the
|
||||
integration bridge using veths or patch ports.
|
||||
|
||||
:param bridge_mappings: map physical network names to bridge names.
|
||||
'''
|
||||
for physical_network, bridge in bridge_mappings.items():
|
||||
LOG.info(_LI("Mapping physical network %(physical_network)s to "
|
||||
"bridge %(bridge)s"),
|
||||
{'physical_network': physical_network,
|
||||
'bridge': bridge})
|
||||
|
||||
int_ofport = self.vswitch_api.create_patch_port(
|
||||
self.integration_bridge,
|
||||
'int-' + bridge,
|
||||
'phy-' + bridge)
|
||||
self.vswitch_api.create_patch_port(
|
||||
bridge,
|
||||
'phy-' + bridge,
|
||||
'int-' + bridge)
|
||||
self.int_ofports[physical_network] = int_ofport
|
||||
|
||||
def switch_features_handler(self, ev):
|
||||
self.setup_physical_bridges(self.bridge_mappings)
|
||||
self.add_flow_go_to_table(const.SERVICES_CLASSIFICATION_TABLE,
|
||||
const.PRIORITY_DEFAULT,
|
||||
const.L2_LOOKUP_TABLE)
|
||||
@ -218,9 +182,6 @@ class L2App(df_base_app.DFlowApp):
|
||||
self._del_multicast_broadcast_handling_for_local(lport_id,
|
||||
topic,
|
||||
local_network_id)
|
||||
self._del_network_flows_on_last_port_down(local_network_id,
|
||||
segmentation_id,
|
||||
network_type)
|
||||
|
||||
def _del_multicast_broadcast_handling_for_local(self,
|
||||
lport_id,
|
||||
@ -363,9 +324,6 @@ class L2App(df_base_app.DFlowApp):
|
||||
ofport = lport.get_external_value('ofport')
|
||||
port_key = lport.get_unique_key()
|
||||
network_id = lport.get_external_value('local_network_id')
|
||||
network_type = lport.get_external_value('network_type')
|
||||
physical_network = lport.get_external_value('physical_network')
|
||||
segmentation_id = lport.get_external_value('segmentation_id')
|
||||
|
||||
if ofport is None or network_id is None:
|
||||
return
|
||||
@ -404,33 +362,12 @@ class L2App(df_base_app.DFlowApp):
|
||||
table_id=const.EGRESS_TABLE,
|
||||
priority=const.PRIORITY_MEDIUM,
|
||||
match=match)
|
||||
self._install_network_flows_on_first_port_up(segmentation_id,
|
||||
physical_network,
|
||||
network_type,
|
||||
network_id)
|
||||
self._add_multicast_broadcast_handling_for_local_port(lport_id,
|
||||
port_key,
|
||||
network_id,
|
||||
topic)
|
||||
self._add_l2_responders(lport)
|
||||
|
||||
def _del_network_flows_on_last_port_down(self,
|
||||
local_network_id,
|
||||
segmentation_id,
|
||||
network_type):
|
||||
network = self.local_networks.get(local_network_id)
|
||||
if network and network.local_ports:
|
||||
return
|
||||
|
||||
LOG.info(_LI("Remove network flows on last port down. Network type "
|
||||
"is %(type)s, and segmentation ID is %(s_id)s."),
|
||||
{'type': network_type, 's_id': segmentation_id})
|
||||
|
||||
if network_type == 'vlan':
|
||||
self._del_network_flows_for_vlan(segmentation_id, local_network_id)
|
||||
elif network_type == 'flat':
|
||||
self._del_network_flows_for_flat(local_network_id)
|
||||
|
||||
def _add_multicast_broadcast_handling_for_local_port(self,
|
||||
lport_id,
|
||||
port_key,
|
||||
@ -523,203 +460,9 @@ class L2App(df_base_app.DFlowApp):
|
||||
|
||||
self._add_l2_responders(lport)
|
||||
|
||||
def _install_network_flows_on_first_port_up(self,
|
||||
segmentation_id,
|
||||
physical_network,
|
||||
network_type,
|
||||
local_network_id):
|
||||
network = self.local_networks.get(local_network_id)
|
||||
if network and network.local_ports:
|
||||
return
|
||||
|
||||
LOG.info(_LI("Install network flows on first port up. Network type "
|
||||
"is %(type)s, and segmentation ID is %(s_id)s."),
|
||||
{'type': network_type, 's_id': segmentation_id})
|
||||
|
||||
if network_type == 'vlan':
|
||||
self._install_network_flows_for_vlan(segmentation_id,
|
||||
physical_network,
|
||||
local_network_id)
|
||||
elif network_type == 'flat':
|
||||
self._install_network_flows_for_flat(physical_network,
|
||||
local_network_id)
|
||||
|
||||
"""
|
||||
Install network flows for vlan
|
||||
"""
|
||||
def _install_network_flows_for_vlan(self, segmentation_id,
|
||||
physical_network, local_network_id):
|
||||
# L2_LOOKUP for Remote ports
|
||||
parser = self.parser
|
||||
ofproto = self.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(
|
||||
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(
|
||||
inst=inst,
|
||||
table_id=const.EGRESS_TABLE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
# Add EGRESS port according to physical_network
|
||||
self._install_output_to_physical_patch(physical_network,
|
||||
local_network_id)
|
||||
|
||||
# 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(
|
||||
inst=inst,
|
||||
table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
def _install_network_flows_for_flat(self, physical_network,
|
||||
local_network_id):
|
||||
parser = self.parser
|
||||
ofproto = self.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(
|
||||
inst=inst,
|
||||
table_id=const.L2_LOOKUP_TABLE,
|
||||
priority=const.PRIORITY_MEDIUM,
|
||||
match=match)
|
||||
|
||||
# EGRESS for Remote ports
|
||||
# Table=Egress
|
||||
match = parser.OFPMatch(metadata=local_network_id)
|
||||
goto_inst = parser.OFPInstructionGotoTable(const.EGRESS_EXTERNAL_TABLE)
|
||||
|
||||
inst = [goto_inst]
|
||||
self.mod_flow(
|
||||
inst=inst,
|
||||
table_id=const.EGRESS_TABLE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
# Add EGRESS port according to physical_network
|
||||
self._install_output_to_physical_patch(physical_network,
|
||||
local_network_id)
|
||||
|
||||
# Ingress
|
||||
match = parser.OFPMatch(vlan_vid=0)
|
||||
actions = [parser.OFPActionSetField(metadata=local_network_id)]
|
||||
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(
|
||||
inst=inst,
|
||||
table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
def _del_network_flows_for_vlan(self, segmentation_id, local_network_id):
|
||||
if segmentation_id is None:
|
||||
return
|
||||
|
||||
parser = self.parser
|
||||
ofproto = self.ofproto
|
||||
match = parser.OFPMatch()
|
||||
match.set_vlan_vid(segmentation_id)
|
||||
self.mod_flow(
|
||||
table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE,
|
||||
command=ofproto.OFPFC_DELETE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
match = parser.OFPMatch(metadata=local_network_id)
|
||||
self.mod_flow(
|
||||
table_id=const.EGRESS_EXTERNAL_TABLE,
|
||||
command=ofproto.OFPFC_DELETE,
|
||||
priority=const.PRIORITY_HIGH,
|
||||
match=match)
|
||||
|
||||
def _get_multicast_broadcast_match(self, network_id):
|
||||
match = self.parser.OFPMatch(eth_dst='01:00:00:00:00:00')
|
||||
addint = haddr_to_bin('01:00:00:00:00:00')
|
||||
match.set_dl_dst_masked(addint, addint)
|
||||
match.set_metadata(network_id)
|
||||
return match
|
||||
|
||||
def _del_network_flows_for_flat(self, local_network_id):
|
||||
parser = self.parser
|
||||
ofproto = self.ofproto
|
||||
match = parser.OFPMatch(vlan_vid=0)
|
||||
self.mod_flow(
|
||||
table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE,
|
||||
command=ofproto.OFPFC_DELETE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
match = parser.OFPMatch(metadata=local_network_id)
|
||||
self.mod_flow(
|
||||
table_id=const.EGRESS_EXTERNAL_TABLE,
|
||||
command=ofproto.OFPFC_DELETE,
|
||||
priority=const.PRIORITY_HIGH,
|
||||
match=match)
|
||||
|
||||
def _install_output_to_physical_patch(self, physical_network,
|
||||
local_network_id):
|
||||
if physical_network not in self.int_ofports:
|
||||
LOG.error(_LE("Physical network %s unknown for dragonflow"),
|
||||
physical_network)
|
||||
return
|
||||
|
||||
parser = self.parser
|
||||
ofproto = self.ofproto
|
||||
match = parser.OFPMatch(metadata=local_network_id)
|
||||
ofport = self.int_ofports[physical_network]
|
||||
actions = [parser.OFPActionOutput(ofport,
|
||||
ofproto.OFPCML_NO_BUFFER)]
|
||||
actions_inst = parser.OFPInstructionActions(
|
||||
ofproto.OFPIT_APPLY_ACTIONS, actions)
|
||||
inst = [actions_inst]
|
||||
self.mod_flow(inst=inst,
|
||||
table_id=const.EGRESS_EXTERNAL_TABLE,
|
||||
priority=const.PRIORITY_HIGH, match=match)
|
||||
|
263
dragonflow/controller/provider_networks_app.py
Normal file
263
dragonflow/controller/provider_networks_app.py
Normal file
@ -0,0 +1,263 @@
|
||||
# Copyright (c) 2017 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from dragonflow._i18n import _LI, _LE
|
||||
from dragonflow.common import utils
|
||||
from dragonflow import conf as cfg
|
||||
from dragonflow.controller.common import constants as const
|
||||
from dragonflow.controller.common import logical_networks
|
||||
from dragonflow.controller import df_base_app
|
||||
from neutron_lib.utils import helpers
|
||||
from oslo_log import log
|
||||
from ryu.lib import mac as mac_api
|
||||
|
||||
|
||||
NET_VLAN = 'vlan'
|
||||
NET_FLAT = 'flat'
|
||||
NETWORK_TYPES = (NET_VLAN, NET_FLAT)
|
||||
VLAN_TAG_BITS = 12
|
||||
VLAN_MASK = utils.get_bitmask(VLAN_TAG_BITS)
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class ProviderNetworksApp(df_base_app.DFlowApp):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProviderNetworksApp, self).__init__(*args, **kwargs)
|
||||
self.integration_bridge = cfg.CONF.df.integration_bridge
|
||||
self.logical_networks = logical_networks.LogicalNetworks()
|
||||
self.bridge_mappings = self._parse_bridge_mappings(
|
||||
cfg.CONF.df_provider_networks.bridge_mappings)
|
||||
self.int_ofports = {}
|
||||
|
||||
def _parse_bridge_mappings(self, bridge_mappings):
|
||||
try:
|
||||
return helpers.parse_mappings(bridge_mappings)
|
||||
except ValueError:
|
||||
LOG.exception(_LE("Failed to parse bridge mapping"))
|
||||
raise
|
||||
|
||||
def _setup_physical_bridges(self, bridge_mappings):
|
||||
'''Setup the physical network bridges.
|
||||
|
||||
Creates physical network bridges and links them to the
|
||||
integration bridge using veths or patch ports.
|
||||
|
||||
:param bridge_mappings: map physical network names to bridge names.
|
||||
'''
|
||||
for physical_network, bridge in bridge_mappings.items():
|
||||
LOG.info(_LI("Mapping physical network %(physical_network)s to "
|
||||
"bridge %(bridge)s"),
|
||||
{'physical_network': physical_network,
|
||||
'bridge': bridge})
|
||||
int_ofport = self.vswitch_api.create_patch_port(
|
||||
self.integration_bridge,
|
||||
'int-' + bridge,
|
||||
'phy-' + bridge)
|
||||
self.vswitch_api.create_patch_port(
|
||||
bridge,
|
||||
'phy-' + bridge,
|
||||
'int-' + bridge)
|
||||
self.int_ofports[physical_network] = int_ofport
|
||||
|
||||
def switch_features_handler(self, ev):
|
||||
self._setup_physical_bridges(self.bridge_mappings)
|
||||
|
||||
def add_local_port(self, lport):
|
||||
network_type = lport.get_external_value('network_type')
|
||||
if network_type not in NETWORK_TYPES:
|
||||
return
|
||||
network_id = lport.get_external_value('local_network_id')
|
||||
port_count = self.logical_networks.get_local_port_count(
|
||||
network_id=network_id,
|
||||
network_type=network_type)
|
||||
LOG.info(_LI("adding %(net_type)s local port %(lport)s"),
|
||||
{'net_type': network_type,
|
||||
'lport': lport})
|
||||
if port_count == 0:
|
||||
self._new_network_flow(lport,
|
||||
network_id,
|
||||
network_type)
|
||||
self.logical_networks.add_local_port(port_id=lport.get_id(),
|
||||
network_id=network_id,
|
||||
network_type=network_type)
|
||||
|
||||
def _match_actions_by_network_type(self, lport, network_id, network_type):
|
||||
actions = [
|
||||
self.parser.OFPActionSetField(metadata=network_id)]
|
||||
match = None
|
||||
if network_type == NET_VLAN:
|
||||
segmentation_id = lport.get_external_value('segmentation_id')
|
||||
match = self.parser.OFPMatch()
|
||||
match.set_vlan_vid(segmentation_id)
|
||||
actions.append(self.parser.OFPActionPopVlan())
|
||||
elif network_type == NET_FLAT:
|
||||
match = self.parser.OFPMatch(vlan_vid=0)
|
||||
|
||||
return match, actions
|
||||
|
||||
def _new_network_flow(self, lport, network_id, network_type):
|
||||
LOG.debug('new %(net_type)s network: %(net_id)s',
|
||||
{'net_type': network_type,
|
||||
'net_id': network_id})
|
||||
self._network_classification_flow(lport, network_id, network_type)
|
||||
self._l2_lookup_flow(network_id)
|
||||
self._egress_flow(lport, network_id, network_type)
|
||||
self._egress_external_flow(lport, network_id)
|
||||
|
||||
def _l2_lookup_flow(self, network_id):
|
||||
LOG.debug('l2 lookup flow for network %(net_id)s',
|
||||
{'net_id': network_id})
|
||||
|
||||
match = self._make_bum_match(metadata=network_id)
|
||||
inst = [self.parser.OFPInstructionGotoTable(const.EGRESS_TABLE)]
|
||||
self.mod_flow(
|
||||
inst=inst,
|
||||
table_id=const.L2_LOOKUP_TABLE,
|
||||
priority=const.PRIORITY_MEDIUM,
|
||||
match=match)
|
||||
|
||||
def _egress_flow(self, lport, network_id, network_type):
|
||||
LOG.debug('egrees flow for network %(net_id)s',
|
||||
{'net_id': network_id})
|
||||
match = self.parser.OFPMatch(metadata=network_id)
|
||||
inst = [self.parser.OFPInstructionGotoTable(
|
||||
const.EGRESS_EXTERNAL_TABLE)]
|
||||
if network_type == NET_VLAN:
|
||||
segmentation_id = lport.get_external_value('segmentation_id')
|
||||
vlan_tag = (segmentation_id & VLAN_MASK)
|
||||
# from open flow documentation:
|
||||
# https://www.opennetworking.org/images/stories/downloads/\
|
||||
# sdn-resources/onf-specifications/openflow/\
|
||||
# openflow-spec-v1.3.3.pdf
|
||||
# "... in particular the OFPVID_PRESENT bit must be set in
|
||||
# OXM_OF_VLAN_VID set-field actions."
|
||||
vlan_tag |= self.ofproto.OFPVID_PRESENT
|
||||
actions = [
|
||||
self.parser.OFPActionPushVlan(),
|
||||
self.parser.OFPActionSetField(vlan_vid=vlan_tag)]
|
||||
action_inst = self.parser.OFPInstructionActions(
|
||||
self.ofproto.OFPIT_APPLY_ACTIONS,
|
||||
actions)
|
||||
inst.insert(0, action_inst)
|
||||
self.mod_flow(
|
||||
inst=inst,
|
||||
table_id=const.EGRESS_TABLE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
def _egress_external_flow(self, lport, network_id):
|
||||
LOG.debug('egrees external flow for network %(net_id)s',
|
||||
{'net_id': network_id})
|
||||
|
||||
physical_network = lport.get_external_value('physical_network')
|
||||
match = self.parser.OFPMatch(metadata=network_id)
|
||||
ofport = self.int_ofports[physical_network]
|
||||
actions = [
|
||||
self.parser.OFPActionOutput(ofport,
|
||||
self.ofproto.OFPCML_NO_BUFFER)]
|
||||
actions_inst = self.parser.OFPInstructionActions(
|
||||
self.ofproto.OFPIT_APPLY_ACTIONS, actions)
|
||||
inst = [actions_inst]
|
||||
self.mod_flow(
|
||||
inst=inst,
|
||||
table_id=const.EGRESS_EXTERNAL_TABLE,
|
||||
priority=const.PRIORITY_HIGH,
|
||||
match=match)
|
||||
|
||||
def _network_classification_flow(self, lport, network_id, network_type):
|
||||
LOG.debug('network classification flow for network_id: %(net_id)s',
|
||||
{'net_id': network_id})
|
||||
match, actions = self._match_actions_by_network_type(lport,
|
||||
network_id,
|
||||
network_type)
|
||||
action_inst = self.parser.OFPInstructionActions(
|
||||
self.ofproto.OFPIT_APPLY_ACTIONS, actions)
|
||||
|
||||
goto_inst = self.parser.OFPInstructionGotoTable(const.L2_LOOKUP_TABLE)
|
||||
|
||||
inst = [action_inst, goto_inst]
|
||||
self.mod_flow(
|
||||
inst=inst,
|
||||
table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
def remove_local_port(self, lport):
|
||||
network_type = lport.get_external_value('network_type')
|
||||
if network_type not in NETWORK_TYPES:
|
||||
return
|
||||
network_id = lport.get_external_value('local_network_id')
|
||||
self.logical_networks.remove_local_port(port_id=lport.get_id(),
|
||||
network_id=network_id,
|
||||
network_type=network_type)
|
||||
port_count = self.logical_networks.get_local_port_count(
|
||||
network_id=network_id,
|
||||
network_type=network_type)
|
||||
if port_count == 0:
|
||||
self._remove_network_flow(lport, network_id, network_type)
|
||||
|
||||
def _remove_network_flow(self, lport, network_id, network_type):
|
||||
self._remove_network_classification_flow(lport,
|
||||
network_id,
|
||||
network_type)
|
||||
self._remove_l2_lookup_flow(network_id)
|
||||
self._remove_egress_flow(lport, network_id)
|
||||
self._remove_egress_external_flow(lport, network_id)
|
||||
|
||||
def _remove_network_classification_flow(self,
|
||||
lport,
|
||||
network_id,
|
||||
network_type):
|
||||
match, actions = self._match_actions_by_network_type(lport,
|
||||
network_id,
|
||||
network_type)
|
||||
self.mod_flow(
|
||||
table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE,
|
||||
command=self.ofproto.OFPFC_DELETE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
def _remove_l2_lookup_flow(self, network_id):
|
||||
match = self._make_bum_match(metadata=network_id)
|
||||
self.mod_flow(
|
||||
table_id=const.L2_LOOKUP_TABLE,
|
||||
command=self.ofproto.OFPFC_DELETE,
|
||||
priority=const.PRIORITY_MEDIUM,
|
||||
match=match)
|
||||
|
||||
def _remove_egress_flow(self, lport, network_id):
|
||||
match = self.parser.OFPMatch(metadata=network_id)
|
||||
self.mod_flow(
|
||||
command=self.ofproto.OFPFC_DELETE,
|
||||
table_id=const.EGRESS_TABLE,
|
||||
priority=const.PRIORITY_LOW,
|
||||
match=match)
|
||||
|
||||
def _remove_egress_external_flow(self, lport, network_id):
|
||||
match = self.parser.OFPMatch(metadata=network_id)
|
||||
self.mod_flow(
|
||||
command=self.ofproto.OFPFC_DELETE,
|
||||
table_id=const.EGRESS_EXTERNAL_TABLE,
|
||||
priority=const.PRIORITY_HIGH,
|
||||
match=match)
|
||||
|
||||
def _make_bum_match(self, metadata):
|
||||
match = self.parser.OFPMatch()
|
||||
match.set_metadata(metadata)
|
||||
encoded_mac = mac_api.haddr_to_bin(mac_api.DONTCARE_STR)
|
||||
encoded_mask = mac_api.haddr_to_bin(mac_api.UNICAST)
|
||||
match.set_dl_dst_masked(encoded_mac, encoded_mask)
|
||||
return match
|
@ -15,14 +15,19 @@ import re
|
||||
from oslo_config import cfg
|
||||
|
||||
import ConfigParser
|
||||
from dragonflow.common import utils as df_utils
|
||||
from dragonflow.controller.common import constants as const
|
||||
from dragonflow.tests.common import utils
|
||||
from dragonflow.tests.fullstack import test_base
|
||||
from dragonflow.tests.fullstack import test_objects as objects
|
||||
|
||||
ML2_CONF_INI = '/etc/neutron/plugins/ml2/ml2_conf.ini'
|
||||
L2_ML2_APP_NAME = 'l2_app.L2App'
|
||||
PROVIDER_NET_APP_NAME = 'provider_networks_app.ProviderNetworksApp'
|
||||
TUNNEL_NET_APP_NAME = 'tunneling_app.TunnelingApp'
|
||||
VLAN_MIN_DEFAULT = 2
|
||||
VLAN_TAG_BITS = 12
|
||||
VLAN_MASK = df_utils.get_bitmask(VLAN_TAG_BITS)
|
||||
OFPVID_PRESENT = 0x1000
|
||||
|
||||
|
||||
class TestL2FLows(test_base.DFTestBase):
|
||||
@ -44,7 +49,7 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
return None
|
||||
|
||||
def test_tunnel_network_flows(self):
|
||||
if self._check_l2_ml2_app_enable() is False:
|
||||
if self._check_tunneling_app_enable() is False:
|
||||
return
|
||||
|
||||
network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api))
|
||||
@ -87,12 +92,13 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
hex(segmentation_id),
|
||||
tunnel_key_hex,
|
||||
mac, ofport)
|
||||
self.assertIsNotNone(r)
|
||||
for key, value in r.items():
|
||||
self.assertIsNotNone(value, key)
|
||||
vm.close()
|
||||
network.close()
|
||||
|
||||
def test_vlan_network_flows(self):
|
||||
if self._check_l2_ml2_app_enable() is False:
|
||||
if not self._check_providers_net_app_enable():
|
||||
return
|
||||
|
||||
physical_network, vlan_min = self._parse_network_vlan_ranges()
|
||||
@ -110,8 +116,8 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
|
||||
# Create subnet
|
||||
subnet_params = {'network_id': network_id,
|
||||
'cidr': '100.64.0.0/24',
|
||||
'gateway_ip': '10.64.0.1',
|
||||
'cidr': '100.64.2.0/24',
|
||||
'gateway_ip': '10.64.2.1',
|
||||
'ip_version': 4,
|
||||
'name': 'private',
|
||||
'enable_dhcp': True}
|
||||
@ -144,7 +150,8 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
vlan_min,
|
||||
port_key_hex,
|
||||
mac)
|
||||
self.assertIsNotNone(r)
|
||||
for key, value in r.items():
|
||||
self.assertIsNotNone(value, key)
|
||||
vm.server.stop()
|
||||
vm.close()
|
||||
network.close()
|
||||
@ -189,12 +196,9 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
if ingress_action in flow['actions']:
|
||||
ingress_check = True
|
||||
|
||||
if (l2_lookup_multicast_check is None or
|
||||
l2_lookup_unicast_check is None or
|
||||
ingress_check is None):
|
||||
return None
|
||||
|
||||
return True
|
||||
return {'l2_lookup_multicast_check': l2_lookup_multicast_check,
|
||||
'l2_lookup_unicast_check': l2_lookup_unicast_check,
|
||||
'ingress_check': ingress_check}
|
||||
|
||||
def _check_vlan_flows(self, flows, metadtata, segmentation_id,
|
||||
port_key_hex, mac):
|
||||
@ -217,14 +221,14 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
|
||||
egress_match = 'metadata=0x' + metadtata
|
||||
egress_action = 'push_vlan:0x8100,set_field:' + \
|
||||
str(int(segmentation_id) + 4096) + \
|
||||
str((segmentation_id & VLAN_MASK) | OFPVID_PRESENT) + \
|
||||
"->vlan_vid,goto_table:" + \
|
||||
str(const.EGRESS_EXTERNAL_TABLE)
|
||||
|
||||
ingress_match = 'dl_vlan=' + str(segmentation_id)
|
||||
ingress_match = 'dl_vlan=%s' % segmentation_id
|
||||
ingress_action = 'set_field:0x' + metadtata + '->metadata,' \
|
||||
'pop_vlan,goto_table:' + \
|
||||
str(const.INGRESS_DESTINATION_PORT_LOOKUP_TABLE)
|
||||
str(const.L2_LOOKUP_TABLE)
|
||||
|
||||
l2_lookup_unicast_check = None
|
||||
l2_lookup_multicast_check = None
|
||||
@ -237,53 +241,55 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
if (l2_lookup_multicast_match in flow['match']):
|
||||
if l2_lookup_multicast_action in flow['actions']:
|
||||
l2_lookup_multicast_check = True
|
||||
continue
|
||||
if (l2_lookup_unicast_match in flow['match']):
|
||||
if l2_lookup_unicast_action in flow['actions']:
|
||||
l2_lookup_unicast_check = True
|
||||
continue
|
||||
if (l2_lookup_unknown_match in flow['match']):
|
||||
if l2_lookup_unkown_action in flow['actions']:
|
||||
l2_lookup_unkown_check = True
|
||||
continue
|
||||
|
||||
if flow['table'] == str(const.EGRESS_TABLE):
|
||||
if (egress_match in flow['match']):
|
||||
if egress_action in flow['actions']:
|
||||
egress_check = True
|
||||
|
||||
continue
|
||||
if flow['table'] == str(
|
||||
const.INGRESS_CLASSIFICATION_DISPATCH_TABLE):
|
||||
if (ingress_match in flow['match']):
|
||||
if ingress_action in flow['actions']:
|
||||
ingress_check = True
|
||||
continue
|
||||
|
||||
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
|
||||
return {'l2_lookup_multicast_check': l2_lookup_multicast_check,
|
||||
'l2_lookup_unicast_check': l2_lookup_unicast_check,
|
||||
'l2_lookup_unkown_check': l2_lookup_unkown_check,
|
||||
'egress_vlan_tag': egress_check,
|
||||
'ingress_check': ingress_check}
|
||||
|
||||
def test_flat_network_flows(self):
|
||||
if self._check_l2_ml2_app_enable() is False:
|
||||
if not self._check_providers_net_app_enable():
|
||||
return
|
||||
|
||||
physical_network = self._parse_flat_network()
|
||||
|
||||
if not physical_network:
|
||||
self.assertIsNotNone(None)
|
||||
return
|
||||
|
||||
# Create network
|
||||
network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api))
|
||||
network_params = {"name": "vlan_1",
|
||||
network_params = {"name": "flat_1",
|
||||
"provider:network_type": "flat",
|
||||
"provider:physical_network": physical_network}
|
||||
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',
|
||||
'cidr': '100.64.1.0/24',
|
||||
'gateway_ip': '10.64.1.1',
|
||||
'ip_version': 4,
|
||||
'name': 'private',
|
||||
'enable_dhcp': True}
|
||||
@ -314,7 +320,9 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
port_key_hex = hex(port_key)
|
||||
r = self._check_flat_flows(ovs.dump(self.integration_bridge),
|
||||
metadataid, port_key_hex, mac)
|
||||
self.assertIsNotNone(r)
|
||||
for key, value in r.items():
|
||||
self.assertIsNotNone(value, key)
|
||||
|
||||
vm.server.stop()
|
||||
vm.close()
|
||||
network.close()
|
||||
@ -345,7 +353,8 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
ingress_match = 'vlan_tci=0x0000/0x1fff'
|
||||
ingress_action = 'set_field:0x' + metadtata + \
|
||||
'->metadata,goto_table:' + \
|
||||
str(const.INGRESS_DESTINATION_PORT_LOOKUP_TABLE)
|
||||
str(const.L2_LOOKUP_TABLE)
|
||||
|
||||
l2_lookup_unicast_check = None
|
||||
l2_lookup_multicast_check = None
|
||||
l2_lookup_unkown_check = None
|
||||
@ -357,30 +366,32 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
if (l2_lookup_multicast_match in flow['match']):
|
||||
if l2_lookup_multicast_action in flow['actions']:
|
||||
l2_lookup_multicast_check = True
|
||||
continue
|
||||
if (l2_lookup_unicast_match in flow['match']):
|
||||
if l2_lookup_unicast_action in flow['actions']:
|
||||
l2_lookup_unicast_check = True
|
||||
continue
|
||||
if (l2_lookup_unkown_match in flow['match']):
|
||||
if l2_lookup_unkown_action in flow['actions']:
|
||||
l2_lookup_unkown_check = True
|
||||
continue
|
||||
if flow['table'] == str(const.EGRESS_TABLE):
|
||||
if (egress_match in flow['match']):
|
||||
if egress_action in flow['actions']:
|
||||
egress_check = True
|
||||
|
||||
continue
|
||||
if flow['table'] == str(
|
||||
const.INGRESS_CLASSIFICATION_DISPATCH_TABLE):
|
||||
if (ingress_match in flow['match']):
|
||||
if ingress_action in flow['actions']:
|
||||
ingress_check = True
|
||||
continue
|
||||
|
||||
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
|
||||
return {'l2_lookup_multicast_check': l2_lookup_multicast_check,
|
||||
'l2_lookup_unicast_check': l2_lookup_unicast_check,
|
||||
'l2_lookup_unkown_check': l2_lookup_unkown_check,
|
||||
'egress_check': egress_check,
|
||||
'ingress_check': ingress_check}
|
||||
|
||||
def _get_config_values(self, section, key):
|
||||
readhandle = None
|
||||
@ -400,9 +411,14 @@ class TestL2FLows(test_base.DFTestBase):
|
||||
return value
|
||||
return value
|
||||
|
||||
def _check_l2_ml2_app_enable(self):
|
||||
apps_list = cfg.CONF.df.apps_list
|
||||
if L2_ML2_APP_NAME in apps_list:
|
||||
def _check_tunneling_app_enable(self):
|
||||
return self._check_if_app_enabled(TUNNEL_NET_APP_NAME)
|
||||
|
||||
def _check_providers_net_app_enable(self):
|
||||
return self._check_if_app_enabled(PROVIDER_NET_APP_NAME)
|
||||
|
||||
def _check_if_app_enabled(self, app_name):
|
||||
if app_name in cfg.CONF.df.apps_list:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
87
dragonflow/tests/unit/test_provider_net_app.py
Normal file
87
dragonflow/tests/unit/test_provider_net_app.py
Normal file
@ -0,0 +1,87 @@
|
||||
# Copyright (c) 2017 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from dragonflow.controller.common import constants as const
|
||||
from dragonflow.tests.unit import test_app_base
|
||||
|
||||
|
||||
make_fake_local_port = test_app_base.make_fake_local_port
|
||||
make_fake_logic_switch = test_app_base.make_fake_logic_switch
|
||||
make_fake_remote_port = test_app_base.make_fake_remote_port
|
||||
|
||||
|
||||
class TestProviderNetsApp(test_app_base.DFAppTestBase):
|
||||
apps_list = "provider_networks_app.ProviderNetworksApp"
|
||||
|
||||
def setUp(self):
|
||||
super(TestProviderNetsApp, self).setUp()
|
||||
fake_vlan_switch1 = make_fake_logic_switch(
|
||||
subnets=test_app_base.fake_lswitch_default_subnets,
|
||||
network_type='vlan',
|
||||
id='fake_vlan_switch1',
|
||||
mtu=1454,
|
||||
physical_network='phynet',
|
||||
router_external=False,
|
||||
unique_key=6,
|
||||
topic='fake_tenant1',
|
||||
segmentation_id=10,
|
||||
name='private')
|
||||
self.controller.update_lswitch(fake_vlan_switch1)
|
||||
self.app = self.open_flow_app.dispatcher.apps[0]
|
||||
self.app.ofproto.OFPVID_PRESENT = 0x1000
|
||||
|
||||
def test_provider_vlan_port(self):
|
||||
fake_local_vlan_port1 = make_fake_local_port(
|
||||
network_type='vlan',
|
||||
lswitch='fake_vlan_switch1',
|
||||
local_network_id=21,
|
||||
segmentation_id=5)
|
||||
self.app.int_ofports['phynet'] = 1
|
||||
self.controller.update_lport(fake_local_vlan_port1)
|
||||
match = self.app.parser.OFPMatch(metadata=21)
|
||||
actions = [
|
||||
self.app.parser.OFPActionOutput(
|
||||
self.app.int_ofports['phynet'],
|
||||
self.app.ofproto.OFPCML_NO_BUFFER)]
|
||||
inst = [self.app.parser.OFPInstructionActions(
|
||||
self.app.ofproto.OFPIT_APPLY_ACTIONS, actions)]
|
||||
self.app.mod_flow.assert_called_with(
|
||||
inst=inst,
|
||||
match=match,
|
||||
priority=const.PRIORITY_HIGH,
|
||||
table_id=const.EGRESS_EXTERNAL_TABLE)
|
||||
self.app.mod_flow.reset_mock()
|
||||
|
||||
fake_local_vlan_port2 = make_fake_local_port(
|
||||
network_type='vlan',
|
||||
lswitch='fake_vlan_switch1',
|
||||
macs=['1a:0b:0c:0d:0f:0f'],
|
||||
ips=['10.0.0.112'],
|
||||
ofport=12)
|
||||
self.controller.update_lport(fake_local_vlan_port2)
|
||||
self.app.mod_flow.assert_not_called()
|
||||
self.app.mod_flow.reset_mock()
|
||||
|
||||
self.controller.delete_lport(fake_local_vlan_port1.get_id())
|
||||
self.app.mod_flow.assert_not_called()
|
||||
self.app.mod_flow.reset_mock()
|
||||
|
||||
self.controller.delete_lport(fake_local_vlan_port2.get_id())
|
||||
self.app.mod_flow.assert_called_with(
|
||||
command=self.app.ofproto.OFPFC_DELETE,
|
||||
table_id=const.EGRESS_EXTERNAL_TABLE,
|
||||
priority=const.PRIORITY_HIGH,
|
||||
match=match)
|
||||
self.app.mod_flow.reset_mock()
|
Loading…
Reference in New Issue
Block a user