Add support at NB BGP Driver for exposing subnets

Change-Id: I45e7269306e5c61cda8b6c429dd06c26828ee07b
This commit is contained in:
Luis Tomas Bolivar 2023-10-26 12:40:45 +02:00
parent 0c6a71314b
commit c63be7c85c
11 changed files with 1143 additions and 53 deletions

View File

@ -17,14 +17,19 @@ OVN_VIF_PORT_TYPES = ("", "chassisredirect", "virtual")
OVN_VIRTUAL_VIF_PORT_TYPE = "virtual"
OVN_VM_VIF_PORT_TYPE = ""
OVN_PATCH_VIF_PORT_TYPE = "patch"
OVN_ROUTER_PORT_TYPE = "router"
OVN_CHASSISREDIRECT_VIF_PORT_TYPE = "chassisredirect"
OVN_LOCALNET_VIF_PORT_TYPE = "localnet"
OVN_DNAT_AND_SNAT = "dnat_and_snat"
OVN_CR_LRP_PORT_TYPE = 'crlrp'
OVN_ROUTER_INTERFACE = 'network:router_interface'
OVN_CIDRS_EXT_ID_KEY = 'neutron:cidrs'
OVN_PORT_NAME_EXT_ID_KEY = 'neutron:port_name'
OVN_LS_NAME_EXT_ID_KEY = 'neutron:network_name'
OVN_LR_NAME_EXT_ID_KEY = 'neutron:router_name'
OVN_DEVICE_ID_EXT_ID_KEY = 'neutron:device_id'
OVN_DEVICE_OWNER_EXT_ID_KEY = 'neutron:device_owner'
OVN_FIP_EXT_ID_KEY = 'neutron:port_fip'
OVN_FIP_NET_EXT_ID_KEY = 'neutron:fip_network_id'
LB_VIP_PORT_PREFIX = "ovn-lb-vip-"

View File

@ -22,6 +22,7 @@ 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 bgp as bgp_utils
from ovn_bgp_agent.drivers.openstack.utils import driver_utils
from ovn_bgp_agent.drivers.openstack.utils import ovn
from ovn_bgp_agent.drivers.openstack.utils import ovs
from ovn_bgp_agent.drivers.openstack.utils import wire as wire_utils
@ -148,7 +149,9 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
"LocalnetCreateDeleteEvent"])
if self._expose_tenant_networks:
events.update(["ChassisRedirectCreateEvent",
"ChassisRedirectDeleteEvent"])
"ChassisRedirectDeleteEvent",
"LogicalSwitchPortSubnetAttachEvent",
"LogicalSwitchPortSubnetDetachEvent"])
return events
@lockutils.synchronized('nbbgp')
@ -171,10 +174,22 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
routing_tables=self.ovn_routing_tables))
LOG.debug("Syncing current routes.")
# add missing reoutes/ips for OVN router gateway ports
# add missing routes/ips for OVN router gateway ports
ports = self.nb_idl.get_active_cr_lrp_on_chassis(self.chassis_id)
for port in ports:
self._ensure_crlrp_exposed(port)
# add missing routes/ips for subnets connected to local gateway ports
ports = self.nb_idl.get_active_local_lrps(
self.ovn_local_cr_lrps.keys())
for port in ports:
ips = port.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split()
subnet_info = {
'associated_router': port.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY),
'address_scopes': driver_utils.get_addr_scopes(port)}
self._expose_subnet(ips, subnet_info)
# add missing routes/ips for IPs on provider network
ports = self.nb_idl.get_active_lsp_on_chassis(self.chassis)
for port in ports:
@ -227,8 +242,8 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
ips = port.addresses[0].strip().split(' ')[1:]
mac = port.addresses[0].strip().split(' ')[0]
self._expose_ip(ips, mac, logical_switch, bridge_device, bridge_vlan,
port.type, [port.external_ids.get(
constants.OVN_CIDRS_EXT_ID_KEY)])
port.type, port.external_ids.get(
constants.OVN_CIDRS_EXT_ID_KEY, "").split())
def _ensure_crlrp_exposed(self, port):
if not port.networks:
@ -249,9 +264,10 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
'bridge_vlan': bridge_vlan,
'localnet': localnet}
ips = [net.split("/")[0] for net in port.networks]
router = port.external_ids.get(constants.OVN_LR_NAME_EXT_ID_KEY)
self._expose_ip(ips, port.mac, logical_switch, bridge_device,
bridge_vlan, constants.OVN_CR_LRP_PORT_TYPE,
port.networks)
port.networks, router=router)
def _expose_provider_port(self, port_ips, mac, logical_switch,
bridge_device, bridge_vlan, localnet,
@ -273,6 +289,7 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
LOG.exception("Unexpected exception while wiring provider port: "
"%s", e)
return False
return True
def _withdraw_provider_port(self, port_ips, logical_switch, bridge_device,
bridge_vlan, proxy_cidrs=None):
@ -335,10 +352,11 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
mac = ips_info.get('mac')
return self._expose_ip(ips, mac, logical_switch, bridge_device,
bridge_vlan, port_type=ips_info['type'],
cidrs=ips_info['cidrs'])
cidrs=ips_info['cidrs'],
router=ips_info.get('router'))
def _expose_ip(self, ips, mac, logical_switch, bridge_device, bridge_vlan,
port_type, cidrs):
port_type, cidrs, router=None):
LOG.debug("Adding BGP route for logical port with ip %s", ips)
localnet = self.ovn_provider_ls[logical_switch]['localnet']
@ -351,7 +369,24 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
bridge_device, bridge_vlan,
localnet, cidrs):
return []
if router and port_type == constants.OVN_CR_LRP_PORT_TYPE:
# Store information about local CR-LRPs that will later be used
# to expose networks
self.ovn_local_cr_lrps[router] = {
'bridge_device': bridge_device,
'bridge_vlan': bridge_vlan,
'ips': ips,
}
# Expose associated subnets
ports = self.nb_idl.get_active_local_lrps([router])
for port in ports:
ips = port.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split()
subnet_info = {
'associated_router': port.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY),
'address_scopes': driver_utils.get_addr_scopes(port)}
self._expose_subnet(ips, subnet_info)
else:
if not self._expose_provider_port(ips, mac, logical_switch,
bridge_device, bridge_vlan,
@ -397,6 +432,23 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
else:
self._withdraw_provider_port(ips, logical_switch, bridge_device,
bridge_vlan)
if ips_info.get('router'):
# It is a Logical Router Port (CR-LRP)
# Withdraw associated subnets
ports = self.nb_idl.get_active_local_lrps([ips_info['router']])
for port in ports:
ips = port.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split()
subnet_info = {
'associated_router': port.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY),
'address_scopes': driver_utils.get_addr_scopes(port)}
self._withdraw_subnet(ips, subnet_info)
try:
del self.ovn_local_cr_lrps[ips_info['router']]
except KeyError:
LOG.debug("Gateway port for router %s already cleanup.",
ips_info['router'])
LOG.debug("Deleted BGP route for logical port with ip %s", ips)
def _get_ls_localnet_info(self, logical_switch):
@ -501,9 +553,118 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
pass
@lockutils.synchronized('nbbgp')
def expose_subnet(self, ip, row):
pass
def expose_subnet(self, ips, subnet_info):
return self._expose_subnet(ips, subnet_info)
@lockutils.synchronized('nbbgp')
def withdraw_subnet(self, ip, row):
pass
def withdraw_subnet(self, ips, subnet_info):
return self._withdraw_subnet(ips, subnet_info)
def _expose_subnet(self, ips, subnet_info):
gateway_router = subnet_info['associated_router']
if not gateway_router:
LOG.debug("Subnet CIDRs %s not exposed as there is no associated "
"router", ips)
return
cr_lrp_info = self.ovn_local_cr_lrps.get(gateway_router)
if not cr_lrp_info:
LOG.debug("Subnet CIDRs %s not exposed as there is no local "
"cr-lrp matching %s", ips, gateway_router)
return
if not self._expose_router_lsp(ips, subnet_info, cr_lrp_info):
LOG.debug("Something happen while exposing the Subnet CIRDs %s "
"and they have not been properly exposed", ips)
return
def _withdraw_subnet(self, ips, subnet_info):
gateway_router = subnet_info['associated_router']
if not gateway_router:
LOG.debug("Subnet CIDRs %s not withdrawn as there is no associated"
" router", ips)
return
cr_lrp_info = self.ovn_local_cr_lrps.get(gateway_router)
if not cr_lrp_info:
# 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
LOG.debug("Subnet CIDRs %s not withdrawn as there is no local "
"cr-lrp matching %s", ips, gateway_router)
return
self._withdraw_router_lsp(ips, subnet_info, cr_lrp_info)
def _expose_router_lsp(self, ips, subnet_info, cr_lrp_info):
if not self._expose_tenant_networks:
return True
success = True
for ip in ips:
if not CONF.expose_tenant_networks:
# This means CONF.expose_ipv6_gua_tenant_networks is enabled
if not driver_utils.is_ipv6_gua(ip):
continue
if not self._address_scope_allowed(ip,
subnet_info['address_scopes']):
continue
try:
if wire_utils.wire_lrp_port(
self.ovn_routing_tables_routes, ip,
cr_lrp_info.get('bridge_device'),
cr_lrp_info.get('bridge_vlan'),
self.ovn_routing_tables, cr_lrp_info.get('ips')):
self._exposed_ips.setdefault(
subnet_info['associated_router'], {}).update(
{ip: {
'bridge_device': cr_lrp_info.get('bridge_device'),
'bridge_vlan': cr_lrp_info.get('bridge_vlan')}})
else:
success = False
except Exception as e:
LOG.exception("Unexpected exception while wiring subnet CIDRs"
" %s: %s", ip, e)
success = False
return success
def _withdraw_router_lsp(self, ips, subnet_info, cr_lrp_info):
if not self._expose_tenant_networks:
return
for ip in ips:
if (not CONF.expose_tenant_networks and
not driver_utils.is_ipv6_gua(ip)):
# This means CONF.expose_ipv6_gua_tenant_networks is enabled
continue
if not self._address_scope_allowed(ip,
subnet_info['address_scopes']):
continue
try:
if wire_utils.unwire_lrp_port(
self.ovn_routing_tables_routes, ip,
cr_lrp_info.get('bridge_device'),
cr_lrp_info.get('bridge_vlan'),
self.ovn_routing_tables, cr_lrp_info.get('ips')):
if self._exposed_ips.get(
subnet_info['associated_router'], {}).get(ip):
self._exposed_ips[
subnet_info['associated_router']].pop(ip)
else:
return False
except Exception as e:
LOG.exception("Unexpected exception while unwiring subnet "
"CIDRs %s: %s", ip, e)
return False
return True
def _address_scope_allowed(self, ip, address_scopes):
if not self.allowed_address_scopes:
# No address scopes to filter on => announce everything
return True
# 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

