dragonflow/dragonflow/controller/apps/provider.py

384 lines
15 KiB
Python

# 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 neutron_lib.utils import helpers
from oslo_log import log
from ryu.lib import mac as mac_api
from ryu.ofproto import nicira_ext
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 dragonflow.db.models import constants as model_const
from dragonflow.db.models import l2
from dragonflow.db.models import switch
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 ProviderApp(df_base_app.DFlowApp):
def __init__(self, *args, **kwargs):
super(ProviderApp, 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.reverse_bridge_mappings = {
v: k for (k, v) in self.bridge_mappings.items()
}
self.int_ofports = {}
self.bridge_macs = {}
def _parse_bridge_mappings(self, bridge_mappings):
try:
return helpers.parse_mappings(bridge_mappings)
except ValueError:
LOG.exception("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("Mapping physical network %(physical_network)s to "
"bridge %(bridge)s",
{'physical_network': physical_network,
'bridge': bridge})
mappings = self.vswitch_api.create_patch_pair(
self.integration_bridge, bridge)
self.int_ofports[physical_network] = \
self.vswitch_api.get_port_ofport(
mappings[0])
mac = self.vswitch_api.get_port_mac_in_use(bridge)
self.bridge_macs[physical_network] = mac
@df_base_app.register_event(switch.SwitchPort, model_const.EVENT_CREATED)
@df_base_app.register_event(switch.SwitchPort, model_const.EVENT_UPDATED)
def _bridge_updated(self, switch_port, orig_switch_port=None):
self._update_bridge_mac(switch_port.name, switch_port.mac_in_use)
@df_base_app.register_event(switch.SwitchPort, model_const.EVENT_DELETED)
def _bridge_deleted(self, switch_port):
self._update_bridge_mac(switch_port.name, None)
def _update_bridge_mac(self, bridge, mac):
if bridge not in self.bridge_macs:
return
old_mac = self.bridge_macs[bridge]
if old_mac == mac:
return
physical_network = self.reverse_bridge_mappings[bridge]
lswitch = self.db_store.get_one(
l2.LogicalSwitch(physical_network=physical_network),
index=l2.LogicalSwitch.get_index('physical_network'))
if old_mac is not None:
self._remove_egress_placeholder_flow(lswitch.unique_key)
if mac is not None:
self._egress_placeholder_flow(lswitch.unique_key)
self.bridge_macs[physical_network] = mac
def switch_features_handler(self, ev):
self._setup_physical_bridges(self.bridge_mappings)
self.add_flow_go_to_table(
const.EXTERNAL_INGRESS_DETECT_SOURCE_TABLE,
const.PRIORITY_DEFAULT,
const.L2_LOOKUP_TABLE
)
@df_base_app.register_event(l2.LogicalPort, l2.EVENT_BIND_LOCAL)
def _add_local_port(self, lport):
lswitch = lport.lswitch
network_type = lswitch.network_type
if network_type not in NETWORK_TYPES:
return
network_id = lswitch.unique_key
port_count = self.logical_networks.get_local_port_count(
network_id=network_id,
network_type=network_type)
LOG.info("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.id,
network_id=network_id,
network_type=network_type)
def _match_actions_by_network_type(self, lport, network_id, network_type):
actions = [
self.parser.NXActionRegLoad(
dst='in_port',
value=0,
ofs_nbits=nicira_ext.ofs_nbits(0, 31),
),
self.parser.OFPActionSetField(metadata=network_id),
]
if network_type == NET_VLAN:
vlan_vid = self.ofproto.OFPVID_PRESENT
vlan_vid |= lport.lswitch.segmentation_id
actions.append(self.parser.OFPActionPopVlan())
elif network_type == NET_FLAT:
vlan_vid = 0
match = self.parser.OFPMatch(
in_port=self.int_ofports[lport.lswitch.physical_network],
vlan_vid=vlan_vid,
)
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('Add egress flow for network %(net_id)s',
{'net_id': network_id})
inst = [self.parser.OFPInstructionGotoTable(
const.EGRESS_EXTERNAL_TABLE)]
if network_type == NET_VLAN:
segmentation_id = lport.lswitch.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(
table_id=const.EGRESS_TABLE,
priority=const.PRIORITY_LOW,
match=self.parser.OFPMatch(metadata=network_id),
inst=inst,
)
# Drop all packets that did not originate on current node.
# Any packet arriving from provider network won't have reg6 set.
self.mod_flow(
table_id=const.EGRESS_TABLE,
priority=const.PRIORITY_LOW + 1,
match=self.parser.OFPMatch(
metadata=network_id,
reg6=0,
),
inst=None,
)
def _egress_external_flow(self, lport, network_id):
LOG.debug('Add egress external flow for network %(net_id)s',
{'net_id': network_id})
physical_network = lport.lswitch.physical_network
port_num = self.int_ofports[physical_network]
# Output without updating MAC:
self.mod_flow(
table_id=const.EGRESS_EXTERNAL_TABLE,
priority=const.PRIORITY_MEDIUM,
match=self.parser.OFPMatch(metadata=network_id),
inst=[
self.parser.OFPInstructionActions(
self.ofproto.OFPIT_APPLY_ACTIONS,
[
self.parser.OFPActionOutput(
port_num,
self.ofproto.OFPCML_NO_BUFFER,
),
]
),
],
)
if self.bridge_macs.get(physical_network) is not None:
self._egress_placeholder_flow(lport)
def _egress_placeholder_flow(self, lport):
# If dest MAC is the placeholder, update it to bridge MAC
network_id = lport.lswitch.unique_key
physical_network = lport.lswitch.physical_network
port_num = self.int_ofports[physical_network]
self.mod_flow(
table_id=const.EGRESS_EXTERNAL_TABLE,
priority=const.PRIORITY_HIGH,
match=self.parser.OFPMatch(
metadata=network_id,
eth_dst=const.EMPTY_MAC,
),
inst=[
self.parser.OFPInstructionActions(
self.ofproto.OFPIT_APPLY_ACTIONS,
[
self.parser.OFPActionSetField(
eth_dst=self.bridge_macs[physical_network],
),
self.parser.OFPActionOutput(
port_num,
self.ofproto.OFPCML_NO_BUFFER,
),
]
),
],
)
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.EXTERNAL_INGRESS_DETECT_SOURCE_TABLE)
inst = [action_inst, goto_inst]
self.mod_flow(
inst=inst,
table_id=const.INGRESS_CLASSIFICATION_DISPATCH_TABLE,
priority=const.PRIORITY_LOW,
match=match)
@df_base_app.register_event(l2.LogicalPort, l2.EVENT_UNBIND_LOCAL)
def _remove_local_port(self, lport):
network_type = lport.lswitch.network_type
if network_type not in NETWORK_TYPES:
return
network_id = lport.lswitch.unique_key
self.logical_networks.remove_local_port(port_id=lport.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(network_id)
self._remove_egress_external_flow(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, 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, 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)
# This removes the placeholder flow as well
def _remove_egress_placeholder_flow(self, network_id):
self.mod_flow(
command=self.ofproto.OFPFC_DELETE,
table_id=const.EGRESS_EXTERNAL_TABLE,
priority=const.PRIORITY_HIGH,
match=self.parser.OFPMatch(
metadata=network_id,
eth_dst=const.EMPTY_MAC,
),
)
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