1086 lines
48 KiB
Python
1086 lines
48 KiB
Python
# 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
|
|
import pyroute2
|
|
import threading
|
|
|
|
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 driver_utils
|
|
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 bgp_watcher as watcher
|
|
from ovn_bgp_agent import exceptions as agent_exc
|
|
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"]
|
|
|
|
|
|
class OVNBGPDriver(driver_api.AgentDriverBase):
|
|
|
|
def __init__(self):
|
|
self._expose_tenant_networks = (CONF.expose_tenant_networks or
|
|
CONF.expose_ipv6_gua_tenant_networks)
|
|
self.allowed_address_scopes = set(CONF.address_scopes or [])
|
|
self.ovn_routing_tables = {} # {'br-ex': 200}
|
|
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()
|
|
# {ovn_lb: VIP1, VIP2}
|
|
self.ovn_lb_vips = collections.defaultdict()
|
|
|
|
self._sb_idl = None
|
|
self._post_fork_event = threading.Event()
|
|
|
|
@property
|
|
def sb_idl(self):
|
|
if not self._sb_idl:
|
|
self._post_fork_event.wait()
|
|
return self._sb_idl
|
|
|
|
@sb_idl.setter
|
|
def sb_idl(self, val):
|
|
self._sb_idl = val
|
|
|
|
def start(self):
|
|
self.ovs_idl = ovs.OvsIdl()
|
|
self.ovs_idl.start(CONF.ovsdb_connection)
|
|
self.chassis = self.ovs_idl.get_own_chassis_name()
|
|
self.ovn_remote = self.ovs_idl.get_ovn_remote()
|
|
LOG.info("Loaded chassis %s.", self.chassis)
|
|
|
|
LOG.info("Starting VRF configuration for advertising routes")
|
|
# Create VRF
|
|
linux_net.ensure_vrf(CONF.bgp_vrf, CONF.bgp_vrf_table_id)
|
|
|
|
# Ensure FRR is configure to leak the routes
|
|
# NOTE: If we want to recheck this every X time, we should move it
|
|
# inside the sync function instead
|
|
frr.vrf_leak(CONF.bgp_vrf, CONF.bgp_AS, CONF.bgp_router_id)
|
|
|
|
# Create OVN dummy device
|
|
linux_net.ensure_ovn_device(CONF.bgp_nic, CONF.bgp_vrf)
|
|
|
|
# Clear vrf routing table
|
|
if CONF.clear_vrf_routes_on_startup:
|
|
linux_net.delete_routes_from_table(CONF.bgp_vrf_table_id)
|
|
|
|
LOG.info("VRF configuration for advertising routes completed")
|
|
if self._expose_tenant_networks and self.allowed_address_scopes:
|
|
LOG.info("Configured allowed address scopes: %s",
|
|
", ".join(self.allowed_address_scopes))
|
|
|
|
events = ()
|
|
for event in self._get_events():
|
|
event_class = getattr(watcher, event)
|
|
events += (event_class(self),)
|
|
|
|
self._post_fork_event.clear()
|
|
# TODO(lucasagomes): The OVN package in the ubuntu LTS is old
|
|
# and does not support Chassis_Private. Once the package is updated
|
|
# we can remove this fallback mode.
|
|
try:
|
|
try:
|
|
self.sb_idl = ovn.OvnSbIdl(
|
|
self.ovn_remote,
|
|
chassis=self.chassis,
|
|
tables=OVN_TABLES + ["Chassis_Private",
|
|
"Logical_DP_Group"],
|
|
events=events).start()
|
|
except AssertionError:
|
|
self.sb_idl = ovn.OvnSbIdl(
|
|
self.ovn_remote,
|
|
chassis=self.chassis,
|
|
tables=OVN_TABLES + ["Chassis_Private"],
|
|
events=events).start()
|
|
except AssertionError:
|
|
self.sb_idl = ovn.OvnSbIdl(
|
|
self.ovn_remote,
|
|
chassis=self.chassis,
|
|
tables=OVN_TABLES,
|
|
events=events).start()
|
|
|
|
# Now IDL connections can be safely used
|
|
self._post_fork_event.set()
|
|
|
|
def _get_events(self):
|
|
events = set(["PortBindingChassisCreatedEvent",
|
|
"PortBindingChassisDeletedEvent",
|
|
"FIPSetEvent",
|
|
"FIPUnsetEvent",
|
|
"OVNLBVIPPortEvent",
|
|
"ChassisCreateEvent"])
|
|
if self._expose_tenant_networks:
|
|
events.update(["SubnetRouterAttachedEvent",
|
|
"SubnetRouterDetachedEvent",
|
|
"TenantPortCreatedEvent",
|
|
"TenantPortDeletedEvent"])
|
|
return events
|
|
|
|
@lockutils.synchronized('bgp')
|
|
def sync(self):
|
|
self._expose_tenant_networks = (CONF.expose_tenant_networks or
|
|
CONF.expose_ipv6_gua_tenant_networks)
|
|
self.ovn_local_cr_lrps = {}
|
|
self.ovn_local_lrps = {}
|
|
self.ovn_routing_tables_routes = collections.defaultdict()
|
|
self.ovn_lb_vips = collections.defaultdict()
|
|
|
|
LOG.debug("Ensuring VRF configuration for advertising routes")
|
|
# Create VRF
|
|
linux_net.ensure_vrf(CONF.bgp_vrf,
|
|
CONF.bgp_vrf_table_id)
|
|
# Create OVN dummy device
|
|
linux_net.ensure_ovn_device(CONF.bgp_nic,
|
|
CONF.bgp_vrf)
|
|
|
|
LOG.debug("Configuring br-ex default rule and routing tables for "
|
|
"each provider network")
|
|
flows_info = {}
|
|
# 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
|
|
extra_routes = {}
|
|
with pyroute2.NDB() as ndb:
|
|
for bridge_index, bridge_mapping in enumerate(bridge_mappings, 1):
|
|
network = bridge_mapping.split(":")[0]
|
|
bridge = bridge_mapping.split(":")[1]
|
|
self.ovn_bridge_mappings[network] = bridge
|
|
|
|
if not extra_routes.get(bridge):
|
|
extra_routes[bridge] = (
|
|
linux_net.ensure_routing_table_for_bridge(
|
|
self.ovn_routing_tables, bridge))
|
|
vlan_tag = self.sb_idl.get_network_vlan_tag_by_network_name(
|
|
network)
|
|
|
|
if vlan_tag:
|
|
vlan_tag = vlan_tag[0]
|
|
linux_net.ensure_vlan_device_for_network(bridge,
|
|
vlan_tag)
|
|
|
|
linux_net.ensure_arp_ndp_enabled_for_bridge(bridge,
|
|
bridge_index,
|
|
vlan_tag)
|
|
|
|
if flows_info.get(bridge):
|
|
continue
|
|
flows_info[bridge] = {
|
|
'mac': ndb.interfaces[bridge]['address'],
|
|
'in_port': set([])}
|
|
# 3) Get in_port for bridge mappings (br-ex, br-ex2)
|
|
ovs.get_ovs_flows_info(bridge, flows_info,
|
|
constants.OVS_RULE_COOKIE)
|
|
# 4) Add/Remove flows for each bridge mappings
|
|
ovs.remove_extra_ovs_flows(flows_info, constants.OVS_RULE_COOKIE)
|
|
|
|
LOG.debug("Syncing current routes.")
|
|
exposed_ips = linux_net.get_exposed_ips(CONF.bgp_nic)
|
|
# get the rules pointing to ovn bridges
|
|
ovn_ip_rules = linux_net.get_ovn_ip_rules(
|
|
self.ovn_routing_tables.values())
|
|
|
|
# add missing routes/ips for IPs on provider network
|
|
ports = self.sb_idl.get_ports_on_chassis(self.chassis)
|
|
for port in ports:
|
|
self._ensure_port_exposed(port, exposed_ips, ovn_ip_rules)
|
|
|
|
# this information is only available when there are cr-lrps add
|
|
# missing routes/ips for FIPs associated to VMs/LBs on the chassis
|
|
cr_lrp_ports = self.sb_idl.get_cr_lrp_ports_on_chassis(
|
|
self.chassis)
|
|
for cr_lrp_port in cr_lrp_ports:
|
|
self._ensure_cr_lrp_associated_ports_exposed(
|
|
cr_lrp_port, exposed_ips, ovn_ip_rules)
|
|
|
|
for cr_lrp_port, cr_lrp_info in self.ovn_local_cr_lrps.items():
|
|
lrp_ports = self.sb_idl.get_lrp_ports_for_router(
|
|
cr_lrp_info['router_datapath'])
|
|
for lrp in lrp_ports:
|
|
self._process_lrp_port(lrp, cr_lrp_port, exposed_ips,
|
|
ovn_ip_rules)
|
|
|
|
# add missing routes/ips related to ovn-octavia loadbalancers
|
|
# on the provider networks
|
|
ovn_lb_vips = self.sb_idl.get_ovn_lb_vips_on_provider_datapath(
|
|
cr_lrp_info['provider_datapath'])
|
|
for ovn_lb_port, ovn_lb_ip in ovn_lb_vips.items():
|
|
self._expose_ovn_lb_on_provider(ovn_lb_ip,
|
|
ovn_lb_port,
|
|
cr_lrp_port,
|
|
exposed_ips,
|
|
ovn_ip_rules)
|
|
|
|
# remove extra routes/ips
|
|
# remove all the leftovers on the list of current ips on dev OVN
|
|
linux_net.delete_exposed_ips(exposed_ips, CONF.bgp_nic)
|
|
# remove all the leftovers on the list of current ip rules for ovn
|
|
# bridges
|
|
linux_net.delete_ip_rules(ovn_ip_rules)
|
|
|
|
# remove all the extra rules not needed
|
|
linux_net.delete_bridge_ip_routes(self.ovn_routing_tables,
|
|
self.ovn_routing_tables_routes,
|
|
extra_routes)
|
|
|
|
def _ensure_cr_lrp_associated_ports_exposed(self, cr_lrp_port,
|
|
exposed_ips, ovn_ip_rules):
|
|
ips, patch_port_row = self.sb_idl.get_cr_lrp_nat_addresses_info(
|
|
cr_lrp_port, self.chassis, self.sb_idl)
|
|
if not ips:
|
|
return
|
|
self._expose_ip(ips, patch_port_row, associated_port=cr_lrp_port)
|
|
for ip in ips:
|
|
if exposed_ips and ip in exposed_ips:
|
|
exposed_ips.remove(ip)
|
|
if ovn_ip_rules:
|
|
ip_version = linux_net.get_ip_version(ip)
|
|
if ip_version == constants.IP_VERSION_6:
|
|
ip_dst = "{}/128".format(ip)
|
|
else:
|
|
ip_dst = "{}/32".format(ip)
|
|
ovn_ip_rules.pop(ip_dst, None)
|
|
|
|
def _ensure_port_exposed(self, port, exposed_ips, ovn_ip_rules):
|
|
if port.type not in constants.OVN_VIF_PORT_TYPES or not port.mac:
|
|
return
|
|
|
|
port_ips = []
|
|
if port.mac == ['unknown']:
|
|
# For FIPs associated to VM ports we don't need the port IP, so
|
|
# we can check if it is a VM on the provider and trigger the
|
|
# expose_ip without passing any port_ips
|
|
try:
|
|
if ((port.type != constants.OVN_VM_VIF_PORT_TYPE and
|
|
port.type != constants.OVN_VIRTUAL_VIF_PORT_TYPE) or
|
|
self.sb_idl.is_provider_network(port.datapath)):
|
|
return
|
|
except agent_exc.DatapathNotFound:
|
|
# There is no need to expose anything related to a removed
|
|
# datapath
|
|
LOG.debug("Port %s not being exposed as its datapath %s was "
|
|
"removed", port.logical_port, port.datapath)
|
|
return
|
|
else:
|
|
if len(port.mac[0].split(' ')) < 2:
|
|
return
|
|
port_ips = port.mac[0].split(' ')[1:]
|
|
|
|
ips_adv = self._expose_ip(port_ips, port)
|
|
|
|
for port_ip in ips_adv:
|
|
ip_address = port_ip.split("/")[0]
|
|
if exposed_ips and ip_address in exposed_ips:
|
|
# remove each ip to add from the list of current ips on dev OVN
|
|
exposed_ips.remove(ip_address)
|
|
if ovn_ip_rules:
|
|
ip_version = linux_net.get_ip_version(port_ip)
|
|
if ip_version == constants.IP_VERSION_6:
|
|
ip_dst = "{}/128".format(ip_address)
|
|
else:
|
|
ip_dst = "{}/32".format(ip_address)
|
|
ovn_ip_rules.pop(ip_dst, None)
|
|
|
|
def _expose_provider_port(self, port_ips, provider_datapath,
|
|
bridge_device=None, bridge_vlan=None,
|
|
lladdr=None):
|
|
linux_net.add_ips_to_dev(CONF.bgp_nic, port_ips)
|
|
|
|
if not bridge_device and not bridge_vlan:
|
|
bridge_device, bridge_vlan = self._get_bridge_for_datapath(
|
|
provider_datapath)
|
|
for ip in port_ips:
|
|
try:
|
|
if lladdr:
|
|
linux_net.add_ip_rule(
|
|
ip, self.ovn_routing_tables[bridge_device],
|
|
bridge_device, lladdr=lladdr)
|
|
else:
|
|
linux_net.add_ip_rule(
|
|
ip, self.ovn_routing_tables[bridge_device],
|
|
bridge_device)
|
|
except agent_exc.InvalidPortIP:
|
|
LOG.exception("Invalid IP to create a rule for port"
|
|
" on the provider network: %s", ip)
|
|
return []
|
|
linux_net.add_ip_route(
|
|
self.ovn_routing_tables_routes, ip,
|
|
self.ovn_routing_tables[bridge_device], bridge_device,
|
|
vlan=bridge_vlan)
|
|
|
|
def _expose_tenant_port(self, port, ip_version, exposed_ips=None,
|
|
ovn_ip_rules=None):
|
|
# specific case for ovn-lb vips on tenant networks
|
|
if not port.mac and not port.chassis and not port.up[0]:
|
|
ext_n_cidr = port.external_ids.get(
|
|
constants.OVN_CIDRS_EXT_ID_KEY)
|
|
if ext_n_cidr:
|
|
ovn_lb_ip = ext_n_cidr.split(" ")[0].split("/")[0]
|
|
linux_net.add_ips_to_dev(
|
|
CONF.bgp_nic, [ovn_lb_ip])
|
|
if exposed_ips and ovn_lb_ip in exposed_ips:
|
|
exposed_ips.remove(ovn_lb_ip)
|
|
if ovn_ip_rules:
|
|
ovn_ip_rules.pop(ext_n_cidr.split(" ")[0], None)
|
|
return
|
|
elif (not port.mac or
|
|
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)):
|
|
return
|
|
|
|
try:
|
|
if port.mac == ['unknown']:
|
|
# Handling the case for unknown MACs when configdrive is used
|
|
# instead of dhcp
|
|
n_cidrs = port.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
|
|
port_ips = [ip.split("/")[0] for ip in n_cidrs.split(" ")]
|
|
else:
|
|
port_ips = port.mac[0].split(' ')[1:]
|
|
except IndexError:
|
|
return
|
|
|
|
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(
|
|
CONF.bgp_nic, [port_ip])
|
|
if exposed_ips and port_ip in exposed_ips:
|
|
exposed_ips.remove(port_ip)
|
|
if ovn_ip_rules:
|
|
if port_ip_version == constants.IP_VERSION_6:
|
|
ip_dst = "{}/128".format(port_ip)
|
|
else:
|
|
ip_dst = "{}/32".format(port_ip)
|
|
ovn_ip_rules.pop(ip_dst, None)
|
|
|
|
def _withdraw_provider_port(self, port_ips, provider_datapath,
|
|
bridge_device=None, bridge_vlan=None,
|
|
lladdr=None):
|
|
linux_net.del_ips_from_dev(CONF.bgp_nic, port_ips)
|
|
|
|
# assuming either you pass both or none
|
|
if not bridge_device and not bridge_vlan:
|
|
bridge_device, bridge_vlan = self._get_bridge_for_datapath(
|
|
provider_datapath)
|
|
for ip in port_ips:
|
|
if lladdr:
|
|
if linux_net.get_ip_version(ip) == constants.IP_VERSION_6:
|
|
cr_lrp_ip = '{}/128'.format(ip)
|
|
else:
|
|
cr_lrp_ip = '{}/32'.format(ip)
|
|
linux_net.del_ip_rule(
|
|
cr_lrp_ip, self.ovn_routing_tables[bridge_device],
|
|
bridge_device, lladdr=lladdr)
|
|
else:
|
|
linux_net.del_ip_rule(
|
|
ip, self.ovn_routing_tables[bridge_device], bridge_device)
|
|
linux_net.del_ip_route(
|
|
self.ovn_routing_tables_routes, ip,
|
|
self.ovn_routing_tables[bridge_device], bridge_device,
|
|
vlan=bridge_vlan)
|
|
|
|
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('bgp')
|
|
def expose_ovn_lb(self, ip, row):
|
|
self._process_ovn_lb(ip, row, constants.EXPOSE)
|
|
|
|
@lockutils.synchronized('bgp')
|
|
def withdraw_ovn_lb(self, ip, row):
|
|
self._process_ovn_lb(ip, row, constants.WITHDRAW)
|
|
|
|
def _process_ovn_lb(self, ip, row, action):
|
|
try:
|
|
provider_network = self.sb_idl.is_provider_network(row.datapath)
|
|
except agent_exc.DatapathNotFound:
|
|
# There is no need to expose anything related to a removed
|
|
# datapath
|
|
LOG.debug("LoadBalancer with VIP %s not being exposed as its "
|
|
"associated datapath %s was removed", ip, row.datapath)
|
|
return
|
|
if not provider_network:
|
|
if not self._expose_tenant_networks:
|
|
return
|
|
if action == constants.EXPOSE:
|
|
return self._expose_remote_ip([ip], row)
|
|
if action == constants.WITHDRAW:
|
|
return self._withdraw_remote_ip([ip], row)
|
|
# if unknown action return
|
|
return
|
|
local_lb_cr_lrp = None
|
|
for cr_lrp_port, cr_lrp_info in self.ovn_local_cr_lrps.items():
|
|
if cr_lrp_info['provider_datapath'] == row.datapath:
|
|
local_lb_cr_lrp = cr_lrp_port
|
|
break
|
|
if not local_lb_cr_lrp:
|
|
return
|
|
vip_port = row.logical_port
|
|
if action == constants.EXPOSE:
|
|
self._expose_ovn_lb_on_provider(ip, vip_port, local_lb_cr_lrp)
|
|
if action == constants.WITHDRAW:
|
|
self._withdraw_ovn_lb_on_provider(vip_port, local_lb_cr_lrp)
|
|
|
|
def _expose_ovn_lb_on_provider(self, ip, ovn_lb_vip_port, cr_lrp,
|
|
exposed_ips=None, ovn_ip_rules=None):
|
|
self.ovn_local_cr_lrps[cr_lrp]['ovn_lb_vips'].append(ovn_lb_vip_port)
|
|
self.ovn_lb_vips.setdefault(ovn_lb_vip_port, []).append(ip)
|
|
bridge_device = self.ovn_local_cr_lrps[cr_lrp]['bridge_device']
|
|
bridge_vlan = self.ovn_local_cr_lrps[cr_lrp]['bridge_vlan']
|
|
|
|
LOG.debug("Adding BGP route for loadbalancer VIP %s", ip)
|
|
self._expose_provider_port([ip], None, bridge_device=bridge_device,
|
|
bridge_vlan=bridge_vlan)
|
|
LOG.debug("Added BGP route for loadbalancer VIP %s", ip)
|
|
if exposed_ips and ip in exposed_ips:
|
|
exposed_ips.remove(ip)
|
|
if ovn_ip_rules:
|
|
ip_version = linux_net.get_ip_version(ip)
|
|
if ip_version == constants.IP_VERSION_6:
|
|
ip_dst = "{}/128".format(ip)
|
|
else:
|
|
ip_dst = "{}/32".format(ip)
|
|
ovn_ip_rules.pop(ip_dst, None)
|
|
|
|
def _withdraw_ovn_lb_on_provider(self, ovn_lb_vip_port, cr_lrp):
|
|
bridge_device = self.ovn_local_cr_lrps[cr_lrp]['bridge_device']
|
|
bridge_vlan = self.ovn_local_cr_lrps[cr_lrp]['bridge_vlan']
|
|
|
|
for ip in self.ovn_lb_vips[ovn_lb_vip_port].copy():
|
|
LOG.debug("Deleting BGP route for loadbalancer VIP %s", ip)
|
|
self._withdraw_provider_port([ip], None,
|
|
bridge_device=bridge_device,
|
|
bridge_vlan=bridge_vlan)
|
|
if ip in self.ovn_lb_vips[ovn_lb_vip_port]:
|
|
self.ovn_lb_vips[ovn_lb_vip_port].remove(ip)
|
|
LOG.debug("Deleted BGP route for loadbalancer VIP %s", ip)
|
|
if ovn_lb_vip_port in self.ovn_local_cr_lrps[cr_lrp]['ovn_lb_vips']:
|
|
self.ovn_local_cr_lrps[cr_lrp]['ovn_lb_vips'].remove(
|
|
ovn_lb_vip_port)
|
|
|
|
@lockutils.synchronized('bgp')
|
|
def expose_ip(self, ips, row, associated_port=None):
|
|
'''Advertice BGP route by adding IP to device.
|
|
|
|
This methods ensures BGP advertises the IP of the VM in the provider
|
|
network, or the FIP associated to a VM in a tenant networks.
|
|
|
|
It relies on Zebra, which creates and advertises a route when an IP
|
|
is added to a local interface.
|
|
|
|
This method assumes a device named self.ovn_decice exists (inside a
|
|
VRF), and adds the IP of either:
|
|
- VM IP on the provider network,
|
|
- VM FIP, or
|
|
- CR-LRP OVN port
|
|
'''
|
|
self._expose_ip(ips, row, associated_port)
|
|
|
|
def _expose_ip(self, ips, row, associated_port=None):
|
|
if (row.type == constants.OVN_VM_VIF_PORT_TYPE or
|
|
row.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE):
|
|
try:
|
|
provider_network = self.sb_idl.is_provider_network(
|
|
row.datapath)
|
|
except agent_exc.DatapathNotFound:
|
|
# There is no need to expose anything related to a removed
|
|
# datapath
|
|
LOG.debug("Port %s not being exposed as its associated "
|
|
"datapath %s was removed", row.logical_port,
|
|
row.datapath)
|
|
return
|
|
# VM on provider Network
|
|
if provider_network:
|
|
LOG.debug("Adding BGP route for logical port with ip %s", ips)
|
|
self._expose_provider_port(ips, row.datapath)
|
|
# NOTE: For Amphora Load Balancer with IPv6 VIP on the provider
|
|
# network, we need a NDP Proxy so that the traffic from the
|
|
# amphora can properly be redirected back
|
|
if row.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE:
|
|
bridge_device, bridge_vlan = self._get_bridge_for_datapath(
|
|
row.datapath)
|
|
# NOTE: This is neutron specific as we need the provider
|
|
# prefix to add the ndp proxy
|
|
n_cidr = row.external_ids.get(
|
|
constants.OVN_CIDRS_EXT_ID_KEY)
|
|
if n_cidr and (linux_net.get_ip_version(n_cidr) ==
|
|
constants.IP_VERSION_6):
|
|
linux_net.add_ndp_proxy(n_cidr, bridge_device,
|
|
bridge_vlan)
|
|
LOG.debug("Added BGP route for logical port with ip %s", ips)
|
|
return ips
|
|
# VM with FIP
|
|
else:
|
|
# FIPs are only supported with IPv4
|
|
fip_address, fip_datapath = self.sb_idl.get_fip_associated(
|
|
row.logical_port)
|
|
if fip_address:
|
|
LOG.debug("Adding BGP route for FIP with ip %s",
|
|
fip_address)
|
|
self._expose_provider_port([fip_address], fip_datapath)
|
|
LOG.debug("Added BGP route for FIP with ip %s",
|
|
fip_address)
|
|
return [fip_address]
|
|
|
|
# FIP association to VM
|
|
elif row.type == constants.OVN_PATCH_VIF_PORT_TYPE:
|
|
if (associated_port and self.sb_idl.is_port_on_chassis(
|
|
associated_port, self.chassis)):
|
|
LOG.debug("Adding BGP route for FIP with ip %s", ips)
|
|
self._expose_provider_port(ips, row.datapath)
|
|
LOG.debug("Added BGP route for FIP with ip %s", ips)
|
|
return ips
|
|
|
|
# CR-LRP Port
|
|
elif (row.type == constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE and
|
|
row.logical_port.startswith('cr-')):
|
|
cr_lrp_datapath = self.sb_idl.get_provider_datapath_from_cr_lrp(
|
|
row.logical_port)
|
|
if not cr_lrp_datapath:
|
|
return []
|
|
|
|
bridge_device, bridge_vlan = self._get_bridge_for_datapath(
|
|
cr_lrp_datapath)
|
|
mac = row.mac[0].split(' ')[0]
|
|
# Keeping information about the associated network for
|
|
# tenant network advertisement
|
|
self.ovn_local_cr_lrps[row.logical_port] = {
|
|
'router_datapath': row.datapath,
|
|
'provider_datapath': cr_lrp_datapath,
|
|
'ips': ips,
|
|
'mac': mac,
|
|
'subnets_datapath': {},
|
|
'subnets_cidr': [],
|
|
'ovn_lb_vips': [],
|
|
'bridge_vlan': bridge_vlan,
|
|
'bridge_device': bridge_device
|
|
}
|
|
|
|
self._expose_cr_lrp_port(ips, mac, bridge_device, bridge_vlan,
|
|
router_datapath=row.datapath,
|
|
provider_datapath=cr_lrp_datapath,
|
|
cr_lrp_port=row.logical_port)
|
|
|
|
return ips
|
|
return []
|
|
|
|
@lockutils.synchronized('bgp')
|
|
def withdraw_ip(self, ips, row, associated_port=None):
|
|
'''Withdraw BGP route by removing IP from device.
|
|
|
|
This methods ensures BGP withdraw an advertised IP of a VM, either
|
|
in the provider network, or the FIP associated to a VM in a tenant
|
|
networks.
|
|
|
|
It relies on Zebra, which withdraws the advertisement as soon as the
|
|
IP is deleted from the local interface.
|
|
|
|
This method assumes a device named self.ovn_decice exists (inside a
|
|
VRF), and removes the IP of either:
|
|
- VM IP on the provider network,
|
|
- VM FIP, or
|
|
- CR-LRP OVN port
|
|
'''
|
|
if (row.type == constants.OVN_VM_VIF_PORT_TYPE or
|
|
row.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE):
|
|
try:
|
|
provider_network = self.sb_idl.is_provider_network(
|
|
row.datapath)
|
|
except agent_exc.DatapathNotFound:
|
|
# NOTE(ltomasbo): Datapath has been deleted. This means that:
|
|
# - If it was a provider network we need to withdraw it
|
|
# - It it was a VM with a FIP, the removal would be handled
|
|
# by the FIP dissassociation even (FIP removal) that must
|
|
# happen before removing the subnet from the router, and
|
|
# before being able to remove the subnet
|
|
# This means we only need to process the "provider_network"
|
|
# case
|
|
provider_network = True
|
|
LOG.debug("Port %s belongs to a removed datapath %s. "
|
|
"Assuming it was a provider network to avoid "
|
|
"leaks.", row.logical_port, row.datapath)
|
|
# VM on provider Network
|
|
if provider_network:
|
|
LOG.debug("Deleting BGP route for logical port with ip %s",
|
|
ips)
|
|
self._withdraw_provider_port(ips, row.datapath)
|
|
if row.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE:
|
|
virtual_provider_ports = (
|
|
self.sb_idl.get_virtual_ports_on_datapath_by_chassis(
|
|
row.datapath, self.chassis))
|
|
if not virtual_provider_ports:
|
|
cr_lrps_on_same_provider = [
|
|
p for p in self.ovn_local_cr_lrps.values()
|
|
if p['provider_datapath'] == row.datapath]
|
|
if not cr_lrps_on_same_provider:
|
|
bridge_device, bridge_vlan = (
|
|
self._get_bridge_for_datapath(row.datapath))
|
|
# NOTE: This is neutron specific as we need the
|
|
# provider prefix to add the ndp proxy
|
|
n_cidr = row.external_ids.get(
|
|
constants.OVN_CIDRS_EXT_ID_KEY)
|
|
if n_cidr and (linux_net.get_ip_version(n_cidr) ==
|
|
constants.IP_VERSION_6):
|
|
linux_net.del_ndp_proxy(n_cidr, bridge_device,
|
|
bridge_vlan)
|
|
LOG.debug("Deleted BGP route for logical port with ip %s", ips)
|
|
# VM with FIP
|
|
else:
|
|
# FIPs are only supported with IPv4
|
|
fip_address, fip_datapath = self.sb_idl.get_fip_associated(
|
|
row.logical_port)
|
|
if not fip_address:
|
|
return
|
|
|
|
LOG.debug("Deleting BGP route for FIP with ip %s", fip_address)
|
|
self._withdraw_provider_port([fip_address], fip_datapath)
|
|
LOG.debug("Deleted BGP route for FIP with ip %s", fip_address)
|
|
|
|
# FIP disassociation to VM
|
|
elif row.type == constants.OVN_PATCH_VIF_PORT_TYPE:
|
|
if (associated_port and (
|
|
self.sb_idl.is_port_on_chassis(
|
|
associated_port, self.chassis) or
|
|
self.sb_idl.is_port_deleted(associated_port))):
|
|
LOG.debug("Deleting BGP route for FIP with ip %s", ips)
|
|
self._withdraw_provider_port(ips, row.datapath)
|
|
LOG.debug("Deleted BGP route for FIP with ip %s", ips)
|
|
|
|
# CR-LRP Port
|
|
elif (row.type == constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE and
|
|
row.logical_port.startswith('cr-')):
|
|
cr_lrp_datapath = self.ovn_local_cr_lrps.get(
|
|
row.logical_port, {}).get('provider_datapath')
|
|
if not cr_lrp_datapath:
|
|
return
|
|
|
|
bridge_vlan = self.ovn_local_cr_lrps[row.logical_port].get(
|
|
'bridge_vlan')
|
|
bridge_device = self.ovn_local_cr_lrps[row.logical_port].get(
|
|
'bridge_device')
|
|
mac = row.mac[0].split(' ')[0]
|
|
self._withdraw_cr_lrp_port(ips, mac, bridge_device, bridge_vlan,
|
|
provider_datapath=cr_lrp_datapath,
|
|
cr_lrp_port=row.logical_port)
|
|
|
|
@lockutils.synchronized('bgp')
|
|
def expose_remote_ip(self, ips, row):
|
|
self._expose_remote_ip(ips, row)
|
|
|
|
def _expose_remote_ip(self, ips, row):
|
|
try:
|
|
if (self.sb_idl.is_provider_network(row.datapath) or
|
|
not self._expose_tenant_networks):
|
|
return
|
|
except agent_exc.DatapathNotFound:
|
|
# There is no need to expose anything related to a removed
|
|
# datapath
|
|
LOG.debug("Port %s not being exposed as its datapath %s was "
|
|
"removed", row.logical_port, row.datapath)
|
|
return
|
|
if not CONF.expose_tenant_networks:
|
|
# This means CONF.expose_ipv6_gua_tenant_networks is enabled
|
|
gua_ips = []
|
|
for ip in ips:
|
|
if driver_utils.is_ipv6_gua(ip):
|
|
gua_ips.append(ip)
|
|
if not gua_ips:
|
|
return
|
|
ips = gua_ips
|
|
|
|
ips_to_expose = []
|
|
for ip in ips:
|
|
if self._address_scope_allowed(ip, None, row):
|
|
ips_to_expose.append(ip)
|
|
if not ips_to_expose:
|
|
return
|
|
|
|
port_lrp = self.sb_idl.get_lrp_port_for_datapath(row.datapath)
|
|
if port_lrp in self.ovn_local_lrps.keys():
|
|
LOG.debug("Adding BGP route for tenant IP %s on chassis %s",
|
|
ips_to_expose, self.chassis)
|
|
linux_net.add_ips_to_dev(CONF.bgp_nic, ips_to_expose)
|
|
LOG.debug("Added BGP route for tenant IP %s on chassis %s",
|
|
ips_to_expose, self.chassis)
|
|
|
|
@lockutils.synchronized('bgp')
|
|
def withdraw_remote_ip(self, ips, row, chassis=None):
|
|
self._withdraw_remote_ip(ips, row, chassis)
|
|
|
|
def _withdraw_remote_ip(self, ips, row, chassis=None):
|
|
try:
|
|
if (self.sb_idl.is_provider_network(row.datapath) or
|
|
not self._expose_tenant_networks):
|
|
return
|
|
except agent_exc.DatapathNotFound:
|
|
# There is no need to continue as the subnet removal (patch port
|
|
# removal) will trigger a withdraw_subnet event that will remove
|
|
# the associated IPs
|
|
LOG.debug("Port %s not being withdrawn as its datapath %s was "
|
|
"removed. The subnet withdraw action will take care of "
|
|
"the withdrawal.", row.logical_port, row.datapath)
|
|
return
|
|
if not CONF.expose_tenant_networks:
|
|
# This means CONF.expose_ipv6_gua_tenant_networks is enabled
|
|
gua_ips = []
|
|
for ip in ips:
|
|
if driver_utils.is_ipv6_gua(ip):
|
|
gua_ips.append(ip)
|
|
if not gua_ips:
|
|
return
|
|
ips = gua_ips
|
|
|
|
ips_to_withdraw = []
|
|
for ip in ips:
|
|
if self._address_scope_allowed(ip, None, row):
|
|
ips_to_withdraw.append(ip)
|
|
if not ips_to_withdraw:
|
|
return
|
|
port_lrp = self.sb_idl.get_lrp_port_for_datapath(row.datapath)
|
|
if port_lrp in self.ovn_local_lrps.keys():
|
|
LOG.debug("Deleting BGP route for tenant IP %s on chassis %s",
|
|
ips_to_withdraw, self.chassis)
|
|
linux_net.del_ips_from_dev(CONF.bgp_nic, ips_to_withdraw)
|
|
LOG.debug("Deleted BGP route for tenant IP %s on chassis %s",
|
|
ips_to_withdraw, self.chassis)
|
|
|
|
def _process_lrp_port(self, lrp, associated_cr_lrp, exposed_ips=None,
|
|
ovn_ip_rules=None):
|
|
if (lrp.chassis or
|
|
not lrp.logical_port.startswith('lrp-') or
|
|
"chassis-redirect-port" in lrp.options.keys() or
|
|
associated_cr_lrp.strip('cr-') == lrp.logical_port):
|
|
return
|
|
# add missing route/ips for tenant network VMs
|
|
if self._expose_tenant_networks:
|
|
try:
|
|
lrp_ip = lrp.mac[0].split(' ')[1]
|
|
except IndexError:
|
|
# This should not happen: subnet without CIDR
|
|
return
|
|
|
|
if not lrp.options.get('peer'):
|
|
# if there is no peer associated to the port we need to
|
|
# 1) creation: wait for another re-sync to expose it
|
|
# 2) deletion: no need to add it as it being removed
|
|
return
|
|
if not self._address_scope_allowed(lrp_ip, lrp.options['peer']):
|
|
return
|
|
subnet_datapath = self.sb_idl.get_port_datapath(
|
|
lrp.options['peer'])
|
|
self._expose_lrp_port(lrp_ip, lrp.logical_port,
|
|
associated_cr_lrp, subnet_datapath,
|
|
exposed_ips=exposed_ips,
|
|
ovn_ip_rules=ovn_ip_rules)
|
|
|
|
def _expose_cr_lrp_port(self, ips, mac, bridge_device, bridge_vlan,
|
|
router_datapath, provider_datapath, cr_lrp_port):
|
|
LOG.debug("Adding BGP route for CR-LRP Port %s", ips)
|
|
ips_without_mask = [ip.split("/")[0] for ip in ips]
|
|
self._expose_provider_port(ips_without_mask, provider_datapath,
|
|
bridge_device, bridge_vlan,
|
|
lladdr=mac)
|
|
# add proxy ndp config for ipv6
|
|
for ip in ips:
|
|
if linux_net.get_ip_version(ip) == constants.IP_VERSION_6:
|
|
linux_net.add_ndp_proxy(ip, bridge_device, bridge_vlan)
|
|
LOG.debug("Added BGP route for CR-LRP Port %s", ips)
|
|
|
|
# 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(router_datapath)
|
|
for lrp in lrp_ports:
|
|
self._process_lrp_port(lrp, cr_lrp_port)
|
|
|
|
ovn_lb_vips = self.sb_idl.get_ovn_lb_vips_on_provider_datapath(
|
|
provider_datapath)
|
|
for ovn_lb_port, ovn_lb_ip in ovn_lb_vips.items():
|
|
self._expose_ovn_lb_on_provider(ovn_lb_ip,
|
|
ovn_lb_port,
|
|
cr_lrp_port)
|
|
|
|
def _withdraw_cr_lrp_port(self, ips, mac, bridge_device, bridge_vlan,
|
|
provider_datapath, cr_lrp_port):
|
|
LOG.debug("Deleting BGP route for CR-LRP Port %s", ips)
|
|
# Removing information about the associated network for
|
|
# tenant network advertisement
|
|
ips_without_mask = [ip.split("/")[0] for ip in ips]
|
|
self._withdraw_provider_port(ips_without_mask, provider_datapath,
|
|
bridge_device=bridge_device,
|
|
bridge_vlan=bridge_vlan,
|
|
lladdr=mac)
|
|
# del proxy ndp config for ipv6
|
|
for ip in ips_without_mask:
|
|
if linux_net.get_ip_version(ip) == constants.IP_VERSION_6:
|
|
cr_lrps_on_same_provider = [
|
|
p for p in self.ovn_local_cr_lrps.values()
|
|
if p['provider_datapath'] == provider_datapath]
|
|
# if no other cr-lrp port on the same provider
|
|
# delete the ndp proxy
|
|
if (len(cr_lrps_on_same_provider) <= 1):
|
|
linux_net.del_ndp_proxy(ip, bridge_device, bridge_vlan)
|
|
LOG.debug("Deleted BGP route for CR-LRP Port %s", ips)
|
|
|
|
# Check if there are networks attached to the router,
|
|
# and if so delete the needed routes/rules
|
|
local_cr_lrp_info = self.ovn_local_cr_lrps.get(cr_lrp_port)
|
|
for subnet_cidr in local_cr_lrp_info['subnets_cidr']:
|
|
self._withdraw_lrp_port(subnet_cidr, None, cr_lrp_port)
|
|
|
|
# check if there are loadbalancers associated to the router,
|
|
# and if so delete the needed routes/rules
|
|
ovn_lb_vips = self.ovn_local_cr_lrps[cr_lrp_port][
|
|
'ovn_lb_vips'].copy()
|
|
for ovn_lb_vip in ovn_lb_vips:
|
|
self._withdraw_ovn_lb_on_provider(ovn_lb_vip, cr_lrp_port)
|
|
self.ovn_local_cr_lrps[cr_lrp_port]['ovn_lb_vips'].remove(
|
|
ovn_lb_vip)
|
|
try:
|
|
del self.ovn_local_cr_lrps[cr_lrp_port]
|
|
except KeyError:
|
|
LOG.debug("Gateway port %s already cleanup from the agent.",
|
|
cr_lrp_port)
|
|
|
|
def _expose_lrp_port(self, ip, lrp, associated_cr_lrp, subnet_datapath,
|
|
exposed_ips=None, ovn_ip_rules=None):
|
|
if not self._expose_tenant_networks:
|
|
return
|
|
if not CONF.expose_tenant_networks:
|
|
# This means CONF.expose_ipv6_gua_tenant_networks is enabled
|
|
if not driver_utils.is_ipv6_gua(ip):
|
|
return
|
|
cr_lrp_info = self.ovn_local_cr_lrps.get(associated_cr_lrp, {})
|
|
cr_lrp_ips = [ip_address.split('/')[0]
|
|
for ip_address in cr_lrp_info.get('ips', [])]
|
|
|
|
# this is the router gateway port
|
|
if ip.split('/')[0] in cr_lrp_ips:
|
|
return
|
|
|
|
cr_lrp_datapath = cr_lrp_info.get('provider_datapath')
|
|
if not cr_lrp_datapath:
|
|
return
|
|
|
|
bridge_device = cr_lrp_info.get('bridge_device')
|
|
bridge_vlan = cr_lrp_info.get('bridge_vlan')
|
|
|
|
# update information needed for the loadbalancers
|
|
cr_lrp_info['subnets_datapath'].update({lrp: subnet_datapath})
|
|
cr_lrp_info['subnets_cidr'].append(ip)
|
|
self.ovn_local_lrps.update({lrp: associated_cr_lrp})
|
|
|
|
LOG.debug("Adding IP Rules for network %s on chassis %s", ip,
|
|
self.chassis)
|
|
try:
|
|
linux_net.add_ip_rule(
|
|
ip, self.ovn_routing_tables[bridge_device], bridge_device)
|
|
except agent_exc.InvalidPortIP:
|
|
LOG.exception("Invalid IP to create a rule for the "
|
|
"lrp (network router interface) port: %s", ip)
|
|
return
|
|
LOG.debug("Added IP Rules for network %s on chassis %s", ip,
|
|
self.chassis)
|
|
if ovn_ip_rules:
|
|
ovn_ip_rules.pop(ip, None)
|
|
|
|
LOG.debug("Adding IP Routes for network %s on chassis %s", ip,
|
|
self.chassis)
|
|
# NOTE(ltomasbo): This assumes the provider network can only have
|
|
# (at most) 2 subnets, one for IPv4, one for IPv6
|
|
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],
|
|
self.ovn_routing_tables[bridge_device],
|
|
bridge_device,
|
|
vlan=bridge_vlan,
|
|
mask=ip.split("/")[1],
|
|
via=cr_lrp_ip)
|
|
break
|
|
LOG.debug("Added IP Routes for network %s on chassis %s", ip,
|
|
self.chassis)
|
|
|
|
# Check if there are VMs on the network
|
|
# and if so expose the route
|
|
ports = self.sb_idl.get_ports_on_datapath(subnet_datapath)
|
|
for port in ports:
|
|
self._expose_tenant_port(port, ip_version=ip_version,
|
|
exposed_ips=exposed_ips,
|
|
ovn_ip_rules=ovn_ip_rules)
|
|
|
|
def _withdraw_lrp_port(self, ip, lrp, associated_cr_lrp):
|
|
if not self._expose_tenant_networks:
|
|
return
|
|
if not CONF.expose_tenant_networks:
|
|
# This means CONF.expose_ipv6_gua_tenant_networks is enabled
|
|
if not driver_utils.is_ipv6_gua(ip):
|
|
return
|
|
cr_lrp_info = self.ovn_local_cr_lrps.get(associated_cr_lrp, {})
|
|
|
|
LOG.debug("Deleting IP Rules for network %s on chassis %s", ip,
|
|
self.chassis)
|
|
exposed_lrp = False
|
|
if lrp:
|
|
if lrp in self.ovn_local_lrps.keys():
|
|
exposed_lrp = True
|
|
self.ovn_local_lrps.pop(lrp)
|
|
else:
|
|
for subnet_lp in cr_lrp_info['subnets_datapath'].keys():
|
|
if subnet_lp in self.ovn_local_lrps.keys():
|
|
exposed_lrp = True
|
|
self.ovn_local_lrps.pop(subnet_lp)
|
|
break
|
|
cr_lrp_info['subnets_datapath'].pop(lrp, None)
|
|
if not exposed_lrp:
|
|
return
|
|
|
|
cr_lrp_ips = [ip_address.split('/')[0]
|
|
for ip_address in cr_lrp_info.get('ips', [])]
|
|
bridge_device = cr_lrp_info.get('bridge_device')
|
|
bridge_vlan = cr_lrp_info.get('bridge_vlan')
|
|
|
|
linux_net.del_ip_rule(ip, self.ovn_routing_tables[bridge_device],
|
|
bridge_device)
|
|
LOG.debug("Deleted IP Rules for network %s on chassis %s", ip,
|
|
self.chassis)
|
|
|
|
LOG.debug("Deleting IP Routes for network %s on chassis %s", ip,
|
|
self.chassis)
|
|
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],
|
|
self.ovn_routing_tables[bridge_device],
|
|
bridge_device,
|
|
vlan=bridge_vlan,
|
|
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
|
|
LOG.debug("Deleted IP Routes for network %s on chassis %s", ip,
|
|
self.chassis)
|
|
|
|
# Check if there are VMs on the network
|
|
# and if so withdraw the routes
|
|
if net:
|
|
vms_on_net = linux_net.get_exposed_ips_on_network(
|
|
CONF.bgp_nic, net)
|
|
linux_net.delete_exposed_ips(vms_on_net, CONF.bgp_nic)
|
|
|
|
@lockutils.synchronized('bgp')
|
|
def expose_subnet(self, ip, row):
|
|
cr_lrp = self.sb_idl.is_router_gateway_on_chassis(
|
|
row.datapath, self.chassis)
|
|
subnet_datapath = self.sb_idl.get_port_datapath(
|
|
row.options['peer'])
|
|
|
|
if not cr_lrp:
|
|
return
|
|
|
|
if not self._address_scope_allowed(ip, row.options['peer']):
|
|
return
|
|
|
|
self._expose_lrp_port(ip, row.logical_port, cr_lrp, subnet_datapath)
|
|
|
|
@lockutils.synchronized('bgp')
|
|
def withdraw_subnet(self, ip, row):
|
|
try:
|
|
cr_lrp = self.sb_idl.is_router_gateway_on_chassis(
|
|
row.datapath, self.chassis)
|
|
except agent_exc.DatapathNotFound:
|
|
# NOTE(ltomasbo): This happens when the router (datapath) gets
|
|
# deleted at the same time as subnets are detached from it.
|
|
# Usually this will be hit when router is deleted without
|
|
# removing its gateway. In that case we don't need to withdraw
|
|
# the subnet as it is not exposed, just the cr-lrp which is
|
|
# handle in a different event/method (withdraw_ip)
|
|
LOG.debug("Router is being deleted, so it's datapath does "
|
|
"not exists any more. Checking if port %s belongs "
|
|
"to chassis redirect and skip in that case.",
|
|
row.logical_port)
|
|
cr_lrp = [cr_lrp_name
|
|
for cr_lrp_name in self.ovn_local_cr_lrps.keys()
|
|
if row.logical_port in cr_lrp_name]
|
|
# if cr_lrp exists, this means the lrp port is for the router
|
|
# gateway, so there is no need to proceed
|
|
if cr_lrp:
|
|
LOG.debug("Port %s is related to chassis redirect, so "
|
|
"there is no need to do further actions for "
|
|
"subnet withdrawal, as this port was not "
|
|
"triggering a subnet exposure.",
|
|
row.logical_port)
|
|
return
|
|
if not cr_lrp or cr_lrp not in self.ovn_local_cr_lrps.keys():
|
|
# NOTE(ltomasbo) there is a chance the cr-lrp just got moved
|
|
# to this node but was not yet processed. In that case there
|
|
# is no need to withdraw the network as it was not exposed here
|
|
return
|
|
|
|
self._withdraw_lrp_port(ip, row.logical_port, cr_lrp)
|
|
|
|
def _address_scope_allowed(self, ip, port_name, sb_port=None):
|
|
if not self.allowed_address_scopes:
|
|
# No address scopes to filter on => announce everything
|
|
return True
|
|
|
|
if not sb_port:
|
|
sb_port = self.sb_idl.get_port_by_name(port_name)
|
|
if not sb_port:
|
|
LOG.error("Port %s missing, skipping.", port_name)
|
|
return False
|
|
address_scopes = driver_utils.get_addr_scopes(sb_port)
|
|
|
|
# if we should filter on address scopes and this port has no
|
|
# address scopes set we do not need to expose it
|
|
if not any(address_scopes.values()):
|
|
return False
|
|
# if address scope does not match, no need to expose it
|
|
ip_version = linux_net.get_ip_version(ip)
|
|
|
|
return address_scopes[ip_version] in self.allowed_address_scopes
|