# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
# encoding: utf-8
|
|
|
|
# Copyright 2018 Orange
|
|
#
|
|
# 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 oslo_log import log as logging
|
|
|
|
from networking_bagpipe.bagpipe_bgp.common import config
|
|
from networking_bagpipe.bagpipe_bgp.common import dataplane_utils
|
|
from networking_bagpipe.bagpipe_bgp.common import log_decorator
|
|
from networking_bagpipe.bagpipe_bgp import constants as consts
|
|
from networking_bagpipe.bagpipe_bgp.engine import exa
|
|
from networking_bagpipe.bagpipe_bgp.vpn import dataplane_drivers as dp_drivers
|
|
from networking_bagpipe.bagpipe_bgp.vpn import evpn
|
|
|
|
from neutron.plugins.ml2.drivers.openvswitch.agent.common import \
|
|
constants as ovs_const
|
|
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native import \
|
|
br_tun
|
|
from neutron.plugins.ml2.drivers.openvswitch.agent import ovs_neutron_agent
|
|
|
|
from neutron_lib import constants as n_consts
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
FLOOD = "flood"
|
|
FLOW_PRIORITY = 5
|
|
|
|
|
|
class OVSEVIDataplane(evpn.VPNInstanceDataplane):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(OVSEVIDataplane, self).__init__(*args, **kwargs)
|
|
|
|
self.bridge = self.driver.bridge
|
|
# OpenFlow 1.3 is needed for mod_vlan_vid
|
|
self.bridge.use_at_least_protocol(ovs_const.OPENFLOW13)
|
|
|
|
self.tunnel_mgr = dataplane_utils.SharedObjectLifecycleManagerProxy(
|
|
self.driver.tunnel_mgr,
|
|
self.instance_id
|
|
)
|
|
self.flooding_ports = set()
|
|
self.vlan = None
|
|
self.local_ip = self.driver.get_local_address()
|
|
|
|
def cleanup(self):
|
|
self.bridge.delete_flows(strict=True,
|
|
table=ovs_const.FLOOD_TO_TUN,
|
|
priority=FLOW_PRIORITY,
|
|
dl_vlan=self.vlan)
|
|
self.bridge.delete_group(group_id=self.vlan)
|
|
|
|
@log_decorator.log_info
|
|
def vif_plugged(self, mac_address, ip_address_prefix, localport, label,
|
|
direction):
|
|
if 'vlan' not in localport:
|
|
raise Exception("missing localport['vlan'] parameter")
|
|
if self.vlan and localport['vlan'] != self.vlan:
|
|
raise Exception("inconsistent vlan")
|
|
else:
|
|
self.vlan = localport['vlan']
|
|
|
|
# map traffic to this EVI VNI to the right table, similarly as in
|
|
# OVSTunnelBridge.provision_local_vlan
|
|
self.bridge.add_flow(table=ovs_const.VXLAN_TUN_TO_LV,
|
|
priority=FLOW_PRIORITY,
|
|
tun_id=self.instance_label,
|
|
actions=("push_vlan:0x8100,mod_vlan_vid:%d,"
|
|
"resubmit(,%s)" %
|
|
(self.vlan, ovs_const.LEARN_FROM_TUN)))
|
|
|
|
@log_decorator.log_info
|
|
def vif_unplugged(self, mac_address, ip_address_prefix, localport, label,
|
|
direction, last_endpoint=True):
|
|
self.log.debug("nothing to do on unplug")
|
|
|
|
def _local_vni_actions(self, vni):
|
|
# "load:0->NXM_OF_IN_PORT[]" allows the packets coming from br-int
|
|
# via patch-port to go back to br-int via the same port
|
|
return "load:0->NXM_OF_IN_PORT[],set_tunnel:%d,resubmit(,%s)" % (
|
|
vni, ovs_const.VXLAN_TUN_TO_LV)
|
|
|
|
@log_decorator.log_info
|
|
def setup_dataplane_for_remote_endpoint(self, prefix, remote_pe, vni, nlri,
|
|
encaps):
|
|
mac = prefix
|
|
ip = nlri.ip
|
|
|
|
# what is done here is similar as
|
|
# OVSTunnelBridge.install_unicast_to_tun, but with local delivery to
|
|
# table VXLAN_TUN_TO_LV for routes advertized locally
|
|
if remote_pe == self.local_ip:
|
|
actions = self._local_vni_actions(vni)
|
|
else:
|
|
port, _ = self.tunnel_mgr.get_object(remote_pe, (vni, mac))
|
|
actions = "set_tunnel:%d,output:%s" % (vni, port)
|
|
self.bridge.add_flow(table=ovs_const.UCAST_TO_TUN,
|
|
priority=FLOW_PRIORITY,
|
|
dl_vlan=self.vlan,
|
|
dl_dst=mac,
|
|
actions="strip_vlan,%s" % actions)
|
|
|
|
# add ARP responder
|
|
if ip:
|
|
self.bridge.install_arp_responder(self.vlan, str(ip), str(mac))
|
|
|
|
@log_decorator.log
|
|
def remove_dataplane_for_remote_endpoint(self, prefix, remote_pe, vni,
|
|
nlri):
|
|
mac = prefix
|
|
ip = nlri.ip
|
|
|
|
self.bridge.delete_unicast_to_tun(self.vlan, mac)
|
|
|
|
if remote_pe != self.local_ip:
|
|
self.tunnel_mgr.free_object(remote_pe, (vni, mac))
|
|
|
|
# cleanup ARP responder
|
|
if ip:
|
|
self.bridge.delete_arp_responder(self.vlan, str(ip))
|
|
|
|
@log_decorator.log_info
|
|
def add_dataplane_for_bum_endpoint(self, remote_pe, vni, nlri, encaps):
|
|
|
|
if remote_pe == self.local_ip:
|
|
port = "local"
|
|
else:
|
|
port, _ = self.tunnel_mgr.get_object(remote_pe, (vni, FLOOD))
|
|
self.flooding_ports.add((port, vni))
|
|
|
|
self._update_flooding_buckets()
|
|
|
|
@log_decorator.log_info
|
|
def remove_dataplane_for_bum_endpoint(self, remote_pe, vni, nlri):
|
|
|
|
if remote_pe == self.local_ip:
|
|
port = "local"
|
|
else:
|
|
port = self.tunnel_mgr.find_object(remote_pe)
|
|
|
|
if port:
|
|
self.flooding_ports.remove((port, vni))
|
|
|
|
self._update_flooding_buckets()
|
|
|
|
if remote_pe != self.local_ip:
|
|
self.tunnel_mgr.free_object(remote_pe, (vni, FLOOD))
|
|
|
|
def _update_flooding_buckets(self):
|
|
buckets = []
|
|
for port, vni in self.flooding_ports:
|
|
if port == "local":
|
|
buckets.append("bucket=strip_vlan,%s" %
|
|
self._local_vni_actions(vni))
|
|
else:
|
|
buckets.append("bucket=strip_vlan,set_tunnel:%d,output:%s" %
|
|
(vni, port))
|
|
self.bridge.mod_group(group_id=self.vlan,
|
|
type='all',
|
|
buckets=','.join(buckets))
|
|
self.bridge.add_flow(table=ovs_const.FLOOD_TO_TUN,
|
|
priority=FLOW_PRIORITY,
|
|
dl_vlan=self.vlan,
|
|
actions="group:%d" % self.vlan)
|
|
self.log.debug("buckets: %s", buckets)
|
|
|
|
def set_gateway_port(self, linuxif, gateway_ip):
|
|
# nothing to do, because we make the assumption that the
|
|
# IPVPN driver is 'ovs' as well, and setup in conjunction
|
|
# with Neutron OVS BGPVPN extension which does the plugging
|
|
# between L2 and L3
|
|
pass
|
|
|
|
def gateway_port_down(self, linuxif):
|
|
pass
|
|
|
|
# Looking glass ####
|
|
|
|
def get_lg_local_info(self, path_prefix):
|
|
return {
|
|
"vlan": self.vlan,
|
|
"flooding-ports": [{"port": str(port), "vni": vni}
|
|
for port, vni in self.flooding_ports]
|
|
}
|
|
|
|
|
|
class TunnelManager(dataplane_utils.ObjectLifecycleManager):
|
|
|
|
def __init__(self, bridge, local_ip):
|
|
super(TunnelManager, self).__init__()
|
|
|
|
self.bridge = bridge
|
|
self.local_ip = local_ip
|
|
|
|
@log_decorator.log_info
|
|
def create_object(self, remote_ip, *args, **kwargs):
|
|
port_name = ovs_neutron_agent.OVSNeutronAgent.get_tunnel_name(
|
|
n_consts.TYPE_VXLAN, self.local_ip, remote_ip)
|
|
tunnel = self.bridge.add_tunnel_port(port_name,
|
|
remote_ip,
|
|
self.local_ip,
|
|
n_consts.TYPE_VXLAN)
|
|
|
|
self.bridge.setup_tunnel_port(n_consts.TYPE_VXLAN, tunnel)
|
|
LOG.debug("tunnel for %s: %s (%s)", remote_ip, port_name, tunnel)
|
|
|
|
return tunnel
|
|
|
|
@log_decorator.log_info
|
|
def delete_object(self, tunnel):
|
|
self.bridge.delete_port(tunnel)
|
|
|
|
|
|
class OVSDataplaneDriver(dp_drivers.DataplaneDriver):
|
|
|
|
dataplane_instance_class = OVSEVIDataplane
|
|
type = consts.EVPN
|
|
ecmp_support = False
|
|
encaps = [exa.Encapsulation(exa.Encapsulation.Type.VXLAN)]
|
|
|
|
driver_opts = [
|
|
cfg.StrOpt("ovs_bridge", default="br-tun",
|
|
help=("Name of the OVS bridge to use, this has to be the "
|
|
"same as the tunneling bridge of the Neutron OVS "
|
|
"agent, usually br-tun")),
|
|
]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(OVSDataplaneDriver, self).__init__(*args, **kwargs)
|
|
|
|
config.set_default_root_helper()
|
|
|
|
self.bridge = dataplane_utils.OVSBridgeWithGroups(
|
|
br_tun.OVSTunnelBridge(self.config.ovs_bridge)
|
|
)
|
|
self.tunnel_mgr = TunnelManager(self.bridge,
|
|
self.get_local_address())
|
|
|
|
def needs_cleanup_assist(self):
|
|
return True
|
|
|
|
def reset_state(self):
|
|
# cleanup is taken care of by OVS Neutron Agent
|
|
pass
|
|
|
|
# Looking glass ####
|
|
|
|
def get_lg_local_info(self, path_prefix):
|
|
return {
|
|
"tunnels": self.tunnel_mgr.infos(),
|
|
}
|