NETVIRT-1519: MIP entry duplicated in FIB 75/78075/7
authorAnil Kumar Gujele <anilkumar.g@altencalsoftlabs.com>
Fri, 23 Nov 2018 03:50:11 +0000 (09:20 +0530)
committerSam Hague <shague@redhat.com>
Sat, 26 Jan 2019 13:24:59 +0000 (13:24 +0000)
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 <anilkumar.g@altencalsoftlabs.com>
neutronvpn/api/src/main/yang/neutronvpn.yang
neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnManager.java
neutronvpn/impl/src/main/java/org/opendaylight/netvirt/neutronvpn/NeutronvpnUtils.java
vpnmanager/api/src/main/yang/vpn-rpc.yang
vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/SubnetRoutePacketInHandler.java
vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnInterfaceManager.java
vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnRpcServiceImpl.java
vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/VpnUtil.java
vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/AbstractIpLearnNotificationHandler.java
vpnmanager/impl/src/main/java/org/opendaylight/netvirt/vpnmanager/iplearn/LearntVpnVipToPortEventProcessor.java
vpnmanager/impl/src/main/yang/vpnmanager-config.yang

index a23213005b6d1002b47152afc7a3036e88ac5193..8753f191ec8ada6f3364b02c625e30dcc7492826 100644 (file)
@@ -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;}
         }
     }
 
index 65713f693d850a86a1870f67d263d1d890365fa8..003b21ce023b812ea60340e845343025f3098f88 100644 (file)
@@ -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<LearntVpnVipToPort> 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<VpnPortipToPort> build =
+                                neutronvpnUtils.buildVpnPortipToPortIdentifier(oldVpnId.getValue(), mipToQuery);
+                        Optional<VpnPortipToPort> 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());
                         }
                     }
index 3dbf3856a94274294d15b6c39ab389d840aece41..d1c512c56250d338eb974cb09661fa882450a8d8 100644 (file)
@@ -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,
index 1b83391df07bc150597398a7c4a088dc6b04fb24..abf0080929a9dc93da549e4b6674d19087715ddc 100644 (file)
@@ -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;
+            }
+        }
+    }    
 }
index af434338918e64e4709a8e27ac5ca2331b605647..fa9443403e7f9c9471b52480012b0658f0ede383 100644 (file)
@@ -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,
index 1070411cb70ca3ef0b1926c5ad33aac07bced107..cbdc4f542d875e3f4aaac7401aeef906159c55a5 100755 (executable)
@@ -1384,6 +1384,7 @@ public class VpnInterfaceManager extends AsyncDataTreeChangeListenerBase<VpnInte
             InstanceIdentifier<AdjacenciesOp> path = identifier.augmentation(AdjacenciesOp.class);
             Optional<AdjacenciesOp> 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<VpnInte
                         List<String> 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<VpnInte
                     }
                     String ip = nextHop.getIpAddress().split("/")[0];
                     LearntVpnVipToPort vpnVipToPort = vpnUtil.getLearntVpnVipToPort(vpnName, ip);
