CORD-546 Push L3 unicast rules for bgp peers when they are learned

- Change L3 unicast group id/key generation to include src MAC
- Note: Only flows are removed when a peer is gone
  since the group may still be referenced by routes announced by peer.
  It does no harm even if the group is not referenced.
- Note: We assume that peer does not move or update IP

Also fix several SR/VR integration issues, including
- Do not push broadcast group for /32

Change-Id: Ifb03601f5341f8b7717ea1fbccbc569b07f66476
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java b/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
index 54a6966..b85f79f 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
@@ -16,19 +16,25 @@
 
 package org.onosproject.routing.impl;
 
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Maps;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.EthType;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
@@ -45,15 +51,23 @@
 import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.flowobjective.ForwardingObjective;
 import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.routing.RoutingService;
 import org.onosproject.routing.config.RouterConfig;
 import org.slf4j.Logger;
 
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 
 import static org.slf4j.LoggerFactory.getLogger;
+import static com.google.common.base.Preconditions.checkState;
 
 /**
  * Manages connectivity between peers redirecting control traffic to a routing
@@ -64,7 +78,8 @@
 
     private final Logger log = getLogger(getClass());
 
-    private static final int PRIORITY = 40001;
+    private static final int MIN_IP_PRIORITY = 10;
+    private static final int ACL_PRIORITY = 40001;
     private static final int OSPF_IP_PROTO = 0x59;
 
     private static final String APP_NAME = "org.onosproject.cpredirect";
@@ -73,6 +88,7 @@
     private ConnectPoint controlPlaneConnectPoint;
     private boolean ospfEnabled = false;
     private List<String> interfaces = Collections.emptyList();
+    private Map<Host, Set<Integer>> peerNextId = Maps.newConcurrentMap();
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
@@ -89,9 +105,16 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected NetworkConfigService networkConfigService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
     private final InternalDeviceListener deviceListener = new InternalDeviceListener();
     private final InternalNetworkConfigListener networkConfigListener =
             new InternalNetworkConfigListener();
+    private final InternalHostListener hostListener = new InternalHostListener();
 
     @Activate
     public void activate() {
@@ -99,6 +122,7 @@
 
         deviceService.addListener(deviceListener);
         networkConfigService.addListener(networkConfigListener);
+        hostService.addListener(hostListener);
 
         updateConfig();
     }
@@ -107,6 +131,7 @@
     public void deactivate() {
         deviceService.removeListener(deviceListener);
         networkConfigService.removeListener(networkConfigListener);
+        hostService.removeListener(hostListener);
     }
 
     private void updateConfig() {
@@ -309,7 +334,7 @@
             fobBuilder.nextStep(nextId);
         }
         fobBuilder.fromApp(appId)
-            .withPriority(PRIORITY)
+            .withPriority(ACL_PRIORITY)
             .withFlag(ForwardingObjective.Flag.VERSATILE);
 
         return add ? fobBuilder.add() : fobBuilder.remove();
@@ -366,4 +391,149 @@
             }
         }
     }
+
+    /**
+     * Listener for host events.
+     */
+    private class InternalHostListener implements HostListener {
+        private void peerAdded(HostEvent event) {
+            Host peer = event.subject();
+            Optional<Interface> peerIntf =
+                    interfaceService.getInterfacesByPort(peer.location()).stream()
+                    .filter(intf -> interfaces.isEmpty() || interfaces.contains(intf.name()))
+                    .filter(intf -> peer.vlan().equals(intf.vlan()))
+                    .findFirst();
+            if (!peerIntf.isPresent()) {
+                log.debug("Adding peer {}/{} on {} but the interface is not configured",
+                        peer.mac(), peer.vlan(), peer.location());
+                return;
+            }
+
+            // Generate L3 Unicast groups and store it in the map
+            int toRouterL3Unicast = createPeerGroup(peer.mac(), peerIntf.get().mac(),
+                    peer.vlan(), peer.location().deviceId(), controlPlaneConnectPoint.port());
+            int toPeerL3Unicast = createPeerGroup(peerIntf.get().mac(), peer.mac(),
+                    peer.vlan(), peer.location().deviceId(), peer.location().port());
+            peerNextId.put(peer, ImmutableSortedSet.of(toRouterL3Unicast, toPeerL3Unicast));
+
+            // From peer to router
+            peerIntf.get().ipAddresses().forEach(routerIp -> {
+                flowObjectiveService.forward(peer.location().deviceId(),
+                        createPeerObjBuilder(toRouterL3Unicast, routerIp.ipAddress().toIpPrefix()).add());
+            });
+
+            // From router to peer
+            peer.ipAddresses().forEach(peerIp -> {
+                flowObjectiveService.forward(peer.location().deviceId(),
+                        createPeerObjBuilder(toPeerL3Unicast, peerIp.toIpPrefix()).add());
+            });
+        }
+
+        private void peerRemoved(HostEvent event) {
+            Host peer = event.subject();
+            Optional<Interface> peerIntf =
+                    interfaceService.getInterfacesByPort(peer.location()).stream()
+                            .filter(intf -> interfaces.isEmpty() || interfaces.contains(intf.name()))
+                            .filter(intf -> peer.vlan().equals(intf.vlan()))
+                            .findFirst();
+            if (!peerIntf.isPresent()) {
+                log.debug("Removing peer {}/{} on {} but the interface is not configured",
+                        peer.mac(), peer.vlan(), peer.location());
+                return;
+            }
+
+            Set<Integer> nextIds = peerNextId.get(peer);
+            checkState(peerNextId.get(peer) != null,
+                    "Peer nextId should not be null");
+            checkState(peerNextId.get(peer).size() == 2,
+                    "Wrong nextId associated with the peer");
+            Iterator<Integer> iter = peerNextId.get(peer).iterator();
+            int toRouterL3Unicast = iter.next();
+            int toPeerL3Unicast = iter.next();
+
+            // From peer to router
+            peerIntf.get().ipAddresses().forEach(routerIp -> {
+                flowObjectiveService.forward(peer.location().deviceId(),
+                        createPeerObjBuilder(toRouterL3Unicast, routerIp.ipAddress().toIpPrefix()).remove());
+            });
+
+            // From router to peer
+            peer.ipAddresses().forEach(peerIp -> {
+                flowObjectiveService.forward(peer.location().deviceId(),
+                        createPeerObjBuilder(toPeerL3Unicast, peerIp.toIpPrefix()).remove());
+            });
+        }
+
+        private ForwardingObjective.Builder createPeerObjBuilder(
+                int nextId, IpPrefix ipAddresses) {
+            TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+            sbuilder.matchEthType(EthType.EtherType.IPV4.ethType().toShort());
+            sbuilder.matchIPDst(ipAddresses);
+            DefaultForwardingObjective.Builder builder =
+                    DefaultForwardingObjective.builder()
+                    .withSelector(sbuilder.build())
+                    .fromApp(appId)
+                    .withPriority(getPriorityFromPrefix(ipAddresses))
+                    .withFlag(ForwardingObjective.Flag.SPECIFIC);
+            if (nextId != -1) {
+                builder.nextStep(nextId);
+            }
+            return builder;
+        }
+
+        private int createPeerGroup(MacAddress srcMac, MacAddress dstMac,
+                VlanId vlanId, DeviceId deviceId, PortNumber port) {
+            int nextId = flowObjectiveService.allocateNextId();
+            NextObjective.Builder nextObjBuilder = DefaultNextObjective.builder()
+                    .withId(nextId)
+                    .withType(NextObjective.Type.SIMPLE)
+                    .fromApp(appId);
+
+            TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
+            ttBuilder.setEthSrc(srcMac);
+            ttBuilder.setEthDst(dstMac);
+            ttBuilder.setOutput(port);
+            nextObjBuilder.addTreatment(ttBuilder.build());
+
+            TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
+            VlanId matchVlanId = (vlanId.equals(VlanId.NONE)) ?
+                    VlanId.vlanId(SingleSwitchFibInstaller.ASSIGNED_VLAN) :
+                    vlanId;
+            metabuilder.matchVlanId(matchVlanId);
+            nextObjBuilder.withMeta(metabuilder.build());
+
+            flowObjectiveService.next(deviceId, nextObjBuilder.add());
+            return nextId;
+        }
+
+        @Override
+        public void event(HostEvent event) {
+            DeviceId deviceId = event.subject().location().deviceId();
+            if (!mastershipService.isLocalMaster(deviceId)) {
+                return;
+            }
+            switch (event.type()) {
+                case HOST_ADDED:
+                    peerAdded(event);
+                    break;
+                case HOST_MOVED:
+                    //TODO We assume BGP peer does not move for now
+                    break;
+                case HOST_REMOVED:
+                    peerRemoved(event);
+                    break;
+                case HOST_UPDATED:
+                    //TODO We assume BGP peer does not change IP for now
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private int getPriorityFromPrefix(IpPrefix prefix) {
+        return (prefix.isIp4()) ?
+                2000 * prefix.prefixLength() + MIN_IP_PRIORITY :
+                500 * prefix.prefixLength() + MIN_IP_PRIORITY;
+    }
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 271ffa0..f77804f 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -558,12 +558,16 @@
      */
     public void populateSubnetBroadcastRule(DeviceId deviceId) {
         config.getSubnets(deviceId).forEach(subnet -> {
+            if (subnet.prefixLength() == 0 ||
+                    subnet.prefixLength() == IpPrefix.MAX_INET_MASK_LENGTH) {
+                return;
+            }
             int nextId = srManager.getSubnetNextObjectiveId(deviceId, subnet);
             VlanId vlanId = srManager.getSubnetAssignedVlanId(deviceId, subnet);
 
             if (nextId < 0 || vlanId == null) {
-                log.error("Cannot install subnet broadcast rule in dev:{} due"
-                        + "to vlanId:{} or nextId:{}", vlanId, nextId);
+                log.error("Cannot install subnet {} broadcast rule in dev:{} due"
+                        + "to vlanId:{} or nextId:{}", subnet, deviceId, vlanId, nextId);
                 return;
             }
 
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index bf8c4d7..932b8ad 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
@@ -278,6 +278,10 @@
             PortNumber port = entry.getKey();
             Ip4Prefix subnet = entry.getValue();
 
+            if (subnet.prefixLength() == IpPrefix.MAX_INET_MASK_LENGTH) {
+                return;
+            }
+
             if (subnetPortMap.containsKey(subnet)) {
                 subnetPortMap.get(subnet).add(port);
             } else {
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java
index 883182b..3c7394a 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java
@@ -1,6 +1,5 @@
 package org.onosproject.driver.pipeline;
 
-import com.google.common.base.Objects;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.RemovalCause;
@@ -46,6 +45,7 @@
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -333,6 +333,7 @@
         VlanId vlanid = null;
         long portNum = 0;
         boolean setVlan = false, popVlan = false;
+        MacAddress srcMac = MacAddress.ZERO;
         MacAddress dstMac = MacAddress.ZERO;
         for (Instruction ins : treatment.allInstructions()) {
             if (ins.type() == Instruction.Type.L2MODIFICATION) {
@@ -343,7 +344,8 @@
                         outerTtb.setEthDst(dstMac);
                         break;
                     case ETH_SRC:
-                        outerTtb.setEthSrc(((L2ModificationInstruction.ModEtherInstruction) l2ins).mac());
+                        srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
+                        outerTtb.setEthSrc(srcMac);
                         break;
                     case VLAN_ID:
                         vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
@@ -433,11 +435,10 @@
                     mplsgroupkey, nextId);
         } else {
             // outer group is L3Unicast
-            int l3groupId = L3_UNICAST_TYPE |
-                    (TYPE_MASK & (int) (dstMac.toLong() & 0xffff) << 6 | (int) portNum);
-            int l3gk = L3_UNICAST_TYPE |
-                    (TYPE_MASK & (deviceId.hashCode() << 22 |
-                            (int) (dstMac.toLong() & 0xffff) << 6 | (int) portNum));
+            int l3GroupIdHash = Objects.hash(srcMac, dstMac, portNum);
+            int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3GroupIdHash);
+            int l3GroupKeyHash = Objects.hash(deviceId, srcMac, dstMac, portNum);
+            int l3gk = L3_UNICAST_TYPE | (TYPE_MASK & l3GroupKeyHash);
             final GroupKey l3groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3gk));
             outerTtb.group(new DefaultGroupId(l2groupId));
             // create the l3unicast group description to wait for the
@@ -1059,7 +1060,7 @@
             DeviceId deviceId, VlanId vlanId, long portNumber) {
         int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
         long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
-        int hash = Objects.hashCode(deviceId, vlanId, portHigherBits);
+        int hash = Objects.hash(deviceId, vlanId, portHigherBits);
         return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
     }