Create a formal interface between the RIB and FIB.

 * IntentSynchronizer implements a more generalized FibListener interface
 * Updates to the FIB are signalled with FibUpdate to any FibListeners
 * generateRouteIntent logic has been pushed down into the IntentSynchronizer

Change-Id: I6f0ccfd52ee4e16ce9974af5ee549d4ede6c2d0e
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
index 0a0e569..9f3c872 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
@@ -15,22 +15,19 @@
  */
 package org.onosproject.sdnip;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Semaphore;
-
-import org.apache.commons.lang3.tuple.Pair;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criteria.IPCriterion;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.intent.Intent;
@@ -39,16 +36,32 @@
 import org.onosproject.net.intent.IntentState;
 import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.sdnip.config.BgpPeer;
+import org.onosproject.sdnip.config.Interface;
+import org.onosproject.sdnip.config.SdnIpConfigurationService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+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.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+
+import static com.google.common.base.Preconditions.checkArgument;
 
 /**
  * Synchronizes intents between the in-memory intent store and the
  * IntentService.
  */
-public class IntentSynchronizer {
+public class IntentSynchronizer implements FibListener {
     private static final Logger log =
         LoggerFactory.getLogger(IntentSynchronizer.class);
 
@@ -65,18 +78,28 @@
     private volatile boolean isElectedLeader = false;
     private volatile boolean isActivatedLeader = false;
 
+    private final SdnIpConfigurationService configService;
+    private final InterfaceService interfaceService;
+
     /**
      * Class constructor.
      *
      * @param appId the Application ID
      * @param intentService the intent service
+     * @param configService the SDN-IP configuration service
+     * @param interfaceService the interface service
      */
-    IntentSynchronizer(ApplicationId appId, IntentService intentService) {
+    IntentSynchronizer(ApplicationId appId, IntentService intentService,
+                       SdnIpConfigurationService configService,
+                       InterfaceService interfaceService) {
         this.appId = appId;
         this.intentService = intentService;
         peerIntents = new ConcurrentHashMap<>();
         routeIntents = new ConcurrentHashMap<>();
 
+        this.configService = configService;
+        this.interfaceService = interfaceService;
+
         bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
                 new ThreadFactoryBuilder()
                 .setNameFormat("sdnip-intents-synchronizer-%d").build());
@@ -244,16 +267,85 @@
     }
 
     /**
-     * Updates multi-point-to-single-point route intents.
+     * Generates a route intent for a prefix, the next hop IP address, and
+     * the next hop MAC address.
+     * <p/>
+     * This method will find the egress interface for the intent.
+     * Intent will match dst IP prefix and rewrite dst MAC address at all other
+     * border switches, then forward packets according to dst MAC address.
      *
-     * @param submitIntents the intents to submit
-     * @param withdrawPrefixes the IPv4 or IPv6 matching prefixes for the
-     * intents to withdraw
+     * @param prefix            IP prefix of the route to add
+     * @param nextHopIpAddress  IP address of the next hop
+     * @param nextHopMacAddress MAC address of the next hop
+     * @return the generated intent, or null if no intent should be submitted
      */
-    void updateRouteIntents(
-                Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>> submitIntents,
-                Collection<IpPrefix> withdrawPrefixes) {
+    private MultiPointToSinglePointIntent generateRouteIntent(
+            IpPrefix prefix,
+            IpAddress nextHopIpAddress,
+            MacAddress nextHopMacAddress) {
 
+        // Find the attachment point (egress interface) of the next hop
+        Interface egressInterface;
+        if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
+            // Route to a peer
+            log.debug("Route to peer {}", nextHopIpAddress);
+            BgpPeer peer =
+                    configService.getBgpPeers().get(nextHopIpAddress);
+            egressInterface =
+                    interfaceService.getInterface(peer.connectPoint());
+        } else {
+            // Route to non-peer
+            log.debug("Route to non-peer {}", nextHopIpAddress);
+            egressInterface =
+                    interfaceService.getMatchingInterface(nextHopIpAddress);
+            if (egressInterface == null) {
+                log.warn("No outgoing interface found for {}",
+                         nextHopIpAddress);
+                return null;
+            }
+        }
+
+        //
+        // Generate the intent itself
+        //
+        Set<ConnectPoint> ingressPorts = new HashSet<>();
+        ConnectPoint egressPort = egressInterface.connectPoint();
+        log.debug("Generating intent for prefix {}, next hop mac {}",
+                  prefix, nextHopMacAddress);
+
+        for (Interface intf : interfaceService.getInterfaces()) {
+            if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
+                ConnectPoint srcPort = intf.connectPoint();
+                ingressPorts.add(srcPort);
+            }
+        }
+
+        // Match the destination IP prefix at the first hop
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        if (prefix.version() == Ip4Address.VERSION) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+        }
+        selector.matchIPDst(prefix);
+
+        // Rewrite the destination MAC address
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(nextHopMacAddress);
+        if (!egressInterface.vlan().equals(VlanId.NONE)) {
+            treatment.setVlanId(egressInterface.vlan());
+            // If we set VLAN ID, we have to make sure a VLAN tag exists.
+            // TODO support no VLAN -> VLAN routing
+            selector.matchVlanId(VlanId.ANY);
+        }
+
+        return new MultiPointToSinglePointIntent(appId, selector.build(),
+                                                 treatment.build(),
+                                                 ingressPorts, egressPort);
+    }
+
+    @Override
+    public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
         //
         // NOTE: Semantically, we MUST withdraw existing intents before
         // submitting new intents.
@@ -262,14 +354,19 @@
             MultiPointToSinglePointIntent intent;
 
             log.debug("SDN-IP submitting intents = {} withdrawing = {}",
-                     submitIntents.size(), withdrawPrefixes.size());
+                     updates.size(), withdraws.size());
 
             //
             // Prepare the Intent batch operations for the intents to withdraw
             //
             IntentOperations.Builder withdrawBuilder =
                 IntentOperations.builder(appId);
-            for (IpPrefix prefix : withdrawPrefixes) {
+            for (FibUpdate withdraw : withdraws) {
+                checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
+                              "FibUpdate with wrong type in withdraws list");
+
+                IpPrefix prefix = withdraw.entry().prefix();
+
                 intent = routeIntents.remove(prefix);
                 if (intent == null) {
                     log.trace("SDN-IP No intent in routeIntents to delete " +
@@ -287,10 +384,21 @@
             //
             IntentOperations.Builder submitBuilder =
                 IntentOperations.builder(appId);
-            for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
-                     submitIntents) {
-                IpPrefix prefix = pair.getLeft();
-                intent = pair.getRight();
+            for (FibUpdate update : updates) {
+                checkArgument(update.type() == FibUpdate.Type.UPDATE,
+                              "FibUpdate with wrong type in updates list");
+
+                IpPrefix prefix = update.entry().prefix();
+                intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
+                                             update.entry().nextHopMac());
+
+                if (intent == null) {
+                    // This preserves the old semantics - if an intent can't be
+                    // generated, we don't do anything with that prefix. But
+                    // perhaps we should withdraw the old intent anyway?
+                    continue;
+                }
+
                 MultiPointToSinglePointIntent oldIntent =
                     routeIntents.put(prefix, intent);
                 if (isElectedLeader && isActivatedLeader) {