View File

@ -369,7 +369,7 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
# 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)
constants.OVN_CIDRS_EXT_ID_KEY, "")
if ext_n_cidr:
ovn_lb_ip = ext_n_cidr.split(" ")[0].split("/")[0]
bgp_utils.announce_ips([ovn_lb_ip])
@ -390,7 +390,8 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
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)
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].strip().split(' ')[1:]
@ -581,10 +582,10 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
# 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)
constants.OVN_CIDRS_EXT_ID_KEY, "").split()
exposed_port = self._expose_provider_port(
ips, row.datapath, bridge_device, bridge_vlan, None,
[n_cidr])
n_cidr)
else:
exposed_port = self._expose_provider_port(ips,
row.datapath)
@ -721,11 +722,11 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
# 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)
constants.OVN_CIDRS_EXT_ID_KEY, "").split()
if n_cidr:
withdrawn_port = self._withdraw_provider_port(
ips, row.datapath, bridge_device, bridge_vlan, None,
[n_cidr])
n_cidr)
else:
withdrawn_port = self._withdraw_provider_port(ips,
row.datapath)

View File

@ -438,6 +438,18 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
ports.append(row)
return ports
def get_active_local_lrps(self, local_gateway_ports):
ports = []
cmd = self.db_find_rows('Logical_Switch_Port', ('up', '=', True),
('type', '=', constants.OVN_ROUTER_PORT_TYPE))
for row in cmd.execute(check_error=True):
if ((row.external_ids.get(constants.OVN_DEVICE_OWNER_EXT_ID_KEY) ==
constants.OVN_ROUTER_INTERFACE) and
(row.external_ids.get(constants.OVN_DEVICE_ID_EXT_ID_KEY)
in local_gateway_ports)):
ports.append(row)
return ports
# FIXME(ltomasbo): This can be removed once ovsdbapp version is >=2.3.0
def ls_get_localnet_ports(self, logical_switch, if_exists=True):
return LSGetLocalnetPortsCommand(self, logical_switch,
@ -714,7 +726,7 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
# This is depending on the external-id information added by
# neutron, regarding the neutron:cidrs
ip_info = row.external_ids.get(
constants.OVN_CIDRS_EXT_ID_KEY)
constants.OVN_CIDRS_EXT_ID_KEY, "")
if not ip_info:
continue
port_name = row.external_ids.get(

View File

@ -403,11 +403,14 @@ def _cleanup_wiring_underlay(idl, bridge_mappings, ovs_flows, exposed_ips,
ovn_ip_rules = linux_net.get_ovn_ip_rules(routing_tables.values())
if ovn_ip_rules:
for ip in expected_ips:
ip_version = linux_net.get_ip_version(ip)
if ip_version == constants.IP_VERSION_6:
ip_dst = "{}/128".format(ip)
if len(ip.split("/")) == 1:
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)
else:
ip_dst = "{}/32".format(ip)
ip_dst = ip
ovn_ip_rules.pop(ip_dst, None)
linux_net.delete_ip_rules(ovn_ip_rules)
@ -573,12 +576,29 @@ def _unwire_provider_port_ovn(ovn_idl, port_ips):
def wire_lrp_port(routing_tables_routes, ip, bridge_device, bridge_vlan,
routing_table, cr_lrp_ips):
routing_tables, cr_lrp_ips):
if CONF.exposing_method == constants.EXPOSE_METHOD_UNDERLAY:
return _wire_lrp_port_underlay(routing_tables_routes, ip,
bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
elif CONF.exposing_method == constants.EXPOSE_METHOD_OVN:
# TODO(ltomasbo): Add flow on br-ex(-X)
# ovs-ofctl add-flow br-ex
# "cookie=0xbadcaf2,ip,nw_dst=20.0.0.0/24,in_port=enp2s0,priority=100,
# actions=mod_dl_dst:$ENP2S0_MAC,output=$patch"
# Add router route to go through cr-lrp ip:
# ovn-nbctl lr-route-add bgp-router 20.0.0.0/24 172.16.100.143
# bgp-router-public
return
def _wire_lrp_port_underlay(routing_tables_routes, ip, bridge_device,
bridge_vlan, routing_tables, cr_lrp_ips):
if not bridge_device:
return False
LOG.debug("Adding IP Rules for network %s", ip)
try:
linux_net.add_ip_rule(ip, routing_table[bridge_device])
linux_net.add_ip_rule(ip, routing_tables[bridge_device])
except agent_exc.InvalidPortIP:
LOG.exception("Invalid IP to create a rule for the lrp (network "
"router interface) port: %s", ip)
@ -594,7 +614,7 @@ def wire_lrp_port(routing_tables_routes, ip, bridge_device, bridge_vlan,
linux_net.add_ip_route(
routing_tables_routes,
ip.split("/")[0],
routing_table[bridge_device],
routing_tables[bridge_device],
bridge_device,
vlan=bridge_vlan,
mask=ip.split("/")[1],
@ -605,12 +625,23 @@ def wire_lrp_port(routing_tables_routes, ip, bridge_device, bridge_vlan,
def unwire_lrp_port(routing_tables_routes, ip, bridge_device, bridge_vlan,
routing_table, cr_lrp_ips):
routing_tables, cr_lrp_ips):
if CONF.exposing_method == constants.EXPOSE_METHOD_UNDERLAY:
return _unwire_lrp_port_underlay(routing_tables_routes, ip,
bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
elif CONF.exposing_method == constants.EXPOSE_METHOD_OVN:
# TODO(ltomasbo): Remove flow(s) and router route
return
def _unwire_lrp_port_underlay(routing_tables_routes, ip, bridge_device,
bridge_vlan, routing_tables, cr_lrp_ips):
if not bridge_device:
return False
LOG.debug("Deleting IP Rules for network %s", ip)
try:
linux_net.del_ip_rule(ip, routing_table[bridge_device])
linux_net.del_ip_rule(ip, routing_tables[bridge_device])
except agent_exc.InvalidPortIP:
LOG.exception("Invalid IP to delete a rule for the "
"lrp (network router interface) port: %s", ip)
@ -624,7 +655,7 @@ def unwire_lrp_port(routing_tables_routes, ip, bridge_device, bridge_vlan,
linux_net.del_ip_route(
routing_tables_routes,
ip.split("/")[0],
routing_table[bridge_device],
routing_tables[bridge_device],
bridge_device,
vlan=bridge_vlan,
mask=ip.split("/")[1],

View File

@ -261,7 +261,8 @@ class TenantPortCreatedEvent(base_watcher.PortBindingChassisEvent):
# Handling the case for unknown MACs when configdrive is used
# instead of dhcp
if row.mac == ['unknown']:
n_cidrs = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
n_cidrs = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split()
if not n_cidrs:
return False
# single and dual-stack format
@ -280,7 +281,8 @@ class TenantPortCreatedEvent(base_watcher.PortBindingChassisEvent):
if row.mac == ['unknown']:
# Handling the case for unknown MACs when configdrive is used
# instead of dhcp
n_cidrs = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
n_cidrs = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"")
ips = [ip.split("/")[0] for ip in n_cidrs.split(" ")]
else:
ips = row.mac[0].split(' ')[1:]
@ -298,7 +300,8 @@ class TenantPortDeletedEvent(base_watcher.PortBindingChassisEvent):
if row.mac == ['unknown']:
# Handling the case for unknown MACs when configdrive is used
# instead of dhcp
n_cidrs = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
n_cidrs = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split()
if not n_cidrs:
return False
# single and dual-stack format
@ -324,7 +327,8 @@ class TenantPortDeletedEvent(base_watcher.PortBindingChassisEvent):
if row.mac == ['unknown']:
# Handling the case for unknown MACs when configdrive is used
# instead of dhcp
n_cidrs = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
n_cidrs = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"")
ips = [ip.split("/")[0] for ip in n_cidrs.split(" ")]
else:
ips = row.mac[0].split(' ')[1:]
@ -356,7 +360,8 @@ class OVNLBVIPPortEvent(base_watcher.PortBindingChassisEvent):
with _SYNC_STATE_LOCK.read_lock():
# This is depending on the external-id information added by
# neutron, regarding the neutron:cidrs
ext_n_cidr = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
ext_n_cidr = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"")
if not ext_n_cidr:
return
@ -402,7 +407,7 @@ class OVNLBMemberCreateEvent(base_watcher.OVNLBMemberEvent):
vip_port = self.agent.sb_idl.get_ovn_vip_port(row.name)
if not vip_port:
return
vip_ip = vip_port.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
vip_ip = vip_port.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY, "")
if not vip_ip:
return
vip_ip = vip_ip.strip().split(" ")[0].split("/")[0]

View File

@ -16,6 +16,7 @@ from oslo_concurrency import lockutils
from oslo_log import log as logging
from ovn_bgp_agent import constants
from ovn_bgp_agent.drivers.openstack.utils import driver_utils
from ovn_bgp_agent.drivers.openstack.watchers import base_watcher
@ -69,8 +70,8 @@ class LogicalSwitchPortProviderCreateEvent(base_watcher.LSPChassisEvent):
mac = row.addresses[0].strip().split(' ')[0]
ips_info = {
'mac': mac,
'cidrs': [
row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)],
'cidrs': row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split(),
'type': row.type,
'logical_switch': row.external_ids.get(
constants.OVN_LS_NAME_EXT_ID_KEY)
@ -136,8 +137,8 @@ class LogicalSwitchPortProviderDeleteEvent(base_watcher.LSPChassisEvent):
mac = row.addresses[0].strip().split(' ')[0]
ips_info = {
'mac': mac,
'cidrs': [
row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)],
'cidrs': row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split(),
'type': row.type,
'logical_switch': row.external_ids.get(
constants.OVN_LS_NAME_EXT_ID_KEY)
@ -327,9 +328,9 @@ class ChassisRedirectCreateEvent(base_watcher.LRPChassisEvent):
constants.OVN_STATUS_CHASSIS)
if old_hosting_chassis != hosting_chassis:
return True
return False
except (IndexError, AttributeError):
return False
return False
def _run(self, event, row, old):
with _SYNC_STATE_LOCK.read_lock():
@ -339,7 +340,9 @@ class ChassisRedirectCreateEvent(base_watcher.LRPChassisEvent):
'cidrs': row.networks,
'type': constants.OVN_CR_LRP_PORT_TYPE,
'logical_switch': row.external_ids.get(
constants.OVN_LS_NAME_EXT_ID_KEY)
constants.OVN_LS_NAME_EXT_ID_KEY),
'router': row.external_ids.get(
constants.OVN_LR_NAME_EXT_ID_KEY)
}
ips = [net.split("/")[0] for net in row.networks]
self.agent.expose_ip(ips, ips_info)
@ -365,9 +368,9 @@ class ChassisRedirectDeleteEvent(base_watcher.LRPChassisEvent):
if (hosting_chassis != old_hosting_chassis and
old_hosting_chassis == self.agent.chassis_id):
return True
return False
except (IndexError, AttributeError):
return False
return False
def _run(self, event, row, old):
with _SYNC_STATE_LOCK.read_lock():
@ -377,7 +380,145 @@ class ChassisRedirectDeleteEvent(base_watcher.LRPChassisEvent):
'cidrs': row.networks,
'type': constants.OVN_CR_LRP_PORT_TYPE,
'logical_switch': row.external_ids.get(
constants.OVN_LS_NAME_EXT_ID_KEY)
constants.OVN_LS_NAME_EXT_ID_KEY),
'router': row.external_ids.get(
constants.OVN_LR_NAME_EXT_ID_KEY)
}
ips = [net.split("/")[0] for net in row.networks]
self.agent.withdraw_ip(ips, ips_info)
class LogicalSwitchPortSubnetAttachEvent(base_watcher.LSPChassisEvent):
def __init__(self, bgp_agent):
events = (self.ROW_UPDATE,)
super(LogicalSwitchPortSubnetAttachEvent, self).__init__(
bgp_agent, events)
def match_fn(self, event, row, old):
try:
if row.type != constants.OVN_ROUTER_PORT_TYPE:
return False
# skip route_gateway port events
row_device_owner = row.external_ids.get(
constants.OVN_DEVICE_OWNER_EXT_ID_KEY)
if row_device_owner != constants.OVN_ROUTER_INTERFACE:
return False
if not bool(row.up[0]):
return False
associated_router = row.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY)
if associated_router not in self.agent.ovn_local_cr_lrps:
return False
if hasattr(old, 'up') and not bool(old.up[0]):
return True
if hasattr(old, 'external_ids'):
previous_associated_router = old.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY)
if (associated_router != previous_associated_router and
previous_associated_router not in
self.agent.ovn_local_cr_lrps):
return True
except (IndexError, AttributeError):
return False
return False
def _run(self, event, row, old):
with _SYNC_STATE_LOCK.read_lock():
ips = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split()
subnet_info = {
'associated_router': row.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY),
'address_scopes': driver_utils.get_addr_scopes(row)}
self.agent.expose_subnet(ips, subnet_info)
class LogicalSwitchPortSubnetDetachEvent(base_watcher.LSPChassisEvent):
def __init__(self, bgp_agent):
events = (self.ROW_UPDATE, self.ROW_DELETE,)
super(LogicalSwitchPortSubnetDetachEvent, self).__init__(
bgp_agent, events)
def match_fn(self, event, row, old):
try:
if row.type != constants.OVN_ROUTER_PORT_TYPE:
return False
# skip route_gateway port events
row_device_owner = row.external_ids.get(
constants.OVN_DEVICE_OWNER_EXT_ID_KEY)
if row_device_owner != constants.OVN_ROUTER_INTERFACE:
return False
associated_router = row.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY)
if event == self.ROW_DELETE:
if not bool(row.up[0]):
return False
if associated_router in self.agent.ovn_local_cr_lrps:
return True
return False
# ROW UPDATE
# We need to withdraw the subnet in the next cases:
# 1. same/local associated router and status moves from up to down
# 2. status changes to down and also associated router changes to a
# non local one
# 3. status is up (same) but associated router changes to a non
# local one
if hasattr(old, 'up'):
if not bool(old.up[0]):
return False
if hasattr(old, 'external_ids'):
previous_associated_router = old.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY)
if previous_associated_router in (
self.agent.ovn_local_cr_lrps):
return True
else:
if associated_router in self.agent.ovn_local_cr_lrps:
return True
else:
# no change in status
if not bool(row.up[0]):
# it was not exposed
return False
if hasattr(old, 'external_ids'):
previous_associated_router = old.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY)
if (previous_associated_router and
associated_router != previous_associated_router and
previous_associated_router in
self.agent.ovn_local_cr_lrps):
return True
except (IndexError, AttributeError):
return False
return False
def _run(self, event, row, old):
with _SYNC_STATE_LOCK.read_lock():
ips = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY,
"").split()
if event == self.ROW_DELETE:
subnet_info = {
'associated_router': row.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY),
'address_scopes': driver_utils.get_addr_scopes(row)}
else:
associated_router = row.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY)
if hasattr(old, 'external_ids'):
previous_associated_router = old.external_ids.get(
constants.OVN_DEVICE_ID_EXT_ID_KEY)
if previous_associated_router != associated_router:
associated_router = previous_associated_router
subnet_info = {
'associated_router': associated_router,
'address_scopes': driver_utils.get_addr_scopes(row)}
self.agent.withdraw_subnet(ips, subnet_info)

