From: Anil Kumar Gujele Date: Fri, 23 Nov 2018 03:50:11 +0000 (+0530) Subject: NETVIRT-1519: MIP entry duplicated in FIB X-Git-Tag: release/neon~25 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=20e95ffe3b23fb50b15401fa9d6e3f597ff3d126;p=netvirt.git NETVIRT-1519: MIP entry duplicated in FIB Issue: VRRP is configured for MIP-IP on two VMs. After cluster reboot, VRRP-standby VM becomes a master for fraction of second and triggers many GARP request for MIP-IP to controller. In controller ARP NotificationHandler , when GARP is received , still learntVpnIpToPort is not populated.Hence new adjacency gets added onto secondary VM's VpnInterface without deleting the adj on original master VM's VpnInterface. When cluster reboot completes , Arp Responses are received from original master VM. But LearntVpnIpToPort has MIP-IP with secondary VM portname. Now MIP-IP is removed from secondary VM's VpnInterface Adj and FIB is also updated. Route pointing to nexthop as secondary VM is withdrawn from DC-GW. Finally traffic starts dropping. Fixes: To know who held the MIP-IP before cluster reboot, its decided to persist the MIP-IP info. Hence VpnIpToPort config datastore is chosen to store the info. After cluster reboot, when flood of GARP is received, it will be checked if already an entry is present in VpnIpToPort. If present and GARP is from different VM Interface, oldPort entry is removed. During cluster reboot, due to an ELAN bug in its pipelines, both the Master and Slave VRRP VNFs go into Split brain and both of them own the same MIP and respond to ARP requests as though they are both legitimate owners. In order for this to not cause any damage to L3VPN MIP FIB entry, we have introduced a quiescent ("quiet period") of 300 seconds i.e., 5 minutes within the ARP NotificationHandler as soon as its Constructed. During this quiescent period, we will not be permitting re-learning of any existing MIPs. This quiescent period for each existing MIPs (ie., MIPs that were learnt before reboot) is to resolve their split brain issues and thereby settle down between themselves to one Master. After the quiescent period is over, we allow re-learning of all these existing MIPs. Please note that the quiescent period is applicable for only existing MIPs (that were prior to cluster reboot/cluster upgrade) and so the period is not applicable for learning new MIPs. New MIPs can be learned instantly after the cluster reboot completes. We have introduced boot-delay-arp-learning parameter in the VpnConfig for use by the controller orchestrator. The boot-delay-arp-learning parameter controls for how much time after bootup, should the arp-learning be made quiescent in seconds. Change-Id: I2985480050cb0b8ee8434cced0074abb7a05a5cd Signed-off-by: Anil Kumar Gujele --- diff --git a/neutronvpn/api/src/main/yang/neutronvpn.yang b/neutronvpn/api/src/main/yang/neutronvpn.yang index a23213005b..8753f191ec 100644 --- a/neutronvpn/api/src/main/yang/neutronvpn.yang +++ b/neutronvpn/api/src/main/yang/neutronvpn.yang @@ -205,6 +205,7 @@ module neutronvpn { leaf port-name { type string;} leaf mac-address { type string;} leaf subnet-ip { type boolean;} + leaf learnt-ip { type boolean; default false;} } } diff --git a/neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnManager.java b/neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnManager.java index 65713f693d..003b21ce02 100644 --- a/neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnManager.java +++ b/neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnManager.java @@ -141,6 +141,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev15060 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.createl3vpn.input.L3vpn; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.getl3vpn.output.L3vpnInstances; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.getl3vpn.output.L3vpnInstancesBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPort; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.router.interfaces.map.RouterInterfaces; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.router.interfaces.map.RouterInterfacesKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.router.interfaces.map.router.interfaces.Interfaces; @@ -1124,13 +1125,23 @@ public class NeutronvpnManager implements NeutronvpnService, AutoCloseable, Even Optional optionalVpnVipToPort = SingleTransactionDataBroker.syncReadOptional(dataBroker, LogicalDatastoreType.OPERATIONAL, id); - if (optionalVpnVipToPort.isPresent()) { + if (optionalVpnVipToPort.isPresent() + && optionalVpnVipToPort.get().getPortName().equals(infName)) { LOG.trace("Removing adjacencies from vpninterface {} upon dissociation of router {} " + "from VPN {}", infName, vpnId, oldVpnId); adjacencyIter.remove(); neutronvpnUtils.removeLearntVpnVipToPort(oldVpnId.getValue(), mipToQuery); LOG.trace( - "Entry for fixedIP {} for port {} on VPN {} removed from VpnPortFixedIPToPortData", + "Entry for fixedIP {} for port {} on VPN {} removed from LearntVpnVipToPort", + mipToQuery, infName, vpnId.getValue()); + } + InstanceIdentifier build = + neutronvpnUtils.buildVpnPortipToPortIdentifier(oldVpnId.getValue(), mipToQuery); + Optional persistedIp = SingleTransactionDataBroker.syncReadOptional(dataBroker, + LogicalDatastoreType.OPERATIONAL, build); + if (persistedIp.isPresent() && persistedIp.get().getPortName().equals(infName)) { + neutronvpnUtils.removeVpnPortFixedIpToPort(oldVpnId.getValue(), mipToQuery, null); + LOG.trace("Entry for fixedIP {} for port {} on VPN {} removed from VpnPortipToPort", mipToQuery, infName, vpnId.getValue()); } } diff --git a/neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnUtils.java b/neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnUtils.java index 3dbf3856a9..d1c512c562 100644 --- a/neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnUtils.java +++ b/neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnUtils.java @@ -909,7 +909,7 @@ public class NeutronvpnUtils { } else { MDSALUtil.syncDelete(dataBroker, LogicalDatastoreType.CONFIGURATION, id); } - LOG.trace("Neutron router port with fixedIp: {}, vpn {} removed from LearntVpnPortipToPort DS", fixedIp, + LOG.trace("Neutron router port with fixedIp: {}, vpn {} removed from VpnPortipToPort DS", fixedIp, vpnName); } catch (Exception e) { LOG.error("Failure while removing VPNPortFixedIpToPort map for vpn {} - fixedIP {}", vpnName, fixedIp, diff --git a/vpnmanager/api/src/main/yang/vpn-rpc.yang b/vpnmanager/api/src/main/yang/vpn-rpc.yang index 1b83391df0..abf0080929 100644 --- a/vpnmanager/api/src/main/yang/vpn-rpc.yang +++ b/vpnmanager/api/src/main/yang/vpn-rpc.yang @@ -87,4 +87,19 @@ module vpn-rpc { } } } + + rpc apply-arp-config { + description "To apply ARP/GARP related configuration per PL"; + input { + leaf enable-arp-learning { + description "Enable (or) Disable arp based learning dynamically"; + type boolean; + } + } + output { + leaf enable-arp-learning { + type boolean; + } + } + } } diff --git a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/SubnetRoutePacketInHandler.java b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/SubnetRoutePacketInHandler.java index af43433891..fa9443403e 100644 --- a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/SubnetRoutePacketInHandler.java +++ b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/SubnetRoutePacketInHandler.java @@ -56,6 +56,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.Routers; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.routers.ExternalIps; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.networkmaps.NetworkMap; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPort; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.subnetmaps.Subnetmap; import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener; import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService; @@ -97,6 +98,10 @@ public class SubnetRoutePacketInHandler implements PacketProcessingListener { short tableId = notification.getTableId().getValue(); LOG.trace("{} onPacketReceived: Packet punted from table {}", LOGGING_PREFIX, tableId); + if (!vpnUtil.isArpLearningEnabled()) { + LOG.trace("Not handling packet as ARP Based Learning is disabled"); + return; + } byte[] data = notification.getPayload(); if (notification.getMatch() == null || notification.getMatch().getMetadata() == null) { LOG.error("{} onPacketReceived: Received from table {} where the match or metadata are null", @@ -183,7 +188,9 @@ public class SubnetRoutePacketInHandler implements PacketProcessingListener { } String vpnIdVpnInstanceName = vpnIdsOptional.get().getVpnInstanceName(); - if (vpnUtil.getNeutronPortFromVpnPortFixedIp(vpnIdVpnInstanceName, dstIpStr) != null) { + VpnPortipToPort persistedIP = + vpnUtil.getNeutronPortFromVpnPortFixedIp(vpnIdVpnInstanceName, dstIpStr); + if (persistedIP != null && !persistedIP.isLearntIp()) { vpnManagerCounters.subnetRoutePacketIgnored(); LOG.info("{} onPacketReceived: IP Packet received with Target IP {} source IP {} vpnId {} " + "is a valid Neutron port,ignoring subnet route processing", LOGGING_PREFIX, dstIpStr, diff --git a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnInterfaceManager.java b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnInterfaceManager.java index 1070411cb7..cbdc4f542d 100755 --- a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnInterfaceManager.java +++ b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnInterfaceManager.java @@ -1384,6 +1384,7 @@ public class VpnInterfaceManager extends AsyncDataTreeChangeListenerBase path = identifier.augmentation(AdjacenciesOp.class); Optional adjacencies = SingleTransactionDataBroker.syncReadOptional(dataBroker, LogicalDatastoreType.OPERATIONAL, path); + boolean isNonPrimaryAdjIp = Boolean.FALSE; String primaryRd = vpnUtil.getVpnRd(vpnName); LOG.info("removeAdjacenciesFromVpn: For interface {} on dpn {} RD recovered for vpn {} as rd {}", interfaceName, dpnId, vpnName, primaryRd); @@ -1403,6 +1404,7 @@ public class VpnInterfaceManager extends AsyncDataTreeChangeListenerBase nhList; if (nextHop.getAdjacencyType() != AdjacencyType.PrimaryAdjacency) { nhList = getNextHopForNonPrimaryAdjacency(nextHop, vpnName, dpnId, interfaceName); + isNonPrimaryAdjIp = Boolean.TRUE; } else { // This is a primary adjacency nhList = nextHop.getNextHopIpList() != null ? nextHop.getNextHopIpList() @@ -1430,12 +1432,24 @@ public class VpnInterfaceManager extends AsyncDataTreeChangeListenerBase> applyArpConfig(ApplyArpConfigInput input) { + Boolean isArpLearningEnabled = input.isEnableArpLearning(); + LOG.info("isArpLearningEnabled {}", isArpLearningEnabled); + SettableFuture> result = SettableFuture.create(); + ApplyArpConfigOutputBuilder output = new ApplyArpConfigOutputBuilder(); + VpnUtil.enableArpLearning(isArpLearningEnabled); + output.setEnableArpLearning(VpnUtil.isArpLearningEnabled()); + result.set(RpcResultBuilder.success(output).build()); + return result; + } } diff --git a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnUtil.java b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnUtil.java index cccf099d08..856d02eff7 100644 --- a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnUtil.java +++ b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnUtil.java @@ -227,6 +227,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev15060 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.networkmaps.NetworkMap; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.networkmaps.NetworkMapKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPort; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPortBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPortKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.subnetmaps.Subnetmap; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.subnetmaps.SubnetmapKey; @@ -254,6 +255,7 @@ public final class VpnUtil { private static final Logger LOG = LoggerFactory.getLogger(VpnUtil.class); public static final int SINGLE_TRANSACTION_BROKER_NO_RETRY = 1; + private static Boolean arpLearningEnabled = Boolean.TRUE; private final DataBroker dataBroker; private final IdManagerService idManager; @@ -1018,25 +1020,33 @@ public final class VpnUtil { } + // TODO Clean up the exception handling + @SuppressWarnings("checkstyle:IllegalCatch") public void removeMipAdjAndLearntIp(String vpnName, String vpnInterface, String prefix) { synchronized ((vpnName + prefix).intern()) { - InstanceIdentifier id = buildLearntVpnVipToPortIdentifier(vpnName, prefix); - MDSALUtil.syncDelete(dataBroker, LogicalDatastoreType.OPERATIONAL, id); - LOG.info("removeMipAdjAndLearntIp: Delete learned ARP for fixedIp: {}, vpn {} removed from" - + "VpnPortipToPort DS", prefix, vpnName); - String ip = VpnUtil.getIpPrefix(prefix); - InstanceIdentifier vpnInterfaceOpId = VpnUtil - .getVpnInterfaceOpDataEntryIdentifier(vpnInterface, vpnName); - InstanceIdentifier path = vpnInterfaceOpId.augmentation(AdjacenciesOp.class); - Optional adjacenciesOp = read(LogicalDatastoreType.OPERATIONAL, path); - if (adjacenciesOp.isPresent()) { - InstanceIdentifier adjacencyIdentifier = InstanceIdentifier.builder(VpnInterfaces.class) - .child(VpnInterface.class, new VpnInterfaceKey(vpnInterface)).augmentation(Adjacencies.class) - .child(Adjacency.class, new AdjacencyKey(ip)).build(); - MDSALUtil.syncDelete(dataBroker, LogicalDatastoreType.CONFIGURATION, adjacencyIdentifier); - LOG.info("removeMipAdjAndLearntIp: Successfully Deleted Adjacency {} from interface {} vpn {}", ip, - vpnInterface, vpnName); + try { + String ip = VpnUtil.getIpPrefix(prefix); + InstanceIdentifier vpnInterfaceOpId = VpnUtil + .getVpnInterfaceOpDataEntryIdentifier(vpnInterface, vpnName); + InstanceIdentifier path = vpnInterfaceOpId.augmentation(AdjacenciesOp.class); + Optional adjacenciesOp = read(LogicalDatastoreType.OPERATIONAL, path); + if (adjacenciesOp.isPresent()) { + InstanceIdentifier adjacencyIdentifier = InstanceIdentifier.builder(VpnInterfaces.class) + .child(VpnInterface.class, new VpnInterfaceKey(vpnInterface)) + .augmentation(Adjacencies.class).child(Adjacency.class, new AdjacencyKey(ip)).build(); + MDSALUtil.syncDelete(dataBroker, LogicalDatastoreType.CONFIGURATION, adjacencyIdentifier); + LOG.info("removeMipAdjAndLearntIp: Successfully Deleted Adjacency {} from interface {} vpn {}", ip, + vpnInterface, vpnName); + } + InstanceIdentifier id = buildLearntVpnVipToPortIdentifier(vpnName, prefix); + MDSALUtil.syncDelete(dataBroker, LogicalDatastoreType.OPERATIONAL, id); + LOG.info("removeMipAdjAndLearntIp: Delete learned ARP for fixedIp: {}, vpn {} removed from" + + "VpnPortipToPort DS", prefix, vpnName); + } catch (Exception e) { + LOG.error("removeMipAdjAndLearntIp: Exception Deleting learned Ip: {} interface {} vpn {} from " + + "LearntVpnPortipToPort DS", prefix, vpnInterface, vpnName, e); } + VpnUtil.removeVpnPortFixedIpToPort(dataBroker, vpnName, prefix, null); } } @@ -2363,4 +2373,45 @@ public final class VpnUtil { return oldVpnListCopy.size() == 2 && newVpnListCopy.size() == 3 || oldVpnListCopy.size() == 3 && newVpnListCopy.size() == 2; } + + // TODO Clean up the exception handling + @SuppressWarnings("checkstyle:IllegalCatch") + public void createVpnPortFixedIpToPort(String vpnName, String fixedIp, + String portName, boolean isLearntIp, String macAddress, + WriteTransaction writeConfigTxn) { + InstanceIdentifier id = buildVpnPortipToPortIdentifier(vpnName, fixedIp); + VpnPortipToPortBuilder builder = new VpnPortipToPortBuilder().withKey(new VpnPortipToPortKey(fixedIp, vpnName)) + .setVpnName(vpnName).setPortFixedip(fixedIp).setPortName(portName) + .setLearntIp(isLearntIp).setSubnetIp(false).setMacAddress(macAddress.toLowerCase(Locale.getDefault())); + try { + if (writeConfigTxn != null) { + writeConfigTxn.put(LogicalDatastoreType.CONFIGURATION, id, builder.build()); + } else { + syncWrite(LogicalDatastoreType.CONFIGURATION, id, builder.build()); + } + LOG.trace("Port with Ip: {}, vpn {}, interface {}, learntIp {} added to VpnPortipToPort DS", + fixedIp, vpnName, portName, isLearntIp); + } catch (Exception e) { + LOG.error("Failure while creating VpnPortIpToPort map for vpn {} learnIp{}", vpnName, fixedIp, e); + } + } + + protected VpnPortipToPort getVpnPortipToPort(String vpnName, String fixedIp) { + InstanceIdentifier id = buildVpnPortipToPortIdentifier(vpnName, fixedIp); + Optional vpnPortipToPortData = read(LogicalDatastoreType.CONFIGURATION, id); + if (vpnPortipToPortData.isPresent()) { + return vpnPortipToPortData.get(); + } + LOG.error("getVpnPortipToPort: Failed as vpnPortipToPortData DS is absent for VPN {} and fixed IP {}", + vpnName, fixedIp); + return null; + } + + public static void enableArpLearning(Boolean isArpLearningEnabled) { + arpLearningEnabled = isArpLearningEnabled; + } + + public static Boolean isArpLearningEnabled() { + return arpLearningEnabled; + } } diff --git a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/AbstractIpLearnNotificationHandler.java b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/AbstractIpLearnNotificationHandler.java index 8f24c70145..3b45c85d89 100644 --- a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/AbstractIpLearnNotificationHandler.java +++ b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/AbstractIpLearnNotificationHandler.java @@ -44,6 +44,7 @@ public abstract class AbstractIpLearnNotificationHandler { protected final VpnConfig config; protected final VpnUtil vpnUtil; protected final INeutronVpnManager neutronVpnManager; + private long bootupTime = 0L; public AbstractIpLearnNotificationHandler(VpnConfig vpnConfig, VpnUtil vpnUtil, INeutronVpnManager neutronVpnManager) { @@ -56,6 +57,7 @@ public abstract class AbstractIpLearnNotificationHandler { migrateIpCache = CacheBuilder.newBuilder().maximumSize(cacheSize).expireAfterWrite(duration, TimeUnit.MILLISECONDS).build(); + this.bootupTime = System.currentTimeMillis(); } protected void validateAndProcessIpLearning(String srcInterface, IpAddress srcIP, MacAddress srcMac, @@ -89,6 +91,10 @@ public abstract class AbstractIpLearnNotificationHandler { protected void processIpLearning(String srcInterface, IpAddress srcIP, MacAddress srcMac, BigInteger metadata, IpAddress dstIP) { + if (!VpnUtil.isArpLearningEnabled()) { + LOG.trace("Not handling packet as ARP Based Learning is disabled"); + return; + } if (metadata == null || Objects.equals(metadata, BigInteger.ZERO)) { return; } @@ -103,53 +109,71 @@ public abstract class AbstractIpLearnNotificationHandler { String srcIpToQuery = srcIP.stringValue(); String destIpToQuery = dstIP.stringValue(); for (String vpnName : vpnList.get()) { - LOG.info("Received ARP/NA for sender MAC {} and sender IP {} via interface {}", - srcMac.getValue(), srcIpToQuery, srcInterface); - VpnPortipToPort vpnPortipToPort = - vpnUtil.getNeutronPortFromVpnPortFixedIp(vpnName, srcIpToQuery); - // Check if this IP belongs to external network - if (vpnPortipToPort == null) { - String extSubnetId = vpnUtil.getAssociatedExternalSubnet(srcIpToQuery); - if (extSubnetId != null) { - vpnPortipToPort = - vpnUtil.getNeutronPortFromVpnPortFixedIp(extSubnetId, srcIpToQuery); - } - } - if (vpnPortipToPort != null) { - /* This is a well known neutron port and so should be ignored - * from being discovered...unless it is an Octavia VIP - */ - String portName = vpnPortipToPort.getPortName(); - Port neutronPort = neutronVpnManager.getNeutronPort(portName); - - if (neutronPort == null) { - LOG.warn("{} should have been a neutron port but could not retrieve it. Aborting processing", - portName); - continue; + LOG.info("Received ARP/NA for sender MAC {} and sender IP {} via interface {}", srcMac.getValue(), + srcIpToQuery, srcInterface); + synchronized ((vpnName + srcIpToQuery).intern()) { + VpnPortipToPort vpnPortipToPort = vpnUtil.getNeutronPortFromVpnPortFixedIp(vpnName, srcIpToQuery); + // Check if this IP belongs to external network + if (vpnPortipToPort == null) { + String extSubnetId = vpnUtil.getAssociatedExternalSubnet(srcIpToQuery); + if (extSubnetId != null) { + vpnPortipToPort = + vpnUtil.getNeutronPortFromVpnPortFixedIp(extSubnetId, srcIpToQuery); + } } + if (vpnPortipToPort != null && !vpnPortipToPort.isLearntIp()) { + /* + * This is a well known neutron port and so should be ignored from being + * discovered...unless it is an Octavia VIP + */ + String portName = vpnPortipToPort.getPortName(); + Port neutronPort = neutronVpnManager.getNeutronPort(portName); + + if (neutronPort == null) { + LOG.warn("{} should have been a neutron port but could not retrieve it. Aborting processing", + portName); + continue; + } - if (!"Octavia".equals(neutronPort.getDeviceOwner())) { - LOG.debug("Neutron port {} is not an Octavia port, ignoring", portName); - continue; + if (!"Octavia".equals(neutronPort.getDeviceOwner())) { + LOG.debug("Neutron port {} is not an Octavia port, ignoring", portName); + continue; + } } - } - - LearntVpnVipToPort learntVpnVipToPort = vpnUtil.getLearntVpnVipToPort(vpnName, srcIpToQuery); - if (learntVpnVipToPort != null) { - String oldPortName = learntVpnVipToPort.getPortName(); - String oldMac = learntVpnVipToPort.getMacAddress(); - if (!oldMac.equalsIgnoreCase(srcMac.getValue())) { - //MAC has changed for requested IP - LOG.info("ARP/NA Source IP/MAC data modified for IP {} with MAC {} and Port {}", - srcIpToQuery, srcMac, srcInterface); - synchronized ((vpnName + srcIpToQuery).intern()) { - vpnUtil.createLearntVpnVipToPortEvent(vpnName, srcIpToQuery, destIpToQuery, - oldPortName, oldMac, LearntVpnVipToPortEventAction.Delete, null); + // For IPs learnt before cluster-reboot/upgrade, GARP/ArpResponse is received + // within 300sec + // after reboot, it would be ignored. + if (vpnPortipToPort != null && vpnPortipToPort.isLearntIp()) { + if (System.currentTimeMillis() < this.bootupTime + config.getBootDelayArpLearning() * 1000) { + LOG.trace("GARP/Arp Response not handled for IP {} vpnName {} for time {}s", + vpnPortipToPort.getPortFixedip(), vpnName, config.getBootDelayArpLearning()); + continue; + } + } + LearntVpnVipToPort learntVpnVipToPort = vpnUtil.getLearntVpnVipToPort(vpnName, srcIpToQuery); + if (learntVpnVipToPort != null) { + String oldPortName = learntVpnVipToPort.getPortName(); + String oldMac = learntVpnVipToPort.getMacAddress(); + if (!oldMac.equalsIgnoreCase(srcMac.getValue())) { + // MAC has changed for requested IP + LOG.info("ARP/NA Source IP/MAC data modified for IP {} with MAC {} and Port {}", srcIpToQuery, + srcMac, srcInterface); + vpnUtil.createLearntVpnVipToPortEvent(vpnName, srcIpToQuery, destIpToQuery, oldPortName, oldMac, + LearntVpnVipToPortEventAction.Delete, null); putVpnIpToMigrateIpCache(vpnName, srcIpToQuery, srcMac); } + } else if (!isIpInMigrateCache(vpnName, srcIpToQuery)) { + if (vpnPortipToPort != null && !vpnPortipToPort.getPortName().equals(srcInterface)) { + LOG.trace( + "LearntIp: {} vpnName {} is already present in VpnPortIpToPort with " + "PortName {} ", + srcIpToQuery, vpnName, vpnPortipToPort.getPortName()); + vpnUtil.createLearntVpnVipToPortEvent(vpnName, srcIpToQuery, destIpToQuery, + vpnPortipToPort.getPortName(), vpnPortipToPort.getMacAddress(), + LearntVpnVipToPortEventAction.Delete, null); + continue; + } + learnMacFromIncomingPacket(vpnName, srcInterface, srcIP, srcMac, dstIP); } - } else if (!isIpInMigrateCache(vpnName, srcIpToQuery)) { - learnMacFromIncomingPacket(vpnName, srcInterface, srcIP, srcMac, dstIP); } } } diff --git a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/LearntVpnVipToPortEventProcessor.java b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/LearntVpnVipToPortEventProcessor.java index 948b3daeed..ad34ef92e0 100644 --- a/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/LearntVpnVipToPortEventProcessor.java +++ b/vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/LearntVpnVipToPortEventProcessor.java @@ -169,6 +169,8 @@ public class LearntVpnVipToPortEventProcessor return Collections.singletonList(txRunner.callWithNewWriteOnlyTransactionAndSubmit( Datastore.OPERATIONAL, operTx -> { addMipAdjacency(vpnName, interfaceName, srcIpAddress, macAddress, destIpAddress); + vpnUtil.createVpnPortFixedIpToPort(vpnName, srcIpAddress, + interfaceName, Boolean.TRUE, macAddress, null); vpnUtil.createLearntVpnVipToPort(vpnName, srcIpAddress, interfaceName, macAddress, operTx); })); } diff --git a/vpnmanager/impl/src/main/yang/vpnmanager-config.yang b/vpnmanager/impl/src/main/yang/vpnmanager-config.yang index 8541379cd9..fc8430b174 100644 --- a/vpnmanager/impl/src/main/yang/vpnmanager-config.yang +++ b/vpnmanager/impl/src/main/yang/vpnmanager-config.yang @@ -23,6 +23,11 @@ module vpn-config { type uint32; default 2000; } + leaf boot-delay-arp-learning { + description "Boot delay (in seconds) to be enforced for arp learning"; + type uint32; + default 300; + } leaf subnet-route-punt-timeout { description "hard timeout value for learnt flows for subnet route punts (unit - seconds). To turn off the rate limiting and installation of learnt flows, it should be set to 0";