Add initial support for EVPN
Change-Id: I8c6ffc192158b96ea3186501ae6579bd2934d37b
This commit is contained in:
parent
d1d9b63e0f
commit
e3afbb0aba
@ -14,4 +14,4 @@ Features
|
||||
|
||||
* Expose VMs with FIPs or on Provider Networks through BGP on OVN
|
||||
environments.
|
||||
|
||||
* Expose VMs on Tenant Networks through EVPN on OVN environments.
|
||||
|
@ -40,10 +40,10 @@ the OVN overlay.
|
||||
|
||||
This simple design allows the agent to implement different drivers, depending
|
||||
on what OVN SB DB events are being watched (watchers examples at
|
||||
``networking_bgp_onn/drivers/openstack/watchers/``), and what actions are
|
||||
``ovn_bgp_agent/drivers/openstack/watchers/``), and what actions are
|
||||
triggered in reaction to them (drivers examples at
|
||||
``networking_bgp_ovn/drivers/openstack/XXXX_driver.py``, implementing the
|
||||
``networking_bgp_von/drivers/driver_api.py``).
|
||||
``ovn_bgp_agent/drivers/openstack/XXXX_driver.py``, implementing the
|
||||
``ovn_bgp_agent/drivers/driver_api.py``).
|
||||
|
||||
A new driver implements the support for EVPN capabilities with multitenancy
|
||||
(overlapping CIDRs), by leveraging VRFs and EVPN Type-5 Routes. The API used
|
||||
@ -55,7 +55,7 @@ react to the information being added by it into the OVN SB DB (using the
|
||||
Proposed Solution
|
||||
-----------------
|
||||
|
||||
To support EVPN the functionality of the ``networking-bgp-ovn`` agent needs
|
||||
To support EVPN the functionality of the ``ovn_bgp_agent`` needs
|
||||
to be extended with a new driver that performs the extra steps
|
||||
required for the EVPN configuration and steering the traffic to/from the node
|
||||
from/to the OVN overlay. The only configuration needed is to enable the
|
||||
|
@ -33,7 +33,14 @@ IP_VERSION_6 = 6
|
||||
IP_VERSION_4 = 4
|
||||
|
||||
BGP_MODE = 'BGP'
|
||||
EVPN_MODE = 'EVPN'
|
||||
|
||||
OVN_EVPN_VNI_EXT_ID_KEY = 'neutron_bgpvpn:vni'
|
||||
OVN_EVPN_AS_EXT_ID_KEY = 'neutron_bgpvpn:as'
|
||||
OVN_EVPN_VRF_PREFIX = "vrf-"
|
||||
OVN_EVPN_BRIDGE_PREFIX = "br-"
|
||||
OVN_EVPN_VXLAN_PREFIX = "vxlan-"
|
||||
OVN_EVPN_LO_PREFIX = "lo-"
|
||||
OVN_INTEGRATION_BRIDGE = 'br-int'
|
||||
|
||||
LINK_UP = "up"
|
||||
|
@ -37,7 +37,7 @@ LOG = logging.getLogger(__name__)
|
||||
OVN_TABLES = ("Port_Binding", "Chassis", "Datapath_Binding", "Chassis_Private")
|
||||
|
||||
|
||||
class OSPOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
class OVNBGPDriver(driver_api.AgentDriverBase):
|
||||
|
||||
def __init__(self):
|
||||
self._expose_tenant_networks = CONF.expose_tenant_networks
|
||||
@ -52,7 +52,7 @@ class OSPOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
self.ovs_idl.start(constants.OVS_CONNECTION_STRING)
|
||||
self.chassis = self.ovs_idl.get_own_chassis_name()
|
||||
self.ovn_remote = self.ovs_idl.get_ovn_remote()
|
||||
LOG.debug("Loaded chassis {}.".format(self.chassis))
|
||||
LOG.debug("Loaded chassis %s.", self.chassis)
|
||||
|
||||
events = ()
|
||||
for event in self._get_events():
|
||||
|
758
ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py
Normal file
758
ovn_bgp_agent/drivers/openstack/ovn_evpn_driver.py
Normal file
@ -0,0 +1,758 @@
|
||||
# Copyright 2021 Red Hat, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import collections
|
||||
import ipaddress
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from ovn_bgp_agent import constants
|
||||
from ovn_bgp_agent.drivers import driver_api
|
||||
from ovn_bgp_agent.drivers.openstack.utils import frr
|
||||
from ovn_bgp_agent.drivers.openstack.utils import ovn
|
||||
from ovn_bgp_agent.drivers.openstack.utils import ovs
|
||||
from ovn_bgp_agent.drivers.openstack.watchers import evpn_watcher as \
|
||||
watcher
|
||||
from ovn_bgp_agent.utils import linux_net
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
# LOG.setLevel(logging.DEBUG)
|
||||
# logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
OVN_TABLES = ("Port_Binding", "Chassis", "Datapath_Binding", "Chassis_Private")
|
||||
|
||||
|
||||
class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
|
||||
def __init__(self):
|
||||
self.ovn_bridge_mappings = {} # {'public': 'br-ex'}
|
||||
self.ovn_local_cr_lrps = {}
|
||||
self.ovn_local_lrps = {}
|
||||
# {'br-ex': [route1, route2]}
|
||||
self._ovn_routing_tables_routes = collections.defaultdict()
|
||||
self._ovn_exposed_evpn_ips = collections.defaultdict()
|
||||
|
||||
self.ovs_idl = ovs.OvsIdl()
|
||||
self.ovs_idl.start(constants.OVS_CONNECTION_STRING)
|
||||
self.chassis = self.ovs_idl.get_own_chassis_name()
|
||||
self.ovn_remote = self.ovs_idl.get_ovn_remote()
|
||||
LOG.debug("Loaded chassis %s.", self.chassis)
|
||||
|
||||
events = ()
|
||||
for event in self._get_events():
|
||||
event_class = getattr(watcher, event)
|
||||
events += (event_class(self),)
|
||||
|
||||
self._sb_idl = ovn.OvnSbIdl(
|
||||
self.ovn_remote,
|
||||
chassis=self.chassis,
|
||||
tables=OVN_TABLES,
|
||||
events=events)
|
||||
|
||||
def start(self):
|
||||
# start the subscriptions to the OSP events. This ensures the watcher
|
||||
# calls the relevant driver methods upon registered events
|
||||
self.sb_idl = self._sb_idl.start()
|
||||
|
||||
def _get_events(self):
|
||||
events = set(["PortBindingChassisCreatedEvent",
|
||||
"PortBindingChassisDeletedEvent",
|
||||
"SubnetRouterAttachedEvent",
|
||||
"SubnetRouterDetachedEvent",
|
||||
"TenantPortCreatedEvent",
|
||||
"TenantPortDeletedEvent",
|
||||
"ChassisCreateEvent"])
|
||||
return events
|
||||
|
||||
@lockutils.synchronized('evpn')
|
||||
def sync(self):
|
||||
self.ovn_local_cr_lrps = {}
|
||||
self.ovn_local_lrps = {}
|
||||
self._ovn_routing_tables_routes = collections.defaultdict()
|
||||
self._ovn_exposed_evpn_ips = collections.defaultdict()
|
||||
|
||||
# 1) Get bridge mappings: xxxx:br-ex,yyyy:br-ex2
|
||||
bridge_mappings = self.ovs_idl.get_ovn_bridge_mappings()
|
||||
# 2) Get macs for bridge mappings
|
||||
for bridge_mapping in bridge_mappings:
|
||||
network = bridge_mapping.split(":")[0]
|
||||
bridge = bridge_mapping.split(":")[1]
|
||||
self.ovn_bridge_mappings[network] = bridge
|
||||
|
||||
# TO DO
|
||||
# add missing routes/ips for fips/provider VMs
|
||||
ports = self.sb_idl.get_ports_on_chassis(self.chassis)
|
||||
for port in ports:
|
||||
if port.type != constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE:
|
||||
continue
|
||||
self._expose_ip(port, cr_lrp=True)
|
||||
|
||||
self._remove_extra_exposed_ips()
|
||||
self._remove_extra_routes()
|
||||
self._remove_extra_ovs_flows()
|
||||
self._remove_extra_vrfs()
|
||||
|
||||
def _ensure_network_exposed(self, router_port, gateway):
|
||||
evpn_info = self.sb_idl.get_evpn_info_from_lrp_port_name(
|
||||
router_port.logical_port)
|
||||
if not evpn_info:
|
||||
LOG.debug("No EVPN information for LRP Port %s. "
|
||||
"Not exposing it.", router_port)
|
||||
return
|
||||
|
||||
gateway_ips = [ip.split('/')[0] for ip in gateway['ips']]
|
||||
try:
|
||||
router_port_ip = router_port.mac[0].split(' ')[1]
|
||||
except IndexError:
|
||||
return
|
||||
router_ip = router_port_ip.split('/')[0]
|
||||
if router_ip in gateway_ips:
|
||||
return
|
||||
self.ovn_local_lrps[router_port.logical_port] = {
|
||||
'datapath': router_port.datapath,
|
||||
'ip': router_port_ip
|
||||
}
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
gateway['provider_datapath'])
|
||||
|
||||
router_port_ip_version = linux_net.get_ip_version(router_port_ip)
|
||||
for gateway_ip in gateway_ips:
|
||||
if linux_net.get_ip_version(gateway_ip) == router_port_ip_version:
|
||||
linux_net.add_ip_route(
|
||||
self._ovn_routing_tables_routes,
|
||||
router_ip,
|
||||
gateway['vni'],
|
||||
datapath_bridge,
|
||||
vlan=vlan_tag,
|
||||
mask=router_port_ip.split("/")[1],
|
||||
via=gateway_ip)
|
||||
break
|
||||
|
||||
if router_port_ip_version == constants.IP_VERSION_6:
|
||||
net_ip = '{}'.format(ipaddress.IPv6Network(
|
||||
router_port_ip, strict=False))
|
||||
else:
|
||||
net_ip = '{}'.format(ipaddress.IPv4Network(
|
||||
router_port_ip, strict=False))
|
||||
|
||||
strip_vlan = False
|
||||
if vlan_tag:
|
||||
strip_vlan = True
|
||||
ovs.ensure_evpn_ovs_flow(datapath_bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE,
|
||||
gateway['mac'],
|
||||
gateway['vrf'],
|
||||
net_ip,
|
||||
strip_vlan)
|
||||
|
||||
network_port_datapath = self.sb_idl.get_port_datapath(
|
||||
router_port.options['peer'])
|
||||
if not network_port_datapath:
|
||||
return
|
||||
ports = self.sb_idl.get_ports_on_datapath(
|
||||
network_port_datapath)
|
||||
for port in ports:
|
||||
if (port.type not in (constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE) or
|
||||
(port.type == constants.OVN_VM_VIF_PORT_TYPE and
|
||||
not port.chassis)):
|
||||
continue
|
||||
try:
|
||||
port_ips = [port.mac[0].split(' ')[1]]
|
||||
except IndexError:
|
||||
continue
|
||||
if len(port.mac[0].split(' ')) == 3:
|
||||
port_ips.append(port.mac[0].split(' ')[2])
|
||||
|
||||
for port_ip in port_ips:
|
||||
# Only adding the port ips that match the lrp
|
||||
# IP version
|
||||
port_ip_version = linux_net.get_ip_version(port_ip)
|
||||
if port_ip_version == router_port_ip_version:
|
||||
linux_net.add_ips_to_dev(
|
||||
gateway['lo'], [port_ip],
|
||||
clear_local_route_at_table=gateway['vni'])
|
||||
self._ovn_exposed_evpn_ips.setdefault(
|
||||
gateway['lo'], []).extend([port_ip])
|
||||
|
||||
def _get_bridge_for_datapath(self, datapath):
|
||||
network_name, network_tag = self.sb_idl.get_network_name_and_tag(
|
||||
datapath, self.ovn_bridge_mappings.keys())
|
||||
if network_name:
|
||||
if network_tag:
|
||||
return self.ovn_bridge_mappings[network_name], network_tag[0]
|
||||
return self.ovn_bridge_mappings[network_name], None
|
||||
return None, None
|
||||
|
||||
@lockutils.synchronized('evpn')
|
||||
def expose_ip(self, row, cr_lrp=False):
|
||||
'''Advertice BGP route through EVPN.
|
||||
|
||||
This methods ensures BGP advertises the IP through the required
|
||||
VRF/Tenant by using the specified VNI/VXLAN id.
|
||||
|
||||
It relies on Zebra, which creates and advertises a route when an IP
|
||||
is added to a interface in the related VRF.
|
||||
'''
|
||||
self._expose_ip(row, cr_lrp)
|
||||
|
||||
def _expose_ip(self, row, cr_lrp=False):
|
||||
if cr_lrp:
|
||||
cr_lrp_port_name = row.logical_port
|
||||
cr_lrp_port = row
|
||||
else:
|
||||
cr_lrp_port_name = 'cr-lrp-' + row.logical_port
|
||||
cr_lrp_port = self.sb_idl.get_port_if_local_chassis(
|
||||
cr_lrp_port_name, self.chassis)
|
||||
if not cr_lrp_port:
|
||||
# Not in local chassis, no need to proccess
|
||||
return
|
||||
|
||||
_, cr_lrp_datapath = self.sb_idl.get_fip_associated(
|
||||
cr_lrp_port_name)
|
||||
if not cr_lrp_datapath:
|
||||
return
|
||||
|
||||
if (len(cr_lrp_port.mac[0].split(' ')) != 2 and
|
||||
len(cr_lrp_port.mac[0].split(' ')) != 3):
|
||||
return
|
||||
ips = [cr_lrp_port.mac[0].split(' ')[1]]
|
||||
# for dual-stack
|
||||
if len(cr_lrp_port.mac[0].split(' ')) == 3:
|
||||
ips.append(cr_lrp_port.mac[0].split(' ')[2])
|
||||
|
||||
if cr_lrp:
|
||||
evpn_info = self.sb_idl.get_evpn_info_from_crlrp_port_name(
|
||||
cr_lrp_port_name)
|
||||
else:
|
||||
evpn_info = self.sb_idl.get_evpn_info_from_port(row)
|
||||
if not evpn_info:
|
||||
LOG.debug("No EVPN information for CR-LRP Port with IPs %s. "
|
||||
"Not exposing it.", ips)
|
||||
return
|
||||
|
||||
LOG.info("Adding BGP route for CR-LRP Port %s on AS %s and "
|
||||
"VNI %s", ips, evpn_info['bgp_as'], evpn_info['vni'])
|
||||
vrf, lo, bridge, vxlan = self._ensure_evpn_devices(evpn_info['vni'])
|
||||
if not vrf or not lo:
|
||||
return
|
||||
|
||||
self.ovn_local_cr_lrps[cr_lrp_port_name] = {
|
||||
'router_datapath': cr_lrp_port.datapath,
|
||||
'provider_datapath': cr_lrp_datapath,
|
||||
'ips': ips,
|
||||
'mac': cr_lrp_port.mac[0].split(' ')[0],
|
||||
'vni': int(evpn_info['vni']),
|
||||
'bgp_as': evpn_info['bgp_as'],
|
||||
'lo': lo,
|
||||
'bridge': bridge,
|
||||
'vxlan': vxlan,
|
||||
'vrf': vrf
|
||||
}
|
||||
|
||||
frr.vrf_reconfigure(evpn_info, action="add-vrf")
|
||||
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
cr_lrp_datapath)
|
||||
|
||||
ips_without_mask = [ip.split("/")[0] for ip in ips]
|
||||
linux_net.add_ips_to_dev(lo, ips_without_mask)
|
||||
self._ovn_exposed_evpn_ips.setdefault(
|
||||
lo, []).extend(ips_without_mask)
|
||||
|
||||
self._connect_evpn_to_ovn(vrf, ips, datapath_bridge, evpn_info['vni'],
|
||||
vlan_tag)
|
||||
|
||||
# Check if there are networks attached to the router,
|
||||
# and if so, add the needed routes/rules
|
||||
lrp_ports = self.sb_idl.get_lrp_ports_for_router(
|
||||
cr_lrp_port.datapath)
|
||||
for lrp in lrp_ports:
|
||||
if lrp.chassis:
|
||||
continue
|
||||
self._ensure_network_exposed(
|
||||
lrp, self.ovn_local_cr_lrps[cr_lrp_port_name])
|
||||
|
||||
@lockutils.synchronized('evpn')
|
||||
def withdraw_ip(self, row, cr_lrp=False):
|
||||
'''Withdraw BGP route through EVPN.
|
||||
|
||||
This methods ensures BGP withdraw the IP advertised through the
|
||||
required VRF/Tenant by using the specified VNI/VXLAN id.
|
||||
|
||||
It relies on Zebra, which cwithdraws the advertisement as son as the
|
||||
IP is deleted from the interface in the related VRF.
|
||||
'''
|
||||
if cr_lrp:
|
||||
cr_lrp_port_name = row.logical_port
|
||||
else:
|
||||
cr_lrp_port_name = 'cr-lrp-' + row.logical_port
|
||||
|
||||
cr_lrp_info = self.ovn_local_cr_lrps.get(cr_lrp_port_name, {})
|
||||
if not cr_lrp_info:
|
||||
# This means it is in a different chassis
|
||||
return
|
||||
cr_lrp_datapath = cr_lrp_info.get('provider_datapath')
|
||||
if not cr_lrp_datapath:
|
||||
return
|
||||
|
||||
ips = cr_lrp_info.get('ips')
|
||||
evpn_vni = cr_lrp_info.get('vni')
|
||||
if not evpn_vni:
|
||||
LOG.debug("No EVPN information for CR-LRP Port with IPs %s. "
|
||||
"No need to withdraw it.", ips)
|
||||
return
|
||||
|
||||
LOG.info("Delete BGP route for CR-LRP Port %s on VNI %s", ips,
|
||||
evpn_vni)
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
cr_lrp_datapath)
|
||||
|
||||
if vlan_tag:
|
||||
self._disconnect_evpn_to_ovn(evpn_vni, datapath_bridge, ips,
|
||||
vlan_tag=vlan_tag)
|
||||
else:
|
||||
cr_lrps_on_same_provider = [
|
||||
p for p in self.ovn_local_cr_lrps.values()
|
||||
if p['provider_datapath'] == cr_lrp_datapath]
|
||||
if (len(cr_lrps_on_same_provider) > 1):
|
||||
# NOTE: no need to remove the NDP proxy if there are other
|
||||
# cr-lrp ports on the same chassis connected to the same
|
||||
# provider flat network
|
||||
self._disconnect_evpn_to_ovn(evpn_vni, datapath_bridge, ips,
|
||||
cleanup_ndp_proxy=False)
|
||||
else:
|
||||
self._disconnect_evpn_to_ovn(evpn_vni, datapath_bridge, ips)
|
||||
|
||||
self._remove_evpn_devices(evpn_vni)
|
||||
ovs.remove_evpn_router_ovs_flows(datapath_bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE,
|
||||
cr_lrp_info.get('mac'))
|
||||
|
||||
evpn_info = {'vni': evpn_vni, 'bgp_as': cr_lrp_info.get('bgp_as')}
|
||||
frr.vrf_reconfigure(evpn_info, action="del-vrf")
|
||||
|
||||
try:
|
||||
del self.ovn_local_cr_lrps[cr_lrp_port_name]
|
||||
except KeyError:
|
||||
LOG.debug("Gateway port already cleanup from the agent: %s",
|
||||
cr_lrp_port_name)
|
||||
|
||||
@lockutils.synchronized('evpn')
|
||||
def expose_remote_ip(self, ips, row):
|
||||
if self.sb_idl.is_provider_network(row.datapath):
|
||||
return
|
||||
port_lrp = self.sb_idl.get_lrp_port_for_datapath(row.datapath)
|
||||
if port_lrp in self.ovn_local_lrps.keys():
|
||||
evpn_info = self.sb_idl.get_evpn_info_from_lrp_port_name(port_lrp)
|
||||
if not evpn_info:
|
||||
LOG.debug("No EVPN information for LRP Port %s. "
|
||||
"Not exposing IPs: %s.", port_lrp, ips)
|
||||
return
|
||||
LOG.info("Add BGP route for tenant IP %s on chassis %s",
|
||||
ips, self.chassis)
|
||||
lo_name = constants.OVN_EVPN_LO_PREFIX + str(evpn_info['vni'])
|
||||
linux_net.add_ips_to_dev(
|
||||
lo_name, ips, clear_local_route_at_table=evpn_info['vni'])
|
||||
self._ovn_exposed_evpn_ips.setdefault(
|
||||
lo_name, []).extend(ips)
|
||||
|
||||
@lockutils.synchronized('evpn')
|
||||
def withdraw_remote_ip(self, ips, row):
|
||||
if self.sb_idl.is_provider_network(row.datapath):
|
||||
return
|
||||
port_lrp = self.sb_idl.get_lrp_port_for_datapath(row.datapath)
|
||||
if port_lrp in self.ovn_local_lrps.keys():
|
||||
evpn_info = self.sb_idl.get_evpn_info_from_lrp_port_name(port_lrp)
|
||||
if not evpn_info:
|
||||
LOG.debug("No EVPN information for LRP Port %s. "
|
||||
"Not withdrawing IPs: %s.", port_lrp, ips)
|
||||
return
|
||||
LOG.info("Delete BGP route for tenant IP %s on chassis %s",
|
||||
ips, self.chassis)
|
||||
lo_name = constants.OVN_EVPN_LO_PREFIX + str(evpn_info['vni'])
|
||||
linux_net.del_ips_from_dev(lo_name, ips)
|
||||
|
||||
@lockutils.synchronized('evpn')
|
||||
def expose_subnet(self, row):
|
||||
evpn_info = self.sb_idl.get_evpn_info_from_port(row)
|
||||
ip = self.sb_idl.get_ip_from_port_peer(row)
|
||||
if not evpn_info:
|
||||
LOG.debug("No EVPN information for LRP Port %s. "
|
||||
"Not exposing IPs: %s.", row.logical_port, ip)
|
||||
return
|
||||
|
||||
lrp_logical_port = 'lrp-' + row.logical_port
|
||||
lrp_datapath = self.sb_idl.get_port_datapath(lrp_logical_port)
|
||||
|
||||
cr_lrp = self.sb_idl.is_router_gateway_on_chassis(lrp_datapath,
|
||||
self.chassis)
|
||||
if not cr_lrp:
|
||||
return
|
||||
|
||||
LOG.info("Add IP Routes for network %s on chassis %s", ip,
|
||||
self.chassis)
|
||||
self.ovn_local_lrps[lrp_logical_port] = {
|
||||
'datapath': lrp_datapath,
|
||||
'ip': ip
|
||||
}
|
||||
|
||||
cr_lrp_info = self.ovn_local_cr_lrps.get(cr_lrp, {})
|
||||
cr_lrp_datapath = cr_lrp_info.get('provider_datapath')
|
||||
if not cr_lrp_datapath:
|
||||
LOG.info("Subnet not connected to the provider network. "
|
||||
"No need to expose it through EVPN")
|
||||
return
|
||||
if (evpn_info['bgp_as'] != cr_lrp_info.get('bgp_as') or
|
||||
evpn_info['vni'] != cr_lrp_info.get('vni')):
|
||||
LOG.error("EVPN information at router port (vni: %s, as: %s) does"
|
||||
" not match with information at subnet gateway port:"
|
||||
" %s", cr_lrp_info.get('vni'),
|
||||
cr_lrp_info.get('bgp_as'), evpn_info)
|
||||
return
|
||||
|
||||
cr_lrp_ips = [ip_address.split('/')[0]
|
||||
for ip_address in cr_lrp_info.get('ips', [])]
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
cr_lrp_datapath)
|
||||
|
||||
ip_version = linux_net.get_ip_version(ip)
|
||||
for cr_lrp_ip in cr_lrp_ips:
|
||||
if linux_net.get_ip_version(cr_lrp_ip) == ip_version:
|
||||
linux_net.add_ip_route(
|
||||
self._ovn_routing_tables_routes,
|
||||
ip.split("/")[0],
|
||||
evpn_info['vni'],
|
||||
datapath_bridge,
|
||||
vlan=vlan_tag,
|
||||
mask=ip.split("/")[1],
|
||||
via=cr_lrp_ip)
|
||||
break
|
||||
|
||||
if ip_version == constants.IP_VERSION_6:
|
||||
net_ip = '{}'.format(ipaddress.IPv6Network(
|
||||
ip, strict=False))
|
||||
else:
|
||||
net_ip = '{}'.format(ipaddress.IPv4Network(
|
||||
ip, strict=False))
|
||||
|
||||
strip_vlan = False
|
||||
if vlan_tag:
|
||||
strip_vlan = True
|
||||
ovs.ensure_evpn_ovs_flow(datapath_bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE,
|
||||
cr_lrp_info['mac'],
|
||||
cr_lrp_info['vrf'],
|
||||
net_ip,
|
||||
strip_vlan)
|
||||
|
||||
# Check if there are VMs on the network
|
||||
# and if so expose the route
|
||||
network_port_datapath = row.datapath
|
||||
if not network_port_datapath:
|
||||
return
|
||||
ports = self.sb_idl.get_ports_on_datapath(
|
||||
network_port_datapath)
|
||||
for port in ports:
|
||||
if (port.type not in (constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE)):
|
||||
continue
|
||||
try:
|
||||
port_ips = [port.mac[0].split(' ')[1]]
|
||||
except IndexError:
|
||||
continue
|
||||
if len(port.mac[0].split(' ')) == 3:
|
||||
port_ips.append(port.mac[0].split(' ')[2])
|
||||
|
||||
for port_ip in port_ips:
|
||||
# Only adding the port ips that match the lrp
|
||||
# IP version
|
||||
port_ip_version = linux_net.get_ip_version(port_ip)
|
||||
if port_ip_version == ip_version:
|
||||
linux_net.add_ips_to_dev(
|
||||
cr_lrp_info['lo'], [port_ip],
|
||||
clear_local_route_at_table=evpn_info['vni'])
|
||||
self._ovn_exposed_evpn_ips.setdefault(
|
||||
cr_lrp_info['lo'], []).extend([port_ip])
|
||||
|
||||
@lockutils.synchronized('evpn')
|
||||
def withdraw_subnet(self, row):
|
||||
lrp_logical_port = 'lrp-' + row.logical_port
|
||||
lrp_datapath = self.ovn_local_lrps.get(lrp_logical_port, {}).get(
|
||||
'datapath')
|
||||
ip = self.ovn_local_lrps.get(lrp_logical_port, {}).get('ip')
|
||||
if not lrp_datapath:
|
||||
return
|
||||
|
||||
cr_lrp = self.sb_idl.is_router_gateway_on_chassis(lrp_datapath,
|
||||
self.chassis)
|
||||
if not cr_lrp:
|
||||
return
|
||||
|
||||
LOG.info("Delete IP Routes for network %s on chassis %s", ip,
|
||||
self.chassis)
|
||||
|
||||
cr_lrp_info = self.ovn_local_cr_lrps.get(cr_lrp, {})
|
||||
cr_lrp_datapath = cr_lrp_info.get('provider_datapath')
|
||||
if not cr_lrp_datapath:
|
||||
LOG.info("Subnet not connected to the provider network. "
|
||||
"No need to withdraw it from EVPN")
|
||||
return
|
||||
cr_lrp_ips = [ip_address.split('/')[0]
|
||||
for ip_address in cr_lrp_info.get('ips', [])]
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
cr_lrp_datapath)
|
||||
|
||||
ip_version = linux_net.get_ip_version(ip)
|
||||
for cr_lrp_ip in cr_lrp_ips:
|
||||
if linux_net.get_ip_version(cr_lrp_ip) == ip_version:
|
||||
linux_net.del_ip_route(
|
||||
self._ovn_routing_tables_routes,
|
||||
ip.split("/")[0],
|
||||
cr_lrp_info['vni'],
|
||||
datapath_bridge,
|
||||
vlan=vlan_tag,
|
||||
mask=ip.split("/")[1],
|
||||
via=cr_lrp_ip)
|
||||
if (linux_net.get_ip_version(cr_lrp_ip) ==
|
||||
constants.IP_VERSION_6):
|
||||
net = ipaddress.IPv6Network(ip, strict=False)
|
||||
else:
|
||||
net = ipaddress.IPv4Network(ip, strict=False)
|
||||
break
|
||||
|
||||
ovs.remove_evpn_network_ovs_flow(datapath_bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE,
|
||||
cr_lrp_info['mac'],
|
||||
'{}'.format(net))
|
||||
|
||||
# Check if there are VMs on the network
|
||||
# and if so withdraw the routes
|
||||
vms_on_net = linux_net.get_exposed_ips_on_network(
|
||||
cr_lrp_info['lo'], net)
|
||||
linux_net.delete_exposed_ips(vms_on_net,
|
||||
cr_lrp_info['lo'])
|
||||
|
||||
try:
|
||||
del self.ovn_local_lrps[lrp_logical_port]
|
||||
except KeyError:
|
||||
LOG.debug("Router Interface port already cleanup from the agent "
|
||||
"%s", lrp_logical_port)
|
||||
|
||||
def _ensure_evpn_devices(self, vni):
|
||||
# ensure vrf device.
|
||||
# NOTE: It uses vni id as table number
|
||||
vrf_name = constants.OVN_EVPN_VRF_PREFIX + str(vni)
|
||||
linux_net.ensure_vrf(vrf_name, vni)
|
||||
|
||||
# ensure bridge device
|
||||
bridge_name = constants.OVN_EVPN_BRIDGE_PREFIX + str(vni)
|
||||
linux_net.ensure_bridge(bridge_name)
|
||||
# connect bridge to vrf
|
||||
linux_net.set_master_for_device(bridge_name, vrf_name)
|
||||
|
||||
# ensure vxlan device
|
||||
vxlan_name = constants.OVN_EVPN_VXLAN_PREFIX + str(vni)
|
||||
# NOTE: assuming only 1 IP on the loopback device with /32 prefix
|
||||
lo_ip = linux_net.get_nic_ip('lo',
|
||||
ip_version=constants.IP_VERSION_4)[0]
|
||||
if not lo_ip:
|
||||
LOG.error("Loopback IP must have a /32 IP associated for the "
|
||||
"EVPN local ip")
|
||||
return None, None
|
||||
linux_net.ensure_vxlan(vxlan_name, vni, lo_ip)
|
||||
# connect vxlan to bridge
|
||||
linux_net.set_master_for_device(vxlan_name, bridge_name)
|
||||
|
||||
# ensure dummy lo interface
|
||||
lo_name = constants.OVN_EVPN_LO_PREFIX + str(vni)
|
||||
linux_net.ensure_dummy_device(lo_name)
|
||||
# connect dummy to vrf
|
||||
linux_net.set_master_for_device(lo_name, vrf_name)
|
||||
|
||||
return vrf_name, lo_name, bridge_name, vxlan_name
|
||||
|
||||
def _remove_evpn_devices(self, vni):
|
||||
vrf_name = constants.OVN_EVPN_VRF_PREFIX + str(vni)
|
||||
bridge_name = constants.OVN_EVPN_BRIDGE_PREFIX + str(vni)
|
||||
vxlan_name = constants.OVN_EVPN_VXLAN_PREFIX + str(vni)
|
||||
lo_name = constants.OVN_EVPN_LO_PREFIX + str(vni)
|
||||
|
||||
for device in [lo_name, vrf_name, bridge_name, vxlan_name]:
|
||||
linux_net.delete_device(device)
|
||||
|
||||
def _connect_evpn_to_ovn(self, vrf, ips, datapath_bridge, vni, vlan_tag):
|
||||
# add vrf to ovs bridge
|
||||
ovs.add_device_to_ovs_bridge(vrf, datapath_bridge, vlan_tag)
|
||||
|
||||
if vlan_tag:
|
||||
linux_net.ensure_vlan_device_for_network(datapath_bridge, vlan_tag)
|
||||
# add route for ip to ovs provider bridge (at the vrf routing table)
|
||||
for ip in ips:
|
||||
ip_without_mask = ip.split("/")[0]
|
||||
linux_net.add_ip_route(
|
||||
self._ovn_routing_tables_routes, ip_without_mask,
|
||||
vni, datapath_bridge, vlan=vlan_tag)
|
||||
|
||||
# add proxy ndp config for ipv6
|
||||
if (linux_net.get_ip_version(ip_without_mask) ==
|
||||
constants.IP_VERSION_6):
|
||||
linux_net.add_ndp_proxy(ip, datapath_bridge, vlan=vlan_tag)
|
||||
|
||||
# add unreachable route to vrf
|
||||
linux_net.add_unreachable_route(vrf)
|
||||
|
||||
def _disconnect_evpn_to_ovn(self, vni, datapath_bridge, ips,
|
||||
vlan_tag=None, cleanup_ndp_proxy=True):
|
||||
vrf = constants.OVN_EVPN_VRF_PREFIX + str(vni)
|
||||
# remove vrf from ovs bridge
|
||||
ovs.del_device_from_ovs_bridge(vrf, datapath_bridge)
|
||||
|
||||
linux_net.delete_routes_from_table(vni)
|
||||
|
||||
if vlan_tag:
|
||||
linux_net.delete_vlan_device_for_network(datapath_bridge,
|
||||
vlan_tag)
|
||||
elif cleanup_ndp_proxy:
|
||||
for ip in ips:
|
||||
if linux_net.get_ip_version(ip) == constants.IP_VERSION_6:
|
||||
linux_net.del_ndp_proxy(ip, datapath_bridge)
|
||||
|
||||
def _remove_extra_vrfs(self):
|
||||
vrfs, los, bridges, vxlans = ([], [], [], [])
|
||||
for cr_lrp_info in self.ovn_local_cr_lrps.values():
|
||||
vrfs.append(cr_lrp_info['vrf'])
|
||||
los.append(cr_lrp_info['lo'])
|
||||
bridges.append(cr_lrp_info['bridge'])
|
||||
vxlans.append(cr_lrp_info['vxlan'])
|
||||
|
||||
filter_out = ["{}.{}".format(key, value[0]['vlan'])
|
||||
for key, value in self._ovn_routing_tables_routes.items()
|
||||
if value[0]['vlan']]
|
||||
|
||||
interfaces = linux_net.get_interfaces(filter_out)
|
||||
for interface in interfaces:
|
||||
if (interface.startswith(constants.OVN_EVPN_VRF_PREFIX) and
|
||||
interface not in vrfs):
|
||||
linux_net.delete_device(interface)
|
||||
ovs.del_device_from_ovs_bridge(interface)
|
||||
elif (interface.startswith(constants.OVN_EVPN_LO_PREFIX) and
|
||||
interface not in los):
|
||||
linux_net.delete_device(interface)
|
||||
elif (interface.startswith(constants.OVN_EVPN_BRIDGE_PREFIX) and
|
||||
(interface not in bridges and
|
||||
interface != constants.OVN_INTEGRATION_BRIDGE and
|
||||
interface not in set(self.ovn_bridge_mappings.values()))):
|
||||
linux_net.delete_device(interface)
|
||||
elif (interface.startswith(constants.OVN_EVPN_VXLAN_PREFIX) and
|
||||
interface not in vxlans):
|
||||
linux_net.delete_device(interface)
|
||||
|
||||
def _remove_extra_routes(self):
|
||||
table_ids = self._get_table_ids()
|
||||
vrf_routes = linux_net.get_routes_on_tables(table_ids)
|
||||
if not vrf_routes:
|
||||
return
|
||||
# remove from vrf_routes the routes that should be kept
|
||||
for bridge, routes_info in self._ovn_routing_tables_routes.items():
|
||||
for route_info in routes_info:
|
||||
oif = linux_net.get_interface_index(bridge)
|
||||
if route_info['vlan']:
|
||||
vlan_device_name = '{}.{}'.format(bridge,
|
||||
route_info['vlan'])
|
||||
oif = linux_net.get_interface_index(vlan_device_name)
|
||||
if 'gateway' in route_info['route'].keys(): # subnet route
|
||||
possible_matchings = [
|
||||
r for r in vrf_routes
|
||||
if (r['dst'] == route_info['route']['dst'] and
|
||||
r['dst_len'] == route_info['route']['dst_len'] and
|
||||
r['gateway'] == route_info['route']['gateway'] and
|
||||
r['table'] == route_info['route']['table'])]
|
||||
else: # cr-lrp
|
||||
possible_matchings = [
|
||||
r for r in vrf_routes
|
||||
if (r['dst'] == route_info['route']['dst'] and
|
||||
r['dst_len'] == route_info['route']['dst_len'] and
|
||||
r['oif'] == oif and
|
||||
r['table'] == route_info['route']['table'])]
|
||||
for r in possible_matchings:
|
||||
vrf_routes.remove(r)
|
||||
|
||||
linux_net.delete_ip_routes(vrf_routes)
|
||||
|
||||
def _remove_extra_ovs_flows(self):
|
||||
cr_lrp_mac_vrf_mappings = self._get_cr_lrp_mac_vrf_mapping()
|
||||
for bridge in set(self.ovn_bridge_mappings.values()):
|
||||
current_flows = ovs.get_bridge_flows_by_cookie(
|
||||
bridge, constants.OVS_VRF_RULE_COOKIE)
|
||||
for flow in current_flows:
|
||||
flow_info = ovs.get_flow_info(flow)
|
||||
if not flow_info.get('mac'):
|
||||
ovs.del_flow(flow, bridge, constants.OVS_VRF_RULE_COOKIE)
|
||||
elif flow_info['mac'] not in cr_lrp_mac_vrf_mappings.keys():
|
||||
ovs.del_flow(flow, bridge, constants.OVS_VRF_RULE_COOKIE)
|
||||
elif flow_info['port']:
|
||||
if (not flow_info.get('nw_src') and not
|
||||
flow_info.get('ipv6_src')):
|
||||
ovs.del_flow(flow, bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE)
|
||||
else:
|
||||
device = cr_lrp_mac_vrf_mappings[flow_info['mac']]
|
||||
vrf_port = ovs.get_device_port_at_ovs(device)
|
||||
if vrf_port != flow_info['port']:
|
||||
ovs.del_flow(flow, bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE)
|
||||
nw_src_ip = nw_src_mask = None
|
||||
matching_dst = False
|
||||
if flow_info.get('nw_src'):
|
||||
nw_src_ip = flow_info['nw_src'].split('/')[0]
|
||||
nw_src_mask = int(
|
||||
flow_info['nw_src'].split('/')[1])
|
||||
elif flow_info.get('ipv6_src'):
|
||||
nw_src_ip = flow_info['ipv6_src'].split('/')[0]
|
||||
nw_src_mask = int(
|
||||
flow_info['ipv6_src'].split('/')[1])
|
||||
|
||||
for route_info in self._ovn_routing_tables_routes[
|
||||
bridge]:
|
||||
if (route_info['route']['dst'] == nw_src_ip and
|
||||
route_info['route'][
|
||||
'dst_len'] == nw_src_mask):
|
||||
matching_dst = True
|
||||
if not matching_dst:
|
||||
ovs.del_flow(flow, bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE)
|
||||
|
||||
def _remove_extra_exposed_ips(self):
|
||||
for lo, ips in self._ovn_exposed_evpn_ips.items():
|
||||
exposed_ips_on_device = linux_net.get_exposed_ips(lo)
|
||||
for ip in exposed_ips_on_device:
|
||||
if ip not in ips:
|
||||
linux_net.del_ips_from_dev(lo, [ip])
|
||||
|
||||
def _get_table_ids(self):
|
||||
table_ids = []
|
||||
for cr_lrp_info in self.ovn_local_cr_lrps.values():
|
||||
table_ids.append(cr_lrp_info['vni'])
|
||||
return table_ids
|
||||
|
||||
def _get_cr_lrp_mac_vrf_mapping(self):
|
||||
mac_vrf_mappings = {}
|
||||
for cr_lrp_info in self.ovn_local_cr_lrps.values():
|
||||
mac_vrf_mappings[cr_lrp_info['mac']] = cr_lrp_info['vrf']
|
||||
return mac_vrf_mappings
|
@ -133,11 +133,13 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
|
||||
|
||||
def is_provider_network(self, datapath):
|
||||
cmd = self.db_find_rows('Port_Binding', ('datapath', '=', datapath),
|
||||
('type', '=', 'localnet'))
|
||||
('type', '=',
|
||||
constants.OVN_LOCALNET_VIF_PORT_TYPE))
|
||||
return next(iter(cmd.execute(check_error=True)), None)
|
||||
|
||||
def get_fip_associated(self, port):
|
||||
cmd = self.db_find_rows('Port_Binding', ('type', '=', 'patch'))
|
||||
cmd = self.db_find_rows(
|
||||
'Port_Binding', ('type', '=', constants.OVN_PATCH_VIF_PORT_TYPE))
|
||||
for row in cmd.execute(check_error=True):
|
||||
for fip in row.nat_addresses:
|
||||
if port in fip:
|
||||
|
@ -12,12 +12,14 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from ovn_bgp_agent import constants
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_log import log as logging
|
||||
from ovsdbapp.backend.ovs_idl import event as row_event
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from ovn_bgp_agent import constants
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
_SYNC_STATE_LOCK = lockutils.ReaderWriterLock()
|
||||
|
||||
|
||||
@ -29,6 +31,9 @@ class PortBindingChassisEvent(row_event.RowEvent):
|
||||
events, table, None)
|
||||
self.event_name = self.__class__.__name__
|
||||
|
||||
def _check_single_dual_stack_format(mac):
|
||||
return len(mac.split(' ')) in [2, 3]
|
||||
|
||||
|
||||
class PortBindingChassisCreatedEvent(PortBindingChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
@ -39,8 +44,7 @@ class PortBindingChassisCreatedEvent(PortBindingChassisEvent):
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if (len(row.mac[0].split(' ')) != 2 and
|
||||
len(row.mac[0].split(' ')) != 3):
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
return (row.chassis[0].name == self.agent.chassis and
|
||||
not old.chassis)
|
||||
@ -67,8 +71,7 @@ class PortBindingChassisDeletedEvent(PortBindingChassisEvent):
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if (len(row.mac[0].split(' ')) != 2 and
|
||||
len(row.mac[0].split(' ')) != 3):
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
if event == self.ROW_UPDATE:
|
||||
return (old.chassis[0].name == self.agent.chassis and
|
||||
@ -105,7 +108,7 @@ class FIPSetEvent(PortBindingChassisEvent):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type != 'patch':
|
||||
if row.type != constants.OVN_PATCH_VIF_PORT_TYPE:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
for nat in row.nat_addresses:
|
||||
@ -130,7 +133,7 @@ class FIPUnsetEvent(PortBindingChassisEvent):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type != 'patch':
|
||||
if row.type != constants.OVN_PATCH_VIF_PORT_TYPE:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
for nat in old.nat_addresses:
|
||||
@ -149,15 +152,14 @@ class SubnetRouterAttachedEvent(PortBindingChassisEvent):
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if (len(row.mac[0].split(' ')) != 2 and
|
||||
len(row.mac[0].split(' ')) != 3):
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
return (not row.chassis and row.logical_port.startswith('lrp-'))
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type != 'patch':
|
||||
if row.type != constants.OVN_PATCH_VIF_PORT_TYPE:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
ip_address = row.mac[0].split(' ')[1]
|
||||
@ -173,15 +175,14 @@ class SubnetRouterDetachedEvent(PortBindingChassisEvent):
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if (len(row.mac[0].split(' ')) != 2 and
|
||||
len(row.mac[0].split(' ')) != 3):
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
return (not row.chassis and row.logical_port.startswith('lrp-'))
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type != 'patch':
|
||||
if row.type != constants.OVN_PATCH_VIF_PORT_TYPE:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
ip_address = row.mac[0].split(' ')[1]
|
||||
@ -197,8 +198,7 @@ class TenantPortCreatedEvent(PortBindingChassisEvent):
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if (len(row.mac[0].split(' ')) != 2 and
|
||||
len(row.mac[0].split(' ')) != 3):
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
return (not old.chassis and
|
||||
self.agent.ovn_local_lrps != [])
|
||||
@ -226,8 +226,7 @@ class TenantPortDeletedEvent(PortBindingChassisEvent):
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if (len(row.mac[0].split(' ')) != 2 and
|
||||
len(row.mac[0].split(' ')) != 3):
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
return (self.agent.ovn_local_lrps != [])
|
||||
except (IndexError, AttributeError):
|
||||
@ -260,7 +259,7 @@ class ChassisCreateEventBase(row_event.RowEvent):
|
||||
if self.first_time:
|
||||
self.first_time = False
|
||||
else:
|
||||
print("Connection to OVSDB established, doing a full sync")
|
||||
LOG.info("Connection to OVSDB established, doing a full sync")
|
||||
self.agent.sync()
|
||||
|
||||
|
||||
|
246
ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py
Normal file
246
ovn_bgp_agent/drivers/openstack/watchers/evpn_watcher.py
Normal file
@ -0,0 +1,246 @@
|
||||
# Copyright 2021 Red Hat, Inc.
|
||||
#
|
||||
# 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_concurrency import lockutils
|
||||
from oslo_log import log as logging
|
||||
from ovsdbapp.backend.ovs_idl import event as row_event
|
||||
|
||||
from ovn_bgp_agent import constants
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_SYNC_STATE_LOCK = lockutils.ReaderWriterLock()
|
||||
|
||||
|
||||
class PortBindingChassisEvent(row_event.RowEvent):
|
||||
def __init__(self, bgp_agent, events):
|
||||
self.agent = bgp_agent
|
||||
table = 'Port_Binding'
|
||||
super(PortBindingChassisEvent, self).__init__(
|
||||
events, table, None)
|
||||
self.event_name = self.__class__.__name__
|
||||
|
||||
def _check_single_dual_stack_format(mac):
|
||||
return len(mac.split(' ')) in [2, 3]
|
||||
|
||||
|
||||
class PortBindingChassisCreatedEvent(PortBindingChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE,)
|
||||
super(PortBindingChassisCreatedEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
if row.type != constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE:
|
||||
return False
|
||||
# single and dual-stack format
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
return (row.chassis[0].name == self.agent.chassis and
|
||||
not old.chassis)
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type != constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
self.agent.expose_ip(row, cr_lrp=True)
|
||||
|
||||
|
||||
class PortBindingChassisDeletedEvent(PortBindingChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE, self.ROW_DELETE,)
|
||||
super(PortBindingChassisDeletedEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
if row.type != constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE:
|
||||
return False
|
||||
# single and dual-stack format
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
if event == self.ROW_UPDATE:
|
||||
return (old.chassis[0].name == self.agent.chassis and
|
||||
not row.chassis)
|
||||
else:
|
||||
if row.chassis[0].name == self.agent.chassis:
|
||||
return True
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type != constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
self.agent.withdraw_ip(row, cr_lrp=True)
|
||||
|
||||
|
||||
class SubnetRouterAttachedEvent(PortBindingChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE, self.ROW_CREATE,)
|
||||
super(SubnetRouterAttachedEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
if event == self.ROW_UPDATE:
|
||||
return (not row.chassis and
|
||||
not row.logical_port.startswith('lrp-') and
|
||||
row.external_ids[constants.OVN_EVPN_VNI_EXT_ID_KEY] and
|
||||
row.external_ids[constants.OVN_EVPN_AS_EXT_ID_KEY] and
|
||||
(not old.external_ids.get(
|
||||
constants.OVN_EVPN_VNI_EXT_ID_KEY) or
|
||||
not old.external_ids.get(
|
||||
constants.constants.OVN_EVPN_AS_EXT_ID_KEY)))
|
||||
else:
|
||||
return (not row.chassis and
|
||||
not row.logical_port.startswith('lrp-') and
|
||||
row.external_ids[constants.OVN_EVPN_VNI_EXT_ID_KEY] and
|
||||
row.external_ids[constants.OVN_EVPN_AS_EXT_ID_KEY])
|
||||
except (IndexError, AttributeError, KeyError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type != constants.OVN_PATCH_VIF_PORT_TYPE:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
if row.nat_addresses:
|
||||
self.agent.expose_ip(row)
|
||||
else:
|
||||
self.agent.expose_subnet(row)
|
||||
|
||||
|
||||
class SubnetRouterDetachedEvent(PortBindingChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE, self.ROW_DELETE,)
|
||||
super(SubnetRouterDetachedEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
if event == self.ROW_UPDATE:
|
||||
return (not row.chassis and
|
||||
not row.logical_port.startswith('lrp-') and
|
||||
old.external_ids[constants.OVN_EVPN_VNI_EXT_ID_KEY] and
|
||||
old.external_ids[constants.OVN_EVPN_AS_EXT_ID_KEY] and
|
||||
(not row.external_ids.get(
|
||||
constants.OVN_EVPN_VNI_EXT_ID_KEY) or
|
||||
not row.external_ids.get(
|
||||
constants.OVN_EVPN_AS_EXT_ID_KEY)))
|
||||
else:
|
||||
return (not row.chassis and
|
||||
not row.logical_port.startswith('lrp-') and
|
||||
row.external_ids[constants.OVN_EVPN_VNI_EXT_ID_KEY] and
|
||||
row.external_ids[constants.OVN_EVPN_AS_EXT_ID_KEY])
|
||||
except (IndexError, AttributeError, KeyError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type != constants.OVN_PATCH_VIF_PORT_TYPE:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
if row.nat_addresses:
|
||||
self.agent.withdraw_ip(row)
|
||||
else:
|
||||
self.agent.withdraw_subnet(row)
|
||||
|
||||
|
||||
class TenantPortCreatedEvent(PortBindingChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE,)
|
||||
super(TenantPortCreatedEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
return (not old.chassis and row.chassis and
|
||||
self.agent.ovn_local_lrps != [])
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type not in (constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE):
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
ips = [row.mac[0].split(' ')[1]]
|
||||
# for dual-stack
|
||||
if len(row.mac[0].split(' ')) == 3:
|
||||
ips.append(row.mac[0].split(' ')[2])
|
||||
self.agent.expose_remote_ip(ips, row)
|
||||
|
||||
|
||||
class TenantPortDeletedEvent(PortBindingChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_DELETE, self.ROW_UPDATE,)
|
||||
super(TenantPortDeletedEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if not self._check_single_dual_stack_format(row.mac[0]):
|
||||
return False
|
||||
if event == self.ROW_UPDATE:
|
||||
return (old.chassis and not row.chassis and
|
||||
self.agent.ovn_local_lrps != [])
|
||||
else:
|
||||
return (self.agent.ovn_local_lrps != [])
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type not in (constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE):
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
ips = [row.mac[0].split(' ')[1]]
|
||||
# for dual-stack
|
||||
if len(row.mac[0].split(' ')) == 3:
|
||||
ips.append(row.mac[0].split(' ')[2])
|
||||
self.agent.withdraw_remote_ip(ips, row)
|
||||
|
||||
|
||||
class ChassisCreateEventBase(row_event.RowEvent):
|
||||
table = None
|
||||
|
||||
def __init__(self, bgp_agent):
|
||||
self.agent = bgp_agent
|
||||
self.first_time = True
|
||||
events = (self.ROW_CREATE,)
|
||||
super(ChassisCreateEventBase, self).__init__(
|
||||
events, self.table, (('name', '=', self.agent.chassis),))
|
||||
self.event_name = self.__class__.__name__
|
||||
|
||||
def run(self, event, row, old):
|
||||
if self.first_time:
|
||||
self.first_time = False
|
||||
else:
|
||||
LOG.info("Connection to OVSDB established, doing a full sync")
|
||||
self.agent.sync()
|
||||
|
||||
|
||||
class ChassisCreateEvent(ChassisCreateEventBase):
|
||||
table = 'Chassis'
|
||||
|
||||
|
||||
class ChassisPrivateCreateEvent(ChassisCreateEventBase):
|
||||
table = 'Chassis_Private'
|
@ -29,4 +29,5 @@ console_scripts =
|
||||
bgp-agent = ovn_bgp_agent.cmd.agent:start
|
||||
|
||||
ovn_bgp_agent.drivers =
|
||||
osp_ovn_bgp_driver = ovn_bgp_agent.drivers.openstack.ovn_bgp_driver:OSPOVNBGPDriver
|
||||
ovn_bgp_driver = ovn_bgp_agent.drivers.openstack.ovn_bgp_driver:OVNBGPDriver
|
||||
ovn_evpn_driver = ovn_bgp_agent.drivers.openstack.ovn_evpn_driver:OVNEVPNDriver
|
||||
|
Loading…
Reference in New Issue
Block a user