Merge "Add initial support for local OVN cluster instead of kernel-networking"
This commit is contained in:
commit
5a97fa191a
@ -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
|
||||
|
||||