View File

@ -20,6 +20,7 @@ from oslo_config import cfg
from ovn_bgp_agent import constants
from ovn_bgp_agent.drivers.openstack import nb_ovn_bgp_driver
from ovn_bgp_agent.drivers.openstack.utils import bgp as bgp_utils
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
@ -35,6 +36,7 @@ class TestNBOVNBGPDriver(test_base.TestCase):
def setUp(self):
super(TestNBOVNBGPDriver, self).setUp()
CONF.set_override('expose_tenant_networks', True)
self.bridge = 'fake-bridge'
self.nb_bgp_driver = nb_ovn_bgp_driver.NBOVNBGPDriver()
self.nb_bgp_driver._post_start_event = mock.Mock()
@ -53,6 +55,11 @@ class TestNBOVNBGPDriver(test_base.TestCase):
self.fip = '172.24.4.33'
self.mac = 'aa:bb:cc:dd:ee:ff'
self.router1_info = {'bridge_device': self.bridge,
'bridge_vlan': 100,
'ips': ['172.24.4.11']}
self.nb_bgp_driver.ovn_local_cr_lrps = {
'router1': self.router1_info}
self.ovn_routing_tables = {
self.bridge: 100,
'br-vlan': 200}
@ -144,6 +151,11 @@ class TestNBOVNBGPDriver(test_base.TestCase):
crlrp_port = fakes.create_object({
'name': 'crlrp_port'})
lrp0 = fakes.create_object({
'name': 'lrp_port',
'external_ids': {
constants.OVN_CIDRS_EXT_ID_KEY: "10.0.0.1/24",
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'fake-router'}})
port0 = fakes.create_object({
'name': 'port-0',
'type': constants.OVN_VM_VIF_PORT_TYPE})
@ -151,10 +163,13 @@ class TestNBOVNBGPDriver(test_base.TestCase):
'name': 'port-1',
'type': constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE})
self.nb_idl.get_active_cr_lrp_on_chassis.return_value = [crlrp_port]
self.nb_idl.get_active_local_lrps.return_value = [lrp0]
self.nb_idl.get_active_lsp_on_chassis.return_value = [
port0, port1]
mock_ensure_crlrp_exposed = mock.patch.object(
self.nb_bgp_driver, '_ensure_crlrp_exposed').start()
mock_expose_subnet = mock.patch.object(
self.nb_bgp_driver, '_expose_subnet').start()
mock_ensure_lsp_exposed = mock.patch.object(
self.nb_bgp_driver, '_ensure_lsp_exposed').start()
mock_routing_bridge.return_value = ['fake-route']
@ -187,12 +202,15 @@ class TestNBOVNBGPDriver(test_base.TestCase):
mock_remove_flows.assert_has_calls(expected_calls)
mock_get_ip_rules.assert_called_once()
mock_ensure_crlrp_exposed.assert_called_once_with(crlrp_port)
mock_expose_subnet.assert_called_once_with(
["10.0.0.1/24"],
{'associated_router': 'fake-router',
'address_scopes': {4: None, 6: None}})
mock_ensure_lsp_exposed.assert_called_once_with(port0)
mock_del_exposed_ips.assert_called_once_with(
ips, CONF.bgp_nic)
mock_del_ip_rules.assert_called_once_with(fake_ip_rules)
mock_del_ip_routes.assert_called_once()
bridge = set(self.nb_bgp_driver.ovn_bridge_mappings.values()).pop()
mock_delete_vlan_dev.assert_called_once_with(bridge, 12)
@ -267,7 +285,7 @@ class TestNBOVNBGPDriver(test_base.TestCase):
mock_expose_fip.assert_not_called()
mock_expose_ip.assert_called_once_with(
['192.168.0.10'], 'fake_mac', 'test-ls', 'br-ex', 10,
constants.OVN_VM_VIF_PORT_TYPE, [None])
constants.OVN_VM_VIF_PORT_TYPE, [])
def test__ensure_crlrp_exposed(self):
port = fakes.create_object({
@ -286,7 +304,7 @@ class TestNBOVNBGPDriver(test_base.TestCase):
mock_expose_ip.assert_called_once_with(
['172.24.16.2'], 'fake_mac', 'test-ls', 'br-ex', 10,
constants.OVN_CR_LRP_PORT_TYPE, ['172.24.16.2/24'])
constants.OVN_CR_LRP_PORT_TYPE, ['172.24.16.2/24'], router=None)
def test__ensure_crlrp_exposed_no_networks(self):
port = fakes.create_object({
@ -419,6 +437,14 @@ class TestNBOVNBGPDriver(test_base.TestCase):
self.nb_bgp_driver, '_get_ls_localnet_info').start()
mock_get_ls_localnet_info.return_value = ('fake-localnet', 'br-ex', 10)
self.nb_bgp_driver.ovn_bridge_mappings = {'fake-localnet': 'br-ex'}
mock_expose_subnet = mock.patch.object(
self.nb_bgp_driver, '_expose_subnet').start()
lrp0 = fakes.create_object({
'name': 'lrp_port',
'external_ids': {
constants.OVN_CIDRS_EXT_ID_KEY: "10.0.0.1/24",
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'}})
self.nb_idl.get_active_local_lrps.return_value = [lrp0]
self.nb_bgp_driver.expose_ip(ips, ips_info)
@ -433,7 +459,8 @@ class TestNBOVNBGPDriver(test_base.TestCase):
self.nb_bgp_driver.ovn_provider_ls[ips_info['logical_switch']],
{'bridge_device': 'br-ex', 'bridge_vlan': 10,
'localnet': 'fake-localnet'})
if (ips_info['type'] == constants.OVN_VIRTUAL_VIF_PORT_TYPE and
if (ips_info['type'] in [constants.OVN_VIRTUAL_VIF_PORT_TYPE,
constants.OVN_CR_LRP_PORT_TYPE] and
ips_info['cidrs']):
mock_expose_provider_port.assert_called_once_with(
ips, 'fake-mac', 'test-ls', 'br-ex', 10, 'fake-localnet',
@ -442,6 +469,12 @@ class TestNBOVNBGPDriver(test_base.TestCase):
mock_expose_provider_port.assert_called_once_with(
ips, 'fake-mac', 'test-ls', 'br-ex', 10, 'fake-localnet')
if (ips_info.get('router') and
ips_info['type'] == constants.OVN_CR_LRP_PORT_TYPE):
mock_expose_subnet.assert_called_once_with(
["10.0.0.1/24"], {'associated_router': 'router1',
'address_scopes': {4: None, 6: None}})
def test_expose_ip(self):
ips = [self.ipv4, self.ipv6]
ips_info = {
@ -475,6 +508,18 @@ class TestNBOVNBGPDriver(test_base.TestCase):
self._test_expose_ip(ips, ips_info)
def test_expose_ip_router(self):
ips = [self.ipv4, self.ipv6]
ips_info = {
'mac': 'fake-mac',
'cidrs': ['test-cidr'],
'type': constants.OVN_CR_LRP_PORT_TYPE,
'logical_switch': 'test-ls',
'router': 'router1'
}
self._test_expose_ip(ips, ips_info)
@mock.patch.object(linux_net, 'get_ip_version')
def _test_withdraw_ip(self, ips, ips_info, provider, mock_ip_version):
mock_withdraw_provider_port = mock.patch.object(
@ -489,6 +534,15 @@ class TestNBOVNBGPDriver(test_base.TestCase):
else:
mock_get_ls_localnet_info.return_value = (None, None, None)
mock_withdraw_subnet = mock.patch.object(
self.nb_bgp_driver, '_withdraw_subnet').start()
lrp0 = fakes.create_object({
'name': 'lrp_port',
'external_ids': {
constants.OVN_CIDRS_EXT_ID_KEY: "10.0.0.1/24",
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'}})
self.nb_idl.get_active_local_lrps.return_value = [lrp0]
self.nb_bgp_driver.withdraw_ip(ips, ips_info)
if not ips_info['logical_switch']:
@ -503,7 +557,8 @@ class TestNBOVNBGPDriver(test_base.TestCase):
mock_get_ls_localnet_info.assert_called_once_with(
ips_info['logical_switch'])
if (ips_info['type'] == constants.OVN_VIRTUAL_VIF_PORT_TYPE and
if (ips_info['type'] in [constants.OVN_VIRTUAL_VIF_PORT_TYPE,
constants.OVN_CR_LRP_PORT_TYPE] and
ips_info['cidrs']):
mock_withdraw_provider_port.assert_called_once_with(
ips, 'test-ls', 'br-ex', 10, ips_info['cidrs'])
@ -511,6 +566,11 @@ class TestNBOVNBGPDriver(test_base.TestCase):
mock_withdraw_provider_port.assert_called_once_with(
ips, 'test-ls', 'br-ex', 10)
if ips_info.get('router'):
mock_withdraw_subnet.assert_called_once_with(
["10.0.0.1/24"], {'associated_router': 'router1',
'address_scopes': {4: None, 6: None}})
def test_withdraw_ip(self):
ips = [self.ipv4, self.ipv6]
ips_info = {
@ -555,6 +615,18 @@ class TestNBOVNBGPDriver(test_base.TestCase):
self._test_withdraw_ip(ips, ips_info, True)
def test_withdraw_ip_router(self):
ips = [self.ipv4, self.ipv6]
ips_info = {
'mac': 'fake-mac',
'cidrs': ['test-cidr'],
'type': constants.OVN_CR_LRP_PORT_TYPE,
'logical_switch': 'test-ls',
'router': 'router1'
}
self._test_withdraw_ip(ips, ips_info, True)
def test__get_ls_localnet_info(self):
logical_switch = 'lswitch1'
fake_localnet_port = fakes.create_object({
@ -686,3 +758,214 @@ class TestNBOVNBGPDriver(test_base.TestCase):
self.nb_bgp_driver.withdraw_fip(ip, row)
mock_withdraw_provider_port.assert_not_called()
def test_expose_subnet(self):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'router1',
'address_scopes': {4: None, 6: None}}
mock_expose_router_lsp = mock.patch.object(
self.nb_bgp_driver, '_expose_router_lsp').start()
self.nb_bgp_driver.expose_subnet(ips, subnet_info)
mock_expose_router_lsp.assert_called_once_with(
ips, subnet_info, self.router1_info)
def test_expose_subnet_no_router(self):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': None,
'address_scopes': {4: None, 6: None}}
mock_expose_router_lsp = mock.patch.object(
self.nb_bgp_driver, '_expose_router_lsp').start()
self.nb_bgp_driver.expose_subnet(ips, subnet_info)
mock_expose_router_lsp.assert_not_called()
def test_expose_subnet_no_cr_lrp(self):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
mock_expose_router_lsp = mock.patch.object(
self.nb_bgp_driver, '_expose_router_lsp').start()
self.nb_bgp_driver.expose_subnet(ips, subnet_info)
mock_expose_router_lsp.assert_not_called()
def test_withdraw_subnet(self):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'router1',
'address_scopes': {4: None, 6: None}}
mock_withdraw_router_lsp = mock.patch.object(
self.nb_bgp_driver, '_withdraw_router_lsp').start()
self.nb_bgp_driver.withdraw_subnet(ips, subnet_info)
mock_withdraw_router_lsp.assert_called_once_with(
ips, subnet_info, self.router1_info)
def test_withdraw_subnet_no_router(self):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': None,
'address_scopes': {4: None, 6: None}}
mock_withdraw_router_lsp = mock.patch.object(
self.nb_bgp_driver, '_withdraw_router_lsp').start()
self.nb_bgp_driver.withdraw_subnet(ips, subnet_info)
mock_withdraw_router_lsp.assert_not_called()
def test_withdraw_subnet_no_cr_lrp(self):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
mock_withdraw_router_lsp = mock.patch.object(
self.nb_bgp_driver, '_withdraw_router_lsp').start()
self.nb_bgp_driver.withdraw_subnet(ips, subnet_info)
mock_withdraw_router_lsp.assert_not_called()
@mock.patch.object(wire_utils, 'wire_lrp_port')
def test__expose_router_lsp(self, mock_wire):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
ret = self.nb_bgp_driver._expose_router_lsp(ips, subnet_info,
self.router1_info)
self.assertTrue(ret)
mock_wire.assert_called_once_with(
mock.ANY, ips[0], self.router1_info['bridge_device'],
self.router1_info['bridge_vlan'], mock.ANY,
self.router1_info['ips'])
@mock.patch.object(wire_utils, 'wire_lrp_port')
def test__expose_router_lsp_exception(self, mock_wire):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
mock_wire.side_effect = Exception
ret = self.nb_bgp_driver._expose_router_lsp(ips, subnet_info,
self.router1_info)
self.assertFalse(ret)
mock_wire.assert_called_once_with(
mock.ANY, ips[0], self.router1_info['bridge_device'],
self.router1_info['bridge_vlan'], mock.ANY,
self.router1_info['ips'])
@mock.patch.object(wire_utils, 'wire_lrp_port')
def test__expose_router_lsp_no_tenants(self, mock_wire):
CONF.set_override('expose_tenant_networks', False)
self.addCleanup(CONF.clear_override, 'expose_tenant_networks')
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
ret = self.nb_bgp_driver._expose_router_lsp(ips, subnet_info,
self.router1_info)
self.assertTrue(ret)
mock_wire.assert_not_called()
@mock.patch.object(driver_utils, 'is_ipv6_gua')
@mock.patch.object(wire_utils, 'wire_lrp_port')
def test__expose_router_lsp_no_tenants_but_gua(self, mock_wire, mock_gua):
CONF.set_override('expose_tenant_networks', False)
self.addCleanup(CONF.clear_override, 'expose_tenant_networks')
CONF.set_override('expose_ipv6_gua_tenant_networks', True)
self.addCleanup(CONF.clear_override, 'expose_ipv6_gua_tenant_networks')
ips = ['10.0.0.1/24', '2002::1/64']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
mock_gua.side_effect = [False, True]
ret = self.nb_bgp_driver._expose_router_lsp(ips, subnet_info,
self.router1_info)
self.assertTrue(ret)
mock_wire.assert_called_once_with(
mock.ANY, ips[1], self.router1_info['bridge_device'],
self.router1_info['bridge_vlan'], mock.ANY,
self.router1_info['ips'])
@mock.patch.object(wire_utils, 'unwire_lrp_port')
def test__withdraw_router_lsp(self, mock_unwire):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
ret = self.nb_bgp_driver._withdraw_router_lsp(ips, subnet_info,
self.router1_info)
self.assertTrue(ret)
mock_unwire.assert_called_once_with(
mock.ANY, ips[0], self.router1_info['bridge_device'],
self.router1_info['bridge_vlan'], mock.ANY,
self.router1_info['ips'])
@mock.patch.object(wire_utils, 'unwire_lrp_port')
def test__withdraw_router_lsp_exception(self, mock_unwire):
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
mock_unwire.side_effect = Exception
ret = self.nb_bgp_driver._withdraw_router_lsp(ips, subnet_info,
self.router1_info)
self.assertFalse(ret)
mock_unwire.assert_called_once_with(
mock.ANY, ips[0], self.router1_info['bridge_device'],
self.router1_info['bridge_vlan'], mock.ANY,
self.router1_info['ips'])
@mock.patch.object(wire_utils, 'unwire_lrp_port')
def test__withdraw_router_lsp_no_tenants(self, mock_unwire):
CONF.set_override('expose_tenant_networks', False)
self.addCleanup(CONF.clear_override, 'expose_tenant_networks')
ips = ['10.0.0.1/24']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
ret = self.nb_bgp_driver._withdraw_router_lsp(ips, subnet_info,
self.router1_info)
self.assertTrue(ret)
mock_unwire.assert_not_called()
@mock.patch.object(driver_utils, 'is_ipv6_gua')
@mock.patch.object(wire_utils, 'unwire_lrp_port')
def test__withdraw_router_lsp_no_tenants_but_gua(self, mock_unwire,
mock_gua):
CONF.set_override('expose_tenant_networks', False)
self.addCleanup(CONF.clear_override, 'expose_tenant_networks')
CONF.set_override('expose_ipv6_gua_tenant_networks', True)
self.addCleanup(CONF.clear_override, 'expose_ipv6_gua_tenant_networks')
ips = ['10.0.0.1/24', '2002::1/64']
subnet_info = {
'associated_router': 'other-router',
'address_scopes': {4: None, 6: None}}
mock_gua.side_effect = [False, True]
ret = self.nb_bgp_driver._withdraw_router_lsp(ips, subnet_info,
self.router1_info)
self.assertTrue(ret)
mock_unwire.assert_called_once_with(
mock.ANY, ips[1], self.router1_info['bridge_device'],
self.router1_info['bridge_vlan'], mock.ANY,
self.router1_info['ips'])

View File

@ -150,6 +150,35 @@ class TestOvsdbNbOvnIdl(test_base.TestCase):
self.nb_idl.db_list_rows.assert_called_once_with(
'Logical_Router_Port')
def test_get_active_local_lrps(self):
local_gateway_ports = ['router1']
row1 = fakes.create_object({
'external_ids': {
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
constants.OVN_ROUTER_INTERFACE,
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'
}})
row2 = fakes.create_object({
'external_ids': {
constants.OVN_DEVICE_OWNER_EXT_ID_KEY: 'other_device_owner',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'
}})
row3 = fakes.create_object({
'external_ids': {
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
constants.OVN_ROUTER_INTERFACE,
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'other_router'
}})
self.nb_idl.db_find_rows.return_value.execute.return_value = [
row1, row2, row3]
ret = self.nb_idl.get_active_local_lrps(local_gateway_ports)
self.assertEqual([row1], ret)
self.nb_idl.db_find_rows.assert_called_once_with(
'Logical_Switch_Port',
('up', '=', True), ('type', '=', constants.OVN_ROUTER_PORT_TYPE))
class TestOvsdbSbOvnIdl(test_base.TestCase):

View File

@ -21,6 +21,7 @@ from ovn_bgp_agent import constants
from ovn_bgp_agent.drivers.openstack.utils import ovn as ovn_utils
from ovn_bgp_agent.drivers.openstack.utils import ovs as ovs_utils
from ovn_bgp_agent.drivers.openstack.utils import wire
from ovn_bgp_agent import exceptions as agent_exc
from ovn_bgp_agent.tests import base as test_base
from ovn_bgp_agent.utils import linux_net
@ -453,3 +454,146 @@ class TestWire(test_base.TestCase):
port_ips = []
wire._unwire_provider_port_ovn(self.nb_idl, port_ips)
m_cmds.assert_not_called()
@mock.patch.object(wire, '_wire_lrp_port_underlay')
def test_wire_lrp_port_underlay(self, mock_underlay):
routing_tables_routes = {}
ip = 'fake-ip'
bridge_device = 'fake-bridge'
bridge_vlan = '101'
routing_tables = {'fake-bridge': 5}
cr_lrp_ips = ['fake-crlrp-ip']
wire.wire_lrp_port(routing_tables_routes, ip, bridge_device,
bridge_vlan, routing_tables, cr_lrp_ips)
mock_underlay.assert_called_once_with(
routing_tables_routes, ip, bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
@mock.patch.object(wire, '_unwire_lrp_port_underlay')
def test_unwire_lrp_port_underlay(self, mock_underlay):
routing_tables_routes = {}
ip = 'fake-ip'
bridge_device = 'fake-bridge'
bridge_vlan = '101'
routing_tables = {'fake-bridge': 5}
cr_lrp_ips = ['fake-crlrp-ip']
wire.unwire_lrp_port(routing_tables_routes, ip, bridge_device,
bridge_vlan, routing_tables, cr_lrp_ips)
mock_underlay.assert_called_once_with(
routing_tables_routes, ip, bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
@mock.patch.object(linux_net, 'add_ip_route')
@mock.patch.object(linux_net, 'get_ip_version')
@mock.patch.object(linux_net, 'add_ip_rule')
def test__wire_lrp_port_underlay(self, m_ip_rule, m_ip_version,
m_ip_route):
routing_tables_routes = {}
ip = '10.0.0.1/24'
bridge_device = 'fake-bridge'
bridge_vlan = '101'
routing_tables = {'fake-bridge': 5}
cr_lrp_ips = ['fake-crlrp-ip']
ret = wire._wire_lrp_port_underlay(routing_tables_routes, ip,
bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
self.assertTrue(ret)
m_ip_rule.assert_called_once_with(ip, 5)
m_ip_route.assert_called_once_with(
routing_tables_routes, '10.0.0.1', 5, 'fake-bridge',
vlan='101', mask='24', via='fake-crlrp-ip')
@mock.patch.object(linux_net, 'add_ip_rule')
def test__wire_lrp_port_underlay_no_bridge(self, m_ip_rule):
routing_tables_routes = {}
ip = 'fake-ip'
bridge_device = None
bridge_vlan = None
routing_tables = {'fake-bridge': 5}
cr_lrp_ips = ['fake-crlrp-ip']
ret = wire._wire_lrp_port_underlay(routing_tables_routes, ip,
bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
self.assertFalse(ret)
m_ip_rule.assert_not_called()
@mock.patch.object(linux_net, 'get_ip_version')
@mock.patch.object(linux_net, 'add_ip_rule')
def test__wire_lrp_port_underlay_invalid_ip(self, m_ip_rule, m_ip_version):
routing_tables_routes = {}
ip = 'fake-ip'
bridge_device = 'fake-bridge'
bridge_vlan = '101'
routing_tables = {'fake-bridge': 5}
cr_lrp_ips = ['fake-crlrp-ip']
m_ip_rule.side_effect = agent_exc.InvalidPortIP(ip=ip)
ret = wire._wire_lrp_port_underlay(routing_tables_routes, ip,
bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
self.assertFalse(ret)
m_ip_rule.assert_called_once_with(ip, 5)
m_ip_version.assert_not_called()
@mock.patch.object(linux_net, 'del_ip_route')
@mock.patch.object(linux_net, 'get_ip_version')
@mock.patch.object(linux_net, 'del_ip_rule')
def test__unwire_lrp_port_underlay(self, m_ip_rule, m_ip_version,
m_ip_route):
routing_tables_routes = {}
ip = '10.0.0.1/24'
bridge_device = 'fake-bridge'
bridge_vlan = '101'
routing_tables = {'fake-bridge': 5}
cr_lrp_ips = ['fake-crlrp-ip']
ret = wire._unwire_lrp_port_underlay(routing_tables_routes, ip,
bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
self.assertTrue(ret)
m_ip_rule.assert_called_once_with(ip, 5)
m_ip_route.assert_called_once_with(
routing_tables_routes, '10.0.0.1', 5, 'fake-bridge',
vlan='101', mask='24', via='fake-crlrp-ip')
@mock.patch.object(linux_net, 'del_ip_rule')
def test__unwire_lrp_port_underlay_no_bridge(self, m_ip_rule):
routing_tables_routes = {}
ip = 'fake-ip'
bridge_device = None
bridge_vlan = None
routing_tables = {'fake-bridge': 5}
cr_lrp_ips = ['fake-crlrp-ip']
ret = wire._unwire_lrp_port_underlay(routing_tables_routes, ip,
bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
self.assertFalse(ret)
m_ip_rule.assert_not_called()
@mock.patch.object(linux_net, 'get_ip_version')
@mock.patch.object(linux_net, 'del_ip_rule')
def test__unwire_lrp_port_underlay_invalid_ip(self, m_ip_rule,
m_ip_version):
routing_tables_routes = {}
ip = 'fake-ip'
bridge_device = 'fake-bridge'
bridge_vlan = '101'
routing_tables = {'fake-bridge': 5}
cr_lrp_ips = ['fake-crlrp-ip']
m_ip_rule.side_effect = agent_exc.InvalidPortIP(ip=ip)
ret = wire._unwire_lrp_port_underlay(routing_tables_routes, ip,
bridge_device, bridge_vlan,
routing_tables, cr_lrp_ips)
self.assertFalse(ret)
m_ip_rule.assert_called_once_with(ip, 5)
m_ip_version.assert_not_called()

View File

@ -95,9 +95,9 @@ class TestLogicalSwitchPortProviderCreateEvent(test_base.TestCase):
up=[True])
ips_info = {
'mac': 'mac',
'cidrs': [None],
'cidrs': [],
'type': constants.OVN_VM_VIF_PORT_TYPE,
'logical_switch': 'test-ls'
'logical_switch': 'test-ls',
}
self.event.run(mock.Mock(), row, mock.Mock())
self.agent.expose_ip.assert_called_once_with(['192.168.0.1'], ips_info)
@ -188,7 +188,7 @@ class TestLogicalSwitchPortProviderDeleteEvent(test_base.TestCase):
up=[True])
ips_info = {
'mac': 'mac',
'cidrs': [None],
'cidrs': [],
'type': constants.OVN_VM_VIF_PORT_TYPE,
'logical_switch': 'test-ls'
}
@ -550,7 +550,8 @@ class TestChassisRedirectCreateEvent(test_base.TestCase):
ips_info = {'mac': 'fake-mac',
'cidrs': ['192.168.0.2/24'],
'type': constants.OVN_CR_LRP_PORT_TYPE,
'logical_switch': 'test-ls'}
'logical_switch': 'test-ls',
'router': None}
self.event.run(None, row, None)
self.agent.expose_ip.assert_called_once_with(['192.168.0.2'], ips_info)
@ -622,7 +623,8 @@ class TestChassisRedirectDeleteEvent(test_base.TestCase):
ips_info = {'mac': 'fake-mac',
'cidrs': ['192.168.0.2/24'],
'type': constants.OVN_CR_LRP_PORT_TYPE,
'logical_switch': 'test-ls'}
'logical_switch': 'test-ls',
'router': None}
self.event.run(None, row, None)
self.agent.withdraw_ip.assert_called_once_with(['192.168.0.2'],
ips_info)
@ -635,3 +637,279 @@ class TestChassisRedirectDeleteEvent(test_base.TestCase):
external_ids={constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls'})
self.event.run(None, row, None)
self.agent.withdraw_ip.assert_not_called()
class TestLogicalSwitchPortSubnetAttachEvent(test_base.TestCase):
def setUp(self):
super(TestLogicalSwitchPortSubnetAttachEvent, self).setUp()
self.chassis = 'fake-chassis'
self.chassis_id = 'fake-chassis-id'
self.agent = mock.Mock(chassis=self.chassis,
chassis_id=self.chassis_id)
self.agent.ovn_local_cr_lrps = {
'router1': {'bridge_device': 'br-ex',
'bridge_vlan': None,
'ips': ['172.24.16.2']}}
self.event = nb_bgp_watcher.LogicalSwitchPortSubnetAttachEvent(
self.agent)
def test_match_fn(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
old = utils.create_row(up=[False])
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
def test_match_fn_associate_router(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
old = utils.create_row(
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface'})
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
def test_match_fn_exception(self):
row = utils.create_row(
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
def test_match_fn_wrong_type(self):
row = utils.create_row(
type=constants.OVN_VM_VIF_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
def test_match_fn_wrong_device_owner(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_gateway',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
def test_match_fn_not_up(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[False])
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
def test_match_fn_not_local_crlrp(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router2'},
up=[True])
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
def test_run(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_CIDRS_EXT_ID_KEY: "192.168.24.1/24",
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
subnet_info = {
'associated_router': 'router1',
'address_scopes': {4: None, 6: None}}
self.event.run(None, row, None)
self.agent.expose_subnet.assert_called_once_with(["192.168.24.1/24"],
subnet_info)
class TestLogicalSwitchPortSubnetDetachEventt(test_base.TestCase):
def setUp(self):
super(TestLogicalSwitchPortSubnetDetachEventt, self).setUp()
self.chassis = 'fake-chassis'
self.chassis_id = 'fake-chassis-id'
self.agent = mock.Mock(chassis=self.chassis,
chassis_id=self.chassis_id)
self.agent.ovn_local_cr_lrps = {
'router1': {'bridge_device': 'br-ex',
'bridge_vlan': None,
'ips': ['172.24.16.2']}}
self.event = nb_bgp_watcher.LogicalSwitchPortSubnetDetachEvent(
self.agent)
def test_match_fn(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[False])
old = utils.create_row(up=[True])
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
def test_match_fn_delete(self):
event = self.event.ROW_DELETE
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
self.assertTrue(self.event.match_fn(event, row, mock.Mock()))
def test_match_fn_delete_down(self):
event = self.event.ROW_DELETE
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[False])
self.assertFalse(self.event.match_fn(event, row, mock.Mock()))
def test_match_fn_disassociate_router(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface'},
up=[True])
old = utils.create_row(
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'})
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
def test_match_fn_exception(self):
row = utils.create_row(
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
def test_match_fn_wrong_type(self):
row = utils.create_row(
type=constants.OVN_VM_VIF_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
def test_match_fn_wrong_device_owner(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_gateway',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
def test_match_fn_not_up(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
old = utils.create_row(up=[False])
self.assertFalse(self.event.match_fn(mock.Mock(), row, old))
def test_match_fn_not_local_crlrp(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface'},
up=[True])
old = utils.create_row(
external_ids={
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'other_router'})
self.assertFalse(self.event.match_fn(mock.Mock(), row, old))
def test_run(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_CIDRS_EXT_ID_KEY: "192.168.24.1/24",
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface'},
up=[True])
old = utils.create_row(
external_ids={
constants.OVN_CIDRS_EXT_ID_KEY: "192.168.24.1/24",
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'})
subnet_info = {
'associated_router': 'router1',
'address_scopes': {4: None, 6: None}}
self.event.run(None, row, old)
self.agent.withdraw_subnet.assert_called_once_with(
["192.168.24.1/24"], subnet_info)
def test_run_no_old_external_ids(self):
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_CIDRS_EXT_ID_KEY: "192.168.24.1/24",
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
old = utils.create_row()
subnet_info = {
'associated_router': 'router1',
'address_scopes': {4: None, 6: None}}
self.event.run(None, row, old)
self.agent.withdraw_subnet.assert_called_once_with(
["192.168.24.1/24"], subnet_info)
def test_run_delete(self):
event = self.event.ROW_DELETE
row = utils.create_row(
type=constants.OVN_ROUTER_PORT_TYPE,
external_ids={
constants.OVN_CIDRS_EXT_ID_KEY: "192.168.24.1/24",
constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
'network:router_interface',
constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'},
up=[True])
subnet_info = {
'associated_router': 'router1',
'address_scopes': {4: None, 6: None}}
self.event.run(event, row, mock.Mock())
self.agent.withdraw_subnet.assert_called_once_with(
["192.168.24.1/24"], subnet_info)