-                    if (vpnVipToPort != null) {
+                    if (vpnVipToPort != null && vpnVipToPort.getPortName().equals(interfaceName)) {
                         vpnUtil.removeLearntVpnVipToPort(vpnName, ip, null);
                         LOG.info("removeAdjacenciesFromVpn: VpnInterfaceManager removed LearntVpnVipToPort entry"
                                  + " for Interface {} ip {} on dpn {} for vpn {}",
                                 vpnVipToPort.getPortName(), ip, dpnId, vpnName);
                     }
+                    // Remove the MIP-IP from VpnPortIpToPort.
+                    if (isNonPrimaryAdjIp) {
+                        VpnPortipToPort persistedIp = vpnUtil.getVpnPortipToPort(vpnName, ip);
+                        if (persistedIp != null && persistedIp.isLearntIp()
+                                && persistedIp.getPortName().equals(interfaceName)) {
+                            VpnUtil.removeVpnPortFixedIpToPort(dataBroker, vpnName, ip, null);
+                            LOG.info(
+                                    "removeAdjacenciesFromVpn: Learnt-IP: {} interface {} of vpn {} removed "
+                                            + "from VpnPortipToPort",
+                                    persistedIp.getPortFixedip(), persistedIp.getPortName(), vpnName);
+                        }
+                    }
                     VpnPortipToPort vpnPortipToPort = vpnUtil.getNeutronPortFromVpnPortFixedIp(vpnName, ip);
                     if (vpnPortipToPort != null) {
                         VpnUtil.removeVpnPortFixedIpToPort(dataBroker, vpnName, ip, null);
index 941a0223adf1c23de53de7a2a54f7db32289bd02..1b5fd38b30f7fca15bf55eeec945fd116a928124 100644 (file)
@@ -34,6 +34,9 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.AddStaticRouteInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.AddStaticRouteOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.AddStaticRouteOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.ApplyArpConfigInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.ApplyArpConfigOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.ApplyArpConfigOutputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.GenerateVpnLabelInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.GenerateVpnLabelOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.rpc.rev160201.GenerateVpnLabelOutputBuilder;
@@ -276,4 +279,16 @@ public class VpnRpcServiceImpl implements VpnRpcService {
         logger.accept(message);
         return message;
     }
+
+    @Override
+    public ListenableFuture<RpcResult<ApplyArpConfigOutput>> applyArpConfig(ApplyArpConfigInput input) {
+        Boolean isArpLearningEnabled = input.isEnableArpLearning();
+        LOG.info("isArpLearningEnabled {}", isArpLearningEnabled);
+        SettableFuture<RpcResult<ApplyArpConfigOutput>> result = SettableFuture.create();
+        ApplyArpConfigOutputBuilder output = new ApplyArpConfigOutputBuilder();
+        VpnUtil.enableArpLearning(isArpLearningEnabled);
+        output.setEnableArpLearning(VpnUtil.isArpLearningEnabled());
+        result.set(RpcResultBuilder.success(output).build());
+        return result;
+    }
 }
index cccf099d085700e51aa04a38d72b379510e8f9ab..856d02eff739dc3429ffa9d27cf7c2698bcb1e34 100644 (file)
@@ -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<LearntVpnVipToPort> 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<VpnInterfaceOpDataEntry> vpnInterfaceOpId = VpnUtil
-                    .getVpnInterfaceOpDataEntryIdentifier(vpnInterface, vpnName);
-            InstanceIdentifier<AdjacenciesOp> path = vpnInterfaceOpId.augmentation(AdjacenciesOp.class);
-            Optional<AdjacenciesOp> adjacenciesOp = read(LogicalDatastoreType.OPERATIONAL, path);
-            if (adjacenciesOp.isPresent()) {
-                InstanceIdentifier<Adjacency> 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<VpnInterfaceOpDataEntry> vpnInterfaceOpId = VpnUtil
+                        .getVpnInterfaceOpDataEntryIdentifier(vpnInterface, vpnName);
+                InstanceIdentifier<AdjacenciesOp> path = vpnInterfaceOpId.augmentation(AdjacenciesOp.class);
+                Optional<AdjacenciesOp> adjacenciesOp = read(LogicalDatastoreType.OPERATIONAL, path);
+                if (adjacenciesOp.isPresent()) {
+                    InstanceIdentifier<Adjacency> 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<LearntVpnVipToPort> 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<VpnPortipToPort> 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<VpnPortipToPort> id = buildVpnPortipToPortIdentifier(vpnName, fixedIp);
+        Optional<VpnPortipToPort> 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;
+    }
 }
index 8f24c70145eb544f1b7921883ae4853b7990ed65..3b45c85d895bdde79b8be1456cd716dc6f86fa6b 100644 (file)
@@ -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);
             }
         }
     }
index 948b3daeed0ec46b3769d74f6bb61f74592530d1..ad34ef92e0eb2427cbea776e4219db861c7c80f7 100644 (file)
@@ -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);
                 }));
         }
index 8541379cd9e89c909698aa49873a3266c8b3f78f..fc8430b1747bf84f6486e02e319364fc13685bc3 100644 (file)
@@ -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";