Add initial support for local OVN cluster instead of kernel-networking
This patch adds support for configuring a local (in-node) OVN cluster to do the actions that were being done by the kernel networking. This way, things like ovs-dpdk or HWOL could be supported. This also includes adaptation to the tox.ini coverage so that test files are not considered. This was making coveraged artificially higher that it was, so this patch is also adjusting the minimun coverage accordingly. Missing items to be handled in follow up patch sets: - Documentation - Support for IPv6 - Support for BFD - Support for vlan provider networks Change-Id: Ieff19d88a22d7267bd58f8d1d4ecbcdc668c6bfb
This commit is contained in:
parent
629c6dff34
commit
938cdc2643
@ -66,6 +66,11 @@ agent_opts = [
|
||||
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_sb_connection',
|
||||
default='',
|
||||
help='The connection string for the OVN_Southbound OVSDB.\n'
|
||||
'Use tcp:IP:PORT for TCP connection.\n'
|
||||
'Use unix:FILE for unix domain socket connection.'),
|
||||
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 '
|
||||
@ -78,6 +83,11 @@ agent_opts = [
|
||||
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_connection',
|
||||
default='',
|
||||
help='The connection string for the OVN_Northbound OVSDB.\n'
|
||||
'Use tcp:IP:PORT for TCP connection.\n'
|
||||
'Use unix:FILE for unix domain socket connection.'),
|
||||
cfg.StrOpt('bgp_AS',
|
||||
default='64999',
|
||||
help='AS number to be used by the Agent when running in BGP '
|
||||
@ -173,6 +183,25 @@ https://docs.openstack.org/oslo.rootwrap/latest/user/usage.html#daemon-mode
|
||||
""")),
|
||||
]
|
||||
|
||||
local_ovn_cluster_opts = [
|
||||
cfg.StrOpt('ovn_nb_connection',
|
||||
default='unix:/var/run/ovn/ovnnb_db.sock',
|
||||
help='The connection string for the OVN_Northbound OVSDB.\n'
|
||||
'Use tcp:IP:PORT for TCP connection.\n'
|
||||
'Use unix:FILE for unix domain socket connection.'),
|
||||
cfg.ListOpt('external_nics',
|
||||
default=[],
|
||||
help='List of NICS that the local OVN cluster needs to be '
|
||||
'connected to for the external connectivity.'),
|
||||
cfg.ListOpt('peer_ips',
|
||||
default=[],
|
||||
help='List of peer IPs used for redirecting the outgoing '
|
||||
'traffic (ECMP supported).'),
|
||||
cfg.ListOpt('provider_networks_pool_prefixes',
|
||||
default=['192.168.0.0/16'],
|
||||
help='List of prefixes for provider networks'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
EXTRA_LOG_LEVEL_DEFAULTS = [
|
||||
'oslo.privsep.daemon=INFO'
|
||||
@ -183,7 +212,8 @@ logging.register_options(CONF)
|
||||
|
||||
def register_opts():
|
||||
CONF.register_opts(agent_opts)
|
||||
CONF.register_opts(root_helper_opts, "AGENT")
|
||||
CONF.register_opts(root_helper_opts, "agent")
|
||||
CONF.register_opts(local_ovn_cluster_opts, "local_ovn_cluster")
|
||||
|
||||
|
||||
def init(args, **kwargs):
|
||||
@ -198,7 +228,7 @@ def setup_logging():
|
||||
|
||||
|
||||
def get_root_helper(conf):
|
||||
return conf.AGENT.root_helper
|
||||
return conf.agent.root_helper
|
||||
|
||||
|
||||
def setup_privsep():
|
||||
@ -208,5 +238,6 @@ def setup_privsep():
|
||||
def list_opts():
|
||||
return [
|
||||
("DEFAULT", agent_opts),
|
||||
("AGENT", root_helper_opts)
|
||||
("agent", root_helper_opts),
|
||||
("ovn", local_ovn_cluster_opts),
|
||||
]
|
||||
|
@ -77,3 +77,15 @@ EXPOSE_METHOD_L2VNI = 'l2vni'
|
||||
EXPOSE_METHOD_VRF = 'vrf'
|
||||
EXPOSE_METHOD_OVN = 'ovn'
|
||||
EXPOSE_METHOD_DYNAMIC = 'dynamic'
|
||||
|
||||
# OVN Cluster related constants
|
||||
OVN_CLUSTER_BRIDGE = 'bgp'
|
||||
OVN_CLUSTER_ROUTER = 'bgp-router'
|
||||
OVN_CLUSTER_ROUTER_INTERNAL_MAC = '40:44:00:00:00:06'
|
||||
|
||||
# FIXME(ltomasbo): This can be removed once ovsdbapp version is >=1.13.0
|
||||
POLICY_ACTION_REROUTE = 'reroute'
|
||||
POLICY_ACTION_TYPES = (POLICY_ACTION_REROUTE)
|
||||
LR_POLICY_PRIORITY_MAX = 32767
|
||||
|
||||
ROUTE_DISCARD = 'discard'
|
||||
|
@ -34,7 +34,12 @@ LOG = logging.getLogger(__name__)
|
||||
# LOG.setLevel(logging.DEBUG)
|
||||
# logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
OVN_TABLES = ["Logical_Switch_Port", "NAT", "Logical_Switch"]
|
||||
OVN_TABLES = ['Logical_Switch_Port', 'NAT', 'Logical_Switch']
|
||||
LOCAL_CLUSTER_OVN_TABLES = ['Logical_Switch', 'Logical_Switch_Port',
|
||||
'Logical_Router', 'Logical_Router_Port',
|
||||
'Logical_Router_Policy',
|
||||
'Logical_Router_Static_Route', 'Gateway_Chassis',
|
||||
'Static_MAC_Binding']
|
||||
|
||||
|
||||
class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
@ -47,6 +52,7 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
self._init_vars()
|
||||
|
||||
self._nb_idl = None
|
||||
self._local_nb_idl = None
|
||||
self._post_start_event = threading.Event()
|
||||
|
||||
@property
|
||||
@ -55,10 +61,20 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
self._post_start_event.wait()
|
||||
return self._nb_idl
|
||||
|
||||
@property
|
||||
def local_nb_idl(self):
|
||||
if not self._local_nb_idl:
|
||||
self._post_start_event.wait()
|
||||
return self._local_nb_idl
|
||||
|
||||
@nb_idl.setter
|
||||
def nb_idl(self, val):
|
||||
self._nb_idl = val
|
||||
|
||||
@local_nb_idl.setter
|
||||
def local_nb_idl(self, val):
|
||||
self._local_nb_idl = val
|
||||
|
||||
def _init_vars(self):
|
||||
self.ovn_bridge_mappings = {} # {'public': 'br-ex'}
|
||||
self.ovs_flows = {}
|
||||
@ -83,8 +99,7 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
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")
|
||||
self.ovn_remote = self.ovs_idl.get_ovn_remote(nb=True)
|
||||
LOG.info("Loaded chassis %s.", self.chassis)
|
||||
|
||||
LOG.info("Starting VRF configuration for advertising routes")
|
||||
@ -110,6 +125,15 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
self.ovn_remote,
|
||||
tables=OVN_TABLES,
|
||||
events=events).start()
|
||||
|
||||
# if local OVN cluster, gets an idl for it
|
||||
if CONF.exposing_method == constants.EXPOSE_METHOD_OVN:
|
||||
self.local_nb_idl = ovn.OvnNbIdl(
|
||||
CONF.local_ovn_cluster.ovn_nb_connection,
|
||||
tables=LOCAL_CLUSTER_OVN_TABLES,
|
||||
events=[],
|
||||
leader_only=True).start()
|
||||
|
||||
# Now IDL connections can be safely used
|
||||
self._post_start_event.set()
|
||||
|
||||
@ -136,13 +160,11 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
self._init_vars()
|
||||
|
||||
LOG.debug("Configuring default wiring for each provider network")
|
||||
# Get bridge mappings: xxxx:br-ex,yyyy:br-ex2
|
||||
bridge_mappings = self.ovs_idl.get_ovn_bridge_mappings()
|
||||
|
||||
# Apply base configuration for each bridge
|
||||
self.ovn_bridge_mappings, self.ovs_flows = (
|
||||
wire_utils.ensure_base_wiring_config(self.nb_idl, bridge_mappings,
|
||||
self.ovn_routing_tables))
|
||||
wire_utils.ensure_base_wiring_config(
|
||||
self.nb_idl, self.ovs_idl, ovn_idl=self.local_nb_idl,
|
||||
routing_tables=self.ovn_routing_tables))
|
||||
|
||||
LOG.debug("Syncing current routes.")
|
||||
# add missing routes/ips for IPs on provider network
|
||||
@ -164,10 +186,11 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
def _ensure_port_exposed(self, port):
|
||||
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)
|
||||
external_ip, external_mac, ls_name = (
|
||||
self.get_port_external_ip_and_ls(port.name))
|
||||
if not external_ip or not ls_name:
|
||||
return
|
||||
return self._expose_fip(external_ip, ls_name, port)
|
||||
return self._expose_fip(external_ip, external_mac, ls_name, port)
|
||||
|
||||
logical_switch = port.external_ids.get(
|
||||
constants.OVN_LS_NAME_EXT_ID_KEY)
|
||||
@ -194,18 +217,21 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
'bridge_vlan': bridge_vlan,
|
||||
'localnet': localnet}
|
||||
ips = port.addresses[0].strip().split(' ')[1:]
|
||||
self._expose_ip(ips, logical_switch, bridge_device, bridge_vlan,
|
||||
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))
|
||||
|
||||
def _expose_provider_port(self, port_ips, logical_switch, bridge_device,
|
||||
bridge_vlan, localnet, proxy_cidrs=None):
|
||||
def _expose_provider_port(self, port_ips, mac, logical_switch,
|
||||
bridge_device, bridge_vlan, localnet,
|
||||
proxy_cidrs=None):
|
||||
# Connect to OVN
|
||||
try:
|
||||
if wire_utils.wire_provider_port(
|
||||
self.ovn_routing_tables_routes, self.ovs_flows, port_ips,
|
||||
bridge_device, bridge_vlan, localnet,
|
||||
self.ovn_routing_tables, proxy_cidrs):
|
||||
self.ovn_routing_tables, proxy_cidrs, mac=mac,
|
||||
ovn_idl=self.local_nb_idl):
|
||||
# Expose the IP now that it is connected
|
||||
bgp_utils.announce_ips(port_ips)
|
||||
for ip in port_ips:
|
||||
@ -226,7 +252,8 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
try:
|
||||
wire_utils.unwire_provider_port(
|
||||
self.ovn_routing_tables_routes, port_ips, bridge_device,
|
||||
bridge_vlan, self.ovn_routing_tables, proxy_cidrs)
|
||||
bridge_vlan, self.ovn_routing_tables, proxy_cidrs,
|
||||
ovn_idl=self.local_nb_idl)
|
||||
except Exception as e:
|
||||
LOG.exception("Unexpected exception while unwiring provider port: "
|
||||
"%s", e)
|
||||
@ -239,7 +266,7 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
bridge_vlan = None
|
||||
network_name = localnet.options.get('network_name')
|
||||
if network_name:
|
||||
bridge_device = self.ovn_bridge_mappings[network_name]
|
||||
bridge_device = self.ovn_bridge_mappings.get(network_name)
|
||||
if localnet.tag:
|
||||
bridge_vlan = localnet.tag[0]
|
||||
return bridge_device, bridge_vlan
|
||||
@ -274,11 +301,13 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
'bridge_device': bridge_device,
|
||||
'bridge_vlan': bridge_vlan,
|
||||
'localnet': localnet}
|
||||
return self._expose_ip(ips, logical_switch, bridge_device, bridge_vlan,
|
||||
port_type=row.type, cidr=row.external_ids.get(
|
||||
mac = row.addresses[0].strip().split(' ')[0]
|
||||
return self._expose_ip(ips, mac, logical_switch, bridge_device,
|
||||
bridge_vlan, port_type=row.type,
|
||||
cidr=row.external_ids.get(
|
||||
constants.OVN_CIDRS_EXT_ID_KEY))
|
||||
|
||||
def _expose_ip(self, ips, logical_switch, bridge_device, bridge_vlan,
|
||||
def _expose_ip(self, ips, mac, logical_switch, bridge_device, bridge_vlan,
|
||||
port_type, cidr):
|
||||
LOG.debug("Adding BGP route for logical port with ip %s", ips)
|
||||
localnet = self.ovn_provider_ls[logical_switch]['localnet']
|
||||
@ -287,13 +316,13 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
# NOTE: For Amphora Load Balancer with IPv6 VIP on the provider
|
||||
# network, we need a NDP Proxy so that the traffic from the
|
||||
# amphora can properly be redirected back
|
||||
if not self._expose_provider_port(ips, logical_switch,
|
||||
if not self._expose_provider_port(ips, mac, logical_switch,
|
||||
bridge_device, bridge_vlan,
|
||||
localnet, [cidr]):
|
||||
return []
|
||||
|
||||
else:
|
||||
if not self._expose_provider_port(ips, logical_switch,
|
||||
if not self._expose_provider_port(ips, mac, logical_switch,
|
||||
bridge_device, bridge_vlan,
|
||||
localnet):
|
||||
return []
|
||||
@ -352,15 +381,16 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
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
|
||||
return None, None, None
|
||||
net_id = nat_entry.external_ids.get(constants.OVN_FIP_NET_EXT_ID_KEY)
|
||||
if not net_id:
|
||||
return nat_entry.external_ip, None
|
||||
return nat_entry.external_ip, nat_entry.external_mac, None
|
||||
else:
|
||||
return nat_entry.external_ip, "neutron-{}".format(net_id)
|
||||
ls_name = "neutron-{}".format(net_id)
|
||||
return nat_entry.external_ip, nat_entry.external_mac, ls_name
|
||||
|
||||
@lockutils.synchronized('nbbgp')
|
||||
def expose_fip(self, ip, logical_switch, row):
|
||||
def expose_fip(self, ip, mac, logical_switch, row):
|
||||
'''Advertice BGP route by adding IP to device.
|
||||
|
||||
This methods ensures BGP advertises the FIP associated to a VM in a
|
||||
@ -372,9 +402,9 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
VRF), and adds the IP of:
|
||||
- VM FIP
|
||||
'''
|
||||
return self._expose_fip(ip, logical_switch, row)
|
||||
return self._expose_fip(ip, mac, logical_switch, row)
|
||||
|
||||
def _expose_fip(self, ip, logical_switch, row):
|
||||
def _expose_fip(self, ip, mac, logical_switch, row):
|
||||
localnet, bridge_device, bridge_vlan = self._get_ls_localnet_info(
|
||||
logical_switch)
|
||||
if not bridge_device:
|
||||
@ -394,7 +424,7 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase):
|
||||
return
|
||||
self.ovn_tenant_ls[tenant_logical_switch] = True
|
||||
LOG.debug("Adding BGP route for FIP with ip %s", ip)
|
||||
if not self._expose_provider_port([ip], tenant_logical_switch,
|
||||
if not self._expose_provider_port([ip], mac, tenant_logical_switch,
|
||||
bridge_device, bridge_vlan,
|
||||
localnet):
|
||||
return False
|
||||
|
@ -354,7 +354,7 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
|
||||
if wire_utils.wire_provider_port(
|
||||
self.ovn_routing_tables_routes, self.ovs_flows, port_ips,
|
||||
bridge_device, bridge_vlan, localnet,
|
||||
self.ovn_routing_tables, proxy_cidrs, lladdr):
|
||||
self.ovn_routing_tables, proxy_cidrs, lladdr=lladdr):
|
||||
# Expose the IP now that it is connected
|
||||
bgp_utils.announce_ips(port_ips)
|
||||
return True
|
||||
@ -428,7 +428,8 @@ class OVNBGPDriver(driver_api.AgentDriverBase):
|
||||
try:
|
||||
return wire_utils.unwire_provider_port(
|
||||
self.ovn_routing_tables_routes, port_ips, bridge_device,
|
||||
bridge_vlan, self.ovn_routing_tables, proxy_cidrs, lladdr)
|
||||
bridge_vlan, self.ovn_routing_tables, proxy_cidrs,
|
||||
lladdr=lladdr)
|
||||
except Exception as e:
|
||||
LOG.exception("Unexpected exception while unwiring provider port: "
|
||||
"%s", e)
|
||||
|
@ -12,6 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import netaddr
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
@ -51,7 +53,8 @@ class OvnDbNotifyHandler(event.RowEventHandler):
|
||||
class OvnNbIdl(OvnIdl):
|
||||
SCHEMA = 'OVN_Northbound'
|
||||
|
||||
def __init__(self, connection_string, events=None, tables=None):
|
||||
def __init__(self, connection_string, events=None, tables=None,
|
||||
leader_only=False):
|
||||
if connection_string.startswith("ssl"):
|
||||
self._check_and_set_ssl_files(self.SCHEMA)
|
||||
helper = self._get_ovsdb_helper(connection_string)
|
||||
@ -61,7 +64,7 @@ class OvnNbIdl(OvnIdl):
|
||||
for table in tables:
|
||||
helper.register_table(table)
|
||||
super(OvnNbIdl, self).__init__(
|
||||
None, connection_string, helper, leader_only=False)
|
||||
None, connection_string, helper, leader_only=leader_only)
|
||||
|
||||
def _get_ovsdb_helper(self, connection_string):
|
||||
return idlutils.get_schema_helper(connection_string, self.SCHEMA)
|
||||
@ -176,6 +179,200 @@ class LSGetLocalnetPortsCommand(command.ReadOnlyCommand):
|
||||
raise RuntimeError(msg) from e
|
||||
|
||||
|
||||
# FIXME(ltomasbo): This can be removed once ovsdbapp version is >=2.1.0
|
||||
class _LrpNetworksCommand(command.BaseCommand):
|
||||
table = 'Logical_Router_Port'
|
||||
|
||||
def __init__(self, api, port, networks, exists):
|
||||
super().__init__(api)
|
||||
self.port = port
|
||||
self.exists = exists
|
||||
if isinstance(networks, (str, bytes)):
|
||||
networks = [networks]
|
||||
self.networks = [str(netaddr.IPNetwork(network))
|
||||
for network in networks]
|
||||
|
||||
|
||||
# FIXME(ltomasbo): This can be removed once ovsdbapp version is >=2.1.0
|
||||
class LrpAddNetworksCommand(_LrpNetworksCommand):
|
||||
def run_idl(self, txn):
|
||||
lrp = self.api.lookup(self.table, self.port)
|
||||
for network in self.networks:
|
||||
if network in lrp.networks and not self.exists:
|
||||
msg = "Network '%s' already exist in networks of port %s" % (
|
||||
network, lrp.uuid)
|
||||
raise RuntimeError(msg)
|
||||
lrp.addvalue('networks', network)
|
||||
|
||||
|
||||
# FIXME(ltomasbo): This can be removed once ovsdbapp supports it
|
||||
class LrRouteAddCommand(command.BaseCommand):
|
||||
def __init__(self, api, router, prefix, nexthop, port=None,
|
||||
policy='dst-ip', ecmp=False, may_exist=False):
|
||||
prefix = str(netaddr.IPNetwork(prefix))
|
||||
if nexthop != constants.ROUTE_DISCARD:
|
||||
nexthop = str(netaddr.IPAddress(nexthop))
|
||||
super().__init__(api)
|
||||
self.router = router
|
||||
self.prefix = prefix
|
||||
self.nexthop = nexthop
|
||||
self.port = port
|
||||
self.policy = policy
|
||||
self.ecmp = ecmp
|
||||
self.may_exist = may_exist
|
||||
|
||||
def run_idl(self, txn):
|
||||
lr = self.api.lookup('Logical_Router', self.router)
|
||||
for route in lr.static_routes:
|
||||
if self.prefix == route.ip_prefix:
|
||||
if self.ecmp and self.nexthop != route.nexthop:
|
||||
continue
|
||||
if not self.may_exist:
|
||||
msg = "Route %s already exists on router %s" % (
|
||||
self.prefix, self.router)
|
||||
raise RuntimeError(msg)
|
||||
route.nexthop = self.nexthop
|
||||
route.policy = self.policy
|
||||
if self.port:
|
||||
route.output_port = self.port
|
||||
self.result = rowview.RowView(route)
|
||||
return
|
||||
route = txn.insert(self.api.tables['Logical_Router_Static_Route'])
|
||||
route.ip_prefix = self.prefix
|
||||
route.nexthop = self.nexthop
|
||||
route.policy = self.policy
|
||||
if self.port:
|
||||
route.output_port = self.port
|
||||
lr.addvalue('static_routes', route)
|
||||
self.result = route.uuid
|
||||
|
||||
|
||||
# FIXME(ltomasbo): This can be removed once ovsdbapp supports it
|
||||
class LrRouteDelCommand(command.BaseCommand):
|
||||
def __init__(self, api, router,
|
||||
prefix=None, nexthop=None, if_exists=False):
|
||||
if prefix is not None:
|
||||
prefix = str(netaddr.IPNetwork(prefix))
|
||||
super().__init__(api)
|
||||
self.router = router
|
||||
self.prefix = prefix
|
||||
self.nexthop = nexthop
|
||||
self.if_exists = if_exists
|
||||
|
||||
def run_idl(self, txn):
|
||||
lr = self.api.lookup('Logical_Router', self.router)
|
||||
if not self.prefix:
|
||||
lr.static_routes = []
|
||||
return
|
||||
for route in lr.static_routes:
|
||||
if self.prefix == route.ip_prefix:
|
||||
if self.nexthop and route.nexthop != self.nexthop:
|
||||
continue
|
||||
lr.delvalue('static_routes', route)
|
||||
return
|
||||
if not self.if_exists:
|
||||
msg = "Route for %s in router %s does not exist" % (
|
||||
self.prefix, self.router)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
class StaticMACBindingFindCommand(command.DbFindCommand):
|
||||
table = 'Static_MAC_Binding'
|
||||
|
||||
def __init__(self, api, port, ip):
|
||||
super().__init__(
|
||||
api,
|
||||
self.table,
|
||||
('logical_port', '=', port),
|
||||
('ip', '=', ip),
|
||||
row=True,
|
||||
)
|
||||
|
||||
|
||||
# FIXME(ltomasbo): This can be removed once ovsdbapp supports it
|
||||
class StaticMACBindingAddCommand(command.AddCommand):
|
||||
table_name = 'Static_MAC_Binding'
|
||||
|
||||
def __init__(self, api, port, ip, mac, override_dynamic_mac=False,
|
||||
may_exist=False, **columns):
|
||||
super().__init__(api)
|
||||
self.port = port
|
||||
self.ip = ip
|
||||
self.mac = mac
|
||||
self.override_dynamic_mac = override_dynamic_mac
|
||||
self.may_exist = may_exist
|
||||
self.columns = columns
|
||||
|
||||
def run_idl(self, txn):
|
||||
cmd = StaticMACBindingFindCommand(self.api, self.port, self.ip)
|
||||
cmd.run_idl(txn)
|
||||
static_mac_binding_result = cmd.result
|
||||
if static_mac_binding_result:
|
||||
if len(static_mac_binding_result) > 1:
|
||||
# With the current database schema, this cannot happen, but
|
||||
# better safe than sorry.
|
||||
raise RuntimeError(
|
||||
"Unexpected duplicates in database for port %s "
|
||||
"and ip %s" % (self.port, self.ip))
|
||||
binding = static_mac_binding_result[0]
|
||||
if self.may_exist:
|
||||
# When no changes are made to a record, the parent
|
||||
# `post_commit` method will not be called.
|
||||
#
|
||||
# Ensure consistent return to caller of `Command.execute()`
|
||||
# even when no changes have been applied.
|
||||
self.result = rowview.RowView(binding)
|
||||
return
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Static MAC Binding entry for port %s and ip %s exists" % (
|
||||
self.port, self.ip))
|
||||
binding = txn.insert(self.api.tables[self.table_name])
|
||||
binding.logical_port = self.port
|
||||
binding.ip = self.ip
|
||||
binding.mac = self.mac
|
||||
binding.override_dynamic_mac = self.override_dynamic_mac
|
||||
self.set_columns(binding, **self.columns)
|
||||
# Setting the result to something other than a :class:`rowview.RowView`
|
||||
# or :class:`ovs.db.idl.Row` typed value will make the parent
|
||||
# `post_commit` method retrieve the newly insterted row from IDL and
|
||||
# return that to the caller.
|
||||
self.result = binding.uuid
|
||||
|
||||
|
||||
# FIXME(ltomasbo): This can be removed once ovsdbapp supports it
|
||||
class StaticMACBindingDelCommand(command.BaseCommand):
|
||||
table_name = 'Static_MAC_Binding'
|
||||
|
||||
def __init__(self, api, port, ip, if_exists=False, **columns):
|
||||
super().__init__(api)
|
||||
self.port = port
|
||||
self.ip = ip
|
||||
self.if_exists = if_exists
|
||||
self.columns = columns
|
||||
|
||||
def run_idl(self, txn):
|
||||
cmd = StaticMACBindingFindCommand(self.api, self.port, self.ip)
|
||||
cmd.run_idl(txn)
|
||||
static_mac_binding_result = cmd.result
|
||||
if static_mac_binding_result:
|
||||
if len(static_mac_binding_result) > 1:
|
||||
# With the current database schema, this cannot happen, but
|
||||
# better safe than sorry.
|
||||
raise RuntimeError(
|
||||
"Unexpected duplicates in database for port %s "
|
||||
"and ip %s" % (self.port, self.ip))
|
||||
binding = static_mac_binding_result[0]
|
||||
binding.delete()
|
||||
return
|
||||
if self.if_exists:
|
||||
return
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Static MAC Binding entry for port %s and ip %s does not "
|
||||
"exist" % (self.port, self.ip))
|
||||
|
||||
|
||||
class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
||||
def __init__(self, connection):
|
||||
super(OvsdbNbOvnIdl, self).__init__(connection)
|
||||
@ -231,6 +428,26 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
|
||||
return LSGetLocalnetPortsCommand(self, logical_switch,
|
||||
if_exists=if_exists)
|
||||
|
||||
# FIXME(ltomasbo): This can be removed once ovsdbapp version is >=2.1.0
|
||||
def lrp_add_networks(self, port, networks, may_exist=False):
|
||||
return LrpAddNetworksCommand(self, port, networks, may_exist)
|
||||
|
||||
def lr_route_add(self, router, prefix, nexthop, port=None,
|
||||
policy='dst-ip', ecmp=False, may_exist=False):
|
||||
return LrRouteAddCommand(self, router, prefix, nexthop, port,
|
||||
policy, ecmp, may_exist)
|
||||
|
||||
def lr_route_del(self, router, prefix=None, nexthop=None, if_exists=False):
|
||||
return LrRouteDelCommand(self, router, prefix, nexthop, if_exists)
|
||||
|
||||
def static_mac_binding_add(self, port, ip, mac, override_dynamic_mac=False,
|
||||
may_exist=False):
|
||||
return StaticMACBindingAddCommand(self, port, ip, mac,
|
||||
override_dynamic_mac, may_exist)
|
||||
|
||||
def static_mac_binding_del(self, port, ip, if_exists=False):
|
||||
return StaticMACBindingDelCommand(self, port, ip, if_exists)
|
||||
|
||||
|
||||
class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
|
||||
def __init__(self, connection):
|
||||
|
@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from ovs.db import idl
|
||||
from ovsdbapp.backend.ovs_idl import connection
|
||||
@ -24,6 +25,7 @@ from ovn_bgp_agent import exceptions as agent_exc
|
||||
import ovn_bgp_agent.privileged.ovs_vsctl
|
||||
from ovn_bgp_agent.utils import linux_net
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -52,12 +54,17 @@ def get_device_port_at_ovs(device):
|
||||
'ovs-vsctl', ['get', 'Interface', device, 'ofport'])[0].rstrip()
|
||||
|
||||
|
||||
def get_ovs_patch_ports_info(bridge):
|
||||
in_ports = []
|
||||
def get_ovs_ports_info(bridge):
|
||||
ovs_ports = ovn_bgp_agent.privileged.ovs_vsctl.ovs_cmd(
|
||||
'ovs-vsctl', ['list-ports', bridge])[0].rstrip()
|
||||
for ovs_port in ovs_ports.split("\n"):
|
||||
if ovs_port.startswith('patch-provnet-'):
|
||||
return ovs_ports.split("\n")
|
||||
|
||||
|
||||
def get_ovs_patch_ports_info(bridge, prefix='patch-provnet-'):
|
||||
in_ports = []
|
||||
ovs_ports = get_ovs_ports_info(bridge)
|
||||
for ovs_port in ovs_ports:
|
||||
if ovs_port.startswith(prefix):
|
||||
ovs_ofport = get_device_port_at_ovs(ovs_port)
|
||||
in_ports.append(ovs_ofport)
|
||||
return in_ports
|
||||
@ -133,6 +140,11 @@ def remove_extra_ovs_flows(ovs_flows, bridge, cookie):
|
||||
'ovs-ofctl', ['del-flows', bridge, del_flow])
|
||||
|
||||
|
||||
def ensure_flow(bridge, flow):
|
||||
ovn_bgp_agent.privileged.ovs_vsctl.ovs_cmd(
|
||||
'ovs-ofctl', ['add-flow', bridge, flow])
|
||||
|
||||
|
||||
def ensure_evpn_ovs_flow(bridge, cookie, mac, output_port, port_dst, net,
|
||||
strip_vlan=False):
|
||||
ovs_port = _find_ovs_port(bridge)
|
||||
@ -282,20 +294,27 @@ class OvsIdl(object):
|
||||
"""
|
||||
return self._get_from_ext_ids('hostname')
|
||||
|
||||
def get_ovn_remote(self):
|
||||
def get_ovn_remote(self, nb=False):
|
||||
"""Return the external_ids:ovn-remote value of the Open_vSwitch table.
|
||||
|
||||
"""
|
||||
return self._get_from_ext_ids('ovn-remote')
|
||||
if nb:
|
||||
return (CONF.ovn_nb_connection if CONF.ovn_nb_connection
|
||||
else self._get_from_ext_ids('ovn-nb-remote'))
|
||||
return (CONF.ovn_sb_connection if CONF.ovn_sb_connection
|
||||
else self._get_from_ext_ids('ovn-remote'))
|
||||
|
||||
def get_ovn_bridge_mappings(self):
|
||||
def get_ovn_bridge_mappings(self, bridge=None):
|
||||
"""Return a list of bridge mappings
|
||||
|
||||
Return a list of bridge mappings based on the
|
||||
external_ids:ovn-bridge-mappings value of the Open_vSwitch table.
|
||||
"""
|
||||
key = 'ovn-bridge-mappings'
|
||||
if bridge:
|
||||
key = key + '-' + str(bridge)
|
||||
try:
|
||||
return [i.strip() for i in
|
||||
self._get_from_ext_ids('ovn-bridge-mappings').split(',')]
|
||||
self._get_from_ext_ids(key).split(',')]
|
||||
except KeyError:
|
||||
return []
|
||||
|
@ -26,15 +26,18 @@ CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def ensure_base_wiring_config(idl, bridge_mappings, routing_tables):
|
||||
def ensure_base_wiring_config(idl, ovs_idl, ovn_idl=None, routing_tables={}):
|
||||
if CONF.exposing_method == constants.EXPOSE_METHOD_UNDERLAY:
|
||||
return _ensure_base_wiring_config_underlay(idl, bridge_mappings,
|
||||
return _ensure_base_wiring_config_underlay(idl, ovs_idl,
|
||||
routing_tables)
|
||||
elif CONF.exposing_method == constants.EXPOSE_METHOD_OVN:
|
||||
raise NotImplementedError()
|
||||
return _ensure_base_wiring_config_ovn(ovs_idl, ovn_idl)
|
||||
|
||||
|
||||
def _ensure_base_wiring_config_underlay(idl, bridge_mappings, routing_tables):
|
||||
def _ensure_base_wiring_config_underlay(idl, ovs_idl, routing_tables):
|
||||
# Get bridge mappings: xxxx:br-ex,yyyy:br-ex2
|
||||
bridge_mappings = ovs_idl.get_ovn_bridge_mappings()
|
||||
|
||||
ovn_bridge_mappings = {}
|
||||
flows_info = {}
|
||||
for bridge_index, bridge_mapping in enumerate(bridge_mappings, 1):
|
||||
@ -65,6 +68,308 @@ def _ensure_base_wiring_config_underlay(idl, bridge_mappings, routing_tables):
|
||||
return ovn_bridge_mappings, flows_info
|
||||
|
||||
|
||||
def _ensure_base_wiring_config_ovn(ovs_idl, ovn_idl):
|
||||
"""Base configuration for extra OVN cluster instead of kernel networking
|
||||
|
||||
This function is in charge of the steps to ensure the in-node OVN cluster
|
||||
is properly configured by:
|
||||
1. Get the information about the OpenStack provider bridge(s) and the flows
|
||||
info, such as the mac and the in_port
|
||||
2. Add the egress ovs flows so that the destination mac is the one on the
|
||||
extra ovn-cluster, in the LR
|
||||
3. Create the LR in the in-node OVN cluster
|
||||
4. Create the LR policy in the in-node OVN cluster to redirect the traffic
|
||||
(with ECMP support) to the nexthops
|
||||
5. Create the LR routes in the in-node OVN cluster to route any traffic the
|
||||
peers IPs
|
||||
6. Create the LS (+ Localnet port) for the connection between the router
|
||||
and the OpenStack OVN networks. then it connects it to the LR
|
||||
7. Create the LS (+ Localnet port) for the connection between the router
|
||||
and the external network. Then it connects it to the LR
|
||||
8. Create the ingress_flow at the external OVN provider bridges to
|
||||
redirect the needed traffic to the in-cluster OVN networks
|
||||
|
||||
:param ovs_idl: The idl to communicate with local ovs DB
|
||||
:param ovn_idl: The idl to communicate with local (in-node) NB DB
|
||||
:return: ovn_bridge_mappings (network and bridges association) and the
|
||||
flows_info per bridge
|
||||
"""
|
||||
# OpenStack Egress part
|
||||
# Get bridge mappings: xxxx:br-ex,yyyy:br-ex2
|
||||
bridge_mappings = ovs_idl.get_ovn_bridge_mappings()
|
||||
ovn_bridge_mappings = {}
|
||||
flows_info = {}
|
||||
for bridge_mapping in bridge_mappings:
|
||||
network, bridge = helpers.parse_bridge_mapping(bridge_mapping)
|
||||
if not network:
|
||||
continue
|
||||
ovn_bridge_mappings[network] = bridge
|
||||
|
||||
if not flows_info.get(bridge):
|
||||
mac = linux_net.get_interface_address(bridge)
|
||||
flows_info[bridge] = {'mac': mac, 'in_port': set([])}
|
||||
flows_info[bridge]['in_port'] = ovs.get_ovs_patch_ports_info(
|
||||
bridge)
|
||||
_ensure_egress_flows(bridge, flows_info[bridge]['in_port'])
|
||||
|
||||
# Extra OVN cluster configuration
|
||||
provider_cidrs = CONF.local_ovn_cluster.provider_networks_pool_prefixes
|
||||
|
||||
# LR
|
||||
cmds = []
|
||||
cmds.extend(_ensure_ovn_router(ovn_idl))
|
||||
# FIXME(ltomasbo): we need to firsts create the router and then the
|
||||
# policies and routes in a different transaction until ovsdbapp
|
||||
# allows it to do it in one transaction. Once that happen the next
|
||||
# 2 lines can be removed
|
||||
_execute_commands(ovn_idl, cmds)
|
||||
cmds = []
|
||||
cmds.extend(_ensure_ovn_policies(ovn_idl, CONF.local_ovn_cluster.peer_ips))
|
||||
cmds.extend(_ensure_ovn_routes(ovn_idl, CONF.local_ovn_cluster.peer_ips))
|
||||
# Creation of all router related cmds in a single transaction
|
||||
_execute_commands(ovn_idl, cmds)
|
||||
|
||||
# LS
|
||||
bgp_bridge_mappings = ovs_idl.get_ovn_bridge_mappings(
|
||||
bridge=constants.OVN_CLUSTER_BRIDGE)
|
||||
for bridge_mapping in bgp_bridge_mappings:
|
||||
network, bridge = helpers.parse_bridge_mapping(bridge_mapping)
|
||||
if not network:
|
||||
continue
|
||||
# Create LS + Localnet port on it
|
||||
_ensure_ovn_switch(ovn_idl, network)
|
||||
|
||||
# differentiate between internal LS (connecting to OpenStack)
|
||||
# and external LS (connecting to the NICs)
|
||||
if bridge in ovn_bridge_mappings.values():
|
||||
# Internal Bridge connecting to OpenStack OVN cluster
|
||||
_ensure_ovn_network_link(ovn_idl, network, 'internal',
|
||||
provider_cidrs=provider_cidrs)
|
||||
else:
|
||||
ip, mac = linux_net.get_nic_info(bridge)
|
||||
# External Bridge connecting to the external networks
|
||||
_ensure_ovn_network_link(ovn_idl, network, 'external',
|
||||
ip=ip, mac=mac)
|
||||
_ensure_ingress_flows(bridge, mac, network, provider_cidrs)
|
||||
|
||||
return ovn_bridge_mappings, flows_info
|
||||
|
||||
|
||||
def _ensure_ovn_router(ovn_idl):
|
||||
return [ovn_idl.lr_add(constants.OVN_CLUSTER_ROUTER, may_exist=True)]
|
||||
|
||||
|
||||
def _ensure_ovn_switch(ovn_idl, switch_name):
|
||||
ovn_idl.ls_add(switch_name, may_exist=True).execute(check_error=True)
|
||||
|
||||
# Add localnet port to them
|
||||
localnet_port = "{}-localnet".format(switch_name)
|
||||
options = {'network_name': switch_name}
|
||||
cmds = _ensure_lsp_cmds(ovn_idl, localnet_port, switch_name, 'localnet',
|
||||
'unknown', **options)
|
||||
_execute_commands(ovn_idl, cmds)
|
||||
|
||||
|
||||
def _execute_commands(idl, cmds):
|
||||
with idl.transaction(check_error=True) as txn:
|
||||
for command in cmds:
|
||||
txn.add(command)
|
||||
|
||||
|
||||
def _ensure_ovn_network_link(ovn_idl, switch_name, direction,
|
||||
provider_cidrs=None, ip=None, mac=None):
|
||||
"""Base configuration for connecting LR and LSs
|
||||
|
||||
This function is in charge of connecting the LR to the external or internal
|
||||
LS
|
||||
|
||||
For the internal LS it configures:
|
||||
1. Creates LRP to connect to the internal switch
|
||||
2. If networks (provider_cidrs) are different, adding the new networks
|
||||
3. Create LSP related to the LRP with the right options, including the
|
||||
arp_proxy
|
||||
4. Bind the LRP to the local chassis
|
||||
|
||||
For the external LS it configures:
|
||||
1. Creates LRP to connect to the external switch
|
||||
2. If networks (ip) is different than the nic network add the nic network
|
||||
and remove the extra ones
|
||||
3. Create LSP related to the LRP with the right options
|
||||
|
||||
:param ovn_idl: The idl to communicate with local (in-node) NB DB
|
||||
:param switch_name: the name of the logical switch to configure
|
||||
:param direction: can be 'internal' or 'external'
|
||||
:param provider_cidrs (optional): CIDRs to configure the networks of the
|
||||
LRP, as well as to configure the ARP
|
||||
proxy on the internal LSP
|
||||
(only for the internal)
|
||||
:param ip (optional): IP to configure in the LRP connected to the external
|
||||
switch (only for the external)
|
||||
:param mac (optional): MAC to configure in the LRP connected to the
|
||||
external switch (only for the external)
|
||||
"""
|
||||
# It accepts 2 values for direction: internal or external
|
||||
cmds = []
|
||||
if direction == 'internal':
|
||||
# Connect BGP router to the internal logical switch
|
||||
r_port_name = "{}-openstack".format(constants.OVN_CLUSTER_ROUTER)
|
||||
try:
|
||||
ovn_idl.lrp_add(constants.OVN_CLUSTER_ROUTER, r_port_name,
|
||||
constants.OVN_CLUSTER_ROUTER_INTERNAL_MAC,
|
||||
provider_cidrs, peer=[], may_exist=True).execute(
|
||||
check_error=True)
|
||||
except RuntimeError as rte:
|
||||
# TODO(ltomasbo): Change OVSDBAPP to return a different error for
|
||||
# this to avoid having to compare strings as this is error prone
|
||||
networks_message = 'with different networks'
|
||||
if networks_message not in str(rte):
|
||||
raise
|
||||
# Trying to sync the networks by adding them
|
||||
cmds.append(ovn_idl.lrp_add_networks(r_port_name, provider_cidrs,
|
||||
may_exist=True))
|
||||
|
||||
s_port_name = "openstack-{}".format(constants.OVN_CLUSTER_ROUTER)
|
||||
# NOTE(ltomasbo): require v23.06.0 so that proxy-arp works as expected.
|
||||
# If older version the provider_cidrs should contain all the provider
|
||||
# network cidrs, pointing to the gateway IP of the network.
|
||||
cidrs = ','.join(provider_cidrs) if provider_cidrs else '0.0.0.0/0'
|
||||
options = {'router-port': r_port_name, 'arp_proxy': cidrs}
|
||||
cmds.extend(_ensure_lsp_cmds(ovn_idl, s_port_name, switch_name,
|
||||
'router', 'router', **options))
|
||||
# bind to local chassis
|
||||
# ovn-nbctl lrp-set-gateway-chassis bgp-router-public bgp 1
|
||||
cmds.append(ovn_idl.lrp_set_gateway_chassis(
|
||||
r_port_name, constants.OVN_CLUSTER_BRIDGE, 1))
|
||||
else: # direction == 'external'
|
||||
# Connect BGP router to the external logical switch
|
||||
r_port_name = "{}-{}".format(constants.OVN_CLUSTER_ROUTER, switch_name)
|
||||
# LRP
|
||||
try:
|
||||
ovn_idl.lrp_add(constants.OVN_CLUSTER_ROUTER, r_port_name,
|
||||
mac, [ip], peer=[], may_exist=True).execute(
|
||||
check_error=True)
|
||||
except RuntimeError as rte:
|
||||
# TODO(ltomasbo): Change OVSDBAPP to return a different error for
|
||||
# this to avoid having to compare strings as this is error prone
|
||||
networks_message = 'with different networks'
|
||||
if networks_message not in str(rte):
|
||||
raise
|
||||
# Trying to sync the networks by adding them
|
||||
cmds.append(ovn_idl.lrp_add_networks(r_port_name,
|
||||
[ip],
|
||||
may_exist=True))
|
||||
lrp = ovn_idl.lrp_get(r_port_name).execute(check_error=True)
|
||||
for net in lrp.networks:
|
||||
if net != ip:
|
||||
cmds.append(ovn_idl.lrp_del_networks(r_port_name,
|
||||
[net],
|
||||
if_exists=True))
|
||||
# LSP
|
||||
s_port_name = "{}-{}".format(switch_name, constants.OVN_CLUSTER_ROUTER)
|
||||
options = {'router-port': r_port_name}
|
||||
cmds.extend(_ensure_lsp_cmds(ovn_idl, s_port_name, switch_name,
|
||||
'router', 'router', **options))
|
||||
|
||||
if cmds:
|
||||
_execute_commands(ovn_idl, cmds)
|
||||
|
||||
|
||||
def _ensure_lsp_cmds(ovn_idl, port_name, switch, port_type, addresses,
|
||||
**options):
|
||||
cmds = []
|
||||
cmds.append(ovn_idl.lsp_add(switch, port_name, may_exist=True))
|
||||
cmds.append(ovn_idl.lsp_set_type(port_name, port_type))
|
||||
cmds.append(ovn_idl.lsp_set_addresses(port_name,
|
||||
addresses=[addresses]))
|
||||
cmds.append(ovn_idl.lsp_set_options(port_name, **options))
|
||||
return cmds
|
||||
|
||||
|
||||
def _ensure_ovn_policies(ovn_idl, next_hops):
|
||||
priority = 10
|
||||
match = 'inport=="{}-openstack"'.format(constants.OVN_CLUSTER_ROUTER)
|
||||
action = 'reroute'
|
||||
columns = {}
|
||||
if len(next_hops) > 1:
|
||||
columns = {'nexthops': next_hops}
|
||||
elif len(next_hops) == 1:
|
||||
columns = {'nexthop': next_hops[0]}
|
||||
|
||||
return [ovn_idl.lr_policy_add(constants.OVN_CLUSTER_ROUTER, priority,
|
||||
match, action, may_exist=True, **columns)]
|
||||
|
||||
|
||||
def _ensure_ovn_routes(ovn_idl, peer_ips):
|
||||
prefix = '0.0.0.0/0'
|
||||
cmds = []
|
||||
for ip in peer_ips:
|
||||
cmds.append(ovn_idl.lr_route_add(constants.OVN_CLUSTER_ROUTER, prefix,
|
||||
ip, ecmp=True, may_exist=True))
|
||||
return cmds
|
||||
|
||||
|
||||
def _ensure_ingress_flows(bridge, mac, switch_name, provider_cidrs):
|
||||
# incomming traffic flows
|
||||
# patch=`ovs-ofctl show br-ex | grep patch | cut -d "(" -f1 | xargs`
|
||||
# ovs-ofctl add-flow br-ex
|
||||
# "cookie=0xbadcaf2,ip,nw_dst=$PROVIDER_NET,in_port=enp2s0,priority=100,
|
||||
# actions=mod_dl_dst:$ENP2S0_MAC,output=$patch"
|
||||
if not provider_cidrs:
|
||||
return
|
||||
patch_port_prefix = 'patch-{}-'.format(switch_name)
|
||||
patch_ports = ovs.get_ovs_patch_ports_info(bridge,
|
||||
prefix=patch_port_prefix)
|
||||
if not patch_ports:
|
||||
return
|
||||
bridge_ports = set(ovs.get_ovs_ports_info(bridge))
|
||||
external_nic = list(bridge_ports.intersection(
|
||||
set(CONF.local_ovn_cluster.external_nics)))
|
||||
|
||||
if not external_nic:
|
||||
LOG.warning("NIC ports (%s) not found for bridge %s. Not possible to "
|
||||
"create the ingress flows. It will be retried if "
|
||||
"reconcile cycle is not disabled",
|
||||
CONF.local_ovn_cluster.external_nics, bridge)
|
||||
return
|
||||
else:
|
||||
# only one external_nic expected per bridge
|
||||
external_nic = external_nic[0]
|
||||
|
||||
for provider_cidr in provider_cidrs:
|
||||
ip_version = linux_net.get_ip_version(provider_cidr)
|
||||
if ip_version == constants.IP_VERSION_6:
|
||||
ip = 'ipv6'
|
||||
else:
|
||||
ip = 'ip'
|
||||
flow = (
|
||||
"cookie={},priority=1000,{},nw_dst={},in_port={},"
|
||||
" actions=mod_dl_dst:{},output={}".format(
|
||||
constants.OVS_RULE_COOKIE, ip, provider_cidr, external_nic,
|
||||
mac, patch_ports[0]))
|
||||
ovs.ensure_flow(bridge, flow)
|
||||
|
||||
|
||||
def _ensure_egress_flows(bridge, patch_ports):
|
||||
# outcomming traffic flows
|
||||
# patch=`ovs-ofctl show br-provider | grep patch | grep provnet |
|
||||
# cut -d "(" -f1 | xargs`
|
||||
# ovs-ofctl add-flow br-provider "cookie=0xbadcaf3,ip,in_port=$patch,
|
||||
# actions=mod_dl_dst:$ROUTER_MAC,NORMAL"
|
||||
for patch_port in patch_ports:
|
||||
flow = (
|
||||
"cookie={},priority=1000,ip,in_port={},"
|
||||
" actions=mod_dl_dst:{},NORMAL".format(
|
||||
constants.OVS_RULE_COOKIE, patch_port,
|
||||
constants.OVN_CLUSTER_ROUTER_INTERNAL_MAC))
|
||||
flow_v6 = (
|
||||
"cookie={},priority=1000,ipv6,in_port={},"
|
||||
" actions=mod_dl_dst:{},NORMAL".format(
|
||||
constants.OVS_RULE_COOKIE, patch_port,
|
||||
constants.OVN_CLUSTER_ROUTER_INTERNAL_MAC))
|
||||
ovs.ensure_flow(bridge, flow)
|
||||
ovs.ensure_flow(bridge, flow_v6)
|
||||
|
||||
|
||||
def cleanup_wiring(idl, bridge_mappings, ovs_flows, exposed_ips,
|
||||
routing_tables, routing_tables_routes):
|
||||
if CONF.exposing_method == constants.EXPOSE_METHOD_UNDERLAY:
|
||||
@ -72,7 +377,8 @@ def cleanup_wiring(idl, bridge_mappings, ovs_flows, exposed_ips,
|
||||
exposed_ips, routing_tables,
|
||||
routing_tables_routes)
|
||||
elif CONF.exposing_method == constants.EXPOSE_METHOD_OVN:
|
||||
raise NotImplementedError()
|
||||
# TODO(ltomasbo): clean up old policies, routes and proxy_arps cidrs
|
||||
return True
|
||||
|
||||
|
||||
def _cleanup_wiring_underlay(idl, bridge_mappings, ovs_flows, exposed_ips,
|
||||
@ -125,7 +431,7 @@ def delete_vlan_devices_leftovers(idl, bridge_mappings):
|
||||
|
||||
def wire_provider_port(routing_tables_routes, ovs_flows, port_ips,
|
||||
bridge_device, bridge_vlan, localnet, routing_table,
|
||||
proxy_cidrs, lladdr=None):
|
||||
proxy_cidrs, lladdr=None, mac=None, ovn_idl=None):
|
||||
if CONF.exposing_method == constants.EXPOSE_METHOD_UNDERLAY:
|
||||
return _wire_provider_port_underlay(routing_tables_routes, ovs_flows,
|
||||
port_ips, bridge_device,
|
||||
@ -133,20 +439,25 @@ def wire_provider_port(routing_tables_routes, ovs_flows, port_ips,
|
||||
routing_table, proxy_cidrs,
|
||||
lladdr=lladdr)
|
||||
elif CONF.exposing_method == constants.EXPOSE_METHOD_OVN:
|
||||
# No need to wire anything
|
||||
return True
|
||||
# We need to add a static mac binding due to proxy-arp issue in
|
||||
# core ovn that would reply on the incomming traffic from the LR,
|
||||
# while it should not
|
||||
return _wire_provider_port_ovn(ovn_idl, port_ips, mac)
|
||||
|
||||
|
||||
def unwire_provider_port(routing_tables_routes, port_ips, bridge_device,
|
||||
bridge_vlan, routing_table, proxy_cidrs, lladdr=None):
|
||||
bridge_vlan, routing_table, proxy_cidrs, lladdr=None,
|
||||
ovn_idl=None):
|
||||
if CONF.exposing_method == constants.EXPOSE_METHOD_UNDERLAY:
|
||||
return _unwire_provider_port_underlay(routing_tables_routes, port_ips,
|
||||
bridge_device, bridge_vlan,
|
||||
routing_table, proxy_cidrs,
|
||||
lladdr=lladdr)
|
||||
elif CONF.exposing_method == constants.EXPOSE_METHOD_OVN:
|
||||
# No need to wire anything
|
||||
return True
|
||||
# We need to remove thestatic mac binding added due to proxy-arp issue
|
||||
# in core ovn that would reply on the incomming traffic from the LR,
|
||||
# while it should not
|
||||
return _unwire_provider_port_ovn(ovn_idl, port_ips)
|
||||
|
||||
|
||||
def _ensure_updated_mac_tweak_flows(localnet, bridge_device, ovs_flows):
|
||||
@ -199,6 +510,18 @@ def _wire_provider_port_underlay(routing_tables_routes, ovs_flows, port_ips,
|
||||
return True
|
||||
|
||||
|
||||
def _wire_provider_port_ovn(ovn_idl, port_ips, mac):
|
||||
cmds = []
|
||||
port = "{}-openstack".format(constants.OVN_CLUSTER_ROUTER)
|
||||
for port_ip in port_ips:
|
||||
cmds.append(ovn_idl.static_mac_binding_add(
|
||||
port, port_ip, mac, override_dynamic_mac=True, may_exist=True))
|
||||
if cmds:
|
||||
_execute_commands(ovn_idl, cmds)
|
||||
# to keep it consisten with the underlay method
|
||||
return True
|
||||
|
||||
|
||||
def _unwire_provider_port_underlay(routing_tables_routes, port_ips,
|
||||
bridge_device, bridge_vlan, routing_table,
|
||||
proxy_cidrs, lladdr=None):
|
||||
@ -237,6 +560,18 @@ def _unwire_provider_port_underlay(routing_tables_routes, port_ips,
|
||||
return True
|
||||
|
||||
|
||||
def _unwire_provider_port_ovn(ovn_idl, port_ips):
|
||||
cmds = []
|
||||
port = "{}-openstack".format(constants.OVN_CLUSTER_ROUTER)
|
||||
for port_ip in port_ips:
|
||||
cmds.append(ovn_idl.static_mac_binding_del(
|
||||
port, port_ip, if_exists=True))
|
||||
if cmds:
|
||||
_execute_commands(ovn_idl, cmds)
|
||||
# to keep it consisten with the underlay method
|
||||
return True
|
||||
|
||||
|
||||
def wire_lrp_port(routing_tables_routes, ip, bridge_device, bridge_vlan,
|
||||
routing_table, cr_lrp_ips):
|
||||
if not bridge_device:
|
||||
|
@ -182,12 +182,13 @@ class LogicalSwitchPortFIPCreateEvent(base_watcher.LSPChassisEvent):
|
||||
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)
|
||||
external_ip, external_mac, 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, row)
|
||||
self.agent.expose_fip(external_ip, external_mac, ls_name, row)
|
||||
|
||||
|
||||
class LogicalSwitchPortFIPDeleteEvent(base_watcher.LSPChassisEvent):
|
||||
|
@ -80,6 +80,22 @@ class GetInterfaceTestCase(base_functional.BaseFunctionalTestCase):
|
||||
self.assertRaises(agent_exc.NetworkInterfaceNotFound,
|
||||
linux_net.get_interface_address, 'no_interface_name')
|
||||
|
||||
def test_get_nic_info(self):
|
||||
dev_name = uuidutils.generate_uuid()[:15]
|
||||
ip = '172.24.10.100/32'
|
||||
self.addCleanup(self._delete_interfaces, [dev_name])
|
||||
mac_address = net_utils.get_random_mac(
|
||||
'fa:16:3e:00:00:00'.split(':'))
|
||||
priv_linux_net.create_interface(dev_name, 'dummy',
|
||||
address=mac_address)
|
||||
priv_linux_net.add_ip_address(ip, dev_name)
|
||||
ret = linux_net.get_nic_info(dev_name)
|
||||
self.assertEqual((ip, mac_address), ret)
|
||||
|
||||
def test_get_nic_info_no_interface(self):
|
||||
self.assertRaises(agent_exc.NetworkInterfaceNotFound,
|
||||
linux_net.get_nic_info, 'no_interface_name')
|
||||
|
||||
def test_get_exposed_ips(self):
|
||||
ips = ['240.0.0.1', 'fd00::1']
|
||||
dev_name = uuidutils.generate_uuid()[:15]
|
||||
|
@ -194,6 +194,7 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
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",
|
||||
"fake-mac",
|
||||
"test-ls")
|
||||
mock_expose_fip = mock.patch.object(
|
||||
self.nb_bgp_driver, '_expose_fip').start()
|
||||
@ -203,8 +204,8 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
self.nb_bgp_driver._ensure_port_exposed(port0)
|
||||
|
||||
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",
|
||||
port0)
|
||||
mock_expose_fip.assert_called_once_with("192.168.0.10", "fake-mac",
|
||||
"test-ls", port0)
|
||||
mock_expose_ip.assert_not_called()
|
||||
|
||||
def test__ensure_port_exposed_tenant_ls(self):
|
||||
@ -255,7 +256,7 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
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'], 'test-ls', 'br-ex', 10,
|
||||
['192.168.0.10'], 'fake_mac', 'test-ls', 'br-ex', 10,
|
||||
constants.OVN_VM_VIF_PORT_TYPE, None)
|
||||
|
||||
@mock.patch.object(wire_utils, 'wire_provider_port')
|
||||
@ -269,12 +270,13 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
proxy_cidrs = ['192.168.0.0/24']
|
||||
|
||||
self.nb_bgp_driver._expose_provider_port(
|
||||
port_ips, 'teset-ls', bridge_device, bridge_vlan, 'fake-localnet',
|
||||
proxy_cidrs)
|
||||
port_ips, 'fake-mac', 'test-ls', bridge_device, bridge_vlan,
|
||||
'fake-localnet', proxy_cidrs)
|
||||
|
||||
mock_wire_provider_port.assert_called_once_with(
|
||||
self.ovn_routing_tables_routes, {}, port_ips, bridge_device,
|
||||
bridge_vlan, 'fake-localnet', self.ovn_routing_tables, proxy_cidrs)
|
||||
bridge_vlan, 'fake-localnet', self.ovn_routing_tables,
|
||||
proxy_cidrs, mac='fake-mac', ovn_idl=mock.ANY)
|
||||
mock_announce_ips.assert_called_once_with(port_ips)
|
||||
|
||||
@mock.patch.object(wire_utils, 'wire_provider_port')
|
||||
@ -288,12 +290,13 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
proxy_cidrs = ['192.168.0.0/24']
|
||||
|
||||
self.nb_bgp_driver._expose_provider_port(
|
||||
port_ips, 'test-ls', bridge_device, bridge_vlan, 'fake-localnet',
|
||||
proxy_cidrs)
|
||||
port_ips, 'fake-mac', 'test-ls', bridge_device, bridge_vlan,
|
||||
'fake-localnet', proxy_cidrs)
|
||||
|
||||
mock_wire_provider_port.assert_called_once_with(
|
||||
self.ovn_routing_tables_routes, {}, port_ips, bridge_device,
|
||||
bridge_vlan, 'fake-localnet', self.ovn_routing_tables, proxy_cidrs)
|
||||
bridge_vlan, 'fake-localnet', self.ovn_routing_tables, proxy_cidrs,
|
||||
mac='fake-mac', ovn_idl=mock.ANY)
|
||||
mock_announce_ips.assert_not_called()
|
||||
|
||||
@mock.patch.object(wire_utils, 'unwire_provider_port')
|
||||
@ -311,7 +314,8 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
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, proxy_cidrs)
|
||||
bridge_vlan, self.ovn_routing_tables, proxy_cidrs,
|
||||
ovn_idl=mock.ANY)
|
||||
|
||||
def test__get_bridge_for_localnet_port(self):
|
||||
localnet = fakes.create_object({
|
||||
@ -357,14 +361,16 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
'localnet': 'fake-localnet'})
|
||||
if row.type == constants.OVN_VIRTUAL_VIF_PORT_TYPE and cidr:
|
||||
mock_expose_provider_port.assert_called_once_with(
|
||||
ips, 'test-ls', 'br-ex', 10, 'fake-localnet', [cidr])
|
||||
ips, 'fake-mac', 'test-ls', 'br-ex', 10, 'fake-localnet',
|
||||
[cidr])
|
||||
else:
|
||||
mock_expose_provider_port.assert_called_once_with(
|
||||
ips, 'test-ls', 'br-ex', 10, 'fake-localnet')
|
||||
ips, 'fake-mac', 'test-ls', 'br-ex', 10, 'fake-localnet')
|
||||
|
||||
def test_expose_ip(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'addresses': ['fake-mac {} {}'.format(self.ipv4, self.ipv6)],
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE,
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls'}})
|
||||
|
||||
@ -373,6 +379,7 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
def test_expose_ip_virtual(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'addresses': ['fake-mac {} {}'.format(self.ipv4, self.ipv6)],
|
||||
'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'}})
|
||||
@ -382,6 +389,7 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
def test_expose_ip_no_switch(self):
|
||||
ips = [self.ipv4, self.ipv6]
|
||||
row = fakes.create_object({
|
||||
'addresses': ['fake-mac {} {}'.format(self.ipv4, self.ipv6)],
|
||||
'type': constants.OVN_VM_VIF_PORT_TYPE,
|
||||
'external_ids': {}})
|
||||
|
||||
@ -493,12 +501,14 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
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'})
|
||||
'external_ip': 'fake-ip',
|
||||
'external_mac': 'fake-mac'})
|
||||
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")
|
||||
expected_result = (nat_entry.external_ip, nat_entry.external_mac,
|
||||
"neutron-net1")
|
||||
self.assertEqual(ret, expected_result)
|
||||
|
||||
def test_get_port_external_ip_and_ls_no_nat_entry(self):
|
||||
@ -506,20 +516,23 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
|
||||
ret = self.nb_bgp_driver.get_port_external_ip_and_ls('fake-port')
|
||||
|
||||
self.assertIsNone(ret)
|
||||
self.assertEqual(ret, (None, None, None))
|
||||
|
||||
def test_get_port_external_ip_and_ls_no_external_id(self):
|
||||
nat_entry = fakes.create_object({
|
||||
'external_ids': {},
|
||||
'external_ip': 'fake-ip'})
|
||||
'external_ip': 'fake-ip',
|
||||
'external_mac': 'fake-mac'})
|
||||
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))
|
||||
self.assertEqual(ret,
|
||||
(nat_entry.external_ip, nat_entry.external_mac, None))
|
||||
|
||||
def test_expose_fip(self):
|
||||
ip = '10.0.0.1'
|
||||
mac = 'fake-mac'
|
||||
logical_switch = 'lswitch1'
|
||||
mock_get_ls_localnet_info = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_ls_localnet_info').start()
|
||||
@ -531,16 +544,17 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
row = fakes.create_object({
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls'}})
|
||||
|
||||
ret = self.nb_bgp_driver.expose_fip(ip, logical_switch, row)
|
||||
ret = self.nb_bgp_driver.expose_fip(ip, mac, logical_switch, row)
|
||||
|
||||
mock_get_ls_localnet_info.assert_called_once_with(logical_switch)
|
||||
mock_expose_provider_port.assert_called_once_with([ip], 'test-ls',
|
||||
mock_expose_provider_port.assert_called_once_with([ip], mac, 'test-ls',
|
||||
'br-ex', 100,
|
||||
'fake-localnet')
|
||||
self.assertTrue(ret)
|
||||
|
||||
def test_expose_fip_no_device(self):
|
||||
ip = '10.0.0.1'
|
||||
mac = 'fake-mac'
|
||||
logical_switch = 'lswitch1'
|
||||
mock_get_ls_localnet_info = mock.patch.object(
|
||||
self.nb_bgp_driver, '_get_ls_localnet_info').start()
|
||||
@ -550,7 +564,7 @@ class TestNBOVNBGPDriver(test_base.TestCase):
|
||||
row = fakes.create_object({
|
||||
'external_ids': {constants.OVN_LS_NAME_EXT_ID_KEY: 'test-ls'}})
|
||||
|
||||
ret = self.nb_bgp_driver.expose_fip(ip, logical_switch, row)
|
||||
ret = self.nb_bgp_driver.expose_fip(ip, mac, logical_switch, row)
|
||||
|
||||
mock_get_ls_localnet_info.assert_called_once_with(logical_switch)
|
||||
mock_expose_provider_port.assert_not_called()
|
||||
|
@ -19,6 +19,7 @@ from ovsdbapp.schema.open_vswitch import impl_idl as idl_ovs
|
||||
|
||||
from ovn_bgp_agent import constants
|
||||
from ovn_bgp_agent.drivers.openstack.utils import ovs as ovs_utils
|
||||
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
|
||||
|
||||
@ -72,6 +73,70 @@ class TestOVS(test_base.TestCase):
|
||||
self.mock_ovs_vsctl.ovs_cmd.assert_called_once_with(
|
||||
'ovs-vsctl', ['get', 'Interface', port, 'ofport'])
|
||||
|
||||
def test_get_ovs_ports_info(self):
|
||||
bridge = 'fake-bridge'
|
||||
bridge_ports = ['br-ex']
|
||||
self.mock_ovs_vsctl.ovs_cmd.return_value = bridge_ports
|
||||
|
||||
ret = ovs_utils.get_ovs_ports_info(bridge)
|
||||
|
||||
self.assertEqual(bridge_ports, ret)
|
||||
self.mock_ovs_vsctl.ovs_cmd.assert_called_once_with(
|
||||
'ovs-vsctl', ['list-ports', bridge])
|
||||
|
||||
def test_get_ovs_patch_port_ofport(self):
|
||||
patch = 'fake-patch'
|
||||
ofport = ['1']
|
||||
self.mock_ovs_vsctl.ovs_cmd.return_value = ofport
|
||||
|
||||
ret = ovs_utils.get_ovs_patch_port_ofport(patch)
|
||||
|
||||
self.assertEqual(ofport[0], ret)
|
||||
self.mock_ovs_vsctl.ovs_cmd.assert_called_once_with(
|
||||
'ovs-vsctl',
|
||||
['get', 'Interface', 'patch-fake-patch-to-br-int', 'ofport'])
|
||||
|
||||
def test_get_ovs_patch_port_ofport_exception(self):
|
||||
patch = 'fake-patch'
|
||||
self.mock_ovs_vsctl.ovs_cmd.side_effect = Exception
|
||||
|
||||
self.assertRaises(agent_exc.PatchPortNotFound,
|
||||
ovs_utils.get_ovs_patch_port_ofport, patch)
|
||||
|
||||
expected_calls = [
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport']),
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport']),
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport']),
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport']),
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport'])]
|
||||
self.mock_ovs_vsctl.ovs_cmd.assert_has_calls(expected_calls)
|
||||
|
||||
def test_get_ovs_patch_port_ofport_no_port(self):
|
||||
patch = 'fake-patch'
|
||||
ofport = ['[]']
|
||||
self.mock_ovs_vsctl.ovs_cmd.return_value = ofport
|
||||
|
||||
self.assertRaises(agent_exc.PatchPortNotFound,
|
||||
ovs_utils.get_ovs_patch_port_ofport, patch)
|
||||
|
||||
expected_calls = [
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport']),
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport']),
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport']),
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport']),
|
||||
mock.call('ovs-vsctl', ['get', 'Interface',
|
||||
'patch-fake-patch-to-br-int', 'ofport'])]
|
||||
self.mock_ovs_vsctl.ovs_cmd.assert_has_calls(expected_calls)
|
||||
|
||||
@mock.patch.object(ovs_utils, 'get_bridge_flows')
|
||||
def test_remove_extra_ovs_flows(self, mock_flows):
|
||||
port_iface = '1'
|
||||
@ -100,6 +165,15 @@ class TestOVS(test_base.TestCase):
|
||||
'ovs-ofctl', ['del-flows', self.bridge, expected_del_flow])
|
||||
mock_flows.assert_called_once_with(self.bridge, self.cookie_id)
|
||||
|
||||
def test_ensure_flow(self):
|
||||
bridge = 'fake-bridge'
|
||||
flow = 'fake-flow'
|
||||
|
||||
ovs_utils.ensure_flow(bridge, flow)
|
||||
|
||||
self.mock_ovs_vsctl.ovs_cmd.assert_called_once_with(
|
||||
'ovs-ofctl', ['add-flow', bridge, flow])
|
||||
|
||||
@mock.patch.object(ovs_utils, 'get_device_port_at_ovs')
|
||||
@mock.patch.object(linux_net, 'get_interface_address')
|
||||
@mock.patch.object(linux_net, 'get_ip_version')
|
||||
@ -374,12 +448,27 @@ class TestOvsIdl(test_base.TestCase):
|
||||
self._test_ovs_ext_ids_getters(
|
||||
self.ovs_idl.get_own_chassis_id, row, expected_return)
|
||||
|
||||
def test_get_own_chassis_name(self):
|
||||
expected_return = 'fake-name'
|
||||
row = {'hostname': expected_return}
|
||||
self._test_ovs_ext_ids_getters(
|
||||
self.ovs_idl.get_own_chassis_name, row, expected_return)
|
||||
|
||||
def test_get_ovn_remote(self):
|
||||
expected_return = 'fake-ovn-remote'
|
||||
row = {'ovn-remote': expected_return}
|
||||
self._test_ovs_ext_ids_getters(
|
||||
self.ovs_idl.get_ovn_remote, row, expected_return)
|
||||
|
||||
def test_get_ovn_remote_nb(self):
|
||||
expected_return = 'fake-ovn-remote'
|
||||
row = {'ovn-nb-remote': expected_return}
|
||||
self.execute_ref.return_value = row
|
||||
ret = self.ovs_idl.get_ovn_remote(nb=True)
|
||||
self.assertEqual(expected_return, ret)
|
||||
self.ovs_idl.idl_ovs.db_get.assert_called_once_with(
|
||||
'Open_vSwitch', '.', 'external_ids')
|
||||
|
||||
def test_get_ovn_bridge_mappings(self):
|
||||
self.execute_ref.return_value = {
|
||||
'ovn-bridge-mappings': 'net0:bridge0,net1:bridge1, net2:bridge2'}
|
||||
@ -394,3 +483,13 @@ class TestOvsIdl(test_base.TestCase):
|
||||
self.assertEqual([], ret)
|
||||
self.ovs_idl.idl_ovs.db_get.assert_called_once_with(
|
||||
'Open_vSwitch', '.', 'external_ids')
|
||||
|
||||
def test_get_ovn_bridge_mappings_bridge(self):
|
||||
bridge = 'bgp'
|
||||
self.execute_ref.return_value = {
|
||||
'ovn-bridge-mappings-bgp':
|
||||
'net0:bridge0,net1:bridge1, net2:bridge2'}
|
||||
ret = self.ovs_idl.get_ovn_bridge_mappings(bridge=bridge)
|
||||
self.assertEqual(['net0:bridge0', 'net1:bridge1', 'net2:bridge2'], ret)
|
||||
self.ovs_idl.idl_ovs.db_get.assert_called_once_with(
|
||||
'Open_vSwitch', '.', 'external_ids')
|
||||
|
455
ovn_bgp_agent/tests/unit/drivers/openstack/utils/test_wire.py
Normal file
455
ovn_bgp_agent/tests/unit/drivers/openstack/utils/test_wire.py
Normal file
@ -0,0 +1,455 @@
|
||||
# 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.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.tests import base as test_base
|
||||
from ovn_bgp_agent.utils import linux_net
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestWire(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestWire, self).setUp()
|
||||
self.nb_idl = ovn_utils.OvsdbNbOvnIdl(mock.Mock())
|
||||
self.sb_idl = ovn_utils.OvsdbSbOvnIdl(mock.Mock())
|
||||
self.ovs_idl = mock.Mock()
|
||||
|
||||
# Helper variables that are used across multiple methods
|
||||
self.bridge_mappings = 'datacentre:br-ex'
|
||||
|
||||
# Monkey-patch parent class methods
|
||||
self.nb_idl.ls_add = mock.Mock()
|
||||
self.nb_idl.lr_add = mock.Mock()
|
||||
self.nb_idl.lrp_add = mock.Mock()
|
||||
self.nb_idl.lrp_set_gateway_chassis = mock.Mock()
|
||||
self.nb_idl.lrp_add_networks = mock.Mock()
|
||||
self.nb_idl.lr_route_add = mock.Mock()
|
||||
self.nb_idl.lr_policy_add = mock.Mock()
|
||||
|
||||
@mock.patch.object(wire, '_ensure_base_wiring_config_underlay')
|
||||
def test_ensure_base_wiring_config(self, mock_underlay):
|
||||
wire.ensure_base_wiring_config(self.sb_idl, self.ovs_idl,
|
||||
routing_tables={})
|
||||
mock_underlay.assert_called_once_with(self.sb_idl, self.ovs_idl, {})
|
||||
|
||||
@mock.patch.object(wire, '_ensure_base_wiring_config_ovn')
|
||||
def test_ensure_base_wiring_config_ovn(self, mock_ovn):
|
||||
CONF.set_override('exposing_method', 'ovn')
|
||||
self.addCleanup(CONF.clear_override, 'exposing_method')
|
||||
|
||||
wire.ensure_base_wiring_config(self.sb_idl, self.ovs_idl,
|
||||
ovn_idl=self.nb_idl)
|
||||
mock_ovn.assert_called_once_with(self.ovs_idl, self.nb_idl)
|
||||
|
||||
@mock.patch.object(wire, '_ensure_base_wiring_config_underlay')
|
||||
@mock.patch.object(wire, '_ensure_base_wiring_config_ovn')
|
||||
def test_ensure_base_wiring_config_not_implemeneted(self, mock_ovn,
|
||||
mock_underlay):
|
||||
CONF.set_override('exposing_method', 'vrf')
|
||||
self.addCleanup(CONF.clear_override, 'exposing_method')
|
||||
|
||||
wire.ensure_base_wiring_config(self.sb_idl, self.ovs_idl,
|
||||
ovn_idl=self.nb_idl)
|
||||
mock_ovn.assert_not_called()
|
||||
mock_underlay.assert_not_called()
|
||||
|
||||
def test__ensure_base_wiring_config_ovn(self):
|
||||
pass
|
||||
|
||||
def test__ensure_ovn_router(self):
|
||||
wire._ensure_ovn_router(self.nb_idl)
|
||||
self.nb_idl.lr_add.assert_called_once_with(
|
||||
constants.OVN_CLUSTER_ROUTER, may_exist=True)
|
||||
|
||||
@mock.patch.object(wire, '_execute_commands')
|
||||
@mock.patch.object(wire, '_ensure_lsp_cmds')
|
||||
def test__ensure_ovn_switch(self, m_ensure_lsp, m_cmds):
|
||||
ls_name = 'test-ls'
|
||||
localnet_port = "{}-localnet".format(ls_name)
|
||||
options = {'network_name': ls_name}
|
||||
|
||||
wire._ensure_ovn_switch(self.nb_idl, ls_name)
|
||||
self.nb_idl.ls_add.assert_called_once_with(ls_name, may_exist=True)
|
||||
m_ensure_lsp.assert_called_once_with(
|
||||
self.nb_idl, localnet_port, ls_name, 'localnet', 'unknown',
|
||||
**options)
|
||||
|
||||
@mock.patch.object(wire, '_execute_commands')
|
||||
@mock.patch.object(wire, '_ensure_lsp_cmds')
|
||||
def test__ensure_ovn_network_link_internal(self, m_ensure_lsp, m_cmds):
|
||||
switch_name = 'internal'
|
||||
provider_cidrs = ['172.16.0.0/16']
|
||||
r_port_name = "{}-openstack".format(constants.OVN_CLUSTER_ROUTER)
|
||||
options = {'router-port': r_port_name, 'arp_proxy': '172.16.0.0/16'}
|
||||
|
||||
wire._ensure_ovn_network_link(self.nb_idl, switch_name, 'internal',
|
||||
provider_cidrs=provider_cidrs)
|
||||
|
||||
self.nb_idl.lrp_add.assert_called_once_with(
|
||||
constants.OVN_CLUSTER_ROUTER, r_port_name,
|
||||
constants.OVN_CLUSTER_ROUTER_INTERNAL_MAC, provider_cidrs, peer=[],
|
||||
may_exist=True)
|
||||
m_ensure_lsp.assert_called_once_with(
|
||||
self.nb_idl, mock.ANY, switch_name, 'router', 'router', **options)
|
||||
self.nb_idl.lrp_set_gateway_chassis.assert_called_once_with(
|
||||
r_port_name, constants.OVN_CLUSTER_BRIDGE, 1)
|
||||
|
||||
@mock.patch.object(wire, '_execute_commands')
|
||||
@mock.patch.object(wire, '_ensure_lsp_cmds')
|
||||
def test__ensure_ovn_network_link_internal_runtime_error(
|
||||
self, m_ensure_lsp, m_cmds):
|
||||
switch_name = 'internal'
|
||||
provider_cidrs = ['172.16.0.0/16']
|
||||
r_port_name = "{}-openstack".format(constants.OVN_CLUSTER_ROUTER)
|
||||
options = {'router-port': r_port_name, 'arp_proxy': '172.16.0.0/16'}
|
||||
self.nb_idl.lrp_add.side_effect = RuntimeError(
|
||||
'with different networks')
|
||||
|
||||
wire._ensure_ovn_network_link(self.nb_idl, switch_name, 'internal',
|
||||
provider_cidrs=provider_cidrs)
|
||||
|
||||
self.nb_idl.lrp_add.assert_called_once_with(
|
||||
constants.OVN_CLUSTER_ROUTER, r_port_name,
|
||||
constants.OVN_CLUSTER_ROUTER_INTERNAL_MAC, provider_cidrs, peer=[],
|
||||
may_exist=True)
|
||||
self.nb_idl.lrp_add_networks.assert_called_once_with(
|
||||
r_port_name, provider_cidrs, may_exist=True)
|
||||
m_ensure_lsp.assert_called_once_with(
|
||||
self.nb_idl, mock.ANY, switch_name, 'router', 'router', **options)
|
||||
self.nb_idl.lrp_set_gateway_chassis.assert_called_once_with(
|
||||
r_port_name, constants.OVN_CLUSTER_BRIDGE, 1)
|
||||
|
||||
@mock.patch.object(wire, '_ensure_lsp_cmds')
|
||||
def test__ensure_ovn_network_link_external(self, m_ensure_lsp):
|
||||
switch_name = 'external'
|
||||
ip = '1.1.1.2'
|
||||
mac = 'fake-map'
|
||||
r_port_name = "{}-{}".format(constants.OVN_CLUSTER_ROUTER, switch_name)
|
||||
options = {'router-port': r_port_name}
|
||||
|
||||
wire._ensure_ovn_network_link(self.nb_idl, switch_name, 'external',
|
||||
ip=ip, mac=mac)
|
||||
|
||||
self.nb_idl.lrp_add.assert_called_once_with(
|
||||
constants.OVN_CLUSTER_ROUTER, r_port_name,
|
||||
mac, [ip], peer=[], may_exist=True)
|
||||
m_ensure_lsp.assert_called_once_with(
|
||||
self.nb_idl, mock.ANY, switch_name, 'router', 'router', **options)
|
||||
|
||||
def _ensure_ovn_policies(self, next_hops):
|
||||
if len(next_hops) > 1:
|
||||
columns = {'nexthops': next_hops}
|
||||
elif len(next_hops) == 1:
|
||||
columns = {'nexthop': next_hops[0]}
|
||||
|
||||
wire._ensure_ovn_policies(self.nb_idl, next_hops)
|
||||
|
||||
self.nb_idl.lr_policy_add.assert_called_once_with(
|
||||
constants.OVN_CLUSTER_ROUTER, 10, mock.ANY, 'reroute',
|
||||
may_exist=True, **columns)
|
||||
|
||||
def test__ensure_ovn_policies_dual_nexthop(self):
|
||||
next_hops = ['1.1.1.1', '2.2.2.2']
|
||||
self._ensure_ovn_policies(next_hops)
|
||||
|
||||
def test__ensure_ovn_policies_single_nexthop(self):
|
||||
next_hops = ['1.1.1.1']
|
||||
self._ensure_ovn_policies(next_hops)
|
||||
|
||||
@mock.patch.object(wire, '_execute_commands')
|
||||
def test_ensure_ovn_routes(self, m_cmds):
|
||||
peer_ips = ['1.1.1.1']
|
||||
wire._ensure_ovn_routes(self.nb_idl, peer_ips)
|
||||
self.nb_idl.lr_route_add.assert_called_once_with(
|
||||
constants.OVN_CLUSTER_ROUTER, '0.0.0.0/0', peer_ips[0],
|
||||
ecmp=True, may_exist=True)
|
||||
|
||||
@mock.patch.object(ovs_utils, 'ensure_flow')
|
||||
@mock.patch.object(linux_net, 'get_ip_version')
|
||||
@mock.patch.object(ovs_utils, 'get_ovs_ports_info')
|
||||
@mock.patch.object(ovs_utils, 'get_ovs_patch_ports_info')
|
||||
def test__ensure_ingress_flows(self, m_ovs_patch, m_ovs_get, m_ip_version,
|
||||
m_ovn_flow):
|
||||
CONF.set_override('external_nics', ['eth1'], group='local_ovn_cluster')
|
||||
self.addCleanup(CONF.clear_override, 'external_nics',
|
||||
group='local_ovn_cluster')
|
||||
bridge = 'br-ex'
|
||||
mac = 'fake-mac'
|
||||
switch_name = 'test-ls'
|
||||
provider_cidrs = ['172.16.0.0/16']
|
||||
patch_port_prefix = 'patch-{}-'.format(switch_name)
|
||||
m_ovs_patch.return_value = ['fake-patch']
|
||||
m_ovs_get.return_value = ['eth1']
|
||||
wire._ensure_ingress_flows(bridge, mac, switch_name, provider_cidrs)
|
||||
m_ovs_patch.assert_called_once_with(bridge, prefix=patch_port_prefix)
|
||||
m_ovs_get.assert_called_once_with(bridge)
|
||||
m_ip_version.assert_called_once_with(provider_cidrs[0])
|
||||
m_ovn_flow.assert_called_once_with(bridge, mock.ANY)
|
||||
|
||||
@mock.patch.object(ovs_utils, 'get_ovs_patch_ports_info')
|
||||
def test__ensure_ingress_flows_no_network(self, m_ovs):
|
||||
bridge = 'br-ex'
|
||||
mac = 'fake-mac'
|
||||
switch_name = 'test-ls'
|
||||
provider_cidrs = []
|
||||
wire._ensure_ingress_flows(bridge, mac, switch_name, provider_cidrs)
|
||||
m_ovs.assert_not_called()
|
||||
|
||||
@mock.patch.object(ovs_utils, 'get_ovs_ports_info')
|
||||
@mock.patch.object(ovs_utils, 'get_ovs_patch_ports_info')
|
||||
def test__ensure_ingress_flows_no_patch_port(self, m_ovs_patch, m_ovs_get):
|
||||
bridge = 'br-ex'
|
||||
mac = 'fake-mac'
|
||||
switch_name = 'test-ls'
|
||||
provider_cidrs = ['172.16.0.0/16']
|
||||
patch_port_prefix = 'patch-{}-'.format(switch_name)
|
||||
m_ovs_patch.return_value = []
|
||||
wire._ensure_ingress_flows(bridge, mac, switch_name, provider_cidrs)
|
||||
m_ovs_patch.assert_called_once_with(bridge, prefix=patch_port_prefix)
|
||||
m_ovs_get.assert_not_called()
|
||||
|
||||
@mock.patch.object(linux_net, 'get_ip_version')
|
||||
@mock.patch.object(ovs_utils, 'get_ovs_ports_info')
|
||||
@mock.patch.object(ovs_utils, 'get_ovs_patch_ports_info')
|
||||
def test__ensure_ingress_flows_no_external_nic(self, m_ovs_patch,
|
||||
m_ovs_get, m_ip_version):
|
||||
bridge = 'br-ex'
|
||||
mac = 'fake-mac'
|
||||
switch_name = 'test-ls'
|
||||
provider_cidrs = ['172.16.0.0/16']
|
||||
patch_port_prefix = 'patch-{}-'.format(switch_name)
|
||||
m_ovs_patch.return_value = ['fake-patch']
|
||||
m_ovs_get.return_value = ['eth1']
|
||||
wire._ensure_ingress_flows(bridge, mac, switch_name, provider_cidrs)
|
||||
m_ovs_patch.assert_called_once_with(bridge, prefix=patch_port_prefix)
|
||||
m_ovs_get.assert_called_once_with(bridge)
|
||||
m_ip_version.assert_not_called()
|
||||
|
||||
@mock.patch.object(wire, '_cleanup_wiring_underlay')
|
||||
def test_cleanup_wiring_underlay(self, mock_underlay):
|
||||
ovs_flows = {}
|
||||
exposed_ips = {}
|
||||
routing_tables = {}
|
||||
routing_tables_routes = {}
|
||||
wire.cleanup_wiring(self.sb_idl, self.bridge_mappings, ovs_flows,
|
||||
exposed_ips, routing_tables, routing_tables_routes)
|
||||
mock_underlay.assert_called_once_with(
|
||||
self.sb_idl, self.bridge_mappings, ovs_flows, exposed_ips,
|
||||
routing_tables, routing_tables_routes)
|
||||
|
||||
def test_cleanup_wiring_ovn(self):
|
||||
CONF.set_override('exposing_method', 'ovn')
|
||||
self.addCleanup(CONF.clear_override, 'exposing_method')
|
||||
|
||||
ovs_flows = {}
|
||||
exposed_ips = {}
|
||||
routing_tables = {}
|
||||
routing_tables_routes = {}
|
||||
ret = wire.cleanup_wiring(self.sb_idl, self.bridge_mappings, ovs_flows,
|
||||
exposed_ips, routing_tables,
|
||||
routing_tables_routes)
|
||||
self.assertTrue(ret)
|
||||
|
||||
@mock.patch.object(wire, '_cleanup_wiring_underlay')
|
||||
def test_cleanup_wiring_not_implemeneted(self, mock_underlay):
|
||||
CONF.set_override('exposing_method', 'vrf')
|
||||
self.addCleanup(CONF.clear_override, 'exposing_method')
|
||||
|
||||
ovs_flows = {}
|
||||
exposed_ips = {}
|
||||
routing_tables = {}
|
||||
routing_tables_routes = {}
|
||||
wire.cleanup_wiring(self.sb_idl, self.bridge_mappings, ovs_flows,
|
||||
exposed_ips, routing_tables, routing_tables_routes)
|
||||
|
||||
mock_underlay.assert_not_called()
|
||||
|
||||
@mock.patch.object(wire, '_wire_provider_port_underlay')
|
||||
def test_wire_provider_port_underlay(self, mock_underlay):
|
||||
routing_tables_routes = {}
|
||||
ovs_flows = {}
|
||||
port_ips = []
|
||||
bridge_device = 'fake-bridge'
|
||||
bridge_vlan = '101'
|
||||
localnet = 'fake-localnet'
|
||||
routing_table = 5
|
||||
proxy_cidrs = []
|
||||
|
||||
wire.wire_provider_port(routing_tables_routes, ovs_flows, port_ips,
|
||||
bridge_device, bridge_vlan, localnet,
|
||||
routing_table, proxy_cidrs)
|
||||
mock_underlay.assert_called_once_with(
|
||||
routing_tables_routes, ovs_flows, port_ips, bridge_device,
|
||||
bridge_vlan, localnet, routing_table, proxy_cidrs, lladdr=None)
|
||||
|
||||
@mock.patch.object(wire, '_wire_provider_port_ovn')
|
||||
def test_wire_provider_port_ovn(self, mock_ovn):
|
||||
CONF.set_override('exposing_method', 'ovn')
|
||||
self.addCleanup(CONF.clear_override, 'exposing_method')
|
||||
|
||||
routing_tables_routes = {}
|
||||
ovs_flows = {}
|
||||
port_ips = []
|
||||
bridge_device = 'fake-bridge'
|
||||
bridge_vlan = '101'
|
||||
localnet = 'fake-localnet'
|
||||
routing_table = 5
|
||||
proxy_cidrs = []
|
||||
mac = 'fake-mac'
|
||||
|
||||
wire.wire_provider_port(routing_tables_routes, ovs_flows, port_ips,
|
||||
bridge_device, bridge_vlan, localnet,
|
||||
routing_table, proxy_cidrs, mac=mac,
|
||||
ovn_idl=self.nb_idl)
|
||||
mock_ovn.assert_called_once_with(self.nb_idl, port_ips, mac)
|
||||
|
||||
@mock.patch.object(wire, '_wire_provider_port_underlay')
|
||||
@mock.patch.object(wire, '_wire_provider_port_ovn')
|
||||
def test_wire_provider_port_not_implemented(self, mock_ovn, mock_underlay):
|
||||
CONF.set_override('exposing_method', 'vrf')
|
||||
self.addCleanup(CONF.clear_override, 'exposing_method')
|
||||
|
||||
routing_tables_routes = {}
|
||||
ovs_flows = {}
|
||||
port_ips = []
|
||||
bridge_device = 'fake-bridge'
|
||||
bridge_vlan = '101'
|
||||
localnet = 'fake-localnet'
|
||||
routing_table = 5
|
||||
proxy_cidrs = []
|
||||
|
||||
wire.wire_provider_port(routing_tables_routes, ovs_flows, port_ips,
|
||||
bridge_device, bridge_vlan, localnet,
|
||||
routing_table, proxy_cidrs)
|
||||
|
||||
mock_ovn.assert_not_called()
|
||||
mock_underlay.assert_not_called()
|
||||
|
||||
@mock.patch.object(wire, '_execute_commands')
|
||||
def test__wire_provider_port_ovn(self, m_cmds):
|
||||
port_ips = ['1.1.1.1', '2.2.2.2']
|
||||
mac = 'fake-mac'
|
||||
port = "{}-openstack".format(constants.OVN_CLUSTER_ROUTER)
|
||||
|
||||
wire._wire_provider_port_ovn(self.nb_idl, port_ips, mac)
|
||||
|
||||
cmds = [
|
||||
ovn_utils.StaticMACBindingAddCommand(
|
||||
self.nb_idl, port, port_ips[0], mac, True, may_exist=True),
|
||||
ovn_utils.StaticMACBindingAddCommand(
|
||||
self.nb_idl, port, port_ips[1], mac, True, may_exist=True)]
|
||||
|
||||
# FIXME(ltomasbo): The standard assert called ones is not working
|
||||
# with the object
|
||||
# m_cmds.assert_called_once_with(self.nb_idl, cmds)
|
||||
# so we are checking this by comparing the object dict instead
|
||||
self.assertEqual(
|
||||
m_cmds.call_args_list[0][0][1][0].__dict__,
|
||||
cmds[0].__dict__
|
||||
)
|
||||
|
||||
@mock.patch.object(wire, '_execute_commands')
|
||||
def test__wire_provider_port_ovn_no_action(self, m_cmds):
|
||||
port_ips = []
|
||||
mac = 'fake-mac'
|
||||
wire._wire_provider_port_ovn(self.nb_idl, port_ips, mac)
|
||||
m_cmds.assert_not_called()
|
||||
|
||||
@mock.patch.object(wire, '_unwire_provider_port_underlay')
|
||||
def test_unwire_provider_port_underlay(self, mock_underlay):
|
||||
routing_tables_routes = {}
|
||||
port_ips = []
|
||||
bridge_device = 'fake-bridge'
|
||||
bridge_vlan = '101'
|
||||
routing_table = 5
|
||||
proxy_cidrs = []
|
||||
|
||||
wire.unwire_provider_port(routing_tables_routes, port_ips,
|
||||
bridge_device, bridge_vlan, routing_table,
|
||||
proxy_cidrs)
|
||||
mock_underlay.assert_called_once_with(
|
||||
routing_tables_routes, port_ips, bridge_device, bridge_vlan,
|
||||
routing_table, proxy_cidrs, lladdr=None)
|
||||
|
||||
@mock.patch.object(wire, '_unwire_provider_port_ovn')
|
||||
def test_unwire_provider_port_ovn(self, mock_ovn):
|
||||
CONF.set_override('exposing_method', 'ovn')
|
||||
self.addCleanup(CONF.clear_override, 'exposing_method')
|
||||
|
||||
routing_tables_routes = {}
|
||||
port_ips = []
|
||||
bridge_device = 'fake-bridge'
|
||||
bridge_vlan = '101'
|
||||
routing_table = 5
|
||||
proxy_cidrs = []
|
||||
|
||||
wire.unwire_provider_port(routing_tables_routes, port_ips,
|
||||
bridge_device, bridge_vlan, routing_table,
|
||||
proxy_cidrs, ovn_idl=self.nb_idl)
|
||||
mock_ovn.assert_called_once_with(self.nb_idl, port_ips)
|
||||
|
||||
@mock.patch.object(wire, '_unwire_provider_port_underlay')
|
||||
@mock.patch.object(wire, '_unwire_provider_port_ovn')
|
||||
def test_unwire_provider_port_not_implemented(self, mock_ovn,
|
||||
mock_underlay):
|
||||
CONF.set_override('exposing_method', 'vrf')
|
||||
self.addCleanup(CONF.clear_override, 'exposing_method')
|
||||
|
||||
routing_tables_routes = {}
|
||||
port_ips = []
|
||||
bridge_device = 'fake-bridge'
|
||||
bridge_vlan = '101'
|
||||
routing_table = 5
|
||||
proxy_cidrs = []
|
||||
|
||||
wire.unwire_provider_port(routing_tables_routes, port_ips,
|
||||
bridge_device, bridge_vlan, routing_table,
|
||||
proxy_cidrs)
|
||||
mock_ovn.assert_not_called()
|
||||
mock_underlay.assert_not_called()
|
||||
|
||||
@mock.patch.object(wire, '_execute_commands')
|
||||
def test__unwire_provider_port_ovn(self, m_cmds):
|
||||
port_ips = ['1.1.1.1']
|
||||
port = "{}-openstack".format(constants.OVN_CLUSTER_ROUTER)
|
||||
|
||||
wire._unwire_provider_port_ovn(self.nb_idl, port_ips)
|
||||
|
||||
cmds = [ovn_utils.StaticMACBindingDelCommand(
|
||||
self.nb_idl, port, port_ips[0], if_exists=True)]
|
||||
|
||||
# FIXME(ltomasbo): The standard assert called ones is not working
|
||||
# with the object
|
||||
# m_cmds.assert_called_once_with(self.nb_idl, cmds)
|
||||
# so we are checking this by comparing the object dict instead
|
||||
self.assertEqual(
|
||||
m_cmds.call_args_list[0][0][1][0].__dict__,
|
||||
cmds[0].__dict__
|
||||
)
|
||||
|
||||
@mock.patch.object(wire, '_execute_commands')
|
||||
def test__unwire_provider_port_ovn_no_action(self, m_cmds):
|
||||
port_ips = []
|
||||
wire._unwire_provider_port_ovn(self.nb_idl, port_ips)
|
||||
m_cmds.assert_not_called()
|
@ -288,19 +288,23 @@ class TestLogicalSwitchPortFIPCreateEvent(test_base.TestCase):
|
||||
external_ip = '10.0.0.10'
|
||||
ls_name = 'neutron-net-id'
|
||||
self.agent.get_port_external_ip_and_ls.return_value = (external_ip,
|
||||
'mac',
|
||||
ls_name)
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
name='net-id')
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.expose_fip.assert_called_once_with(external_ip, ls_name,
|
||||
row)
|
||||
self.agent.expose_fip.assert_called_once_with(external_ip, 'mac',
|
||||
ls_name, row)
|
||||
|
||||
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,
|
||||
'mac',
|
||||
ls_name)
|
||||
row = utils.create_row(type=constants.OVN_VM_VIF_PORT_TYPE,
|
||||
addresses=['mac 192.168.0.1'],
|
||||
name='net-id')
|
||||
self.event.run(mock.Mock(), row, mock.Mock())
|
||||
self.agent.expose_fip.assert_not_called()
|
||||
|
@ -98,6 +98,24 @@ class TestLinuxNet(test_base.TestCase):
|
||||
self.assertRaises(agent_exc.NetworkInterfaceNotFound,
|
||||
linux_net.get_interface_address, 'fake-nic')
|
||||
|
||||
def test_get_nic_info(self):
|
||||
device_idx = 7
|
||||
nic_addr = IPRouteDict({'prefixlen': 32,
|
||||
'attrs': [('IFA_ADDRESS', self.ip)]})
|
||||
self.fake_ipr.link_lookup.return_value = [device_idx]
|
||||
self.fake_ipr.get_addr.return_value = [nic_addr]
|
||||
fake_link = mock.MagicMock()
|
||||
fake_link.get_attr.return_value = self.mac
|
||||
self.fake_ipr.get_links.return_value = [fake_link]
|
||||
|
||||
ret = linux_net.get_nic_info('fake-nic')
|
||||
self.assertEqual(('{}/32'.format(self.ip), self.mac), ret)
|
||||
|
||||
def test_get_nic_info_index_error(self):
|
||||
self.fake_ipr.link_lookup.return_value = ''
|
||||
self.assertRaises(agent_exc.NetworkInterfaceNotFound,
|
||||
linux_net.get_nic_info, 'fake-nic')
|
||||
|
||||
@mock.patch('ovn_bgp_agent.privileged.linux_net.ensure_vrf')
|
||||
def test_ensure_vrf(self, mock_ensure_vrf):
|
||||
linux_net.ensure_vrf('fake-vrf', 10)
|
||||
|
@ -77,6 +77,26 @@ def get_interface_address(nic):
|
||||
raise agent_exc.NetworkInterfaceNotFound(device=nic)
|
||||
|
||||
|
||||
@tenacity.retry(
|
||||
retry=tenacity.retry_if_exception_type(
|
||||
netlink_exceptions.NetlinkDumpInterrupted),
|
||||
wait=tenacity.wait_exponential(multiplier=0.02, max=1),
|
||||
stop=tenacity.stop_after_delay(8),
|
||||
reraise=True)
|
||||
def get_nic_info(nic):
|
||||
try:
|
||||
with pyroute2.IPRoute() as ipr:
|
||||
idx = ipr.link_lookup(ifname=nic)[0]
|
||||
nic_addr = ipr.get_addr(index=idx)[0]
|
||||
ip = '{}/{}'.format(
|
||||
nic_addr.get_attr('IFA_ADDRESS'),
|
||||
nic_addr.get('prefixlen'))
|
||||
mac = ipr.get_links(idx)[0].get_attr('IFLA_ADDRESS')
|
||||
return ip, mac
|
||||
except IndexError:
|
||||
raise agent_exc.NetworkInterfaceNotFound(device=nic)
|
||||
|
||||
|
||||
def ensure_vrf(vrf_name, vrf_table):
|
||||
ovn_bgp_agent.privileged.linux_net.ensure_vrf(vrf_name, vrf_table)
|
||||
|
||||
|
@ -13,7 +13,7 @@ oslo.privsep>=2.3.0 # Apache-2.0
|
||||
oslo.rootwrap>=5.15.0 # Apache-2.0
|
||||
oslo.service>=1.40.2 # Apache-2.0
|
||||
ovs>=2.8.0 # Apache-2.0
|
||||
ovsdbapp>=1.4.0 # Apache-2.0
|
||||
ovsdbapp>=1.16.0 # Apache-2.0
|
||||
pyroute2>=0.6.6;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2)
|
||||
stevedore>=1.20.0 # Apache-2.0
|
||||
tenacity>=6.0.0 # Apache-2.0
|
||||
|
6
tox.ini
6
tox.ini
@ -42,9 +42,9 @@ setenv =
|
||||
commands =
|
||||
stestr run --exclude-regex ".tests.functional" {posargs}
|
||||
coverage combine
|
||||
coverage html -d cover --omit='*tests*'
|
||||
coverage xml -o cover/coverage.xml --omit='*tests*'
|
||||
coverage report --fail-under=85 --skip-covered --omit='*tests*'
|
||||
coverage html -d cover --omit='ovn_bgp_agent/tests/*'
|
||||
coverage xml -o cover/coverage.xml --omit='ovn_bgp_agent/tests/*'
|
||||
coverage report --fail-under=82 --skip-covered --omit='ovn_bgp_agent/tests/*,ovn_bgp_agent/privileged/linux_net.py,ovn_bgp_agent/utils/linux_net.py'
|
||||
|
||||
[testenv:docs]
|
||||
deps = -r{toxinidir}/doc/requirements.txt
|
||||
|
Loading…
x
Reference in New Issue
Block a user