Merge "Add a new driver that uses NB DB instead of SB DB"
This commit is contained in:
commit
042016d3d7
@ -45,18 +45,33 @@ agent_opts = [
|
||||
help='The connection string for the native OVSDB backend.\n'
|
||||
'Use tcp:IP:PORT for TCP connection.\n'
|
||||
'Use unix:FILE for unix domain socket connection.'),
|
||||
cfg.IntOpt('ovsdb_connection_timeout',
|
||||
default=180,
|
||||
help='Timeout in seconds for the OVSDB connection transaction'),
|
||||
cfg.StrOpt('ovn_sb_private_key',
|
||||
default='/etc/pki/tls/private/ovn_controller.key',
|
||||
default='/etc/pki/tls/private/ovn_bgp_agent.key',
|
||||
help='The PEM file with private key for SSL connection to '
|
||||
'OVN-SB-DB'),
|
||||
cfg.StrOpt('ovn_sb_certificate',
|
||||
default='/etc/pki/tls/certs/ovn_controller.crt',
|
||||
default='/etc/pki/tls/certs/ovn_bgp_agent.crt',
|
||||
help='The PEM file with certificate that certifies the '
|
||||
'private key specified in ovn_sb_private_key'),
|
||||
cfg.StrOpt('ovn_sb_ca_cert',
|
||||
default='/etc/ipa/ca.crt',
|
||||
help='The PEM file with CA certificate that OVN should use to'
|
||||
' verify certificates presented to it by SSL peers'),
|
||||
cfg.StrOpt('ovn_nb_private_key',
|
||||
default='/etc/pki/tls/private/ovn_bgp_agent.key',
|
||||
help='The PEM file with private key for SSL connection to '
|
||||
'OVN-NB-DB'),
|
||||
cfg.StrOpt('ovn_nb_certificate',
|
||||
default='/etc/pki/tls/certs/ovn_bgp_agent.crt',
|
||||
help='The PEM file with certificate that certifies the '
|
||||
'private key specified in ovn_nb_private_key'),
|
||||
cfg.StrOpt('ovn_nb_ca_cert',
|
||||
default='/etc/ipa/ca.crt',
|
||||
help='The PEM file with CA certificate that OVN should use to'
|
||||
' verify certificates presented to it by SSL peers'),
|
||||
cfg.StrOpt('bgp_AS',
|
||||
default='64999',
|
||||
help='AS number to be used by the Agent when running in BGP '
|
||||
|
@ -19,9 +19,13 @@ OVN_VM_VIF_PORT_TYPE = ""
|
||||
OVN_PATCH_VIF_PORT_TYPE = "patch"
|
||||
OVN_CHASSISREDIRECT_VIF_PORT_TYPE = "chassisredirect"
|
||||
OVN_LOCALNET_VIF_PORT_TYPE = "localnet"
|
||||
OVN_DNAT_AND_SNAT = "dnat_and_snat"
|
||||
|
||||
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_FIP_EXT_ID_KEY = 'neutron:port_fip'
|
||||
OVN_FIP_NET_EXT_ID_KEY = 'neutron:fip_network_id'
|
||||
LB_VIP_PORT_PREFIX = "ovn-lb-vip-"
|
||||
|
||||
OVS_RULE_COOKIE = "999"
|
||||
@ -61,3 +65,5 @@ SUBNET_POOL_ADDR_SCOPE6 = "neutron:subnet_pool_addr_scope6"
|
||||
|
||||
EXPOSE = "expose"
|
||||
WITHDRAW = "withdraw"
|
||||
|
||||
OVN_REQUESTED_CHASSIS = "requested-chassis"
|
||||
|
460
ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py
Normal file
460
ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py
Normal file
@ -0,0 +1,460 @@
|
||||
# Copyright 2023 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 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 bgp as bgp_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.utils import wire as wire_utils
|
||||
from ovn_bgp_agent.drivers.openstack.watchers import nb_bgp_watcher as watcher
|
||||
from ovn_bgp_agent.utils import linux_net
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
# LOG.setLevel(logging.DEBUG)
|
||||
# logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
OVN_TABLES = ["Logical_Switch_Port", "NAT", "Logical_Switch"]
|
||||
|
||||
|
||||
class NBOVNBGPDriver(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.ovn_fips = {} # {'fip': {'bridge_device': X, 'bridge_vlan': Y}}
|
||||
# {'ls_name': {'bridge_device': X, 'bridge_vlan': Y}}
|
||||
self.ovn_provider_ls = {}
|
||||
# dict instead of list to speed up look ups
|
||||
self.ovn_tenant_ls = {} # {'ls_name': True}
|
||||
|
||||
self._nb_idl = None
|
||||
self._post_start_event = threading.Event()
|
||||
|
||||
@property
|
||||
def nb_idl(self):
|
||||
if not self._nb_idl:
|
||||
self._post_start_event.wait()
|
||||
return self._nb_idl
|
||||
|
||||
@nb_idl.setter
|
||||
def nb_idl(self, val):
|
||||
self._nb_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()
|
||||
# NOTE(ltomasbo): remote should point to NB DB port instead of SB DB,
|
||||
# so changing 6642 by 6641
|
||||
self.ovn_remote = self.ovs_idl.get_ovn_remote().replace(":6642",
|
||||
":6641")
|
||||
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_start_event.clear()
|
||||
self.nb_idl = ovn.OvnNbIdl(
|
||||
self.ovn_remote,
|
||||
tables=OVN_TABLES,
|
||||
events=events).start()
|
||||
# Now IDL connections can be safely used
|
||||
self._post_start_event.set()
|
||||
|
||||
def _get_events(self):
|
||||
events = set(["LogicalSwitchPortProviderCreateEvent",
|
||||
"LogicalSwitchPortProviderDeleteEvent",
|
||||
"LogicalSwitchPortFIPCreateEvent",
|
||||
"LogicalSwitchPortFIPDeleteEvent",
|
||||
"LocalnetCreateDeleteEvent"])
|
||||
if self._expose_tenant_networks:
|
||||
events.update([])
|
||||
return events
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
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()
|
||||
self.ovn_provider_ls = {}
|
||||
self.ovn_tenant_ls = {}
|
||||
|
||||
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,
|
||||
CONF.bgp_vrf_table_id))
|
||||
vlan_tag = self.nb_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.nb_idl.get_active_ports_on_chassis(self.chassis)
|
||||
for port in ports:
|
||||
if port.type not in [constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE]:
|
||||
continue
|
||||
self._ensure_port_exposed(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_port_exposed(self, port, exposed_ips, ovn_ip_rules):
|
||||
port_fip = port.external_ids.get(constants.OVN_FIP_EXT_ID_KEY)
|
||||
if port_fip:
|
||||
external_ip, ls_name = self.get_port_external_ip_and_ls(port.name)
|
||||
if not external_ip or not ls_name:
|
||||
return
|
||||
if self._expose_fip(external_ip, ls_name):
|
||||
ip_version = linux_net.get_ip_version(external_ip)
|
||||
if ip_version == constants.IP_VERSION_6:
|
||||
ip_dst = "{}/128".format(external_ip)
|
||||
else:
|
||||
ip_dst = "{}/32".format(external_ip)
|
||||
if external_ip in exposed_ips:
|
||||
exposed_ips.remove(external_ip)
|
||||
ovn_ip_rules.pop(ip_dst, None)
|
||||
return
|
||||
|
||||
logical_switch = port.external_ids.get(
|
||||
constants.OVN_LS_NAME_EXT_ID_KEY)
|
||||
if not logical_switch:
|
||||
return
|
||||
if self.ovn_tenant_ls.get(logical_switch):
|
||||
return
|
||||
|
||||
bridge_info = self.ovn_provider_ls.get(logical_switch)
|
||||
if bridge_info:
|
||||
# already known provider ls
|
||||
bridge_device = bridge_info['bridge_device']
|
||||
bridge_vlan = bridge_info['bridge_vlan']
|
||||
else:
|
||||
bridge_device, bridge_vlan = self._get_ls_localnet_info(
|
||||
logical_switch)
|
||||
if not bridge_device:
|
||||
# This means it is not a provider network
|
||||
self.ovn_tenant_ls[logical_switch] = True
|
||||
return False
|
||||
self.ovn_provider_ls[logical_switch] = {
|
||||
'bridge_device': bridge_device,
|
||||
'bridge_vlan': bridge_vlan}
|
||||
ips = port.addresses[0].strip().split(' ')[1:]
|
||||
ips_adv = self._expose_ip(ips, bridge_device, bridge_vlan, port.type,
|
||||
port.external_ids.get(
|
||||
constants.OVN_CIDRS_EXT_ID_KEY))
|
||||
for ip in ips_adv:
|
||||
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)
|
||||
if ip in exposed_ips:
|
||||
exposed_ips.remove(ip)
|
||||
ovn_ip_rules.pop(ip_dst, None)
|
||||
|
||||
def _expose_provider_port(self, port_ips, bridge_device, bridge_vlan,
|
||||
proxy_cidrs=None):
|
||||
# Connect to OVN
|
||||
if wire_utils.wire_provider_port(
|
||||
self.ovn_routing_tables_routes, port_ips, bridge_device,
|
||||
bridge_vlan, self.ovn_routing_tables[bridge_device],
|
||||
proxy_cidrs):
|
||||
# Expose the IP now that it is connected
|
||||
bgp_utils.announce_ips(port_ips)
|
||||
|
||||
def _withdraw_provider_port(self, port_ips, bridge_device, bridge_vlan,
|
||||
proxy_cidrs=None):
|
||||
# Withdraw IP before disconnecting it
|
||||
bgp_utils.withdraw_ips(port_ips)
|
||||
|
||||
# Disconnect IP from OVN
|
||||
wire_utils.unwire_provider_port(
|
||||
self.ovn_routing_tables_routes, port_ips, bridge_device,
|
||||
bridge_vlan, self.ovn_routing_tables[bridge_device], proxy_cidrs)
|
||||
|
||||
def _get_bridge_for_localnet_port(self, localnet):
|
||||
bridge_device = None
|
||||
bridge_vlan = None
|
||||
network_name = localnet.options.get('network_name')
|
||||
if network_name:
|
||||
bridge_device = self.ovn_bridge_mappings[network_name]
|
||||
if localnet.tag:
|
||||
bridge_vlan = localnet.tag[0]
|
||||
return bridge_device, bridge_vlan
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def expose_ip(self, ips, row):
|
||||
'''Advertice BGP route by adding IP to device.
|
||||
|
||||
This methods ensures BGP advertises the IP of the VM in the provider
|
||||
network.
|
||||
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_device exists (inside a
|
||||
VRF), and adds the IP of:
|
||||
- VM IP on the provider network
|
||||
'''
|
||||
logical_switch = row.external_ids.get(constants.OVN_LS_NAME_EXT_ID_KEY)
|
||||
if not logical_switch:
|
||||
return False
|
||||
bridge_device, bridge_vlan = self._get_ls_localnet_info(logical_switch)
|
||||
if not bridge_device:
|
||||
# This means it is not a provider network
|
||||
self.ovn_tenant_ls[logical_switch] = True
|
||||
return False
|
||||
self.ovn_provider_ls[logical_switch] = {
|
||||
'bridge_device': bridge_device,
|
||||
'bridge_vlan': bridge_vlan}
|
||||
return self._expose_ip(ips, bridge_device, bridge_vlan,
|
||||
port_type=row.type, cidr=row.external_ids.get(
|
||||
constants.OVN_CIDRS_EXT_ID_KEY))
|
||||
|
||||
def _expose_ip(self, ips, bridge_device, bridge_vlan, port_type, cidr):
|
||||
LOG.debug("Adding BGP route for logical port with ip %s", ips)
|
||||
|
||||
if cidr and port_type == constants.OVN_VIRTUAL_VIF_PORT_TYPE:
|
||||
# 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
|
||||
self._expose_provider_port(ips, bridge_device, bridge_vlan, [cidr])
|
||||
else:
|
||||
self._expose_provider_port(ips, bridge_device, bridge_vlan)
|
||||
|
||||
LOG.debug("Added BGP route for logical port with ip %s", ips)
|
||||
return ips
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def withdraw_ip(self, ips, row):
|
||||
'''Withdraw BGP route by removing IP from device.
|
||||
|
||||
This methods ensures BGP withdraw an advertised IP of a VM, either
|
||||
in the provider network.
|
||||
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:
|
||||
- VM IP on the provider network
|
||||
'''
|
||||
logical_switch = row.external_ids.get(constants.OVN_LS_NAME_EXT_ID_KEY)
|
||||
if not logical_switch:
|
||||
return
|
||||
bridge_device, bridge_vlan = self._get_ls_localnet_info(logical_switch)
|
||||
if not bridge_device:
|
||||
# This means it is not a provider network
|
||||
return
|
||||
|
||||
proxy_cidr = None
|
||||
if row.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE:
|
||||
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):
|
||||
if not self.nb_idl.ls_has_virtual_ports(logical_switch):
|
||||
proxy_cidr = n_cidr
|
||||
LOG.debug("Deleting BGP route for logical port with ip %s", ips)
|
||||
if proxy_cidr:
|
||||
self._withdraw_provider_port(ips, bridge_device, bridge_vlan,
|
||||
[proxy_cidr])
|
||||
else:
|
||||
self._withdraw_provider_port(ips, bridge_device, bridge_vlan)
|
||||
LOG.debug("Deleted BGP route for logical port with ip %s", ips)
|
||||
|
||||
def _get_ls_localnet_info(self, logical_switch):
|
||||
localnet_ports = self.nb_idl.ls_get_localnet_ports(
|
||||
logical_switch, if_exists=True).execute(check_error=True)
|
||||
if not localnet_ports:
|
||||
# means it is not a provider network, so no need to expose the IP
|
||||
return None, None
|
||||
# NOTE: assuming only one localnet per LS exists
|
||||
return self._get_bridge_for_localnet_port(localnet_ports[0])
|
||||
|
||||
def get_port_external_ip_and_ls(self, port):
|
||||
nat_entry = self.nb_idl.get_nat_by_logical_port(port)
|
||||
if not nat_entry:
|
||||
return
|
||||
net_id = nat_entry.external_ids.get(constants.OVN_FIP_NET_EXT_ID_KEY)
|
||||
if not net_id:
|
||||
return nat_entry.external_ip, None
|
||||
else:
|
||||
return nat_entry.external_ip, "neutron-{}".format(net_id)
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def expose_fip(self, ip, logical_switch):
|
||||
'''Advertice BGP route by adding IP to device.
|
||||
|
||||
This methods ensures BGP advertises 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_device exists (inside a
|
||||
VRF), and adds the IP of:
|
||||
- VM FIP
|
||||
'''
|
||||
return self._expose_fip(ip, logical_switch)
|
||||
|
||||
def _expose_fip(self, ip, logical_switch):
|
||||
bridge_device, bridge_vlan = self._get_ls_localnet_info(logical_switch)
|
||||
if not bridge_device:
|
||||
# This means it is not a provider network
|
||||
return False
|
||||
LOG.debug("Adding BGP route for FIP with ip %s", ip)
|
||||
self._expose_provider_port([ip], bridge_device, bridge_vlan)
|
||||
self.ovn_fips[ip] = {'bridge_device': bridge_device,
|
||||
'bridge_vlan': bridge_vlan}
|
||||
LOG.debug("Added BGP route for FIP with ip %s", ip)
|
||||
return True
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def withdraw_fip(self, ip):
|
||||
'''Withdraw BGP route by removing IP from device.
|
||||
|
||||
This methods ensures BGP withdraw an advertised 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:
|
||||
- VM FIP
|
||||
'''
|
||||
fip_info = self.ovn_fips.get(ip)
|
||||
if not fip_info:
|
||||
# No information to withdraw the FIP
|
||||
return
|
||||
bridge_device = fip_info['bridge_device']
|
||||
bridge_vlan = fip_info['bridge_vlan']
|
||||
|
||||
LOG.debug("Deleting BGP route for FIP with ip %s", ip)
|
||||
self._withdraw_provider_port([ip], bridge_device, bridge_vlan)
|
||||
LOG.debug("Deleted BGP route for FIP with ip %s", ip)
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def expose_remote_ip(self, ips, row):
|
||||
pass
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def withdraw_remote_ip(self, ips, row, chassis=None):
|
||||
pass
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def expose_subnet(self, ip, row):
|
||||
pass
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def withdraw_subnet(self, ip, row):
|
||||
pass
|
@ -73,7 +73,7 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
|
||||
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.chassis = self.ovs_idl.get_own_chassis_id()
|
||||
self.ovn_remote = self.ovs_idl.get_ovn_remote()
|
||||
LOG.info("Loaded chassis %s.", self.chassis)
|
||||
|
||||
@ -509,7 +509,7 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
|
||||
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
|
||||
This method assumes a device named self.ovn_device exists (inside a
|
||||
VRF), and adds the IP of either:
|
||||
- VM IP on the provider network,
|
||||
- VM FIP, or
|
||||
@ -639,7 +639,7 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
|
||||
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
|
||||
This method assumes a device named self.ovn_device exists (inside a
|
||||
VRF), and removes the IP of either:
|
||||
- VM IP on the provider network,
|
||||
- VM FIP, or
|
||||
|
@ -67,7 +67,7 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
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.chassis = self.ovs_idl.get_own_chassis_id()
|
||||
self.ovn_remote = self.ovs_idl.get_ovn_remote()
|
||||
LOG.debug("Loaded chassis %s.", self.chassis)
|
||||
|
||||
|
@ -88,7 +88,7 @@ class OVNBGPStretchedL2Driver(driver_api.AgentDriverBase):
|
||||
if CONF.clear_vrf_routes_on_startup:
|
||||
linux_net.delete_routes_from_table(CONF.bgp_vrf_table_id)
|
||||
|
||||
self.chassis = self.ovs_idl.get_own_chassis_name()
|
||||
self.chassis = self.ovs_idl.get_own_chassis_id()
|
||||
self.ovn_remote = self.ovs_idl.get_ovn_remote()
|
||||
LOG.debug("Loaded chassis %s.", self.chassis)
|
||||
if self.allowed_address_scopes:
|
||||
|
@ -20,6 +20,7 @@ from ovsdbapp.backend import ovs_idl
|
||||
from ovsdbapp.backend.ovs_idl import connection
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
from ovsdbapp import event
|
||||
from ovsdbapp.schema.ovn_northbound import impl_idl as nb_impl_idl
|
||||
from ovsdbapp.schema.ovn_southbound import impl_idl as sb_impl_idl
|
||||
|
||||
from ovn_bgp_agent import constants
|
||||
@ -45,6 +46,47 @@ class OvnDbNotifyHandler(event.RowEventHandler):
|
||||
self.driver = driver
|
||||
|
||||
|
||||
class OvnNbIdl(OvnIdl):
|
||||
SCHEMA = 'OVN_Northbound'
|
||||
|
||||
def __init__(self, connection_string, events=None, tables=None):
|
||||
if connection_string.startswith("ssl"):
|
||||
self._check_and_set_ssl_files(self.SCHEMA)
|
||||
helper = self._get_ovsdb_helper(connection_string)
|
||||
self._events = events
|
||||
if tables is None:
|
||||
tables = ('Logical_Switch_Port', 'NAT', 'NB_Global')
|
||||
for table in tables:
|
||||
helper.register_table(table)
|
||||
super(OvnNbIdl, self).__init__(
|
||||
None, connection_string, helper, leader_only=False)
|
||||
|
||||
def _get_ovsdb_helper(self, connection_string):
|
||||
return idlutils.get_schema_helper(connection_string, self.SCHEMA)
|
||||
|
||||
def _check_and_set_ssl_files(self, schema_name):
|
||||
priv_key_file = CONF.ovn_nb_private_key
|
||||
cert_file = CONF.ovn_nb_certificate
|
||||
ca_cert_file = CONF.ovn_nb_ca_cert
|
||||
|
||||
if priv_key_file:
|
||||
Stream.ssl_set_private_key_file(priv_key_file)
|
||||
|
||||
if cert_file:
|
||||
Stream.ssl_set_certificate_file(cert_file)
|
||||
|
||||
if ca_cert_file:
|
||||
Stream.ssl_set_ca_cert_file(ca_cert_file)
|
||||
|
||||
def start(self):
|
||||
conn = connection.Connection(
|
||||
self, timeout=CONF.ovsdb_connection_timeout)
|
||||
ovsdbNbConn = OvsdbNbOvnIdl(conn)
|
||||
if self._events:
|
||||
self.notify_handler.watch_events(self._events)
|
||||
return ovsdbNbConn
|
||||
|
||||
|
||||
class OvnSbIdl(OvnIdl):
|
||||
SCHEMA = 'OVN_Southbound'
|
||||
|
||||
@ -85,7 +127,7 @@ class OvnSbIdl(OvnIdl):
|
||||
|
||||
def start(self):
|
||||
conn = connection.Connection(
|
||||
self, timeout=180)
|
||||
self, timeout=CONF.ovsdb_connection_timeout)
|
||||
ovsdbSbConn = OvsdbSbOvnIdl(conn)
|
||||
if self._events:
|
||||
self.notify_handler.watch_events(self._events)
|
||||
@ -109,6 +151,41 @@ class Backend(ovs_idl.Backend):
|
||||
return self.idl.tables
|
||||
|
||||
|
||||
class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
||||
def __init__(self, connection):
|
||||
super(OvsdbNbOvnIdl, self).__init__(connection)
|
||||
self.idl._session.reconnect.set_probe_interval(60000)
|
||||
|
||||
def get_network_vlan_tag_by_network_name(self, network_name):
|
||||
cmd = self.db_find_rows('Logical_Switch_Port', ('type', '=',
|
||||
constants.OVN_LOCALNET_VIF_PORT_TYPE))
|
||||
for row in cmd.execute(check_error=True):
|
||||
if (row.options and
|
||||
row.options.get('network_name') == network_name):
|
||||
return row.tag
|
||||
|
||||
def ls_has_virtual_ports(self, logical_switch):
|
||||
ls = self.lookup('Logical_Switch', logical_switch)
|
||||
for port in ls.ports:
|
||||
if port.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_nat_by_logical_port(self, logical_port):
|
||||
cmd = self.db_find_rows('NAT', ('logical_port', '=', logical_port))
|
||||
nat_info = cmd.execute(check_error=True)
|
||||
return nat_info[0] if nat_info else []
|
||||
|
||||
def get_active_ports_on_chassis(self, chassis):
|
||||
ports = []
|
||||
cmd = self.db_find_rows('Logical_Switch_Port', ('up', '=', True))
|
||||
for row in cmd.execute(check_error=True):
|
||||
if (row.options and
|
||||
row.options.get('requested-chassis') == chassis):
|
||||
ports.append(row)
|
||||
return ports
|
||||
|
||||
|
||||
class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
|
||||
def __init__(self, connection):
|
||||
super(OvsdbSbOvnIdl, self).__init__(connection)
|
||||
|
@ -231,7 +231,7 @@ class OvsIdl(object):
|
||||
return self.idl_ovs.db_get(
|
||||
'Open_vSwitch', '.', 'external_ids').execute()[key]
|
||||
|
||||
def get_own_chassis_name(self):
|
||||
def get_own_chassis_id(self):
|
||||
"""Return the external_ids:system-id value of the Open_vSwitch table.
|
||||
|
||||
As long as ovn-controller is running on this node, the key is
|
||||
@ -239,6 +239,14 @@ class OvsIdl(object):
|
||||
"""
|
||||
return self._get_from_ext_ids('system-id')
|
||||
|
||||
def get_own_chassis_name(self):
|
||||
"""Return the external_ids:hostname value of the Open_vSwitch table.
|
||||
|
||||
As long as ovn-controller is running on this node, the key is
|
||||
guaranteed to exist and will include the chassis name.
|
||||
"""
|
||||
return self._get_from_ext_ids('hostname')
|
||||
|
||||
def get_ovn_remote(self):
|
||||
"""Return the external_ids:ovn-remote value of the Open_vSwitch table.
|
||||
|
||||
|
@ -34,3 +34,15 @@ class OVNLBMemberEvent(row_event.RowEvent):
|
||||
super(OVNLBMemberEvent, self).__init__(
|
||||
events, table, None)
|
||||
self.event_name = self.__class__.__name__
|
||||
|
||||
|
||||
class LSPChassisEvent(row_event.RowEvent):
|
||||
def __init__(self, bgp_agent, events):
|
||||
self.agent = bgp_agent
|
||||
table = 'Logical_Switch_Port'
|
||||
super(LSPChassisEvent, self).__init__(
|
||||
events, table, None)
|
||||
self.event_name = self.__class__.__name__
|
||||
|
||||
def _check_ip_associated(self, mac):
|
||||
return len(mac.strip().split(' ')) > 1
|
||||
|
223
ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py
Normal file
223
ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py
Normal file
@ -0,0 +1,223 @@
|
||||
# Copyright 2023 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from ovn_bgp_agent import constants
|
||||
from ovn_bgp_agent.drivers.openstack.watchers import base_watcher
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
_SYNC_STATE_LOCK = lockutils.ReaderWriterLock()
|
||||
|
||||
|
||||
class LogicalSwitchPortProviderCreateEvent(base_watcher.LSPChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE,)
|
||||
super(LogicalSwitchPortProviderCreateEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if not self._check_ip_associated(row.addresses[0]):
|
||||
return False
|
||||
|
||||
current_chassis = row.options.get(constants.OVN_REQUESTED_CHASSIS)
|
||||
if current_chassis != self.agent.chassis:
|
||||
return False
|
||||
if not row.up:
|
||||
return False
|
||||
old_chassis = old.options.get(constants.OVN_REQUESTED_CHASSIS)
|
||||
if (not old_chassis or current_chassis != old_chassis or
|
||||
not old.up):
|
||||
return True
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type not in [constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE]:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
ips = row.addresses[0].split(' ')[1:]
|
||||
self.agent.expose_ip(ips, row)
|
||||
|
||||
|
||||
class LogicalSwitchPortProviderDeleteEvent(base_watcher.LSPChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE, self.ROW_DELETE,)
|
||||
super(LogicalSwitchPortProviderDeleteEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if not self._check_ip_associated(row.addresses[0]):
|
||||
return False
|
||||
|
||||
current_chassis = row.options.get(constants.OVN_REQUESTED_CHASSIS)
|
||||
if event == self.ROW_DELETE:
|
||||
return current_chassis == self.agent.chassis
|
||||
|
||||
# ROW_UPDATE EVENT
|
||||
old_chassis = old.options.get(constants.OVN_REQUESTED_CHASSIS)
|
||||
if old_chassis != self.agent.chassis:
|
||||
return False
|
||||
if not old.up:
|
||||
return False
|
||||
|
||||
if (not current_chassis or current_chassis != old_chassis or
|
||||
not row.up):
|
||||
return True
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type not in [constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE]:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
ips = row.addresses[0].split(' ')[1:]
|
||||
self.agent.withdraw_ip(ips, row)
|
||||
|
||||
|
||||
class LogicalSwitchPortFIPCreateEvent(base_watcher.LSPChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE,)
|
||||
super(LogicalSwitchPortFIPCreateEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if not self._check_ip_associated(row.addresses[0]):
|
||||
return False
|
||||
|
||||
current_chassis = row.options.get(constants.OVN_REQUESTED_CHASSIS)
|
||||
current_port_fip = row.external_ids.get(
|
||||
constants.OVN_FIP_EXT_ID_KEY)
|
||||
if (current_chassis != self.agent.chassis or not row.up or
|
||||
not current_port_fip):
|
||||
return False
|
||||
|
||||
if hasattr(old, 'options'):
|
||||
# check chassis change
|
||||
old_chassis = old.options.get(constants.OVN_REQUESTED_CHASSIS)
|
||||
if not old_chassis or current_chassis != old_chassis:
|
||||
return True
|
||||
if hasattr(old, 'external_ids'):
|
||||
# check fips addition
|
||||
old_port_fip = old.external_ids.get(
|
||||
constants.OVN_FIP_EXT_ID_KEY)
|
||||
if not old_port_fip or current_port_fip != old_port_fip:
|
||||
return True
|
||||
if hasattr(old, 'up'):
|
||||
# check port status change
|
||||
if not old.up:
|
||||
return True
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type not in [constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE]:
|
||||
return
|
||||
external_ip, ls_name = self.agent.get_port_external_ip_and_ls(row.name)
|
||||
if not external_ip or not ls_name:
|
||||
return
|
||||
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
self.agent.expose_fip(external_ip, ls_name)
|
||||
|
||||
|
||||
class LogicalSwitchPortFIPDeleteEvent(base_watcher.LSPChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_UPDATE, self.ROW_DELETE,)
|
||||
super(LogicalSwitchPortFIPDeleteEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
try:
|
||||
# single and dual-stack format
|
||||
if not self._check_ip_associated(row.addresses[0]):
|
||||
return False
|
||||
|
||||
current_chassis = row.options.get(constants.OVN_REQUESTED_CHASSIS)
|
||||
current_port_fip = row.external_ids.get(
|
||||
constants.OVN_FIP_EXT_ID_KEY)
|
||||
if event == self.ROW_DELETE:
|
||||
if (current_chassis == self.agent.chassis and row.up and
|
||||
current_port_fip):
|
||||
return True
|
||||
return False
|
||||
|
||||
if hasattr(old, 'options'):
|
||||
# check chassis change
|
||||
old_chassis = old.options.get(constants.OVN_REQUESTED_CHASSIS)
|
||||
if (not old_chassis or old_chassis != self.agent.chassis):
|
||||
return False
|
||||
if current_chassis != old_chassis:
|
||||
return True
|
||||
# There was no change in chassis, so only progress if the
|
||||
# chassis matches
|
||||
if current_chassis != self.agent.chassis:
|
||||
return False
|
||||
if hasattr(old, 'external_ids'):
|
||||
# check fips deletion
|
||||
old_port_fip = old.external_ids.get(
|
||||
constants.OVN_FIP_EXT_ID_KEY)
|
||||
if not old_port_fip:
|
||||
return False
|
||||
if old_port_fip != current_port_fip:
|
||||
return True
|
||||
if hasattr(old, 'up'):
|
||||
# check port status change
|
||||
if not old.up:
|
||||
return False
|
||||
if not row.up:
|
||||
return True
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
if row.type not in [constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE]:
|
||||
return
|
||||
fip = row.external_ids.get(constants.OVN_FIP_EXT_ID_KEY)
|
||||
if not fip:
|
||||
fip = old.external_ids.get(constants.OVN_FIP_EXT_ID_KEY)
|
||||
if not fip:
|
||||
return
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
self.agent.withdraw_fip(fip)
|
||||
|
||||
|
||||
class LocalnetCreateDeleteEvent(base_watcher.LSPChassisEvent):
|
||||
def __init__(self, bgp_agent):
|
||||
events = (self.ROW_CREATE, self.ROW_DELETE,)
|
||||
super(LocalnetCreateDeleteEvent, self).__init__(
|
||||
bgp_agent, events)
|
||||
|
||||
def match_fn(self, event, row, old):
|
||||
if row.type == constants.OVN_LOCALNET_VIF_PORT_TYPE:
|
||||
return True
|
||||
return False
|
||||
|
||||
def run(self, event, row, old):
|
||||
with _SYNC_STATE_LOCK.read_lock():
|
||||
self.agent.sync()
|
@ -0,0 +1,564 @@
|
||||
# Copyright 2023 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ovn_bgp_agent import config
|
||||
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 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.utils import wire as wire_utils
|
||||
from ovn_bgp_agent.tests import base as test_base
|
||||
from ovn_bgp_agent.tests.unit import fakes
|
||||
from ovn_bgp_agent.utils import linux_net
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNBOVNBGPDriver, self).setUp()
|
||||
config.register_opts()
|
||||
self.bridge = 'fake-bridge'
|
||||
self.nb_bgp_driver = nb_ovn_bgp_driver.NBOVNBGPDriver()
|
||||
self.nb_bgp_driver._post_start_event = mock.Mock()
|
||||
self.nb_bgp_driver.nb_idl = mock.Mock()
|
||||
self.nb_idl = self.nb_bgp_driver.nb_idl
|
||||
self.nb_bgp_driver.chassis = 'fake-chassis'
|
||||
self.nb_bgp_driver.ovn_bridge_mappings = {'fake-network': self.bridge}
|
||||
|
||||
self.mock_nbdb = mock.patch.object(ovn, 'OvnNbIdl').start()
|
||||
self.mock_ovs_idl = mock.patch.object(ovs, 'OvsIdl').start()
|
||||
self.nb_bgp_driver.ovs_idl = self.mock_ovs_idl
|
||||
|
||||
self.ipv4 = '192.168.1.17'
|
||||
self.ipv6 = '2002::1234:abcd:ffff:c0a8:101'
|
||||
self.fip = '172.24.4.33'
|
||||
self.mac = 'aa:bb:cc:dd:ee:ff'
|
||||
|
||||
self.ovn_routing_tables = {
|
||||
self.bridge: 100,
|
||||
'br-vlan': 200}
|
||||
self.nb_bgp_driver.ovn_routing_tables = self.ovn_routing_tables
|
||||
self.ovn_routing_tables_routes = mock.Mock()
|
||||
self.nb_bgp_driver.ovn_routing_tables_routes = (
|
||||
self.ovn_routing_tables_routes)
|
||||
|
||||
self.conf_ovsdb_connection = 'tcp:127.0.0.1:6642'
|
||||
|
||||
# Mock pyroute2.NDB context manager object
|
||||
self.mock_ndb = mock.patch.object(linux_net.pyroute2, 'NDB').start()
|
||||
self.fake_ndb = self.mock_ndb().__enter__()
|
||||
|
||||
@mock.patch.object(linux_net, 'ensure_vrf')
|
||||
@mock.patch.object(frr, 'vrf_leak')
|
||||
@mock.patch.object(linux_net, 'ensure_ovn_device')
|
||||
@mock.patch.object(linux_net, 'delete_routes_from_table')
|
||||
def test_start(self, mock_delete_routes_from_table,
|
||||
mock_ensure_ovn_device, mock_vrf_leak, mock_ensure_vrf):
|
||||
CONF.set_override('clear_vrf_routes_on_startup', True)
|
||||
self.addCleanup(CONF.clear_override, 'clear_vrf_routes_on_startup')
|
||||
self.mock_ovs_idl.get_own_chassis_name.return_value = 'chassis-name'
|
||||
self.mock_ovs_idl.get_ovn_remote.return_value = (
|
||||
self.conf_ovsdb_connection)
|
||||
|
||||
self.nb_bgp_driver.start()
|
||||
|
||||
# Verify mock object method calls and arguments
|
||||
self.mock_ovs_idl().start.assert_called_once_with(
|
||||
CONF.ovsdb_connection)
|
||||
self.mock_ovs_idl().get_own_chassis_name.assert_called_once()
|
||||
self.mock_ovs_idl().get_ovn_remote.assert_called_once()
|
||||
|
||||
mock_ensure_vrf.assert_called_once_with(
|
||||
CONF.bgp_vrf, CONF.bgp_vrf_table_id)
|
||||
mock_vrf_leak.assert_called_once_with(
|
||||
CONF.bgp_vrf, CONF.bgp_AS, CONF.bgp_router_id)
|
||||
mock_ensure_ovn_device.assert_called_once_with(CONF.bgp_nic,
|
||||
CONF.bgp_vrf)
|
||||
mock_delete_routes_from_table.assert_called_once_with(
|
||||
CONF.bgp_vrf_table_id)
|
||||
self.mock_nbdb().start.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(linux_net, 'delete_bridge_ip_routes')
|
||||
@mock.patch.object(linux_net, 'delete_ip_rules')
|
||||
@mock.patch.object(linux_net, 'delete_exposed_ips')
|
||||
@mock.patch.object(linux_net, 'get_ovn_ip_rules')
|
||||
@mock.patch.object(linux_net, 'get_exposed_ips')
|
||||
@mock.patch.object(ovs, 'remove_extra_ovs_flows')
|
||||
@mock.patch.object(ovs, 'get_ovs_flows_info')
|
||||
@mock.patch.object(linux_net, 'ensure_arp_ndp_enabled_for_bridge')
|
||||
@mock.patch.object(linux_net, 'ensure_vlan_device_for_network')
|
||||
@mock.patch.object(linux_net, 'ensure_routing_table_for_bridge')
|
||||
@mock.patch.object(linux_net, 'ensure_ovn_device')
|
||||
@mock.patch.object(linux_net, 'ensure_vrf')
|
||||
def test_sync(self, mock_ensure_vrf, mock_ensure_ovn_dev,
|
||||
mock_routing_bridge, mock_ensure_vlan_network,
|
||||
mock_ensure_arp, mock_flows_info, mock_remove_flows,
|
||||
mock_exposed_ips, mock_get_ip_rules, mock_del_exposed_ips,
|
||||
mock_del_ip_riles, moock_del_ip_routes):
|
||||
self.mock_ovs_idl.get_ovn_bridge_mappings.return_value = [
|
||||
'net0:bridge0', 'net1:bridge1']
|
||||
self.nb_idl.get_network_vlan_tag_by_network_name.side_effect = (
|
||||
[10], [11])
|
||||
fake_ip_rules = 'fake-ip-rules'
|
||||
mock_get_ip_rules.return_value = fake_ip_rules
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
mock_exposed_ips.return_value = ips
|
||||
|
||||
port0 = fakes.create_object({
|
||||
'name': 'port-0',
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE})
|
||||
port1 = fakes.create_object({
|
||||
'name': 'port-1',
|
||||
'type': constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE})
|
||||
self.nb_idl.get_active_ports_on_chassis.return_value = [
|
||||
port0, port1]
|
||||
mock_ensure_port_exposed = mock.patch.object(
|
||||
self.nb_bgp_driver, '_ensure_port_exposed').start()
|
||||
|
||||
self.nb_bgp_driver.sync()
|
||||
|
||||
mock_ensure_vrf.assert_called_once_with(
|
||||
CONF.bgp_vrf, CONF.bgp_vrf_table_id)
|
||||
mock_ensure_ovn_dev.assert_called_once_with(
|
||||
CONF.bgp_nic, CONF.bgp_vrf)
|
||||
expected_calls = [mock.call(self.ovn_routing_tables, 'bridge0',
|
||||
CONF.bgp_vrf_table_id),
|
||||
mock.call(self.ovn_routing_tables, 'bridge1',
|
||||
CONF.bgp_vrf_table_id)]
|
||||
mock_routing_bridge.assert_has_calls(expected_calls)
|
||||
expected_calls = [mock.call('bridge0', 10), mock.call('bridge1', 11)]
|
||||
mock_ensure_vlan_network.assert_has_calls(expected_calls)
|
||||
expected_calls = [mock.call('bridge0', 1, 10),
|
||||
mock.call('bridge1', 2, 11)]
|
||||
mock_ensure_arp.assert_has_calls(expected_calls)
|
||||
expected_calls = [
|
||||
mock.call(
|
||||
'bridge0', {'bridge0': {'mac': mock.ANY, 'in_port': set()},
|
||||
'bridge1': {'mac': mock.ANY, 'in_port': set()}},
|
||||
constants.OVS_RULE_COOKIE),
|
||||
mock.call(
|
||||
'bridge1', {'bridge0': {'mac': mock.ANY, 'in_port': set()},
|
||||
'bridge1': {'mac': mock.ANY, 'in_port': set()}},
|
||||
constants.OVS_RULE_COOKIE)]
|
||||
mock_flows_info.assert_has_calls(expected_calls)
|
||||
mock_remove_flows.assert_called_once_with({
|
||||
'bridge0': {'mac': mock.ANY, 'in_port': set()},
|
||||
'bridge1': {'mac': mock.ANY, 'in_port': set()}},
|
||||
constants.OVS_RULE_COOKIE)
|
||||
mock_get_ip_rules.assert_called_once()
|
||||
mock_ensure_port_exposed.assert_called_once_with(
|
||||
port0, ips, fake_ip_rules)
|
||||
mock_del_exposed_ips.assert_called_once_with(
|
||||
ips, CONF.bgp_nic)
|
||||
mock_del_ip_riles.assert_called_once_with(fake_ip_rules)
|
||||
moock_del_ip_routes.assert_called_once_with(
|
||||
self.ovn_routing_tables, mock.ANY,
|
||||
{'bridge0': mock.ANY, 'bridge1': mock.ANY})
|
||||
|
||||
def test__ensure_port_exposed_fip(self):
|
||||
port0 = fakes.create_object({
|
||||
'name': 'port-0',
|
||||
'external_ids': {constants.OVN_FIP_EXT_ID_KEY: "fip"}})
|
||||
exposed_ips = ["192.168.0.10"]
|
||||
ovn_ip_rules = {"192.168.0.10/32": "rule1"}
|
||||
|
||||
mock_get_port_external_ip_and_ls = mock.patch.object(
|
||||
self.nb_bgp_driver, 'get_port_external_ip_and_ls').start()
|
||||
mock_get_port_external_ip_and_ls.return_value = ("192.168.0.10",
|
||||
"test-ls")
|
||||
mock_expose_fip = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_fip').start()
|
||||
mock_expose_ip = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_ip').start()
|
||||
|
||||
self.nb_bgp_driver._ensure_port_exposed(port0, exposed_ips,
|
||||
ovn_ip_rules)
|
||||
|
||||
mock_get_port_external_ip_and_ls.assert_called_once_with(port0.name)
|
||||
mock_expose_fip.assert_called_once_with("192.168.0.10", "test-ls")
|
||||
mock_expose_ip.assert_not_called()
|
||||
self.assertEqual(exposed_ips, [])
|
||||
self.assertEqual(ovn_ip_rules, {})
|
||||
|
||||
def test__ensure_port_exposed_tenant_ls(self):
|
||||
port0 = fakes.create_object({
|
||||
'name': 'port-0',
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: "test-ls"}})
|
||||
self.nb_bgp_driver.ovn_tenant_ls = {"test-ls": True}
|
||||
exposed_ips = ["192.168.0.10"]
|
||||
ovn_ip_rules = {"192.168.0.10/32": "rule1"}
|
||||
|
||||
mock_get_port_external_ip_and_ls = mock.patch.object(
|
||||
self.nb_bgp_driver, 'get_port_external_ip_and_ls').start()
|
||||
mock_expose_fip = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_fip').start()
|
||||
mock_expose_ip = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_ip').start()
|
||||
|
||||
self.nb_bgp_driver._ensure_port_exposed(port0, exposed_ips,
|
||||
ovn_ip_rules)
|
||||
|
||||
mock_get_port_external_ip_and_ls.assert_not_called()
|
||||
mock_expose_fip.assert_not_called()
|
||||
mock_expose_ip.assert_not_called()
|
||||
self.assertEqual(exposed_ips, ["192.168.0.10"])
|
||||
self.assertEqual(ovn_ip_rules, {"192.168.0.10/32": "rule1"})
|
||||
|
||||
@mock.patch.object(linux_net, 'get_ip_version')
|
||||
def test__ensure_port_exposed_no_fip_no_tenant_ls(self, mock_ip_version):
|
||||
port0 = fakes.create_object({
|
||||
'name': 'port-0',
|
||||
'addresses': ["fake_mac 192.168.0.10"],
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE,
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: "test-ls"}})
|
||||
|
||||
self.nb_bgp_driver.ovn_tenant_ls = {}
|
||||
self.nb_bgp_driver.ovn_provider_ls = {}
|
||||
exposed_ips = ["192.168.0.10"]
|
||||
ovn_ip_rules = {"192.168.0.10/32": "rule1"}
|
||||
|
||||
mock_get_port_external_ip_and_ls = mock.patch.object(
|
||||
self.nb_bgp_driver, 'get_port_external_ip_and_ls').start()
|
||||
mock_expose_fip = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_fip').start()
|
||||
mock_expose_ip = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_ip').start()
|
||||
mock_expose_ip.return_value = ["192.168.0.10"]
|
||||
mock_get_ls_localnet_info = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_ls_localnet_info').start()
|
||||
mock_get_ls_localnet_info.return_value = ("br-ex", 10)
|
||||
mock_ip_version.return_value = constants.IP_VERSION_4
|
||||
|
||||
self.nb_bgp_driver._ensure_port_exposed(port0, exposed_ips,
|
||||
ovn_ip_rules)
|
||||
|
||||
mock_get_port_external_ip_and_ls.assert_not_called()
|
||||
mock_get_ls_localnet_info.assert_called_once_with('test-ls')
|
||||
mock_expose_fip.assert_not_called()
|
||||
mock_expose_ip.assert_called_once_with(["192.168.0.10"], "br-ex", 10,
|
||||
constants.OVN_VM_VIF_PORT_TYPE,
|
||||
None)
|
||||
self.assertEqual(exposed_ips, [])
|
||||
self.assertEqual(ovn_ip_rules, {})
|
||||
|
||||
@mock.patch.object(wire_utils, 'wire_provider_port')
|
||||
@mock.patch.object(bgp_utils, 'announce_ips')
|
||||
def test__expose_provider_port_successful(self, mock_announce_ips,
|
||||
mock_wire_provider_port):
|
||||
mock_wire_provider_port.return_value = True
|
||||
port_ips = ['192.168.0.1', '192.168.0.2']
|
||||
bridge_device = self.bridge
|
||||
bridge_vlan = None
|
||||
proxy_cidrs = ['192.168.0.0/24']
|
||||
|
||||
self.nb_bgp_driver._expose_provider_port(port_ips, bridge_device,
|
||||
bridge_vlan, proxy_cidrs)
|
||||
|
||||
mock_wire_provider_port.assert_called_once_with(
|
||||
self.ovn_routing_tables_routes, port_ips, bridge_device,
|
||||
bridge_vlan, self.ovn_routing_tables[bridge_device], proxy_cidrs)
|
||||
mock_announce_ips.assert_called_once_with(port_ips)
|
||||
|
||||
@mock.patch.object(wire_utils, 'wire_provider_port')
|
||||
@mock.patch.object(bgp_utils, 'announce_ips')
|
||||
def test__expose_provider_port_failure(self, mock_announce_ips,
|
||||
mock_wire_provider_port):
|
||||
mock_wire_provider_port.return_value = False
|
||||
port_ips = ['192.168.0.1', '192.168.0.2']
|
||||
bridge_device = self.bridge
|
||||
bridge_vlan = None
|
||||
proxy_cidrs = ['192.168.0.0/24']
|
||||
|
||||
self.nb_bgp_driver._expose_provider_port(port_ips, bridge_device,
|
||||
bridge_vlan, proxy_cidrs)
|
||||
|
||||
mock_wire_provider_port.assert_called_once_with(
|
||||
self.ovn_routing_tables_routes, port_ips, bridge_device,
|
||||
bridge_vlan, self.ovn_routing_tables[bridge_device], proxy_cidrs)
|
||||
mock_announce_ips.assert_not_called()
|
||||
|
||||
@mock.patch.object(wire_utils, 'unwire_provider_port')
|
||||
@mock.patch.object(bgp_utils, 'withdraw_ips')
|
||||
def test__withdraw_provider_port(self, mock_withdraw_ips,
|
||||
mock_unwire_provider_port):
|
||||
port_ips = ['192.168.0.1', '192.168.0.2']
|
||||
bridge_device = self.bridge
|
||||
bridge_vlan = None
|
||||
proxy_cidrs = ['192.168.0.0/24']
|
||||
|
||||
self.nb_bgp_driver._withdraw_provider_port(port_ips, bridge_device,
|
||||
bridge_vlan, proxy_cidrs)
|
||||
|
||||
mock_withdraw_ips.assert_called_once_with(port_ips)
|
||||
mock_unwire_provider_port.assert_called_once_with(
|
||||
self.ovn_routing_tables_routes, port_ips, bridge_device,
|
||||
bridge_vlan, self.ovn_routing_tables[bridge_device], proxy_cidrs)
|
||||
|
||||
def test__get_bridge_for_localnet_port(self):
|
||||
localnet = fakes.create_object({
|
||||
'options': {'network_name': 'fake-network'},
|
||||
'tag': [10]})
|
||||
|
||||
bridge_device, bridge_vlan = (
|
||||
self.nb_bgp_driver._get_bridge_for_localnet_port(localnet))
|
||||
self.assertEqual(bridge_device, self.bridge)
|
||||
self.assertEqual(bridge_vlan, 10)
|
||||
|
||||
def test__get_bridge_for_localnet_port_no_network_no_tag(self):
|
||||
localnet = fakes.create_object({
|
||||
'options': {},
|
||||
'tag': None})
|
||||
|
||||
bridge_device, bridge_vlan = (
|
||||
self.nb_bgp_driver._get_bridge_for_localnet_port(localnet))
|
||||
self.assertEqual(bridge_device, None)
|
||||
self.assertEqual(bridge_vlan, None)
|
||||
|
||||
def _test_expose_ip(self, ips, row):
|
||||
mock_expose_provider_port = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_provider_port').start()
|
||||
mock_get_ls_localnet_info = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_ls_localnet_info').start()
|
||||
mock_get_ls_localnet_info.return_value = ('br-ex', 10)
|
||||
|
||||
cidr = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
|
||||
logical_switch = row.external_ids.get(constants.OVN_LS_NAME_EXT_ID_KEY)
|
||||
|
||||
self.nb_bgp_driver.expose_ip(ips, row)
|
||||
|
||||
if not logical_switch:
|
||||
mock_expose_provider_port.assert_not_called()
|
||||
mock_get_ls_localnet_info.assert_not_called()
|
||||
return
|
||||
|
||||
mock_get_ls_localnet_info.assert_called_once_with(logical_switch)
|
||||
self.assertEqual(self.nb_bgp_driver.ovn_provider_ls[logical_switch],
|
||||
{'bridge_device': 'br-ex', 'bridge_vlan': 10})
|
||||
if row.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE and cidr:
|
||||
mock_expose_provider_port.assert_called_once_with(ips, 'br-ex',
|
||||
10, [cidr])
|
||||
else:
|
||||
mock_expose_provider_port.assert_called_once_with(ips, 'br-ex', 10)
|
||||
|
||||
def test_expose_ip(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE,
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls'}})
|
||||
|
||||
self._test_expose_ip(ips, row)
|
||||
|
||||
def test_expose_ip_virtual(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'type': constants.OVN_VIRTUAL_VIF_PORT_TYPE,
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls',
|
||||
constants.OVN_CIDRS_EXT_ID_KEY: 'test-cidr'}})
|
||||
|
||||
self._test_expose_ip(ips, row)
|
||||
|
||||
def test_expose_ip_no_switch(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE,
|
||||
'external_ids': {}})
|
||||
|
||||
self._test_expose_ip(ips, row)
|
||||
|
||||
@mock.patch.object(linux_net, 'get_ip_version')
|
||||
def _test_withdraw_ip(self, ips, row, provider, mock_ip_version):
|
||||
mock_withdraw_provider_port = mock.patch.object(
|
||||
self.nb_bgp_driver, '_withdraw_provider_port').start()
|
||||
mock_get_ls_localnet_info = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_ls_localnet_info').start()
|
||||
mock_ip_version.return_value = constants.IP_VERSION_6
|
||||
self.nb_idl.ls_has_virtual_ports.return_value = False
|
||||
if provider:
|
||||
mock_get_ls_localnet_info.return_value = ('br-ex', 10)
|
||||
else:
|
||||
mock_get_ls_localnet_info.return_value = (None, None)
|
||||
|
||||
cidr = row.external_ids.get(constants.OVN_CIDRS_EXT_ID_KEY)
|
||||
logical_switch = row.external_ids.get(constants.OVN_LS_NAME_EXT_ID_KEY)
|
||||
|
||||
self.nb_bgp_driver.withdraw_ip(ips, row)
|
||||
|
||||
if not logical_switch:
|
||||
mock_get_ls_localnet_info.assert_not_called()
|
||||
mock_withdraw_provider_port.assert_not_called()
|
||||
return
|
||||
if not provider:
|
||||
mock_get_ls_localnet_info.assert_called_once_with(logical_switch)
|
||||
mock_withdraw_provider_port.assert_not_called()
|
||||
return
|
||||
|
||||
mock_get_ls_localnet_info.assert_called_once_with(logical_switch)
|
||||
if row.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE and cidr:
|
||||
mock_withdraw_provider_port.assert_called_once_with(ips, 'br-ex',
|
||||
10, [cidr])
|
||||
else:
|
||||
mock_withdraw_provider_port.assert_called_once_with(ips, 'br-ex',
|
||||
10)
|
||||
|
||||
def test_withdraw_ip(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE,
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls'}})
|
||||
|
||||
self._test_withdraw_ip(ips, row, True)
|
||||
|
||||
def test_withdraw_ip_no_provider(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE,
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls'}})
|
||||
|
||||
self._test_withdraw_ip(ips, row, False)
|
||||
|
||||
def test_withdraw_ip_virtual(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'type': constants.OVN_VIRTUAL_VIF_PORT_TYPE,
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls',
|
||||
constants.OVN_CIDRS_EXT_ID_KEY: 'test-cidr'}})
|
||||
|
||||
self._test_withdraw_ip(ips, row, True)
|
||||
|
||||
def test_withdraw_ip_no_switch(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE,
|
||||
'external_ids': {}})
|
||||
|
||||
self._test_withdraw_ip(ips, row, True)
|
||||
|
||||
def test__get_ls_localnet_info(self):
|
||||
logical_switch = 'lswitch1'
|
||||
localnet_ports = ['fake-localnet-port']
|
||||
self.nb_idl.ls_get_localnet_ports.return_value.execute.return_value = (
|
||||
localnet_ports)
|
||||
mock_get_bridge_for_localnet_port = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_bridge_for_localnet_port').start()
|
||||
|
||||
self.nb_bgp_driver._get_ls_localnet_info(logical_switch)
|
||||
|
||||
self.nb_idl.ls_get_localnet_ports.assert_called_once_with(
|
||||
logical_switch, if_exists=True)
|
||||
mock_get_bridge_for_localnet_port.assert_called_once_with(
|
||||
localnet_ports[0])
|
||||
|
||||
def test_get_ls_localnet_info_not_provider_network(self):
|
||||
logical_switch = 'lswitch1'
|
||||
localnet_ports = []
|
||||
self.nb_idl.ls_get_localnet_ports.return_value.execute.return_value = (
|
||||
localnet_ports)
|
||||
mock_get_bridge_for_localnet_port = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_bridge_for_localnet_port').start()
|
||||
|
||||
ret = self.nb_bgp_driver._get_ls_localnet_info(logical_switch)
|
||||
|
||||
self.nb_idl.ls_get_localnet_ports.assert_called_once_with(
|
||||
logical_switch, if_exists=True)
|
||||
mock_get_bridge_for_localnet_port.assert_not_called()
|
||||
self.assertEqual(ret, (None, None))
|
||||
|
||||
def test_get_port_external_ip_and_ls(self):
|
||||
nat_entry = fakes.create_object({
|
||||
'external_ids': {constants.OVN_FIP_NET_EXT_ID_KEY: 'net1'},
|
||||
'external_ip': 'fake-ip'})
|
||||
self.nb_idl.get_nat_by_logical_port.return_value = nat_entry
|
||||
|
||||
ret = self.nb_bgp_driver.get_port_external_ip_and_ls('fake-port')
|
||||
|
||||
expected_result = (nat_entry.external_ip, "neutron-net1")
|
||||
self.assertEqual(ret, expected_result)
|
||||
|
||||
def test_get_port_external_ip_and_ls_no_nat_entry(self):
|
||||
self.nb_idl.get_nat_by_logical_port.return_value = None
|
||||
|
||||
ret = self.nb_bgp_driver.get_port_external_ip_and_ls('fake-port')
|
||||
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_get_port_external_ip_and_ls_no_external_id(self):
|
||||
nat_entry = fakes.create_object({
|
||||
'external_ids': {},
|
||||
'external_ip': 'fake-ip'})
|
||||
self.nb_idl.get_nat_by_logical_port.return_value = nat_entry
|
||||
|
||||
ret = self.nb_bgp_driver.get_port_external_ip_and_ls('fake-port')
|
||||
|
||||
self.assertEqual(ret, (nat_entry.external_ip, None))
|
||||
|
||||
def test_expose_fip(self):
|
||||
ip = '10.0.0.1'
|
||||
logical_switch = 'lswitch1'
|
||||
mock_get_ls_localnet_info = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_ls_localnet_info').start()
|
||||
mock_get_ls_localnet_info.return_value = ('br-ex', 100)
|
||||
mock_expose_provider_port = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_provider_port').start()
|
||||
|
||||
ret = self.nb_bgp_driver.expose_fip(ip, logical_switch)
|
||||
|
||||
mock_get_ls_localnet_info.assert_called_once_with(logical_switch)
|
||||
mock_expose_provider_port.assert_called_once_with([ip], 'br-ex', 100)
|
||||
self.assertEqual(self.nb_bgp_driver.ovn_fips[ip],
|
||||
{'bridge_device': 'br-ex', 'bridge_vlan': 100})
|
||||
self.assertTrue(ret)
|
||||
|
||||
def test_expose_fip_no_device(self):
|
||||
ip = '10.0.0.1'
|
||||
logical_switch = 'lswitch1'
|
||||
mock_get_ls_localnet_info = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_ls_localnet_info').start()
|
||||
mock_get_ls_localnet_info.return_value = (None, None)
|
||||
mock_expose_provider_port = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_provider_port').start()
|
||||
|
||||
ret = self.nb_bgp_driver.expose_fip(ip, logical_switch)
|
||||
|
||||
mock_get_ls_localnet_info.assert_called_once_with(logical_switch)
|
||||
mock_expose_provider_port.assert_not_called()
|
||||
self.assertNotIn(ip, self.nb_bgp_driver.ovn_fips)
|
||||
self.assertFalse(ret)
|
||||
|
||||
def test_withdraw_fip(self):
|
||||
ip = '10.0.0.1'
|
||||
self.nb_bgp_driver.ovn_fips = {ip: {'bridge_device': 'br-ex',
|
||||
'bridge_vlan': 100}}
|
||||
mock_withdraw_provider_port = mock.patch.object(
|
||||
self.nb_bgp_driver, '_withdraw_provider_port').start()
|
||||
|
||||
self.nb_bgp_driver.withdraw_fip(ip)
|
||||
mock_withdraw_provider_port.assert_called_once_with([ip], "br-ex", 100)
|
||||
|
||||
def test_withdraw_fip_not_found(self):
|
||||
ip = '10.0.0.1'
|
||||
self.nb_bgp_driver.ovn_fips = {}
|
||||
mock_withdraw_provider_port = mock.patch.object(
|
||||
self.nb_bgp_driver, '_withdraw_provider_port').start()
|
||||
|
||||
self.nb_bgp_driver.withdraw_fip(ip)
|
||||
mock_withdraw_provider_port.assert_not_called()
|
@ -30,6 +30,79 @@ from ovn_bgp_agent.tests.unit import fakes
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestOvsdbNbOvnIdl(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvsdbNbOvnIdl, self).setUp()
|
||||
self.nb_idl = ovn_utils.OvsdbNbOvnIdl(mock.Mock())
|
||||
|
||||
# Monkey-patch parent class methods
|
||||
self.nb_idl.db_find_rows = mock.Mock()
|
||||
self.nb_idl.lookup = mock.Mock()
|
||||
|
||||
def test_get_network_vlan_tag_by_network_name(self):
|
||||
network_name = 'net0'
|
||||
tag = 123
|
||||
lsp = fakes.create_object({'name': 'port-0',
|
||||
'options': {'network_name': network_name},
|
||||
'tag': tag})
|
||||
self.nb_idl.db_find_rows.return_value.execute.return_value = [
|
||||
lsp]
|
||||
ret = self.nb_idl.get_network_vlan_tag_by_network_name(network_name)
|
||||
|
||||
self.assertEqual(tag, ret)
|
||||
self.nb_idl.db_find_rows.assert_called_once_with(
|
||||
'Logical_Switch_Port',
|
||||
('type', '=', constants.OVN_LOCALNET_VIF_PORT_TYPE))
|
||||
|
||||
def test_ls_has_virtual_ports(self):
|
||||
ls_name = 'logical_switch'
|
||||
port = fakes.create_object(
|
||||
{'type': constants.OVN_VIRTUAL_VIF_PORT_TYPE})
|
||||
ls = fakes.create_object({'ports': [port]})
|
||||
self.nb_idl.lookup.return_value = ls
|
||||
ret = self.nb_idl.ls_has_virtual_ports(ls_name)
|
||||
|
||||
self.assertEqual(True, ret)
|
||||
self.nb_idl.lookup.assert_called_once_with('Logical_Switch', ls_name)
|
||||
|
||||
def test_ls_has_virtual_ports_not_found(self):
|
||||
ls_name = 'logical_switch'
|
||||
port = fakes.create_object({'type': constants.OVN_VM_VIF_PORT_TYPE})
|
||||
ls = fakes.create_object({'ports': [port]})
|
||||
self.nb_idl.lookup.return_value = ls
|
||||
ret = self.nb_idl.ls_has_virtual_ports(ls_name)
|
||||
|
||||
self.assertEqual(False, ret)
|
||||
self.nb_idl.lookup.assert_called_once_with('Logical_Switch', ls_name)
|
||||
|
||||
def test_get_nat_by_logical_port(self):
|
||||
logical_port = 'logical_port'
|
||||
nat_info = ['nat_info']
|
||||
self.nb_idl.db_find_rows.return_value.execute.return_value = nat_info
|
||||
ret = self.nb_idl.get_nat_by_logical_port(logical_port)
|
||||
|
||||
self.assertEqual('nat_info', ret)
|
||||
self.nb_idl.db_find_rows.assert_called_once_with(
|
||||
'NAT',
|
||||
('logical_port', '=', logical_port))
|
||||
|
||||
def test_get_active_ports_on_chassis(self):
|
||||
chassis = 'local_chassis'
|
||||
row1 = fakes.create_object({
|
||||
'options': {'requested-chassis': chassis}})
|
||||
row2 = fakes.create_object({
|
||||
'options': {'requested-chassis': 'other_chassis'}})
|
||||
self.nb_idl.db_find_rows.return_value.execute.return_value = [
|
||||
row1, row2]
|
||||
ret = self.nb_idl.get_active_ports_on_chassis(chassis)
|
||||
|
||||
self.assertEqual([row1], ret)
|
||||
self.nb_idl.db_find_rows.assert_called_once_with(
|
||||
'Logical_Switch_Port',
|
||||
('up', '=', True))
|
||||
|
||||
|
||||
class TestOvsdbSbOvnIdl(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -561,6 +634,43 @@ class TestOvsdbSbOvnIdl(test_base.TestCase):
|
||||
self.assertEqual(lb2, ret)
|
||||
|
||||
|
||||
class TestOvnNbIdl(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvnNbIdl, self).setUp()
|
||||
config.register_opts()
|
||||
mock.patch.object(idlutils, 'get_schema_helper').start()
|
||||
mock.patch.object(ovn_utils.OvnIdl, '__init__').start()
|
||||
self.nb_idl = ovn_utils.OvnNbIdl('tcp:127.0.0.1:6640')
|
||||
|
||||
@mock.patch.object(Stream, 'ssl_set_ca_cert_file')
|
||||
@mock.patch.object(Stream, 'ssl_set_certificate_file')
|
||||
@mock.patch.object(Stream, 'ssl_set_private_key_file')
|
||||
def test__check_and_set_ssl_files(
|
||||
self, mock_ssl_priv_key, mock_ssl_cert, mock_ssl_ca_cert):
|
||||
CONF.set_override('ovn_nb_private_key', 'fake-priv-key')
|
||||
CONF.set_override('ovn_nb_certificate', 'fake-cert')
|
||||
CONF.set_override('ovn_nb_ca_cert', 'fake-ca-cert')
|
||||
|
||||
self.nb_idl._check_and_set_ssl_files('fake-schema')
|
||||
|
||||
mock_ssl_priv_key.assert_called_once_with('fake-priv-key')
|
||||
mock_ssl_cert.assert_called_once_with('fake-cert')
|
||||
mock_ssl_ca_cert.assert_called_once_with('fake-ca-cert')
|
||||
|
||||
@mock.patch.object(connection, 'Connection')
|
||||
def test_start(self, mock_conn):
|
||||
notify_handler = mock.Mock()
|
||||
self.nb_idl.notify_handler = notify_handler
|
||||
self.nb_idl._events = ['fake-event0', 'fake-event1']
|
||||
|
||||
self.nb_idl.start()
|
||||
|
||||
mock_conn.assert_called_once_with(self.nb_idl, timeout=180)
|
||||
notify_handler.watch_events.assert_called_once_with(
|
||||
['fake-event0', 'fake-event1'])
|
||||
|
||||
|
||||
class TestOvnSbIdl(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -405,11 +405,11 @@ class TestOvsIdl(test_base.TestCase):
|
||||
self.ovs_idl.idl_ovs.db_get.assert_called_once_with(
|
||||
'Open_vSwitch', '.', 'external_ids')
|
||||
|
||||
def test_get_own_chassis_name(self):
|
||||
def test_get_own_chassis_id(self):
|
||||
expected_return = 'fake-sys'
|
||||
row = {'system-id': expected_return}
|
||||
self._test_ovs_ext_ids_getters(
|
||||
self.ovs_idl.get_own_chassis_name, row, expected_return)
|
||||
self.ovs_idl.get_own_chassis_id, row, expected_return)
|
||||
|
||||
def test_get_ovn_remote(self):
|
||||
expected_return = 'fake-ovn-remote'
|
||||
|
@ -0,0 +1,406 @@
|
||||
|
||||
# Copyright 2023 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ovn_bgp_agent import constants
|
||||
from ovn_bgp_agent.drivers.openstack.watchers import nb_bgp_watcher
|
||||
from ovn_bgp_agent.tests import base as test_base
|
||||
from ovn_bgp_agent.tests import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestLogicalSwitchPortProviderCreateEvent(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLogicalSwitchPortProviderCreateEvent, self).setUp()
|
||||
self.chassis = 'fake-chassis'
|
||||
self.agent = mock.Mock(chassis=self.chassis)
|
||||
self.event = nb_bgp_watcher.LogicalSwitchPortProviderCreateEvent(
|
||||
self.agent)
|
||||
|
||||
def test_match_fn(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
old = utils.create_row(options={}, up=True)
|
||||
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_match_fn_exception(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
up=False)
|
||||
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_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=False)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_match_fn_invalid_address(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac '],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_match_fn_wrong_chassis(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': 'fake_chassis'},
|
||||
up=True)
|
||||
old = utils.create_row(options={}, up=True)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_run(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.expose_ip.assert_called_once_with(['192.168.0.1'], row)
|
||||
|
||||
def test_run_wrong_type(self):
|
||||
row = utils.create_row(
|
||||
type=constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.expose_ip.assert_not_called()
|
||||
|
||||
|
||||
class TestLogicalSwitchPortProviderDeleteEvent(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLogicalSwitchPortProviderDeleteEvent, self).setUp()
|
||||
self.chassis = 'fake-chassis'
|
||||
self.agent = mock.Mock(chassis=self.chassis)
|
||||
self.event = nb_bgp_watcher.LogicalSwitchPortProviderDeleteEvent(
|
||||
self.agent)
|
||||
|
||||
def test_match_fn_delete(self):
|
||||
event = self.event.ROW_DELETE
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.assertTrue(self.event.match_fn(event, row, mock.Mock()))
|
||||
|
||||
def test_match_fn_update(self):
|
||||
event = self.event.ROW_UPDATE
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=False)
|
||||
old = utils.create_row(options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.assertTrue(self.event.match_fn(event, row, old))
|
||||
|
||||
def test_match_fn_exception(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
up=False)
|
||||
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_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=False)
|
||||
old = utils.create_row(options={'requested-chassis': self.chassis},
|
||||
up=False)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_match_fn_invalid_address(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac '],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_match_fn_wrong_chassis(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
old = utils.create_row(options={'requested-chassis': 'other_chassis'})
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_run(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.withdraw_ip.assert_called_once_with(['192.168.0.1'], row)
|
||||
|
||||
def test_run_wrong_type(self):
|
||||
row = utils.create_row(
|
||||
type=constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.withdraw_ip.assert_not_called()
|
||||
|
||||
|
||||
class TestLogicalSwitchPortFIPCreateEvent(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLogicalSwitchPortFIPCreateEvent, self).setUp()
|
||||
self.chassis = 'fake-chassis'
|
||||
self.agent = mock.Mock(chassis=self.chassis)
|
||||
self.event = nb_bgp_watcher.LogicalSwitchPortFIPCreateEvent(
|
||||
self.agent)
|
||||
|
||||
def test_match_fn_chassis_change(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
old = utils.create_row(options={}, up=True)
|
||||
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_match_fn_status_change(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
old = utils.create_row(options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=False)
|
||||
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_match_fn_fip_addition(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
old = utils.create_row(options={'requested-chassis': self.chassis},
|
||||
external_ids={},
|
||||
up=True)
|
||||
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_match_fn_no_fip(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={},
|
||||
up=True)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_match_fn_wrong_chassis(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': 'wrong_chassis'},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_match_fn_port_down(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=False)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_match_fn_wrong_address(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac '],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_match_fn_exception(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=False)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_run(self):
|
||||
external_ip = '10.0.0.10'
|
||||
ls_name = 'neutron-net-id'
|
||||
self.agent.get_port_external_ip_and_ls.return_value = (external_ip,
|
||||
ls_name)
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
name='net-id')
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.expose_fip.assert_called_once_with(external_ip, ls_name)
|
||||
|
||||
def test_run_no_external_ip(self):
|
||||
external_ip = None
|
||||
ls_name = 'logical_switch'
|
||||
self.agent.get_port_external_ip_and_ls.return_value = (external_ip,
|
||||
ls_name)
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
name='net-id')
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.expose_fip.assert_not_called()
|
||||
|
||||
def test_run_wrong_type(self):
|
||||
row = utils.create_row(
|
||||
type=constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE)
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.expose_fip.assert_not_called()
|
||||
|
||||
|
||||
class TestLogicalSwitchPortFIPDeleteEvent(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLogicalSwitchPortFIPDeleteEvent, self).setUp()
|
||||
self.chassis = 'fake-chassis'
|
||||
self.agent = mock.Mock(chassis=self.chassis)
|
||||
self.event = nb_bgp_watcher.LogicalSwitchPortFIPDeleteEvent(
|
||||
self.agent)
|
||||
|
||||
def test_match_fn_delete(self):
|
||||
event = self.event.ROW_DELETE
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
self.assertTrue(self.event.match_fn(event, row, mock.Mock()))
|
||||
|
||||
def test_match_fn_update(self):
|
||||
event = self.event.ROW_UPDATE
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=False)
|
||||
old = utils.create_row(up=True)
|
||||
self.assertTrue(self.event.match_fn(event, row, old))
|
||||
|
||||
def test_match_fn_exception(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
up=False)
|
||||
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_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=False)
|
||||
old = utils.create_row(options={'requested-chassis': self.chassis},
|
||||
up=False)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_match_fn_invalid_address(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac '],
|
||||
options={'requested-chassis': self.chassis},
|
||||
up=True)
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock()))
|
||||
|
||||
def test_match_fn_wrong_chassis(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
old = utils.create_row(options={'requested-chassis': 'other_chassis'})
|
||||
self.assertFalse(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_match_fn_chassis_update(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': 'other_chassis'},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
old = utils.create_row(options={'requested-chassis': self.chassis})
|
||||
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_match_fn_fip_update(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
options={'requested-chassis': self.chassis},
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'new-fip-ip'},
|
||||
up=True)
|
||||
old = utils.create_row(
|
||||
external_ids={constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'})
|
||||
self.assertTrue(self.event.match_fn(mock.Mock(), row, old))
|
||||
|
||||
def test_run(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
external_ids={
|
||||
constants.OVN_FIP_EXT_ID_KEY: 'fip-ip'},
|
||||
up=True)
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.withdraw_fip.assert_called_once_with('fip-ip')
|
||||
|
||||
def test_run_no_fip(self):
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
external_ids={})
|
||||
old = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
external_ids={})
|
||||
self.event.run(mock.Mock(), row, old)
|
||||
self.agent.withdraw_fip.assert_not_called()
|
||||
|
||||
def test_run_wrong_type(self):
|
||||
row = utils.create_row(
|
||||
type=constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE)
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.withdraw_fip.assert_not_called()
|
||||
|
||||
|
||||
class TestLocalnetCreateDeleteEvent(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLocalnetCreateDeleteEvent, self).setUp()
|
||||
self.agent = mock.Mock()
|
||||
self.event = nb_bgp_watcher.LocalnetCreateDeleteEvent(
|
||||
self.agent)
|
||||
|
||||
def test_match_fn(self):
|
||||
row = utils.create_row(type=constants.OVN_LOCALNET_VIF_PORT_TYPE)
|
||||
self.assertTrue(self.event.match_fn(None, row, None))
|
||||
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE)
|
||||
self.assertFalse(self.event.match_fn(None, row, None))
|
||||
|
||||
def test_run(self):
|
||||
row = utils.create_row(type=constants.OVN_LOCALNET_VIF_PORT_TYPE)
|
||||
self.event.run(None, row, None)
|
||||
self.agent.sync.assert_called_once()
|
9
releasenotes/notes/nb_driver-cc7098183fcedb0a.yaml
Normal file
9
releasenotes/notes/nb_driver-cc7098183fcedb0a.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
This patch introduces a new driver that instead of connecting to the OVN
|
||||
SB DB to watch for relevant events, it connects to the OVN NB DB. The main
|
||||
reasons for doing so are: 1) scalability purposes; and 2) rely on the
|
||||
stable fields offered by the NB DB, instead of the SB DB that may change
|
||||
any time and break our watchers logic (as it has already happened with the
|
||||
OVN Load_Balancer table and its datapath field usage).
|
@ -36,6 +36,7 @@ console_scripts =
|
||||
|
||||
ovn_bgp_agent.drivers =
|
||||
ovn_bgp_driver = ovn_bgp_agent.drivers.openstack.ovn_bgp_driver:OVNBGPDriver
|
||||
nb_ovn_bgp_driver = ovn_bgp_agent.drivers.openstack.nb_ovn_bgp_driver:NBOVNBGPDriver
|
||||
ovn_evpn_driver = ovn_bgp_agent.drivers.openstack.ovn_evpn_driver:OVNEVPNDriver
|
||||
ovn_stretched_l2_bgp_driver = ovn_bgp_agent.drivers.openstack.ovn_stretched_l2_bgp_driver:OVNBGPStretchedL2Driver
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user