[EVPN] Connect VRF to OVS through veth or vlan
Linux kernel forbids to add VRF (layer 3) devices into OVS bridges (layer 2). This patch changes the approach to link EVPN/VRF to OVS bridges by using a veth-pair instead, or a vlan device for the provider vlan networks use case. It also fixes the next: - typo on function definition "disconnect_evpn_to_ovn", changed by "disconnect_evpn_from_ovn" - avoid code duplication to expose subnets, by making an auxiliar function that can be reused by the reconciliation loop too. Change-Id: Iccd1b01014bf026c07e39f48e8aa234a9a303877
This commit is contained in:
parent
9f943cb0d2
commit
5ba2f40262
@ -40,7 +40,10 @@ OVN_EVPN_AS_EXT_ID_KEY = 'neutron_bgpvpn:as'
|
||||
OVN_EVPN_VRF_PREFIX = "vrf-"
|
||||
OVN_EVPN_BRIDGE_PREFIX = "br-"
|
||||
OVN_EVPN_VXLAN_PREFIX = "vxlan-"
|
||||
OVN_EVPN_VLAN_PREFIX = "vlan-"
|
||||
OVN_EVPN_LO_PREFIX = "lo-"
|
||||
OVN_EVPN_VETH_VRF_PREFIX = "veth-vrf-"
|
||||
OVN_EVPN_VETH_OVS_PREFIX = "veth-ovs-"
|
||||
OVN_INTEGRATION_BRIDGE = 'br-int'
|
||||
OVN_LRP_PORT_NAME_PREFIX = 'lrp-'
|
||||
OVN_CRLRP_PORT_NAME_PREFIX = 'cr-lrp-'
|
||||
|
@ -35,6 +35,9 @@ LOG = logging.getLogger(__name__)
|
||||
# logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
OVN_TABLES = ("Port_Binding", "Chassis", "Datapath_Binding", "Chassis_Private")
|
||||
EVPN_INFO = collections.namedtuple(
|
||||
'EVPNInfo', ['vrf_name', 'lo_name', 'bridge_name', 'vxlan_name',
|
||||
'veth_vrf', 'veth_ovs', 'vlan_name'])
|
||||
|
||||
|
||||
class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
@ -130,65 +133,11 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
gateway['provider_datapath'])
|
||||
|
||||
router_port_ip_version = linux_net.get_ip_version(router_port_ip)
|
||||
for gateway_ip in gateway_ips:
|
||||
if linux_net.get_ip_version(gateway_ip) == router_port_ip_version:
|
||||
linux_net.add_ip_route(
|
||||
self._ovn_routing_tables_routes,
|
||||
router_ip,
|
||||
gateway['vni'],
|
||||
datapath_bridge,
|
||||
vlan=vlan_tag,
|
||||
mask=router_port_ip.split("/")[1],
|
||||
via=gateway_ip)
|
||||
break
|
||||
|
||||
if router_port_ip_version == constants.IP_VERSION_6:
|
||||
net_ip = '{}'.format(ipaddress.IPv6Network(
|
||||
router_port_ip, strict=False))
|
||||
else:
|
||||
net_ip = '{}'.format(ipaddress.IPv4Network(
|
||||
router_port_ip, strict=False))
|
||||
|
||||
strip_vlan = False
|
||||
if vlan_tag:
|
||||
strip_vlan = True
|
||||
ovs.ensure_evpn_ovs_flow(datapath_bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE,
|
||||
gateway['mac'],
|
||||
gateway['vrf'],
|
||||
net_ip,
|
||||
strip_vlan)
|
||||
|
||||
network_port_datapath = self.sb_idl.get_port_datapath(
|
||||
network_datapath = self.sb_idl.get_port_datapath(
|
||||
router_port.options['peer'])
|
||||
if not network_port_datapath:
|
||||
return
|
||||
ports = self.sb_idl.get_ports_on_datapath(
|
||||
network_port_datapath)
|
||||
for port in ports:
|
||||
if (port.type not in (constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE) or
|
||||
(port.type == constants.OVN_VM_VIF_PORT_TYPE and
|
||||
not port.chassis)):
|
||||
continue
|
||||
try:
|
||||
port_ips = [port.mac[0].split(' ')[1]]
|
||||
except IndexError:
|
||||
continue
|
||||
if len(port.mac[0].split(' ')) == 3:
|
||||
port_ips.append(port.mac[0].split(' ')[2])
|
||||
|
||||
for port_ip in port_ips:
|
||||
# Only adding the port ips that match the lrp
|
||||
# IP version
|
||||
port_ip_version = linux_net.get_ip_version(port_ip)
|
||||
if port_ip_version == router_port_ip_version:
|
||||
linux_net.add_ips_to_dev(
|
||||
gateway['lo'], [port_ip],
|
||||
clear_local_route_at_table=gateway['vni'])
|
||||
self._ovn_exposed_evpn_ips.setdefault(
|
||||
gateway['lo'], []).extend([port_ip])
|
||||
self._expose_subnet(router_port_ip, gateway_ips, gateway,
|
||||
datapath_bridge, vlan_tag, network_datapath)
|
||||
|
||||
def _get_bridge_for_datapath(self, datapath):
|
||||
network_name, network_tag = self.sb_idl.get_network_name_and_tag(
|
||||
@ -246,10 +195,15 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
"Not exposing it.", ips)
|
||||
return
|
||||
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
cr_lrp_datapath)
|
||||
|
||||
LOG.info("Adding BGP route for CR-LRP Port %s on AS %s and "
|
||||
"VNI %s", ips, evpn_info['bgp_as'], evpn_info['vni'])
|
||||
vrf, lo, bridge, vxlan = self._ensure_evpn_devices(evpn_info['vni'])
|
||||
if not vrf or not lo:
|
||||
evpn_devices = self._ensure_evpn_devices(datapath_bridge,
|
||||
evpn_info['vni'],
|
||||
vlan_tag)
|
||||
if not evpn_devices.vrf_name or not evpn_devices.lo_name:
|
||||
return
|
||||
|
||||
self.ovn_local_cr_lrps[cr_lrp_port_name] = {
|
||||
@ -259,32 +213,27 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
'mac': cr_lrp_port.mac[0].split(' ')[0],
|
||||
'vni': int(evpn_info['vni']),
|
||||
'bgp_as': evpn_info['bgp_as'],
|
||||
'lo': lo,
|
||||
'bridge': bridge,
|
||||
'vxlan': vxlan,
|
||||
'vrf': vrf
|
||||
'lo': evpn_devices.lo_name,
|
||||
'bridge': evpn_devices.bridge_name,
|
||||
'vxlan': evpn_devices.vxlan_name,
|
||||
'vrf': evpn_devices.vrf_name,
|
||||
'veth_vrf': evpn_devices.veth_vrf,
|
||||
'veth_ovs': evpn_devices.veth_ovs,
|
||||
'vlan': evpn_devices.vlan_name
|
||||
}
|
||||
|
||||
frr.vrf_reconfigure(evpn_info, action="add-vrf")
|
||||
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
cr_lrp_datapath)
|
||||
|
||||
ips_without_mask = [ip.split("/")[0] for ip in ips]
|
||||
linux_net.add_ips_to_dev(lo, ips_without_mask)
|
||||
self._ovn_exposed_evpn_ips.setdefault(
|
||||
lo, []).extend(ips_without_mask)
|
||||
|
||||
self._connect_evpn_to_ovn(vrf, ips, datapath_bridge, evpn_info['vni'],
|
||||
self._connect_evpn_to_ovn(evpn_devices.vrf_name, evpn_devices.veth_vrf,
|
||||
evpn_devices.veth_ovs, ips, datapath_bridge,
|
||||
evpn_info['vni'], evpn_devices.vlan_name,
|
||||
vlan_tag)
|
||||
|
||||
nei_bridge = datapath_bridge
|
||||
if vlan_tag:
|
||||
nei_bridge = '{}.{}'.format(datapath_bridge, vlan_tag)
|
||||
ips_without_mask = [ip.split("/")[0] for ip in ips]
|
||||
nei_dev = evpn_devices.vlan_name if vlan_tag else evpn_devices.veth_vrf
|
||||
for ip in ips_without_mask:
|
||||
linux_net.add_ip_nei(
|
||||
ip, self.ovn_local_cr_lrps[cr_lrp_port_name]['mac'],
|
||||
nei_bridge)
|
||||
ip, self.ovn_local_cr_lrps[cr_lrp_port_name]['mac'], nei_dev)
|
||||
|
||||
# Check if there are networks attached to the router,
|
||||
# and if so, add the needed routes/rules
|
||||
@ -332,8 +281,8 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
cr_lrp_datapath)
|
||||
|
||||
if vlan_tag:
|
||||
self._disconnect_evpn_to_ovn(evpn_vni, datapath_bridge, ips,
|
||||
vlan_tag=vlan_tag)
|
||||
self._disconnect_evpn_from_ovn(evpn_vni, datapath_bridge, ips,
|
||||
vlan_tag=vlan_tag)
|
||||
else:
|
||||
cr_lrps_on_same_provider = [
|
||||
p for p in self.ovn_local_cr_lrps.values()
|
||||
@ -342,18 +291,14 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
# NOTE: no need to remove the NDP proxy if there are other
|
||||
# cr-lrp ports on the same chassis connected to the same
|
||||
# provider flat network
|
||||
self._disconnect_evpn_to_ovn(evpn_vni, datapath_bridge, ips,
|
||||
cleanup_ndp_proxy=False)
|
||||
self._disconnect_evpn_from_ovn(evpn_vni, datapath_bridge, ips,
|
||||
cleanup_ndp_proxy=False)
|
||||
else:
|
||||
self._disconnect_evpn_to_ovn(evpn_vni, datapath_bridge, ips)
|
||||
self._disconnect_evpn_from_ovn(evpn_vni, datapath_bridge, ips)
|
||||
|
||||
nei_bridge = datapath_bridge
|
||||
if vlan_tag:
|
||||
nei_bridge = '{}.{}'.format(datapath_bridge, vlan_tag)
|
||||
nei_dev = cr_lrp_info['vlan'] if vlan_tag else cr_lrp_info['veth_vrf']
|
||||
for ip in ips:
|
||||
linux_net.del_ip_nei(
|
||||
ip, self.ovn_local_cr_lrps[cr_lrp_port_name]['mac'],
|
||||
nei_bridge)
|
||||
linux_net.del_ip_nei(ip, cr_lrp_info['mac'], nei_dev)
|
||||
|
||||
self._remove_evpn_devices(evpn_vni)
|
||||
ovs.remove_evpn_router_ovs_flows(datapath_bridge,
|
||||
@ -447,46 +392,66 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
cr_lrp_datapath)
|
||||
|
||||
ip_version = linux_net.get_ip_version(ip)
|
||||
self._expose_subnet(ip, cr_lrp_ips, cr_lrp_info, datapath_bridge,
|
||||
vlan_tag, row.datapath)
|
||||
|
||||
def _expose_subnet(self, router_interface, cr_lrp_ips, cr_lrp_info,
|
||||
datapath_bridge, vlan_tag, network_datapath):
|
||||
router_interface_ip_version = linux_net.get_ip_version(
|
||||
router_interface)
|
||||
if vlan_tag:
|
||||
dev = cr_lrp_info['vlan']
|
||||
dev_ovs = dev
|
||||
strip_vlan = True
|
||||
else:
|
||||
dev = cr_lrp_info['veth_vrf']
|
||||
dev_ovs = cr_lrp_info['veth_ovs']
|
||||
strip_vlan = False
|
||||
|
||||
for cr_lrp_ip in cr_lrp_ips:
|
||||
if linux_net.get_ip_version(cr_lrp_ip) == ip_version:
|
||||
if (linux_net.get_ip_version(cr_lrp_ip) ==
|
||||
router_interface_ip_version):
|
||||
linux_net.add_ip_route(
|
||||
self._ovn_routing_tables_routes,
|
||||
ip.split("/")[0],
|
||||
evpn_info['vni'],
|
||||
datapath_bridge,
|
||||
vlan=vlan_tag,
|
||||
mask=ip.split("/")[1],
|
||||
router_interface.split("/")[0],
|
||||
cr_lrp_info['vni'],
|
||||
dev,
|
||||
mask=router_interface.split("/")[1],
|
||||
via=cr_lrp_ip)
|
||||
break
|
||||
|
||||
if ip_version == constants.IP_VERSION_6:
|
||||
if router_interface_ip_version == constants.IP_VERSION_6:
|
||||
net_ip = '{}'.format(ipaddress.IPv6Network(
|
||||
ip, strict=False))
|
||||
router_interface, strict=False))
|
||||
else:
|
||||
net_ip = '{}'.format(ipaddress.IPv4Network(
|
||||
ip, strict=False))
|
||||
router_interface, strict=False))
|
||||
|
||||
strip_vlan = False
|
||||
if vlan_tag:
|
||||
strip_vlan = True
|
||||
# NOTE(ltomasbo): strip_vlan is used for subnets/routers associated to
|
||||
# provider vlan networks assuming the EVPN VXLAN header is replacing
|
||||
# the vlan id in the fabric. If that is not the case, we could simply
|
||||
# set this to False in all the cases and have the traffic sent with
|
||||
# both vxlan header (for the EVPN) plus the vlan header (related to
|
||||
# the provider vlan id being used)
|
||||
ovs.ensure_evpn_ovs_flow(datapath_bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE,
|
||||
cr_lrp_info['mac'],
|
||||
cr_lrp_info['vrf'],
|
||||
dev_ovs,
|
||||
dev,
|
||||
net_ip,
|
||||
strip_vlan)
|
||||
strip_vlan=strip_vlan)
|
||||
|
||||
# Check if there are VMs on the network
|
||||
# and if so expose the route
|
||||
network_port_datapath = row.datapath
|
||||
if not network_port_datapath:
|
||||
if not network_datapath:
|
||||
return
|
||||
ports = self.sb_idl.get_ports_on_datapath(
|
||||
network_port_datapath)
|
||||
network_datapath)
|
||||
for port in ports:
|
||||
if (port.type not in (constants.OVN_VM_VIF_PORT_TYPE,
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE)):
|
||||
constants.OVN_VIRTUAL_VIF_PORT_TYPE) or
|
||||
(port.type == constants.OVN_VM_VIF_PORT_TYPE and
|
||||
not port.chassis)):
|
||||
continue
|
||||
try:
|
||||
port_ips = [port.mac[0].split(' ')[1]]
|
||||
@ -499,10 +464,10 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
# Only adding the port ips that match the lrp
|
||||
# IP version
|
||||
port_ip_version = linux_net.get_ip_version(port_ip)
|
||||
if port_ip_version == ip_version:
|
||||
if port_ip_version == router_interface_ip_version:
|
||||
linux_net.add_ips_to_dev(
|
||||
cr_lrp_info['lo'], [port_ip],
|
||||
clear_local_route_at_table=evpn_info['vni'])
|
||||
clear_local_route_at_table=cr_lrp_info['vni'])
|
||||
self._ovn_exposed_evpn_ips.setdefault(
|
||||
cr_lrp_info['lo'], []).extend([port_ip])
|
||||
|
||||
@ -534,6 +499,11 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
datapath_bridge, vlan_tag = self._get_bridge_for_datapath(
|
||||
cr_lrp_datapath)
|
||||
|
||||
if vlan_tag:
|
||||
dev = cr_lrp_info['vlan']
|
||||
else:
|
||||
dev = cr_lrp_info['veth_vrf']
|
||||
|
||||
ip_version = linux_net.get_ip_version(ip)
|
||||
for cr_lrp_ip in cr_lrp_ips:
|
||||
if linux_net.get_ip_version(cr_lrp_ip) == ip_version:
|
||||
@ -541,8 +511,7 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
self._ovn_routing_tables_routes,
|
||||
ip.split("/")[0],
|
||||
cr_lrp_info['vni'],
|
||||
datapath_bridge,
|
||||
vlan=vlan_tag,
|
||||
dev,
|
||||
mask=ip.split("/")[1],
|
||||
via=cr_lrp_ip)
|
||||
if (linux_net.get_ip_version(cr_lrp_ip) ==
|
||||
@ -570,7 +539,25 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
LOG.debug("Router Interface port already cleanup from the agent "
|
||||
"%s", lrp_logical_port)
|
||||
|
||||
def _ensure_evpn_devices(self, vni):
|
||||
def _ensure_evpn_devices(self, datapath_bridge, vni, vlan_tag):
|
||||
'''Create the needed devices for EVPN connectivity
|
||||
|
||||
This method creates and associate the needed devices for EVPN
|
||||
connectivity. It creates:
|
||||
- VRF device
|
||||
- Linux Bridge device, associated to the VRF
|
||||
- VXLAN device, using loopback IP, associate to the bridge
|
||||
- Dummy device to expose the IPs, associated to the VRF
|
||||
- If vlan_tag, create vlan device on OVS bridge, associated to the VRF
|
||||
- If no vlan_tag, create veth pair, one end associated to the VRF
|
||||
|
||||
param datapath_bridge: OVS bridge to connect the vlan device
|
||||
param vni: VNI number to use for vxlan tunnel ids and vrf routing table
|
||||
param vlan_tag: vlan id to use for connectivity
|
||||
|
||||
return: a namedtuple with the name of the devices created: vrf_name,
|
||||
lo_name, bridge_name, vxlan_name, veth_vrf, veth_ovs, and vlan_name.
|
||||
'''
|
||||
# ensure vrf device.
|
||||
# NOTE: It uses vni id as table number
|
||||
vrf_name = constants.OVN_EVPN_VRF_PREFIX + str(vni)
|
||||
@ -601,61 +588,100 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
# connect dummy to vrf
|
||||
linux_net.set_master_for_device(lo_name, vrf_name)
|
||||
|
||||
return vrf_name, lo_name, bridge_name, vxlan_name
|
||||
if vlan_tag:
|
||||
vlan_name = constants.OVN_EVPN_VLAN_PREFIX + str(vni)
|
||||
# add vlan port to OVS bridge
|
||||
ovs.add_vlan_port_to_ovs_bridge(datapath_bridge, vlan_name,
|
||||
vlan_tag)
|
||||
linux_net.set_device_status(vlan_name, constants.LINK_UP)
|
||||
# connect vlan to vrf
|
||||
linux_net.set_master_for_device(vlan_name, vrf_name)
|
||||
# ensure proxy NDP is enabled for ipv6 traffic
|
||||
linux_net.enable_proxy_ndp(vlan_name)
|
||||
|
||||
return EVPN_INFO(vrf_name, lo_name, bridge_name, vxlan_name, None,
|
||||
None, vlan_name)
|
||||
else:
|
||||
# ensure veth-pair interfaces
|
||||
veth_vrf = constants.OVN_EVPN_VETH_VRF_PREFIX + str(vni)
|
||||
veth_ovs = constants.OVN_EVPN_VETH_OVS_PREFIX + str(vni)
|
||||
linux_net.ensure_veth(veth_vrf, veth_ovs)
|
||||
# connect veth to vrf
|
||||
linux_net.set_master_for_device(veth_vrf, vrf_name)
|
||||
|
||||
return EVPN_INFO(vrf_name, lo_name, bridge_name, vxlan_name,
|
||||
veth_vrf, veth_ovs, None)
|
||||
|
||||
def _remove_evpn_devices(self, vni):
|
||||
vrf_name = constants.OVN_EVPN_VRF_PREFIX + str(vni)
|
||||
bridge_name = constants.OVN_EVPN_BRIDGE_PREFIX + str(vni)
|
||||
vxlan_name = constants.OVN_EVPN_VXLAN_PREFIX + str(vni)
|
||||
lo_name = constants.OVN_EVPN_LO_PREFIX + str(vni)
|
||||
veth_name = constants.OVN_EVPN_VETH_VRF_PREFIX + str(vni)
|
||||
vlan_name = constants.OVN_EVPN_VLAN_PREFIX + str(vni)
|
||||
|
||||
for device in [lo_name, vrf_name, bridge_name, vxlan_name]:
|
||||
for device in [lo_name, vrf_name, bridge_name, vxlan_name, veth_name,
|
||||
vlan_name]:
|
||||
linux_net.delete_device(device)
|
||||
|
||||
def _connect_evpn_to_ovn(self, vrf, ips, datapath_bridge, vni, vlan_tag):
|
||||
# add vrf to ovs bridge
|
||||
ovs.add_device_to_ovs_bridge(vrf, datapath_bridge, vlan_tag)
|
||||
def _connect_evpn_to_ovn(self, vrf, veth_vrf, veth_ovs, ips,
|
||||
datapath_bridge, vni, vlan, vlan_tag):
|
||||
# NOTE(ltomasbo): vlan device is already attached to ovs bridge
|
||||
# when created
|
||||
if not vlan_tag:
|
||||
# add veth to ovs bridge
|
||||
ovs.add_device_to_ovs_bridge(veth_ovs, datapath_bridge)
|
||||
|
||||
if vlan_tag:
|
||||
linux_net.ensure_vlan_device_for_network(datapath_bridge, vlan_tag)
|
||||
# add route for ip to ovs provider bridge (at the vrf routing table)
|
||||
for ip in ips:
|
||||
ip_without_mask = ip.split("/")[0]
|
||||
linux_net.add_ip_route(
|
||||
self._ovn_routing_tables_routes, ip_without_mask,
|
||||
vni, datapath_bridge, vlan=vlan_tag)
|
||||
|
||||
# add proxy ndp config for ipv6
|
||||
if (linux_net.get_ip_version(ip_without_mask) ==
|
||||
constants.IP_VERSION_6):
|
||||
linux_net.add_ndp_proxy(ip, datapath_bridge, vlan=vlan_tag)
|
||||
if vlan_tag:
|
||||
# ip route add GW_PORT_IP dev VLAN_DEVICE table VRF_TABLE_ID
|
||||
linux_net.add_ip_route(
|
||||
self._ovn_routing_tables_routes, ip_without_mask,
|
||||
vni, vlan)
|
||||
# add proxy ndp config for ipv6
|
||||
if (linux_net.get_ip_version(ip_without_mask) ==
|
||||
constants.IP_VERSION_6):
|
||||
linux_net.add_ndp_proxy(ip, vlan)
|
||||
else:
|
||||
linux_net.add_ip_route(
|
||||
self._ovn_routing_tables_routes, ip_without_mask,
|
||||
vni, veth_vrf)
|
||||
# add proxy ndp config for ipv6
|
||||
if (linux_net.get_ip_version(ip_without_mask) ==
|
||||
constants.IP_VERSION_6):
|
||||
linux_net.add_ndp_proxy(ip, datapath_bridge)
|
||||
|
||||
# add unreachable route to vrf
|
||||
linux_net.add_unreachable_route(vrf)
|
||||
|
||||
def _disconnect_evpn_to_ovn(self, vni, datapath_bridge, ips,
|
||||
vlan_tag=None, cleanup_ndp_proxy=True):
|
||||
vrf = constants.OVN_EVPN_VRF_PREFIX + str(vni)
|
||||
# remove vrf from ovs bridge
|
||||
ovs.del_device_from_ovs_bridge(vrf, datapath_bridge)
|
||||
def _disconnect_evpn_from_ovn(self, vni, datapath_bridge, ips,
|
||||
vlan_tag=None, cleanup_ndp_proxy=True):
|
||||
if vlan_tag:
|
||||
# remove vlan from ovs bridge
|
||||
device = constants.OVN_EVPN_VLAN_PREFIX + str(vni)
|
||||
else:
|
||||
# remove veth from ovs bridge
|
||||
device = constants.OVN_EVPN_VETH_OVS_PREFIX + str(vni)
|
||||
ovs.del_device_from_ovs_bridge(device, datapath_bridge)
|
||||
|
||||
linux_net.delete_routes_from_table(vni)
|
||||
|
||||
if vlan_tag:
|
||||
linux_net.delete_vlan_device_for_network(datapath_bridge,
|
||||
vlan_tag)
|
||||
elif cleanup_ndp_proxy:
|
||||
if cleanup_ndp_proxy:
|
||||
for ip in ips:
|
||||
if linux_net.get_ip_version(ip) == constants.IP_VERSION_6:
|
||||
linux_net.del_ndp_proxy(ip, datapath_bridge)
|
||||
|
||||
def _remove_extra_vrfs(self):
|
||||
vrfs, los, bridges, vxlans = ([], [], [], [])
|
||||
vrfs, los, bridges, vxlans, veths, vlans = ([], [], [], [], [], [])
|
||||
for cr_lrp_info in self.ovn_local_cr_lrps.values():
|
||||
vrfs.append(cr_lrp_info['vrf'])
|
||||
los.append(cr_lrp_info['lo'])
|
||||
bridges.append(cr_lrp_info['bridge'])
|
||||
vxlans.append(cr_lrp_info['vxlan'])
|
||||
veths.append(cr_lrp_info['veth_vrf'])
|
||||
vlans.append(cr_lrp_info['vlan'])
|
||||
|
||||
filter_out = ["{}.{}".format(key, value[0]['vlan'])
|
||||
for key, value in self._ovn_routing_tables_routes.items()
|
||||
@ -666,7 +692,6 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
if (interface.startswith(constants.OVN_EVPN_VRF_PREFIX) and
|
||||
interface not in vrfs):
|
||||
linux_net.delete_device(interface)
|
||||
ovs.del_device_from_ovs_bridge(interface)
|
||||
elif (interface.startswith(constants.OVN_EVPN_LO_PREFIX) and
|
||||
interface not in los):
|
||||
linux_net.delete_device(interface)
|
||||
@ -678,6 +703,13 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
elif (interface.startswith(constants.OVN_EVPN_VXLAN_PREFIX) and
|
||||
interface not in vxlans):
|
||||
linux_net.delete_device(interface)
|
||||
elif (interface.startswith(constants.OVN_EVPN_VETH_VRF_PREFIX) and
|
||||
interface not in veths):
|
||||
linux_net.delete_device(interface)
|
||||
ovs.del_device_from_ovs_bridge(interface)
|
||||
elif (interface.startswith(constants.OVN_EVPN_VLAN_PREFIX) and
|
||||
interface not in vlans):
|
||||
ovs.del_device_from_ovs_bridge(interface)
|
||||
|
||||
def _remove_extra_routes(self):
|
||||
table_ids = self._get_table_ids()
|
||||
@ -685,13 +717,9 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
if not vrf_routes:
|
||||
return
|
||||
# remove from vrf_routes the routes that should be kept
|
||||
for bridge, routes_info in self._ovn_routing_tables_routes.items():
|
||||
for device, routes_info in self._ovn_routing_tables_routes.items():
|
||||
for route_info in routes_info:
|
||||
oif = linux_net.get_interface_index(bridge)
|
||||
if route_info['vlan']:
|
||||
vlan_device_name = '{}.{}'.format(bridge,
|
||||
route_info['vlan'])
|
||||
oif = linux_net.get_interface_index(vlan_device_name)
|
||||
oif = linux_net.get_interface_index(device)
|
||||
if 'gateway' in route_info['route'].keys(): # subnet route
|
||||
possible_matchings = [
|
||||
r for r in vrf_routes
|
||||
@ -712,7 +740,7 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
linux_net.delete_ip_routes(vrf_routes)
|
||||
|
||||
def _remove_extra_ovs_flows(self):
|
||||
cr_lrp_mac_vrf_mappings = self._get_cr_lrp_mac_vrf_mapping()
|
||||
cr_lrp_mac_mappings = self._get_cr_lrp_mac_mapping()
|
||||
cookie_id = "cookie={}/-1".format(constants.OVS_VRF_RULE_COOKIE)
|
||||
for bridge in set(self.ovn_bridge_mappings.values()):
|
||||
current_flows = ovs.get_bridge_flows(bridge, filter_=cookie_id)
|
||||
@ -720,7 +748,7 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
flow_info = ovs.get_flow_info(flow)
|
||||
if not flow_info.get('mac'):
|
||||
ovs.del_flow(flow, bridge, constants.OVS_VRF_RULE_COOKIE)
|
||||
elif flow_info['mac'] not in cr_lrp_mac_vrf_mappings.keys():
|
||||
elif flow_info['mac'] not in cr_lrp_mac_mappings.keys():
|
||||
ovs.del_flow(flow, bridge, constants.OVS_VRF_RULE_COOKIE)
|
||||
elif flow_info['port']:
|
||||
if (not flow_info.get('nw_src') and not
|
||||
@ -728,9 +756,17 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
ovs.del_flow(flow, bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE)
|
||||
else:
|
||||
device = cr_lrp_mac_vrf_mappings[flow_info['mac']]
|
||||
vrf_port = ovs.get_device_port_at_ovs(device)
|
||||
if vrf_port != flow_info['port']:
|
||||
dev_info = cr_lrp_mac_mappings[flow_info['mac']]
|
||||
if dev_info.get('vlan'):
|
||||
dev = dev_info['vlan']
|
||||
dev_ovs = dev
|
||||
else:
|
||||
dev = dev_info['veth_vrf']
|
||||
dev_ovs = dev_info['veth_ovs']
|
||||
dev_ovs_port = ovs.get_device_port_at_ovs(
|
||||
dev_ovs)
|
||||
|
||||
if dev_ovs_port != flow_info['port']:
|
||||
ovs.del_flow(flow, bridge,
|
||||
constants.OVS_VRF_RULE_COOKIE)
|
||||
nw_src_ip = nw_src_mask = None
|
||||
@ -745,7 +781,7 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
flow_info['ipv6_src'].split('/')[1])
|
||||
|
||||
for route_info in self._ovn_routing_tables_routes[
|
||||
bridge]:
|
||||
dev]:
|
||||
if (route_info['route']['dst'] == nw_src_ip and
|
||||
route_info['route'][
|
||||
'dst_len'] == nw_src_mask):
|
||||
@ -767,8 +803,11 @@ class OVNEVPNDriver(driver_api.AgentDriverBase):
|
||||
table_ids.append(cr_lrp_info['vni'])
|
||||
return table_ids
|
||||
|
||||
def _get_cr_lrp_mac_vrf_mapping(self):
|
||||
mac_vrf_mappings = {}
|
||||
def _get_cr_lrp_mac_mapping(self):
|
||||
mac_mappings = {}
|
||||
for cr_lrp_info in self.ovn_local_cr_lrps.values():
|
||||
mac_vrf_mappings[cr_lrp_info['mac']] = cr_lrp_info['vrf']
|
||||
return mac_vrf_mappings
|
||||
mac_mappings[cr_lrp_info['mac']] = {
|
||||
'veth_vrf': cr_lrp_info['veth_vrf'],
|
||||
'veth_ovs': cr_lrp_info['veth_ovs'],
|
||||
'vlan': cr_lrp_info['vlan']}
|
||||
return mac_mappings
|
||||
|
@ -29,6 +29,8 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _find_ovs_port(bridge):
|
||||
# TODO(ltomasbo): What happens if there are several patch ports on the
|
||||
# same bridge?
|
||||
ovs_port = None
|
||||
ovs_ports = ovn_bgp_agent.privileged.ovs_vsctl.ovs_cmd(
|
||||
'ovs-vsctl', ['list-ports', bridge])[0].rstrip()
|
||||
@ -95,12 +97,13 @@ def remove_extra_ovs_flows(flows_info, cookie):
|
||||
'ovs-ofctl', ['del-flows', bridge, del_flow])
|
||||
|
||||
|
||||
def ensure_evpn_ovs_flow(bridge, cookie, mac, port, net, strip_vlan=False):
|
||||
def ensure_evpn_ovs_flow(bridge, cookie, mac, output_port, port_dst, net,
|
||||
strip_vlan=False):
|
||||
ovs_port = _find_ovs_port(bridge)
|
||||
if not ovs_port:
|
||||
return
|
||||
ovs_ofport = get_device_port_at_ovs(ovs_port)
|
||||
vrf_ofport = get_device_port_at_ovs(port)
|
||||
vrf_ofport = get_device_port_at_ovs(output_port)
|
||||
|
||||
strip_vlan_opt = 'strip_vlan,' if strip_vlan else ''
|
||||
ip_version = linux_net.get_ip_version(net)
|
||||
@ -110,14 +113,14 @@ def ensure_evpn_ovs_flow(bridge, cookie, mac, port, net, strip_vlan=False):
|
||||
"cookie={},priority=1000,ipv6,in_port={},dl_src:{},"
|
||||
"ipv6_src={} actions=mod_dl_dst:{},{}output={}".format(
|
||||
cookie, ovs_ofport, mac, net,
|
||||
ndb.interfaces[bridge]['address'], strip_vlan_opt,
|
||||
ndb.interfaces[port_dst]['address'], strip_vlan_opt,
|
||||
vrf_ofport))
|
||||
else:
|
||||
flow = (
|
||||
"cookie={},priority=1000,ip,in_port={},dl_src:{},nw_src={}"
|
||||
"actions=mod_dl_dst:{},{}output={}".format(
|
||||
cookie, ovs_ofport, mac, net,
|
||||
ndb.interfaces[bridge]['address'], strip_vlan_opt,
|
||||
ndb.interfaces[port_dst]['address'], strip_vlan_opt,
|
||||
vrf_ofport))
|
||||
ovn_bgp_agent.privileged.ovs_vsctl.ovs_cmd(
|
||||
'ovs-ofctl', ['add-flow', bridge, flow])
|
||||
@ -216,6 +219,15 @@ def del_device_from_ovs_bridge(device, bridge=None):
|
||||
ovn_bgp_agent.privileged.ovs_vsctl.ovs_cmd('ovs-vsctl', args)
|
||||
|
||||
|
||||
def add_vlan_port_to_ovs_bridge(bridge, vlan, vlan_tag):
|
||||
# ovs-vsctl add-port BRIDGE VLAN tag=VALN_ID
|
||||
# -- set interface VLAN type=internal
|
||||
args = [
|
||||
'--may-exist', 'add-port', bridge, vlan, 'tag={}'.format(vlan_tag),
|
||||
'--', 'set', 'interface', vlan, 'type=internal']
|
||||
ovn_bgp_agent.privileged.ovs_vsctl.ovs_cmd('ovs-vsctl', args)
|
||||
|
||||
|
||||
def del_flow(flow, bridge, cookie):
|
||||
cookie_id = "cookie={}/-1".format(cookie)
|
||||
f = '{},priority{}'.format(
|
||||
|
@ -142,10 +142,11 @@ class TestOVS(test_base.TestCase):
|
||||
@mock.patch.object(linux_net, 'get_ip_version')
|
||||
def _test_ensure_evpn_ovs_flow(self, mock_ip_version, mock_ofport,
|
||||
ip_version, strip_vlan=False):
|
||||
address = '172.24.200.7'
|
||||
self.fake_ndb.interfaces[self.bridge] = {'address': address}
|
||||
address = '00:00:00:00:00:00'
|
||||
mock_ip_version.return_value = ip_version
|
||||
port = 'fake-port'
|
||||
port_dst = 'fake-port-dst'
|
||||
self.fake_ndb.interfaces[port_dst] = {'address': address}
|
||||
ovs_port = constants.OVS_PATCH_PROVNET_PORT_PREFIX + 'fake-port'
|
||||
port_iface = '1'
|
||||
ovs_port_iface = '2'
|
||||
@ -156,7 +157,7 @@ class TestOVS(test_base.TestCase):
|
||||
|
||||
# Invoke the method
|
||||
ovs_utils.ensure_evpn_ovs_flow(
|
||||
self.bridge, self.cookie, self.mac, port, net,
|
||||
self.bridge, self.cookie, self.mac, port, port_dst, net,
|
||||
strip_vlan=strip_vlan)
|
||||
|
||||
mock_ip_version.assert_called_once_with(net)
|
||||
@ -202,7 +203,8 @@ class TestOVS(test_base.TestCase):
|
||||
self.mock_ovs_vsctl.ovs_cmd.return_value = [port]
|
||||
|
||||
ret = ovs_utils.ensure_evpn_ovs_flow(
|
||||
self.bridge, self.cookie, self.mac, port, 'fake-net')
|
||||
self.bridge, self.cookie, self.mac, port, 'fake-port-dst',
|
||||
'fake-net')
|
||||
|
||||
self.assertIsNone(ret)
|
||||
self.mock_ovs_vsctl.ovs_cmd.assert_called_once_with(
|
||||
|
@ -84,6 +84,17 @@ def ensure_vxlan(vxlan_name, vni, lo_ip):
|
||||
'state', constants.LINK_UP).commit()
|
||||
|
||||
|
||||
def ensure_veth(veth_name, veth_peer):
|
||||
try:
|
||||
set_device_status(veth_name, constants.LINK_UP)
|
||||
except KeyError:
|
||||
with pyroute2.NDB() as ndb:
|
||||
ndb.interfaces.create(
|
||||
kind="veth", ifname=veth_name, peer=veth_peer).set(
|
||||
'state', constants.LINK_UP).commit()
|
||||
set_device_status(veth_peer, constants.LINK_UP)
|
||||
|
||||
|
||||
def set_master_for_device(device, master):
|
||||
with pyroute2.NDB() as ndb:
|
||||
# Check if already associated to the master, and associate it if not
|
||||
@ -93,6 +104,13 @@ def set_master_for_device(device, master):
|
||||
iface.set('master', ndb.interfaces[master]['index'])
|
||||
|
||||
|
||||
def set_device_status(device, status):
|
||||
with pyroute2.NDB() as ndb:
|
||||
with ndb.interfaces[device] as dev:
|
||||
if dev['state'] != status:
|
||||
dev['state'] = status
|
||||
|
||||
|
||||
def ensure_dummy_device(device):
|
||||
with pyroute2.NDB() as ndb:
|
||||
try:
|
||||
@ -237,10 +255,9 @@ def ensure_vlan_device_for_network(bridge, vlan_tag):
|
||||
link=ndb.interfaces[bridge]['index']).set(
|
||||
'state', constants.LINK_UP).commit()
|
||||
|
||||
ipv4_flag = "net.ipv4.conf.{}/{}.proxy_arp".format(bridge, vlan_tag)
|
||||
ovn_bgp_agent.privileged.linux_net.set_kernel_flag(ipv4_flag, 1)
|
||||
ipv6_flag = "net.ipv6.conf.{}/{}.proxy_ndp".format(bridge, vlan_tag)
|
||||
ovn_bgp_agent.privileged.linux_net.set_kernel_flag(ipv6_flag, 1)
|
||||
device = "{}/{}".format(bridge, vlan_tag)
|
||||
enable_proxy_arp(device)
|
||||
enable_proxy_ndp(device)
|
||||
|
||||
|
||||
def delete_vlan_device_for_network(bridge, vlan_tag):
|
||||
@ -248,6 +265,16 @@ def delete_vlan_device_for_network(bridge, vlan_tag):
|
||||
delete_device(vlan_device_name)
|
||||
|
||||
|
||||
def enable_proxy_ndp(device):
|
||||
flag = "net.ipv6.conf.{}.proxy_ndp".format(device)
|
||||
ovn_bgp_agent.privileged.linux_net.set_kernel_flag(flag, 1)
|
||||
|
||||
|
||||
def enable_proxy_arp(device):
|
||||
flag = "net.ipv4.conf.{}.proxy_arp".format(device)
|
||||
ovn_bgp_agent.privileged.linux_net.set_kernel_flag(flag, 1)
|
||||
|
||||
|
||||
def get_exposed_ips(nic):
|
||||
exposed_ips = []
|
||||
with pyroute2.NDB() as ndb:
|
||||
@ -272,10 +299,15 @@ def get_nic_ip(nic, ip_version):
|
||||
def get_exposed_ips_on_network(nic, network):
|
||||
exposed_ips = []
|
||||
with pyroute2.NDB() as ndb:
|
||||
exposed_ips = [ip.address
|
||||
for ip in ndb.interfaces[nic].ipaddr.summary()
|
||||
if ((ip.prefixlen == 32 or ip.prefixlen == 128) and
|
||||
ipaddress.ip_address(ip.address) in network)]
|
||||
try:
|
||||
exposed_ips = [ip.address
|
||||
for ip in ndb.interfaces[nic].ipaddr.summary()
|
||||
if ((ip.prefixlen == 32 or ip.prefixlen == 128) and
|
||||
ipaddress.ip_address(ip.address) in network)]
|
||||
except KeyError:
|
||||
# Nic does not exists
|
||||
LOG.debug("Nic %s does not yet exists, so it does not have "
|
||||
"exposed IPs", nic)
|
||||
return exposed_ips
|
||||
|
||||
|
||||
@ -411,10 +443,11 @@ def del_ndp_proxy(ip, dev, vlan=None):
|
||||
|
||||
|
||||
def add_ips_to_dev(nic, ips, clear_local_route_at_table=False):
|
||||
with pyroute2.NDB() as ndb:
|
||||
already_added_ips = []
|
||||
for ip in ips:
|
||||
try:
|
||||
with ndb.interfaces[nic] as iface:
|
||||
for ip in ips:
|
||||
with pyroute2.NDB() as ndb:
|
||||
with ndb.interfaces[nic] as iface:
|
||||
address = '{}/32'.format(ip)
|
||||
if get_ip_version(ip) == constants.IP_VERSION_6:
|
||||
address = '{}/128'.format(ip)
|
||||
@ -422,16 +455,21 @@ def add_ips_to_dev(nic, ips, clear_local_route_at_table=False):
|
||||
except KeyError:
|
||||
# NDB raises KeyError: 'object exists'
|
||||
# if the ip is already added
|
||||
pass
|
||||
already_added_ips.append(ip)
|
||||
|
||||
if clear_local_route_at_table:
|
||||
with pyroute2.NDB() as ndb:
|
||||
for ip in ips:
|
||||
for ip in ips:
|
||||
with pyroute2.NDB() as ndb:
|
||||
oif = ndb.interfaces[nic]['index']
|
||||
if ip in already_added_ips:
|
||||
continue
|
||||
route = {'table': clear_local_route_at_table,
|
||||
'proto': 2,
|
||||
'scope': 254,
|
||||
'dst': ip}
|
||||
'dst': ip,
|
||||
'oif': oif}
|
||||
try:
|
||||
LOG.debug("Deleting local route: %s", route)
|
||||
with ndb.routes[route] as r:
|
||||
r.remove()
|
||||
except (KeyError, ValueError):
|
||||
@ -546,8 +584,14 @@ def del_ip_nei(ip, lladdr, dev):
|
||||
# This is doing something like:
|
||||
# sudo ip nei del 172.24.4.69
|
||||
# lladdr fa:16:3e:d3:5d:7b dev br-ex nud permanent
|
||||
network_bridge_if = iproute.link_lookup(
|
||||
ifname=dev)[0]
|
||||
try:
|
||||
network_bridge_if = iproute.link_lookup(
|
||||
ifname=dev)[0]
|
||||
except IndexError:
|
||||
# Neigbhbor device does not exists, continuing
|
||||
LOG.debug("No need to remove nei for dev %s as it does not "
|
||||
"exists", dev)
|
||||
return
|
||||
if ip_version == constants.IP_VERSION_6:
|
||||
iproute.neigh('del',
|
||||
dst=ip.split("/")[0],
|
||||
@ -607,6 +651,7 @@ def add_ip_route(ovn_routing_tables_routes, ip_address, route_table, dev,
|
||||
with ndb.routes[route] as r:
|
||||
LOG.debug("Route already existing: %s", r)
|
||||
except KeyError:
|
||||
LOG.debug("Creating route at table %s: %s", route_table, route)
|
||||
ndb.routes.create(route).commit()
|
||||
LOG.debug("Route created at table %s: %s", route_table, route)
|
||||
route_info = {'vlan': vlan, 'route': route}
|
||||
@ -631,11 +676,18 @@ def del_ip_route(ovn_routing_tables_routes, ip_address, route_table, dev,
|
||||
ip, strict=False).network_address)
|
||||
|
||||
with pyroute2.NDB() as ndb:
|
||||
if vlan:
|
||||
oif_name = '{}.{}'.format(dev, vlan)
|
||||
oif = ndb.interfaces[oif_name]['index']
|
||||
else:
|
||||
oif = ndb.interfaces[dev]['index']
|
||||
try:
|
||||
if vlan:
|
||||
oif_name = '{}.{}'.format(dev, vlan)
|
||||
oif = ndb.interfaces[oif_name]['index']
|
||||
else:
|
||||
oif = ndb.interfaces[dev]['index']
|
||||
except KeyError:
|
||||
LOG.debug("Device %s does not exists, so the associated "
|
||||
"routes should have been automatically deleted.", dev)
|
||||
if ovn_routing_tables_routes.get(dev):
|
||||
del ovn_routing_tables_routes[dev]
|
||||
return
|
||||
|
||||
route = {'dst': net_ip, 'dst_len': int(mask), 'oif': oif,
|
||||
'table': int(route_table), 'proto': 3}
|
||||
@ -649,6 +701,7 @@ def del_ip_route(ovn_routing_tables_routes, ip_address, route_table, dev,
|
||||
|
||||
with pyroute2.NDB() as ndb:
|
||||
try:
|
||||
LOG.debug("Deleting route at table %s: %s", route_table, route)
|
||||
with ndb.routes[route] as r:
|
||||
r.remove()
|
||||
LOG.debug("Route deleted at table %s: %s", route_table, route)
|
||||
|
Loading…
x
Reference in New Issue
Block a user