Generalize IntentSynchronizer and separate reactive routing code

 * IntentSynchronizer can now handle any intent rather than having use
   case specific APIs
 * IntentSynchronizer does not generate or store intents anymore, it only
   perform synchronization
 * SdnIpFib generates and manages the procative route-based intents
 * ReactiveRoutingFib generates and manages the reactive intents
 * Unit tests have been tightned up to only test single components, rather
   than multiple components together
 * PeerConnectivityManager uses meaningful keys when creating intents

Change-Id: I4bb036ec8d056f43ece46f7dfc71d5e5a136b77d
diff --git a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/LocationType.java b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/LocationType.java
new file mode 100644
index 0000000..01f4f70
--- /dev/null
+++ b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/LocationType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.reactive.routing;
+
+/**
+ * Specifies the type of an IP address or an IP prefix location.
+ */
+enum LocationType {
+    /**
+     * The location of an IP address or an IP prefix is in local SDN network.
+     */
+    LOCAL,
+    /**
+     * The location of an IP address or an IP prefix is outside local SDN network.
+     */
+    INTERNET,
+    /**
+     * There is no route for this IP address or IP prefix.
+     */
+    NO_ROUTE
+}
diff --git a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingFib.java b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingFib.java
new file mode 100644
index 0000000..8e86056
--- /dev/null
+++ b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/ReactiveRoutingFib.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.reactive.routing;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import org.onlab.packet.Ethernet;
+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.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+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.host.HostService;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.routing.IntentRequestListener;
+import org.onosproject.routing.IntentSynchronizationService;
+import org.onosproject.routing.config.RoutingConfigurationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * FIB component for reactive routing intents.
+ */
+public class ReactiveRoutingFib implements IntentRequestListener {
+
+    private static final int PRIORITY_OFFSET = 100;
+    private static final int PRIORITY_MULTIPLIER = 5;
+    protected static final ImmutableList<Constraint> CONSTRAINTS
+            = ImmutableList.of(new PartialFailureConstraint());
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final ApplicationId appId;
+    private final HostService hostService;
+    private final RoutingConfigurationService configService;
+    private final InterfaceService interfaceService;
+    private final IntentSynchronizationService intentSynchronizer;
+
+    private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
+
+    /**
+     * Class constructor.
+     *
+     * @param appId application ID to use to generate intents
+     * @param hostService host service
+     * @param configService routing configuration service
+     * @param interfaceService interface service
+     * @param intentSynchronizer intent synchronization service
+     */
+    public ReactiveRoutingFib(ApplicationId appId, HostService hostService,
+                              RoutingConfigurationService configService,
+                              InterfaceService interfaceService,
+                              IntentSynchronizationService intentSynchronizer) {
+        this.appId = appId;
+        this.hostService = hostService;
+        this.configService = configService;
+        this.interfaceService = interfaceService;
+        this.intentSynchronizer = intentSynchronizer;
+
+        routeIntents = Maps.newConcurrentMap();
+    }
+
+    @Override
+    public void setUpConnectivityInternetToHost(IpAddress hostIpAddress) {
+        checkNotNull(hostIpAddress);
+        Set<ConnectPoint> ingressPoints =
+                configService.getBgpPeerConnectPoints();
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+
+        if (hostIpAddress.isIp4()) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+        }
+
+        // Match the destination IP prefix at the first hop
+        IpPrefix ipPrefix = hostIpAddress.toIpPrefix();
+        selector.matchIPDst(ipPrefix);
+
+        // Rewrite the destination MAC address
+        MacAddress hostMac = null;
+        ConnectPoint egressPoint = null;
+        for (Host host : hostService.getHostsByIp(hostIpAddress)) {
+            if (host.mac() != null) {
+                hostMac = host.mac();
+                egressPoint = host.location();
+                break;
+            }
+        }
+        if (hostMac == null) {
+            hostService.startMonitoringIp(hostIpAddress);
+            return;
+        }
+
+        TrafficTreatment.Builder treatment =
+                DefaultTrafficTreatment.builder().setEthDst(hostMac);
+        Key key = Key.of(ipPrefix.toString(), appId);
+        int priority = ipPrefix.prefixLength() * PRIORITY_MULTIPLIER
+                + PRIORITY_OFFSET;
+        MultiPointToSinglePointIntent intent =
+                MultiPointToSinglePointIntent.builder()
+                        .appId(appId)
+                        .key(key)
+                        .selector(selector.build())
+                        .treatment(treatment.build())
+                        .ingressPoints(ingressPoints)
+                        .egressPoint(egressPoint)
+                        .priority(priority)
+                        .constraints(CONSTRAINTS)
+                        .build();
+
+        log.trace("Generates ConnectivityInternetToHost intent {}", intent);
+        submitReactiveIntent(ipPrefix, intent);
+    }
+
+    @Override
+    public void setUpConnectivityHostToInternet(IpAddress hostIp, IpPrefix prefix,
+                                                IpAddress nextHopIpAddress) {
+        // Find the attachment point (egress interface) of the next hop
+        Interface egressInterface = interfaceService.getMatchingInterface(nextHopIpAddress);
+        if (egressInterface == null) {
+            log.warn("No outgoing interface found for {}",
+                    nextHopIpAddress);
+            return;
+        }
+
+        Set<Host> hosts = hostService.getHostsByIp(nextHopIpAddress);
+        if (hosts.isEmpty()) {
+            log.warn("No host found for next hop IP address");
+            return;
+        }
+        MacAddress nextHopMacAddress = null;
+        for (Host host : hosts) {
+            nextHopMacAddress = host.mac();
+            break;
+        }
+
+        hosts = hostService.getHostsByIp(hostIp);
+        if (hosts.isEmpty()) {
+            log.warn("No host found for host IP address");
+            return;
+        }
+        Host host = hosts.stream().findFirst().get();
+        ConnectPoint ingressPoint = host.location();
+
+        // Generate the intent itself
+        ConnectPoint egressPort = egressInterface.connectPoint();
+        log.debug("Generating intent for prefix {}, next hop mac {}",
+                prefix, nextHopMacAddress);
+
+        // Match the destination IP prefix at the first hop
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        if (prefix.isIp4()) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+            selector.matchIPDst(prefix);
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+            selector.matchIPv6Dst(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);
+        }
+
+        int priority = prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
+        Key key = Key.of(prefix.toString() + "-reactive", appId);
+        MultiPointToSinglePointIntent intent = MultiPointToSinglePointIntent.builder()
+                .appId(appId)
+                .key(key)
+                .selector(selector.build())
+                .treatment(treatment.build())
+                .ingressPoints(Collections.singleton(ingressPoint))
+                .egressPoint(egressPort)
+                .priority(priority)
+                .constraints(CONSTRAINTS)
+                .build();
+
+        submitReactiveIntent(prefix, intent);
+    }
+
+    @Override
+    public void setUpConnectivityHostToHost(IpAddress dstIpAddress,
+                                            IpAddress srcIpAddress,
+                                            MacAddress srcMacAddress,
+                                            ConnectPoint srcConnectPoint) {
+        checkNotNull(dstIpAddress);
+        checkNotNull(srcIpAddress);
+        checkNotNull(srcMacAddress);
+        checkNotNull(srcConnectPoint);
+
+        IpPrefix srcIpPrefix = srcIpAddress.toIpPrefix();
+        IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
+        ConnectPoint dstConnectPoint = null;
+        MacAddress dstMacAddress = null;
+
+        for (Host host : hostService.getHostsByIp(dstIpAddress)) {
+            if (host.mac() != null) {
+                dstMacAddress = host.mac();
+                dstConnectPoint = host.location();
+                break;
+            }
+        }
+        if (dstMacAddress == null) {
+            hostService.startMonitoringIp(dstIpAddress);
+            return;
+        }
+
+        //
+        // Handle intent from source host to destination host
+        //
+        MultiPointToSinglePointIntent srcToDstIntent =
+                hostToHostIntentGenerator(dstIpAddress, dstConnectPoint,
+                        dstMacAddress, srcConnectPoint);
+        submitReactiveIntent(dstIpPrefix, srcToDstIntent);
+
+        //
+        // Handle intent from destination host to source host
+        //
+
+        // Since we proactively handle the intent from destination host to
+        // source host, we should check whether there is an exiting intent
+        // first.
+        if (mp2pIntentExists(srcIpPrefix)) {
+            updateExistingMp2pIntent(srcIpPrefix, dstConnectPoint);
+            return;
+        } else {
+            // There is no existing intent, create a new one.
+            MultiPointToSinglePointIntent dstToSrcIntent =
+                    hostToHostIntentGenerator(srcIpAddress, srcConnectPoint,
+                            srcMacAddress, dstConnectPoint);
+            submitReactiveIntent(srcIpPrefix, dstToSrcIntent);
+        }
+    }
+
+    /**
+     * Generates MultiPointToSinglePointIntent for both source host and
+     * destination host located in local SDN network.
+     *
+     * @param dstIpAddress the destination IP address
+     * @param dstConnectPoint the destination host connect point
+     * @param dstMacAddress the MAC address of destination host
+     * @param srcConnectPoint the connect point where packet-in from
+     * @return the generated MultiPointToSinglePointIntent
+     */
+    private MultiPointToSinglePointIntent hostToHostIntentGenerator(
+            IpAddress dstIpAddress,
+            ConnectPoint dstConnectPoint,
+            MacAddress dstMacAddress,
+            ConnectPoint srcConnectPoint) {
+        checkNotNull(dstIpAddress);
+        checkNotNull(dstConnectPoint);
+        checkNotNull(dstMacAddress);
+        checkNotNull(srcConnectPoint);
+
+        Set<ConnectPoint> ingressPoints = new HashSet<>();
+        ingressPoints.add(srcConnectPoint);
+        IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        if (dstIpAddress.isIp4()) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+            selector.matchIPDst(dstIpPrefix);
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+            selector.matchIPv6Dst(dstIpPrefix);
+        }
+
+        // Rewrite the destination MAC address
+        TrafficTreatment.Builder treatment =
+                DefaultTrafficTreatment.builder().setEthDst(dstMacAddress);
+
+        Key key = Key.of(dstIpPrefix.toString(), appId);
+        int priority = dstIpPrefix.prefixLength() * PRIORITY_MULTIPLIER
+                + PRIORITY_OFFSET;
+        MultiPointToSinglePointIntent intent =
+                MultiPointToSinglePointIntent.builder()
+                        .appId(appId)
+                        .key(key)
+                        .selector(selector.build())
+                        .treatment(treatment.build())
+                        .ingressPoints(ingressPoints)
+                        .egressPoint(dstConnectPoint)
+                        .priority(priority)
+                        .constraints(CONSTRAINTS)
+                        .build();
+
+        log.trace("Generates ConnectivityHostToHost = {} ", intent);
+        return intent;
+    }
+
+    @Override
+    public void updateExistingMp2pIntent(IpPrefix ipPrefix,
+                                         ConnectPoint ingressConnectPoint) {
+        checkNotNull(ipPrefix);
+        checkNotNull(ingressConnectPoint);
+
+        MultiPointToSinglePointIntent existingIntent =
+                getExistingMp2pIntent(ipPrefix);
+        if (existingIntent != null) {
+            Set<ConnectPoint> ingressPoints = existingIntent.ingressPoints();
+            // Add host connect point into ingressPoints of the existing intent
+            if (ingressPoints.add(ingressConnectPoint)) {
+                MultiPointToSinglePointIntent updatedMp2pIntent =
+                        MultiPointToSinglePointIntent.builder()
+                                .appId(appId)
+                                .key(existingIntent.key())
+                                .selector(existingIntent.selector())
+                                .treatment(existingIntent.treatment())
+                                .ingressPoints(ingressPoints)
+                                .egressPoint(existingIntent.egressPoint())
+                                .priority(existingIntent.priority())
+                                .constraints(CONSTRAINTS)
+                                .build();
+
+                log.trace("Update an existing MultiPointToSinglePointIntent "
+                        + "to new intent = {} ", updatedMp2pIntent);
+                submitReactiveIntent(ipPrefix, updatedMp2pIntent);
+            }
+            // If adding ingressConnectPoint to ingressPoints failed, it
+            // because between the time interval from checking existing intent
+            // to generating new intent, onos updated this intent due to other
+            // packet-in and the new intent also includes the
+            // ingressConnectPoint. This will not affect reactive routing.
+        }
+    }
+
+    /**
+     * Submits a reactive intent to the intent synchronizer.
+     *
+     * @param ipPrefix IP prefix of the intent
+     * @param intent intent to submit
+     */
+    void submitReactiveIntent(IpPrefix ipPrefix, MultiPointToSinglePointIntent intent) {
+        routeIntents.put(ipPrefix, intent);
+
+        intentSynchronizer.submit(intent);
+    }
+
+    /**
+     * Gets the existing MultiPointToSinglePointIntent from memory for a given
+     * IP prefix.
+     *
+     * @param ipPrefix the IP prefix used to find MultiPointToSinglePointIntent
+     * @return the MultiPointToSinglePointIntent if found, otherwise null
+     */
+    private MultiPointToSinglePointIntent getExistingMp2pIntent(IpPrefix ipPrefix) {
+        checkNotNull(ipPrefix);
+        return routeIntents.get(ipPrefix);
+    }
+
+    @Override
+    public boolean mp2pIntentExists(IpPrefix ipPrefix) {
+        checkNotNull(ipPrefix);
+        return routeIntents.get(ipPrefix) != null;
+    }
+}
diff --git a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
index ad78a1c..a05e394 100644
--- a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
+++ b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
@@ -25,27 +25,39 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 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.net.ConnectPoint;
+import org.onosproject.net.Host;
 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.host.HostService;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.packet.PacketContext;
 import org.onosproject.net.packet.PacketProcessor;
 import org.onosproject.net.packet.PacketService;
+import org.onosproject.routing.IntentRequestListener;
+import org.onosproject.routing.RouteEntry;
 import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.SdnIpService;
 import org.onosproject.routing.config.RoutingConfigurationService;
 import org.slf4j.Logger;
 
 import java.nio.ByteBuffer;
+import java.util.Optional;
+import java.util.Set;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.packet.Ethernet.TYPE_ARP;
 import static org.onlab.packet.Ethernet.TYPE_IPV4;
 import static org.onosproject.net.packet.PacketPriority.REACTIVE;
@@ -74,16 +86,32 @@
     protected RoutingService routingService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected SdnIpService sdnIpService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected RoutingConfigurationService config;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
     private ApplicationId appId;
 
+    private IntentRequestListener intentRequestListener;
+
     private ReactiveRoutingProcessor processor =
             new ReactiveRoutingProcessor();
 
     @Activate
     public void activate() {
         appId = coreService.registerApplication(APP_NAME);
+
+        intentRequestListener = new ReactiveRoutingFib(appId, hostService,
+                config, interfaceService,
+                sdnIpService.getIntentSynchronizationService());
+
         packetService.addProcessor(processor, PacketProcessor.director(2));
         requestIntercepts();
         log.info("SDN-IP Reactive Routing Started");
@@ -168,12 +196,11 @@
                 IpAddress srcIp =
                         IpAddress.valueOf(ipv4Packet.getSourceAddress());
                 MacAddress srcMac = ethPkt.getSourceMAC();
-                routingService.packetReactiveProcessor(dstIp, srcIp,
-                                                       srcConnectPoint, srcMac);
+                packetReactiveProcessor(dstIp, srcIp, srcConnectPoint, srcMac);
 
                 // TODO emit packet first or packetReactiveProcessor first
                 ConnectPoint egressConnectPoint = null;
-                egressConnectPoint = routingService.getEgressConnectPoint(dstIp);
+                egressConnectPoint = getEgressConnectPoint(dstIp);
                 if (egressConnectPoint != null) {
                     forwardPacketToDst(context, egressConnectPoint);
                 }
@@ -185,6 +212,170 @@
     }
 
     /**
+     * Routes packet reactively.
+     *
+     * @param dstIpAddress the destination IP address of a packet
+     * @param srcIpAddress the source IP address of a packet
+     * @param srcConnectPoint the connect point where a packet comes from
+     * @param srcMacAddress the source MAC address of a packet
+     */
+    private void packetReactiveProcessor(IpAddress dstIpAddress,
+                                        IpAddress srcIpAddress,
+                                        ConnectPoint srcConnectPoint,
+                                        MacAddress srcMacAddress) {
+        checkNotNull(dstIpAddress);
+        checkNotNull(srcIpAddress);
+        checkNotNull(srcConnectPoint);
+        checkNotNull(srcMacAddress);
+
+        //
+        // Step1: Try to update the existing intent first if it exists.
+        //
+        IpPrefix ipPrefix = null;
+        RouteEntry routeEntry = null;
+        if (config.isIpAddressLocal(dstIpAddress)) {
+            if (dstIpAddress.isIp4()) {
+                ipPrefix = IpPrefix.valueOf(dstIpAddress,
+                        Ip4Address.BIT_LENGTH);
+            } else {
+                ipPrefix = IpPrefix.valueOf(dstIpAddress,
+                        Ip6Address.BIT_LENGTH);
+            }
+        } else {
+            // Get IP prefix from BGP route table
+            routeEntry = routingService.getLongestMatchableRouteEntry(dstIpAddress);
+            if (routeEntry != null) {
+                ipPrefix = routeEntry.prefix();
+            }
+        }
+        if (ipPrefix != null
+                && intentRequestListener.mp2pIntentExists(ipPrefix)) {
+            intentRequestListener.updateExistingMp2pIntent(ipPrefix,
+                    srcConnectPoint);
+            return;
+        }
+
+        //
+        // Step2: There is no existing intent for the destination IP address.
+        // Check whether it is necessary to create a new one. If necessary then
+        // create a new one.
+        //
+        TrafficType trafficType =
+                trafficTypeClassifier(srcConnectPoint, dstIpAddress);
+
+        switch (trafficType) {
+        case HOST_TO_INTERNET:
+            // If the destination IP address is outside the local SDN network.
+            // The Step 1 has already handled it. We do not need to do anything here.
+            intentRequestListener.setUpConnectivityHostToInternet(srcIpAddress,
+                    ipPrefix, routeEntry.nextHop());
+            break;
+        case INTERNET_TO_HOST:
+            intentRequestListener.setUpConnectivityInternetToHost(dstIpAddress);
+            break;
+        case HOST_TO_HOST:
+            intentRequestListener.setUpConnectivityHostToHost(dstIpAddress,
+                    srcIpAddress, srcMacAddress, srcConnectPoint);
+            break;
+        case INTERNET_TO_INTERNET:
+            log.trace("This is transit traffic, "
+                    + "the intent should be preinstalled already");
+            break;
+        case DROP:
+            // TODO here should setUpDropPacketIntent(...);
+            // We need a new type of intent here.
+            break;
+        case UNKNOWN:
+            log.trace("This is unknown traffic, so we do nothing");
+            break;
+        default:
+            break;
+        }
+    }
+
+    /**
+     * Classifies the traffic and return the traffic type.
+     *
+     * @param srcConnectPoint the connect point where the packet comes from
+     * @param dstIp the destination IP address in packet
+     * @return the traffic type which this packet belongs to
+     */
+    private TrafficType trafficTypeClassifier(ConnectPoint srcConnectPoint,
+                                                             IpAddress dstIp) {
+        LocationType dstIpLocationType = getLocationType(dstIp);
+        Optional<Interface> srcInterface =
+                interfaceService.getInterfacesByPort(srcConnectPoint).stream().findFirst();
+
+        switch (dstIpLocationType) {
+        case INTERNET:
+            if (!srcInterface.isPresent()) {
+                return TrafficType.HOST_TO_INTERNET;
+            } else {
+                return TrafficType.INTERNET_TO_INTERNET;
+            }
+        case LOCAL:
+            if (!srcInterface.isPresent()) {
+                return TrafficType.HOST_TO_HOST;
+            } else {
+                // TODO Currently we only consider local public prefixes.
+                // In the future, we will consider the local private prefixes.
+                // If dstIpLocationType is a local private, we should return
+                // TrafficType.DROP.
+                return TrafficType.INTERNET_TO_HOST;
+            }
+        case NO_ROUTE:
+            return TrafficType.DROP;
+        default:
+            return TrafficType.UNKNOWN;
+        }
+    }
+
+    /**
+     * Evaluates the location of an IP address and returns the location type.
+     *
+     * @param ipAddress the IP address to evaluate
+     * @return the IP address location type
+     */
+    private LocationType getLocationType(IpAddress ipAddress) {
+        if (config.isIpAddressLocal(ipAddress)) {
+            return LocationType.LOCAL;
+        } else if (routingService.getLongestMatchableRouteEntry(ipAddress) != null) {
+            return LocationType.INTERNET;
+        } else {
+            return LocationType.NO_ROUTE;
+        }
+    }
+
+    public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
+        LocationType type = getLocationType(dstIpAddress);
+        if (type == LocationType.LOCAL) {
+            Set<Host> hosts = hostService.getHostsByIp(dstIpAddress);
+            if (!hosts.isEmpty()) {
+                return hosts.iterator().next().location();
+            } else {
+                hostService.startMonitoringIp(dstIpAddress);
+                return null;
+            }
+        } else if (type == LocationType.INTERNET) {
+            IpAddress nextHopIpAddress = null;
+            RouteEntry routeEntry = routingService.getLongestMatchableRouteEntry(dstIpAddress);
+            if (routeEntry != null) {
+                nextHopIpAddress = routeEntry.nextHop();
+                Interface it = interfaceService.getMatchingInterface(nextHopIpAddress);
+                if (it != null) {
+                    return it.connectPoint();
+                } else {
+                    return null;
+                }
+            } else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * Emits the specified packet onto the network.
      *
      * @param context      the packet context
diff --git a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/TrafficType.java b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/TrafficType.java
new file mode 100644
index 0000000..134126b
--- /dev/null
+++ b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/TrafficType.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.reactive.routing;
+
+/**
+ * Specifies the type of traffic.
+ * <p>
+ * We classify traffic by the first packet of each traffic.
+ * </p>
+ */
+enum TrafficType {
+    /**
+     * Traffic from a host located in local SDN network wants to
+     * communicate with destination host located in Internet (outside
+     * local SDN network).
+     */
+    HOST_TO_INTERNET,
+    /**
+     * Traffic from Internet wants to communicate with a host located
+     * in local SDN network.
+     */
+    INTERNET_TO_HOST,
+    /**
+     * Both the source host and destination host of a traffic are in
+     * local SDN network.
+     */
+    HOST_TO_HOST,
+    /**
+     * Traffic from Internet wants to traverse local SDN network.
+     */
+    INTERNET_TO_INTERNET,
+    /**
+     * Any traffic wants to communicate with a destination which has
+     * no route, or traffic from Internet wants to access a local private
+     * IP address.
+     */
+    DROP,
+    /**
+     * Traffic does not belong to the types above.
+     */
+    UNKNOWN
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/IntentRequestListener.java b/apps/routing-api/src/main/java/org/onosproject/routing/IntentRequestListener.java
index 2d1bb31..1069ec5 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/IntentRequestListener.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/IntentRequestListener.java
@@ -47,6 +47,16 @@
                                      ConnectPoint srcConnectPoint);
 
     /**
+     * Sets up connectivity for packet from a local host to the Internet.
+     *
+     * @param hostIp IP address of the local host
+     * @param prefix external IP prefix that the host is talking to
+     * @param nextHopIpAddress IP address of the next hop router for the prefix
+     */
+    void setUpConnectivityHostToInternet(IpAddress hostIp, IpPrefix prefix,
+                                                IpAddress nextHopIpAddress);
+
+    /**
      * Adds one new ingress connect point into ingress points of an existing
      * intent and resubmits the new intent.
      * <p>
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/IntentSynchronizationService.java b/apps/routing-api/src/main/java/org/onosproject/routing/IntentSynchronizationService.java
new file mode 100644
index 0000000..dc6a838
--- /dev/null
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/IntentSynchronizationService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.routing;
+
+import org.onosproject.net.intent.Intent;
+
+/**
+ * Submits and withdraws intents to the IntentService from a single point in
+ * the cluster at any one time. The provided intents will be synchronized with
+ * the IntentService on leadership change.
+ */
+public interface IntentSynchronizationService {
+
+    /**
+     * Submits and intent to the synchronizer.
+     * <p>
+     * The intent will be submitted directly to the IntentService if this node
+     * is the leader, otherwise it will be stored in the synchronizer for
+     * synchronization if this node becomes the leader.
+     * </p>
+     *
+     * @param intent intent to submit
+     */
+    void submit(Intent intent);
+
+    /**
+     * Withdraws an intent from the synchronizer.
+     * <p>
+     * The intent will be withdrawn directly from the IntentService if this node
+     * is the leader. The intent will be removed from the synchronizer's
+     * in-memory storage.
+     * </p>
+     *
+     * @param intent intent to withdraw
+     */
+    void withdraw(Intent intent);
+}
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/RoutingService.java b/apps/routing-api/src/main/java/org/onosproject/routing/RoutingService.java
index 8b7040e..7399ed7 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/RoutingService.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/RoutingService.java
@@ -16,8 +16,6 @@
 package org.onosproject.routing;
 
 import org.onlab.packet.IpAddress;
-import org.onlab.packet.MacAddress;
-import org.onosproject.net.ConnectPoint;
 import org.onosproject.routing.config.BgpConfig;
 
 import java.util.Collection;
@@ -32,63 +30,6 @@
     Class<BgpConfig> CONFIG_CLASS = BgpConfig.class;
 
     /**
-     * Specifies the type of an IP address or an IP prefix location.
-     */
-    enum LocationType {
-        /**
-         * The location of an IP address or an IP prefix is in local SDN network.
-         */
-        LOCAL,
-        /**
-         * The location of an IP address or an IP prefix is outside local SDN network.
-         */
-        INTERNET,
-        /**
-         * There is no route for this IP address or IP prefix.
-         */
-        NO_ROUTE
-    }
-
-    /**
-     * Specifies the type of traffic.
-     * <p>
-     * We classify traffic by the first packet of each traffic.
-     * </p>
-     */
-    enum TrafficType {
-        /**
-         * Traffic from a host located in local SDN network wants to
-         * communicate with destination host located in Internet (outside
-         * local SDN network).
-         */
-        HOST_TO_INTERNET,
-        /**
-         * Traffic from Internet wants to communicate with a host located
-         * in local SDN network.
-         */
-        INTERNET_TO_HOST,
-        /**
-         * Both the source host and destination host of a traffic are in
-         * local SDN network.
-         */
-        HOST_TO_HOST,
-        /**
-         * Traffic from Internet wants to traverse local SDN network.
-         */
-        INTERNET_TO_INTERNET,
-        /**
-         * Any traffic wants to communicate with a destination which has
-         * no route, or traffic from Internet wants to access a local private
-         * IP address.
-         */
-        DROP,
-        /**
-         * Traffic does not belong to the types above.
-         */
-        UNKNOWN
-    }
-
-    /**
      * Starts the routing service.
      */
     void start();
@@ -101,15 +42,6 @@
     void addFibListener(FibListener fibListener);
 
     /**
-     * Adds intent creation and submission listener.
-     *
-     * @param intentRequestListener listener to send intent creation and
-     *        submission request to
-     */
-    void addIntentRequestListener(IntentRequestListener
-                                         intentRequestListener);
-
-    /**
      * Stops the routing service.
      */
     void stop();
@@ -129,14 +61,6 @@
     Collection<RouteEntry> getRoutes6();
 
     /**
-     * Evaluates the location of an IP address and returns the location type.
-     *
-     * @param ipAddress the IP address to evaluate
-     * @return the IP address location type
-     */
-    LocationType getLocationType(IpAddress ipAddress);
-
-    /**
      * Finds out the route entry which has the longest matchable IP prefix.
      *
      * @param ipAddress IP address used to find out longest matchable IP prefix
@@ -145,25 +69,4 @@
      */
     RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress);
 
-    /**
-     * Finds out the egress connect point where to emit the first packet
-     * based on destination IP address.
-     *
-     * @param dstIpAddress the destination IP address
-     * @return the egress connect point if found, otherwise null
-     */
-    ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress);
-
-    /**
-     * Routes packet reactively.
-     *
-     * @param dstIpAddress the destination IP address of a packet
-     * @param srcIpAddress the source IP address of a packet
-     * @param srcConnectPoint the connect point where a packet comes from
-     * @param srcMacAddress the source MAC address of a packet
-     */
-    void packetReactiveProcessor(IpAddress dstIpAddress,
-                                        IpAddress srcIpAddress,
-                                        ConnectPoint srcConnectPoint,
-                                        MacAddress srcMacAddress);
 }
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpService.java b/apps/routing-api/src/main/java/org/onosproject/routing/SdnIpService.java
similarity index 77%
rename from apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpService.java
rename to apps/routing-api/src/main/java/org/onosproject/routing/SdnIpService.java
index d26d306..0945336 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpService.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/SdnIpService.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.sdnip;
+package org.onosproject.routing;
 
 /**
  * Service interface exported by SDN-IP.
@@ -28,4 +28,12 @@
      */
     void modifyPrimary(boolean isPrimary);
 
+    /**
+     * Gets the intent synchronization service.
+     *
+     * @return intent synchronization service
+     */
+    // TODO fix service resolution in SDN-IP
+    IntentSynchronizationService getIntentSynchronizationService();
+
 }
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/Router.java b/apps/routing/src/main/java/org/onosproject/routing/impl/Router.java
index 6700d53..75d789a 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/Router.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/Router.java
@@ -35,9 +35,7 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onosproject.core.CoreService;
-import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceService;
-import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Host;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
@@ -46,7 +44,6 @@
 import org.onosproject.routing.FibEntry;
 import org.onosproject.routing.FibListener;
 import org.onosproject.routing.FibUpdate;
-import org.onosproject.routing.IntentRequestListener;
 import org.onosproject.routing.RouteEntry;
 import org.onosproject.routing.RouteListener;
 import org.onosproject.routing.RouteUpdate;
@@ -61,7 +58,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
@@ -100,7 +96,6 @@
     private final Map<IpAddress, MacAddress> ip2Mac = new ConcurrentHashMap<>();
 
     private FibListener fibComponent;
-    private IntentRequestListener intentRequestListener;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
@@ -145,12 +140,6 @@
     @Override
     public void addFibListener(FibListener fibListener) {
         this.fibComponent = checkNotNull(fibListener);
-
-    }
-
-    @Override
-    public void addIntentRequestListener(IntentRequestListener intentRequestListener) {
-        this.intentRequestListener = checkNotNull(intentRequestListener);
     }
 
     @Override
@@ -287,12 +276,10 @@
     void addRibRoute(RouteEntry routeEntry) {
         if (routeEntry.isIp4()) {
             // IPv4
-            ribTable4.put(createBinaryString(routeEntry.prefix()),
-                    routeEntry);
+            ribTable4.put(createBinaryString(routeEntry.prefix()), routeEntry);
         } else {
             // IPv6
-            ribTable6.put(createBinaryString(routeEntry.prefix()),
-                    routeEntry);
+            ribTable6.put(createBinaryString(routeEntry.prefix()), routeEntry);
         }
     }
 
@@ -553,17 +540,6 @@
     }
 
     @Override
-    public LocationType getLocationType(IpAddress ipAddress) {
-        if (routingConfigurationService.isIpAddressLocal(ipAddress)) {
-            return LocationType.LOCAL;
-        } else if (getLongestMatchableRouteEntry(ipAddress) != null) {
-            return LocationType.INTERNET;
-        } else {
-            return LocationType.NO_ROUTE;
-        }
-    }
-
-    @Override
     public RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress) {
         RouteEntry routeEntry = null;
         Iterable<RouteEntry> routeEntries;
@@ -587,142 +563,4 @@
         return routeEntry;
     }
 
-    @Override
-    public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
-        LocationType type = getLocationType(dstIpAddress);
-        if (type == LocationType.LOCAL) {
-            Set<Host> hosts = hostService.getHostsByIp(dstIpAddress);
-            if (!hosts.isEmpty()) {
-                return hosts.iterator().next().location();
-            } else {
-                hostService.startMonitoringIp(dstIpAddress);
-                return null;
-            }
-        } else if (type == LocationType.INTERNET) {
-            IpAddress nextHopIpAddress = null;
-            RouteEntry routeEntry = getLongestMatchableRouteEntry(dstIpAddress);
-            if (routeEntry != null) {
-                nextHopIpAddress = routeEntry.nextHop();
-                Interface it = interfaceService.getMatchingInterface(nextHopIpAddress);
-                if (it != null) {
-                    return it.connectPoint();
-                } else {
-                    return null;
-                }
-            } else {
-                return null;
-            }
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public void packetReactiveProcessor(IpAddress dstIpAddress,
-                                        IpAddress srcIpAddress,
-                                        ConnectPoint srcConnectPoint,
-                                        MacAddress srcMacAddress) {
-        checkNotNull(dstIpAddress);
-        checkNotNull(srcIpAddress);
-        checkNotNull(srcConnectPoint);
-        checkNotNull(srcMacAddress);
-
-        //
-        // Step1: Try to update the existing intent first if it exists.
-        //
-        IpPrefix ipPrefix = null;
-        if (routingConfigurationService.isIpAddressLocal(dstIpAddress)) {
-            if (dstIpAddress.isIp4()) {
-                ipPrefix = IpPrefix.valueOf(dstIpAddress,
-                        Ip4Address.BIT_LENGTH);
-            } else {
-                ipPrefix = IpPrefix.valueOf(dstIpAddress,
-                        Ip6Address.BIT_LENGTH);
-            }
-        } else {
-            // Get IP prefix from BGP route table
-            RouteEntry routeEntry = getLongestMatchableRouteEntry(dstIpAddress);
-            if (routeEntry != null) {
-                ipPrefix = routeEntry.prefix();
-            }
-        }
-        if (ipPrefix != null
-                && intentRequestListener.mp2pIntentExists(ipPrefix)) {
-            intentRequestListener.updateExistingMp2pIntent(ipPrefix,
-                    srcConnectPoint);
-            return;
-        }
-
-        //
-        // Step2: There is no existing intent for the destination IP address.
-        // Check whether it is necessary to create a new one. If necessary then
-        // create a new one.
-        //
-        TrafficType trafficType =
-                trafficTypeClassifier(srcConnectPoint, dstIpAddress);
-
-        switch (trafficType) {
-        case HOST_TO_INTERNET:
-            // If the destination IP address is outside the local SDN network.
-            // The Step 1 has already handled it. We do not need to do anything here.
-            break;
-        case INTERNET_TO_HOST:
-            intentRequestListener.setUpConnectivityInternetToHost(dstIpAddress);
-            break;
-        case HOST_TO_HOST:
-            intentRequestListener.setUpConnectivityHostToHost(dstIpAddress,
-                    srcIpAddress, srcMacAddress, srcConnectPoint);
-            break;
-        case INTERNET_TO_INTERNET:
-            log.trace("This is transit traffic, "
-                    + "the intent should be preinstalled already");
-            break;
-        case DROP:
-            // TODO here should setUpDropPaccketIntent(...);
-            // We need a new type of intent here.
-            break;
-        case UNKNOWN:
-            log.trace("This is unknown traffic, so we do nothing");
-            break;
-        default:
-            break;
-        }
-    }
-
-    /**
-     * Classifies the traffic and return the traffic type.
-     *
-     * @param srcConnectPoint the connect point where the packet comes from
-     * @param dstIp the destination IP address in packet
-     * @return the traffic type which this packet belongs to
-     */
-    private TrafficType trafficTypeClassifier(ConnectPoint srcConnectPoint,
-                                              IpAddress dstIp) {
-        LocationType dstIpLocationType = getLocationType(dstIp);
-        Optional<Interface> srcInterface =
-                interfaceService.getInterfacesByPort(srcConnectPoint).stream().findFirst();
-
-        switch (dstIpLocationType) {
-        case INTERNET:
-            if (!srcInterface.isPresent()) {
-                return TrafficType.HOST_TO_INTERNET;
-            } else {
-                return TrafficType.INTERNET_TO_INTERNET;
-            }
-        case LOCAL:
-            if (!srcInterface.isPresent()) {
-                return TrafficType.HOST_TO_HOST;
-            } else {
-                // TODO Currently we only consider local public prefixes.
-                // In the future, we will consider the local private prefixes.
-                // If dstIpLocationType is a local private, we should return
-                // TrafficType.DROP.
-                return TrafficType.INTERNET_TO_HOST;
-            }
-        case NO_ROUTE:
-            return TrafficType.DROP;
-        default:
-            return TrafficType.UNKNOWN;
-        }
-    }
 }
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/StaticRouter.java b/apps/routing/src/main/java/org/onosproject/routing/impl/StaticRouter.java
index 29526ff..3c86820 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/StaticRouter.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/StaticRouter.java
@@ -18,10 +18,7 @@
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.IpAddress;
-import org.onlab.packet.MacAddress;
-import org.onosproject.net.ConnectPoint;
 import org.onosproject.routing.FibListener;
-import org.onosproject.routing.IntentRequestListener;
 import org.onosproject.routing.RouteEntry;
 import org.onosproject.routing.RoutingService;
 import org.onosproject.routing.StaticRoutingService;
@@ -49,11 +46,6 @@
     }
 
     @Override
-    public void addIntentRequestListener(IntentRequestListener intentRequestListener) {
-
-    }
-
-    @Override
     public void stop() {
 
     }
@@ -69,27 +61,11 @@
     }
 
     @Override
-    public LocationType getLocationType(IpAddress ipAddress) {
-        return null;
-    }
-
-    @Override
     public RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress) {
         return null;
     }
 
     @Override
-    public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
-        return null;
-    }
-
-    @Override
-    public void packetReactiveProcessor(IpAddress dstIpAddress, IpAddress srcIpAddress,
-                                        ConnectPoint srcConnectPoint, MacAddress srcMacAddress) {
-
-    }
-
-    @Override
     public FibListener getFibListener() {
         return fibListener;
     }
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 d8d8f45..eaabed3 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
@@ -15,118 +15,79 @@
  */
 package org.onosproject.sdnip;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import org.onlab.packet.Ethernet;
-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.incubator.net.intf.Interface;
-import org.onosproject.incubator.net.intf.InterfaceService;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.Host;
-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.Criterion;
-import org.onosproject.net.flow.criteria.IPCriterion;
-import org.onosproject.net.host.HostService;
-import org.onosproject.net.intent.Constraint;
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.intent.IntentState;
 import org.onosproject.net.intent.Key;
-import org.onosproject.net.intent.MultiPointToSinglePointIntent;
-import org.onosproject.net.intent.PointToPointIntent;
-import org.onosproject.net.intent.constraint.PartialFailureConstraint;
-import org.onosproject.routing.FibListener;
-import org.onosproject.routing.FibUpdate;
-import org.onosproject.routing.IntentRequestListener;
-import org.onosproject.routing.config.RoutingConfigurationService;
+import org.onosproject.routing.IntentSynchronizationService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-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;
-import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
 
 /**
  * Synchronizes intents between the in-memory intent store and the
  * IntentService.
  */
-public class IntentSynchronizer implements FibListener, IntentRequestListener {
-    private static final int PRIORITY_OFFSET = 100;
-    private static final int PRIORITY_MULTIPLIER = 5;
-    protected static final ImmutableList<Constraint> CONSTRAINTS
-            = ImmutableList.of(new PartialFailureConstraint());
+public class IntentSynchronizer implements IntentSynchronizationService {
 
     private static final Logger log =
         LoggerFactory.getLogger(IntentSynchronizer.class);
 
     private final ApplicationId appId;
     private final IntentService intentService;
-    private final HostService hostService;
-    private final InterfaceService interfaceService;
-    private final Map<IntentKey, PointToPointIntent> peerIntents;
-    private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
+
+    private final Map<Key, Intent> intents;
 
     //
     // State to deal with SDN-IP Leader election and pushing Intents
     //
     private final ExecutorService bgpIntentsSynchronizerExecutor;
-    private final Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
     private volatile boolean isElectedLeader = false;
     private volatile boolean isActivatedLeader = false;
 
-    private final RoutingConfigurationService configService;
-
     /**
      * Class constructor.
      *
      * @param appId the Application ID
      * @param intentService the intent service
-     * @param hostService the host service
-     * @param configService the SDN-IP configuration service
-     * @param interfaceService the interface service
+     */
+    IntentSynchronizer(ApplicationId appId, IntentService intentService) {
+        this(appId, intentService,
+                newSingleThreadExecutor(groupedThreads("onos/sdnip", "sync")));
+    }
+
+    /**
+     * Class constructor.
+     *
+     * @param appId the Application ID
+     * @param intentService the intent service
+     * @param executorService executor service for synchronization thread
      */
     IntentSynchronizer(ApplicationId appId, IntentService intentService,
-                       HostService hostService,
-                       RoutingConfigurationService configService,
-                       InterfaceService interfaceService) {
+                       ExecutorService executorService) {
         this.appId = appId;
         this.intentService = intentService;
-        this.hostService = hostService;
-        this.interfaceService = interfaceService;
-        peerIntents = new ConcurrentHashMap<>();
-        routeIntents = new ConcurrentHashMap<>();
 
-        this.configService = configService;
+        intents = new ConcurrentHashMap<>();
 
-        bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
-                new ThreadFactoryBuilder()
-                .setNameFormat("sdnip-intents-synchronizer-%d").build());
+        bgpIntentsSynchronizerExecutor = executorService;
     }
 
     /**
      * Starts the synchronizer.
      */
     public void start() {
-        bgpIntentsSynchronizerExecutor.execute(this::doIntentSynchronizationThread);
+
     }
 
     /**
@@ -187,6 +148,28 @@
         }
     }
 
+    @Override
+    public void submit(Intent intent) {
+        synchronized (this) {
+            intents.put(intent.key(), intent);
+            if (isElectedLeader && isActivatedLeader) {
+                log.trace("SDN-IP Submitting intent: {}", intent);
+                intentService.submit(intent);
+            }
+        }
+    }
+
+    @Override
+    public void withdraw(Intent intent) {
+        synchronized (this) {
+            intents.remove(intent.key(), intent);
+            if (isElectedLeader && isActivatedLeader) {
+                log.trace("SDN-IP Withdrawing intent: {}", intent);
+                intentService.withdraw(intent);
+            }
+        }
+    }
+
     /**
      * Signals the synchronizer that the SDN-IP leadership has changed.
      *
@@ -203,778 +186,80 @@
         this.isActivatedLeader = false;
         this.isElectedLeader = true;
 
-        //
-        // Tell the Intents Synchronizer thread to start the synchronization
-        //
-        intentsSynchronizerSemaphore.release();
+        // Run the synchronization method off-thread
+        bgpIntentsSynchronizerExecutor.execute(this::synchronizeIntents);
     }
 
-    /**
-     * Gets the route intents.
-     *
-     * @return the route intents
-     */
-    public Collection<MultiPointToSinglePointIntent> getRouteIntents() {
-        List<MultiPointToSinglePointIntent> result = new LinkedList<>();
-
-        for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
-            routeIntents.entrySet()) {
-            result.add(entry.getValue());
-        }
-        return result;
-    }
-
-    /**
-     * Thread for Intent Synchronization.
-     */
-    private void doIntentSynchronizationThread() {
-        boolean interrupted = false;
-        try {
-            while (!interrupted) {
-                try {
-                    intentsSynchronizerSemaphore.acquire();
-                    //
-                    // Drain all permits, because a single synchronization is
-                    // sufficient.
-                    //
-                    intentsSynchronizerSemaphore.drainPermits();
-                } catch (InterruptedException e) {
-                    interrupted = true;
-                    break;
-                }
-                synchronizeIntents();
+    private void synchronizeIntents() {
+        Map<Key, Intent> serviceIntents = new HashMap<>();
+        intentService.getIntents().forEach(i -> {
+            if (i.appId().equals(appId)) {
+                serviceIntents.put(i.key(), i);
             }
-        } finally {
-            if (interrupted) {
-                Thread.currentThread().interrupt();
-            }
-        }
-    }
+        });
 
-    /**
-     * Submits a collection of point-to-point intents.
-     *
-     * @param intents the intents to submit
-     */
-    void submitPeerIntents(Collection<PointToPointIntent> intents) {
-        synchronized (this) {
-            // Store the intents in memory
-            for (PointToPointIntent intent : intents) {
-                peerIntents.put(new IntentKey(intent), intent);
-            }
+        List<Intent> intentsToAdd = new LinkedList<>();
+        List<Intent> intentsToRemove = new LinkedList<>();
 
-            // Push the intents
-            if (isElectedLeader && isActivatedLeader) {
-                log.debug("SDN-IP Submitting all Peer Intents...");
-                for (Intent intent : intents) {
-                    log.trace("SDN-IP Submitting intents: {}", intent);
-                    intentService.submit(intent);
-                }
-            }
-        }
-    }
-
-    /**
-     * Submits a MultiPointToSinglePointIntent for reactive routing.
-     *
-     * @param ipPrefix the IP prefix to match in a MultiPointToSinglePointIntent
-     * @param intent the intent to submit
-     */
-    void submitReactiveIntent(IpPrefix ipPrefix, MultiPointToSinglePointIntent intent) {
-        synchronized (this) {
-            // Store the intent in memory
-            routeIntents.put(ipPrefix, intent);
-
-            // Push the intent
-            if (isElectedLeader && isActivatedLeader) {
-                log.trace("SDN-IP submitting reactive routing intent: {}", intent);
-                intentService.submit(intent);
-            }
-        }
-    }
-
-    /**
-     * 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 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
-     */
-    private MultiPointToSinglePointIntent generateRouteIntent(
-            IpPrefix prefix,
-            IpAddress nextHopIpAddress,
-            MacAddress nextHopMacAddress) {
-
-        // Find the attachment point (egress interface) of the next hop
-        Interface 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()) {
-            // TODO this should be only peering interfaces
-            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.isIp4()) {
-            selector.matchEthType(Ethernet.TYPE_IPV4);
-            selector.matchIPDst(prefix);
-        } else {
-            selector.matchEthType(Ethernet.TYPE_IPV6);
-            selector.matchIPv6Dst(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);
-        }
-
-        int priority =
-            prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
-        Key key = Key.of(prefix.toString(), appId);
-        return MultiPointToSinglePointIntent.builder()
-                .appId(appId)
-                .key(key)
-                .selector(selector.build())
-                .treatment(treatment.build())
-                .ingressPoints(ingressPorts)
-                .egressPoint(egressPort)
-                .priority(priority)
-                .constraints(CONSTRAINTS)
-                .build();
-    }
-
-    @Override
-    public void setUpConnectivityInternetToHost(IpAddress hostIpAddress) {
-        checkNotNull(hostIpAddress);
-        Set<ConnectPoint> ingressPoints =
-                configService.getBgpPeerConnectPoints();
-
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-
-        if (hostIpAddress.isIp4()) {
-            selector.matchEthType(Ethernet.TYPE_IPV4);
-        } else {
-            selector.matchEthType(Ethernet.TYPE_IPV6);
-        }
-
-        // Match the destination IP prefix at the first hop
-        IpPrefix ipPrefix = hostIpAddress.toIpPrefix();
-        selector.matchIPDst(ipPrefix);
-
-        // Rewrite the destination MAC address
-        MacAddress hostMac = null;
-        ConnectPoint egressPoint = null;
-        for (Host host : hostService.getHostsByIp(hostIpAddress)) {
-            if (host.mac() != null) {
-                hostMac = host.mac();
-                egressPoint = host.location();
-                break;
-            }
-        }
-        if (hostMac == null) {
-            hostService.startMonitoringIp(hostIpAddress);
-            return;
-        }
-
-        TrafficTreatment.Builder treatment =
-                DefaultTrafficTreatment.builder().setEthDst(hostMac);
-        Key key = Key.of(ipPrefix.toString(), appId);
-        int priority = ipPrefix.prefixLength() * PRIORITY_MULTIPLIER
-                + PRIORITY_OFFSET;
-        MultiPointToSinglePointIntent intent =
-                MultiPointToSinglePointIntent.builder()
-                .appId(appId)
-                .key(key)
-                .selector(selector.build())
-                .treatment(treatment.build())
-                .ingressPoints(ingressPoints)
-                .egressPoint(egressPoint)
-                .priority(priority)
-                .constraints(CONSTRAINTS)
-                .build();
-
-        log.trace("Generates ConnectivityInternetToHost intent {}", intent);
-        submitReactiveIntent(ipPrefix, intent);
-    }
-
-
-    @Override
-    public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
-        //
-        // NOTE: Semantically, we MUST withdraw existing intents before
-        // submitting new intents.
-        //
-        synchronized (this) {
-            MultiPointToSinglePointIntent intent;
-
-            log.debug("SDN-IP submitting intents = {} withdrawing = {}",
-                     updates.size(), withdraws.size());
-
-            //
-            // Prepare the Intent batch operations for the intents to withdraw
-            //
-            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 " +
-                              "for prefix: {}", prefix);
-                    continue;
-                }
-                if (isElectedLeader && isActivatedLeader) {
-                    log.trace("SDN-IP Withdrawing intent: {}", intent);
-                    intentService.withdraw(intent);
-                }
-            }
-
-            //
-            // Prepare the Intent batch operations for the intents to submit
-            //
-            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) {
-                    if (oldIntent != null) {
-                        log.trace("SDN-IP Withdrawing old intent: {}",
-                                  oldIntent);
-                        intentService.withdraw(oldIntent);
-                    }
-                    log.trace("SDN-IP Submitting intent: {}", intent);
-                    intentService.submit(intent);
-                }
-            }
-        }
-    }
-
-    /**
-     * Synchronize the in-memory Intents with the Intents in the Intent
-     * framework.
-     */
-    void synchronizeIntents() {
-        synchronized (this) {
-
-            Map<IntentKey, Intent> localIntents = new HashMap<>();
-            Map<IntentKey, Intent> fetchedIntents = new HashMap<>();
-            Collection<Intent> storeInMemoryIntents = new LinkedList<>();
-            Collection<Intent> addIntents = new LinkedList<>();
-            Collection<Intent> deleteIntents = new LinkedList<>();
-
-            if (!isElectedLeader) {
-                return;         // Nothing to do: not the leader anymore
-            }
-            log.debug("SDN-IP synchronizing all intents...");
-
-            // Prepare the local intents
-            for (Intent intent : routeIntents.values()) {
-                localIntents.put(new IntentKey(intent), intent);
-            }
-            for (Intent intent : peerIntents.values()) {
-                localIntents.put(new IntentKey(intent), intent);
-            }
-
-            // Fetch all intents for this application
-            for (Intent intent : intentService.getIntents()) {
-                if (!intent.appId().equals(appId)) {
-                    continue;
-                }
-                fetchedIntents.put(new IntentKey(intent), intent);
-            }
-            if (log.isDebugEnabled()) {
-                for (Intent intent: fetchedIntents.values()) {
-                    log.trace("SDN-IP Intent Synchronizer: fetched intent: {}",
-                              intent);
-                }
-            }
-
-            computeIntentsDelta(localIntents, fetchedIntents,
-                                storeInMemoryIntents, addIntents,
-                                deleteIntents);
-
-            //
-            // Perform the actions:
-            // 1. Store in memory fetched intents that are same. Can be done
-            //    even if we are not the leader anymore
-            // 2. Delete intents: check if the leader before the operation
-            // 3. Add intents: check if the leader before the operation
-            //
-            for (Intent intent : storeInMemoryIntents) {
-                // Store the intent in memory based on its type
-                if (intent instanceof MultiPointToSinglePointIntent) {
-                    MultiPointToSinglePointIntent mp2pIntent =
-                        (MultiPointToSinglePointIntent) intent;
-                    // Find the IP prefix
-                    Criterion c =
-                        mp2pIntent.selector().getCriterion(Criterion.Type.IPV4_DST);
-                    if (c == null) {
-                        // Try IPv6
-                        c =
-                            mp2pIntent.selector().getCriterion(Criterion.Type.IPV6_DST);
-                    }
-                    if (c != null && c instanceof IPCriterion) {
-                        IPCriterion ipCriterion = (IPCriterion) c;
-                        IpPrefix ipPrefix = ipCriterion.ip();
-                        if (ipPrefix == null) {
-                            continue;
-                        }
-                        log.trace("SDN-IP Intent Synchronizer: updating " +
-                                  "in-memory Route Intent for prefix {}",
-                                  ipPrefix);
-                        routeIntents.put(ipPrefix, mp2pIntent);
-                    } else {
-                        log.warn("SDN-IP no IPV4_DST or IPV6_DST criterion found for Intent {}",
-                                 mp2pIntent.id());
-                    }
-                    continue;
-                }
-                if (intent instanceof PointToPointIntent) {
-                    PointToPointIntent p2pIntent = (PointToPointIntent) intent;
-                    log.trace("SDN-IP Intent Synchronizer: updating " +
-                              "in-memory Peer Intent {}", p2pIntent);
-                    peerIntents.put(new IntentKey(intent), p2pIntent);
-                    continue;
-                }
-            }
-
-            // Withdraw Intents
-            for (Intent intent : deleteIntents) {
-                intentService.withdraw(intent);
-                log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
-                      intent);
-            }
-            if (!isElectedLeader) {
-                log.trace("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
-                          "not elected leader anymore");
-                isActivatedLeader = false;
-                return;
-            }
-
-            // Add Intents
-            for (Intent intent : addIntents) {
-                intentService.submit(intent);
-                log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
-                          intent);
-            }
-            if (!isElectedLeader) {
-                log.trace("SDN-IP Intent Synchronizer: cannot submit intents: " +
-                          "not elected leader anymore");
-                isActivatedLeader = false;
-                return;
-            }
-
-            if (isElectedLeader) {
-                isActivatedLeader = true;       // Allow push of Intents
+        for (Intent localIntent : intents.values()) {
+            Intent serviceIntent = serviceIntents.remove(localIntent.key());
+            if (serviceIntent == null) {
+                intentsToAdd.add(localIntent);
             } else {
-                isActivatedLeader = false;
-            }
-            log.debug("SDN-IP intent synchronization completed");
-        }
-    }
-
-    /**
-     * Computes the delta in two sets of Intents: local in-memory Intents,
-     * and intents fetched from the Intent framework.
-     *
-     * @param localIntents the local in-memory Intents
-     * @param fetchedIntents the Intents fetched from the Intent framework
-     * @param storeInMemoryIntents the Intents that should be stored in memory.
-     * Note: This Collection must be allocated by the caller, and it will
-     * be populated by this method.
-     * @param addIntents the Intents that should be added to the Intent
-     * framework. Note: This Collection must be allocated by the caller, and
-     * it will be populated by this method.
-     * @param deleteIntents the Intents that should be deleted from the Intent
-     * framework. Note: This Collection must be allocated by the caller, and
-     * it will be populated by this method.
-     */
-    private void computeIntentsDelta(
-                                final Map<IntentKey, Intent> localIntents,
-                                final Map<IntentKey, Intent> fetchedIntents,
-                                Collection<Intent> storeInMemoryIntents,
-                                Collection<Intent> addIntents,
-                                Collection<Intent> deleteIntents) {
-
-        //
-        // Compute the deltas between the LOCAL in-memory Intents and the
-        // FETCHED Intents:
-        //  - If an Intent is in both the LOCAL and FETCHED sets:
-        //    If the FETCHED Intent is WITHDRAWING or WITHDRAWN, then
-        //    the LOCAL Intent should be added/installed; otherwise the
-        //    FETCHED intent should be stored in the local memory
-        //    (i.e., override the LOCAL Intent) to preserve the original
-        //    Intent ID.
-        //  - if a LOCAL Intent is not in the FETCHED set, then the LOCAL
-        //    Intent should be added/installed.
-        //  - If a FETCHED Intent is not in the LOCAL set, then the FETCHED
-        //    Intent should be deleted/withdrawn.
-        //
-        for (Map.Entry<IntentKey, Intent> entry : localIntents.entrySet()) {
-            IntentKey intentKey = entry.getKey();
-            Intent localIntent = entry.getValue();
-            Intent fetchedIntent = fetchedIntents.get(intentKey);
-
-            if (fetchedIntent == null) {
-                //
-                // No FETCHED Intent found: push the LOCAL Intent.
-                //
-                addIntents.add(localIntent);
-                continue;
-            }
-
-            IntentState state =
-                intentService.getIntentState(fetchedIntent.key());
-            if (state == null ||
-                state == IntentState.WITHDRAWING ||
-                state == IntentState.WITHDRAWN) {
-                // The intent has been withdrawn but according to our route
-                // table it should be installed. We'll reinstall it.
-                addIntents.add(localIntent);
-                continue;
-            }
-            storeInMemoryIntents.add(fetchedIntent);
-        }
-
-        for (Map.Entry<IntentKey, Intent> entry : fetchedIntents.entrySet()) {
-            IntentKey intentKey = entry.getKey();
-            Intent fetchedIntent = entry.getValue();
-            Intent localIntent = localIntents.get(intentKey);
-
-            if (localIntent != null) {
-                continue;
-            }
-
-            IntentState state =
-                intentService.getIntentState(fetchedIntent.key());
-            if (state == null ||
-                state == IntentState.WITHDRAWING ||
-                state == IntentState.WITHDRAWN) {
-                // Nothing to do. The intent has been already withdrawn.
-                continue;
-            }
-            //
-            // No LOCAL Intent found: delete/withdraw the FETCHED Intent.
-            //
-            deleteIntents.add(fetchedIntent);
-        }
-    }
-
-    /**
-     * Helper class that can be used to compute the key for an Intent by
-     * by excluding the Intent ID.
-     */
-    static final class IntentKey {
-        private final Intent intent;
-
-        /**
-         * Constructor.
-         *
-         * @param intent the intent to use
-         */
-        IntentKey(Intent intent) {
-            checkArgument((intent instanceof MultiPointToSinglePointIntent) ||
-                          (intent instanceof PointToPointIntent),
-                          "Intent type not recognized", intent);
-            this.intent = intent;
-        }
-
-        /**
-         * Compares two Multi-Point to Single-Point Intents whether they
-         * represent same logical intention.
-         *
-         * @param intent1 the first Intent to compare
-         * @param intent2 the second Intent to compare
-         * @return true if both Intents represent same logical intention,
-         * otherwise false
-         */
-        static boolean equalIntents(MultiPointToSinglePointIntent intent1,
-                                    MultiPointToSinglePointIntent intent2) {
-            return Objects.equals(intent1.appId(), intent2.appId()) &&
-                Objects.equals(intent1.selector(), intent2.selector()) &&
-                Objects.equals(intent1.treatment(), intent2.treatment()) &&
-                Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
-                Objects.equals(intent1.egressPoint(), intent2.egressPoint());
-        }
-
-        /**
-         * Compares two Point-to-Point Intents whether they represent
-         * same logical intention.
-         *
-         * @param intent1 the first Intent to compare
-         * @param intent2 the second Intent to compare
-         * @return true if both Intents represent same logical intention,
-         * otherwise false
-         */
-        static boolean equalIntents(PointToPointIntent intent1,
-                                    PointToPointIntent intent2) {
-            return Objects.equals(intent1.appId(), intent2.appId()) &&
-                Objects.equals(intent1.selector(), intent2.selector()) &&
-                Objects.equals(intent1.treatment(), intent2.treatment()) &&
-                Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
-                Objects.equals(intent1.egressPoint(), intent2.egressPoint());
-        }
-
-        @Override
-        public int hashCode() {
-            if (intent instanceof PointToPointIntent) {
-                PointToPointIntent p2pIntent = (PointToPointIntent) intent;
-                return Objects.hash(p2pIntent.appId(),
-                                    p2pIntent.resources(),
-                                    p2pIntent.selector(),
-                                    p2pIntent.treatment(),
-                                    p2pIntent.constraints(),
-                                    p2pIntent.ingressPoint(),
-                                    p2pIntent.egressPoint());
-            }
-            if (intent instanceof MultiPointToSinglePointIntent) {
-                MultiPointToSinglePointIntent m2pIntent =
-                    (MultiPointToSinglePointIntent) intent;
-                return Objects.hash(m2pIntent.appId(),
-                                    m2pIntent.resources(),
-                                    m2pIntent.selector(),
-                                    m2pIntent.treatment(),
-                                    m2pIntent.constraints(),
-                                    m2pIntent.ingressPoints(),
-                                    m2pIntent.egressPoint());
-            }
-            checkArgument(false, "Intent type not recognized", intent);
-            return 0;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if ((obj == null) || (!(obj instanceof IntentKey))) {
-                return false;
-            }
-            IntentKey other = (IntentKey) obj;
-
-            if (this.intent instanceof PointToPointIntent) {
-                if (!(other.intent instanceof PointToPointIntent)) {
-                    return false;
+                IntentState state = intentService.getIntentState(serviceIntent.key());
+                if (!IntentUtils.equals(serviceIntent, localIntent) || state == null ||
+                        state == IntentState.WITHDRAW_REQ ||
+                        state == IntentState.WITHDRAWING ||
+                        state == IntentState.WITHDRAWN) {
+                    intentsToAdd.add(localIntent);
                 }
-                return equalIntents((PointToPointIntent) this.intent,
-                                    (PointToPointIntent) other.intent);
-            }
-            if (this.intent instanceof MultiPointToSinglePointIntent) {
-                if (!(other.intent instanceof MultiPointToSinglePointIntent)) {
-                    return false;
-                }
-                return equalIntents(
-                                (MultiPointToSinglePointIntent) this.intent,
-                                (MultiPointToSinglePointIntent) other.intent);
-            }
-            checkArgument(false, "Intent type not recognized", intent);
-            return false;
-        }
-    }
-
-    @Override
-    public void setUpConnectivityHostToHost(IpAddress dstIpAddress,
-                                            IpAddress srcIpAddress,
-                                            MacAddress srcMacAddress,
-                                            ConnectPoint srcConnectPoint) {
-        checkNotNull(dstIpAddress);
-        checkNotNull(srcIpAddress);
-        checkNotNull(srcMacAddress);
-        checkNotNull(srcConnectPoint);
-
-        IpPrefix srcIpPrefix = srcIpAddress.toIpPrefix();
-        IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
-        ConnectPoint dstConnectPoint = null;
-        MacAddress dstMacAddress = null;
-
-        for (Host host : hostService.getHostsByIp(dstIpAddress)) {
-            if (host.mac() != null) {
-                dstMacAddress = host.mac();
-                dstConnectPoint = host.location();
-                break;
             }
         }
-        if (dstMacAddress == null) {
-            hostService.startMonitoringIp(dstIpAddress);
+
+        for (Intent serviceIntent : serviceIntents.values()) {
+            IntentState state = intentService.getIntentState(serviceIntent.key());
+            if (state != null && state != IntentState.WITHDRAW_REQ
+                    && state != IntentState.WITHDRAWING
+                    && state != IntentState.WITHDRAWN) {
+                intentsToRemove.add(serviceIntent);
+            }
+        }
+
+        log.debug("SDN-IP Intent Synchronizer: submitting {}, withdrawing {}",
+                intentsToAdd.size(), intentsToRemove.size());
+
+        // Withdraw Intents
+        for (Intent intent : intentsToRemove) {
+            intentService.withdraw(intent);
+            log.trace("SDN-IP Intent Synchronizer: withdrawing intent: {}",
+                    intent);
+        }
+        if (!isElectedLeader) {
+            log.debug("SDN-IP Intent Synchronizer: cannot withdraw intents: " +
+                    "not elected leader anymore");
+            isActivatedLeader = false;
             return;
         }
 
-        //
-        // Handle intent from source host to destination host
-        //
-        MultiPointToSinglePointIntent srcToDstIntent =
-                hostToHostIntentGenerator(dstIpAddress, dstConnectPoint,
-                                    dstMacAddress, srcConnectPoint);
-        submitReactiveIntent(dstIpPrefix, srcToDstIntent);
-
-        //
-        // Handle intent from destination host to source host
-        //
-
-        // Since we proactively handle the intent from destination host to
-        // source host, we should check whether there is an exiting intent
-        // first.
-        if (mp2pIntentExists(srcIpPrefix)) {
-            updateExistingMp2pIntent(srcIpPrefix, dstConnectPoint);
+        // Add Intents
+        for (Intent intent : intentsToAdd) {
+            intentService.submit(intent);
+            log.trace("SDN-IP Intent Synchronizer: submitting intent: {}",
+                    intent);
+        }
+        if (!isElectedLeader) {
+            log.debug("SDN-IP Intent Synchronizer: cannot submit intents: " +
+                    "not elected leader anymore");
+            isActivatedLeader = false;
             return;
+        }
+
+        if (isElectedLeader) {
+            isActivatedLeader = true;       // Allow push of Intents
         } else {
-            // There is no existing intent, create a new one.
-            MultiPointToSinglePointIntent dstToSrcIntent =
-                    hostToHostIntentGenerator(srcIpAddress, srcConnectPoint,
-                                        srcMacAddress, dstConnectPoint);
-            submitReactiveIntent(srcIpPrefix, dstToSrcIntent);
+            isActivatedLeader = false;
         }
+        log.debug("SDN-IP intent synchronization completed");
     }
 
-    /**
-     * Generates MultiPointToSinglePointIntent for both source host and
-     * destination host located in local SDN network.
-     *
-     * @param dstIpAddress the destination IP address
-     * @param dstConnectPoint the destination host connect point
-     * @param dstMacAddress the MAC address of destination host
-     * @param srcConnectPoint the connect point where packet-in from
-     * @return the generated MultiPointToSinglePointIntent
-     */
-    private MultiPointToSinglePointIntent hostToHostIntentGenerator(
-                                       IpAddress dstIpAddress,
-                                       ConnectPoint dstConnectPoint,
-                                       MacAddress dstMacAddress,
-                                       ConnectPoint srcConnectPoint) {
-        checkNotNull(dstIpAddress);
-        checkNotNull(dstConnectPoint);
-        checkNotNull(dstMacAddress);
-        checkNotNull(srcConnectPoint);
-
-        Set<ConnectPoint> ingressPoints = new HashSet<>();
-        ingressPoints.add(srcConnectPoint);
-        IpPrefix dstIpPrefix = dstIpAddress.toIpPrefix();
-
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        if (dstIpAddress.isIp4()) {
-            selector.matchEthType(Ethernet.TYPE_IPV4);
-            selector.matchIPDst(dstIpPrefix);
-        } else {
-            selector.matchEthType(Ethernet.TYPE_IPV6);
-            selector.matchIPv6Dst(dstIpPrefix);
-        }
-
-        // Rewrite the destination MAC address
-        TrafficTreatment.Builder treatment =
-                DefaultTrafficTreatment.builder().setEthDst(dstMacAddress);
-
-        Key key = Key.of(dstIpPrefix.toString(), appId);
-        int priority = dstIpPrefix.prefixLength() * PRIORITY_MULTIPLIER
-                + PRIORITY_OFFSET;
-        MultiPointToSinglePointIntent intent =
-                MultiPointToSinglePointIntent.builder()
-                .appId(appId)
-                .key(key)
-                .selector(selector.build())
-                .treatment(treatment.build())
-                .ingressPoints(ingressPoints)
-                .egressPoint(dstConnectPoint)
-                .priority(priority)
-                .constraints(CONSTRAINTS)
-                .build();
-
-        log.trace("Generates ConnectivityHostToHost = {} ", intent);
-        return intent;
-    }
-
-    @Override
-    public void updateExistingMp2pIntent(IpPrefix ipPrefix,
-                                         ConnectPoint ingressConnectPoint) {
-        checkNotNull(ipPrefix);
-        checkNotNull(ingressConnectPoint);
-
-        MultiPointToSinglePointIntent existingIntent =
-                getExistingMp2pIntent(ipPrefix);
-        if (existingIntent != null) {
-            Set<ConnectPoint> ingressPoints = existingIntent.ingressPoints();
-            // Add host connect point into ingressPoints of the existing intent
-            if (ingressPoints.add(ingressConnectPoint)) {
-                MultiPointToSinglePointIntent updatedMp2pIntent =
-                        MultiPointToSinglePointIntent.builder()
-                        .appId(appId)
-                        .key(existingIntent.key())
-                        .selector(existingIntent.selector())
-                        .treatment(existingIntent.treatment())
-                        .ingressPoints(ingressPoints)
-                        .egressPoint(existingIntent.egressPoint())
-                        .priority(existingIntent.priority())
-                        .constraints(CONSTRAINTS)
-                        .build();
-
-                log.trace("Update an existing MultiPointToSinglePointIntent "
-                        + "to new intent = {} ", updatedMp2pIntent);
-                submitReactiveIntent(ipPrefix, updatedMp2pIntent);
-            }
-            // If adding ingressConnectPoint to ingressPoints failed, it
-            // because between the time interval from checking existing intent
-            // to generating new intent, onos updated this intent due to other
-            // packet-in and the new intent also includes the
-            // ingressConnectPoint. This will not affect reactive routing.
-        }
-    }
-
-    @Override
-    public boolean mp2pIntentExists(IpPrefix ipPrefix) {
-        checkNotNull(ipPrefix);
-        return routeIntents.get(ipPrefix) != null;
-    }
-
-    /**
-     * Gets the existing MultiPointToSinglePointIntent from memory for a given
-     * IP prefix.
-     *
-     * @param ipPrefix the IP prefix used to find MultiPointToSinglePointIntent
-     * @return the MultiPointToSinglePointIntent if found, otherwise null
-     */
-    private MultiPointToSinglePointIntent getExistingMp2pIntent(IpPrefix
-                                                                ipPrefix) {
-        checkNotNull(ipPrefix);
-        return routeIntents.get(ipPrefix);
-    }
 }
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentUtils.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentUtils.java
new file mode 100644
index 0000000..8e2a3df
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentUtils.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.sdnip;
+
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Utilities for dealing with intents.
+ */
+public final class IntentUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(IntentUtils.class);
+
+    private IntentUtils() {
+
+    }
+
+    /**
+     * Checks if two intents represent the same value.
+     *
+     * <p>({@link Intent#equals(Object)} only checks ID equality)</p>
+     *
+     * <p>Both intents must be of the same type.</p>
+     *
+     * @param one first intent
+     * @param two second intent
+     * @return true if the two intents represent the same value, otherwise false
+     */
+    public static boolean equals(Intent one, Intent two) {
+        checkArgument(one.getClass() == two.getClass(),
+                "Intents are not the same type");
+
+        if (!(Objects.equals(one.appId(), two.appId()) &&
+                Objects.equals(one.key(), two.key()))) {
+            return false;
+        }
+
+        if (one instanceof MultiPointToSinglePointIntent) {
+            MultiPointToSinglePointIntent intent1 = (MultiPointToSinglePointIntent) one;
+            MultiPointToSinglePointIntent intent2 = (MultiPointToSinglePointIntent) two;
+
+            return Objects.equals(intent1.selector(), intent2.selector()) &&
+                    Objects.equals(intent1.treatment(), intent2.treatment()) &&
+                    Objects.equals(intent1.ingressPoints(), intent2.ingressPoints()) &&
+                    Objects.equals(intent1.egressPoint(), intent2.egressPoint());
+        } else if (one instanceof PointToPointIntent) {
+            PointToPointIntent intent1 = (PointToPointIntent) one;
+            PointToPointIntent intent2 = (PointToPointIntent) two;
+
+            return Objects.equals(intent1.selector(), intent2.selector()) &&
+                    Objects.equals(intent1.treatment(), intent2.treatment()) &&
+                    Objects.equals(intent1.ingressPoint(), intent2.ingressPoint()) &&
+                    Objects.equals(intent1.egressPoint(), intent2.egressPoint());
+        } else {
+            log.error("Unimplemented intent type");
+            return false;
+        }
+    }
+}
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java
index 459db2b..b2ce0f8 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java
@@ -15,6 +15,8 @@
  */
 package org.onosproject.sdnip;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IPv6;
@@ -22,16 +24,18 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.TpPort;
 import org.onosproject.core.ApplicationId;
-import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.config.NetworkConfigService;
 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.host.InterfaceIpAddress;
+import org.onosproject.net.intent.Key;
 import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.routing.IntentSynchronizationService;
 import org.onosproject.routing.RoutingService;
 import org.onosproject.routing.config.BgpConfig;
 import org.slf4j.Logger;
@@ -49,18 +53,26 @@
 public class PeerConnectivityManager {
     private static final int PRIORITY_OFFSET = 1000;
 
+    private static final String SUFFIX_DST = "dst";
+    private static final String SUFFIX_SRC = "src";
+    private static final String SUFFIX_ICMP = "icmp";
+
     private static final Logger log = LoggerFactory.getLogger(
             PeerConnectivityManager.class);
 
     private static final short BGP_PORT = 179;
 
-    private final IntentSynchronizer intentSynchronizer;
+    private final IntentSynchronizationService intentSynchronizer;
     private final NetworkConfigService configService;
     private final InterfaceService interfaceService;
 
     private final ApplicationId appId;
     private final ApplicationId routerAppId;
 
+    // Just putting something random here for now. Figure out exactly what
+    // indexes we need when we start making use of them.
+    private final Multimap<BgpConfig.BgpSpeakerConfig, PointToPointIntent> peerIntents;
+
     /**
      * Creates a new PeerConnectivityManager.
      *
@@ -71,7 +83,7 @@
      * @param routerAppId        application ID
      */
     public PeerConnectivityManager(ApplicationId appId,
-                                   IntentSynchronizer intentSynchronizer,
+                                   IntentSynchronizationService intentSynchronizer,
                                    NetworkConfigService configService,
                                    ApplicationId routerAppId,
                                    InterfaceService interfaceService) {
@@ -80,6 +92,8 @@
         this.configService = configService;
         this.routerAppId = routerAppId;
         this.interfaceService = interfaceService;
+
+        peerIntents = HashMultimap.create();
     }
 
     /**
@@ -100,8 +114,6 @@
      * BGP speakers and external BGP peers.
      */
     private void setUpConnectivity() {
-        List<PointToPointIntent> intents = new ArrayList<>();
-
         BgpConfig config = configService.getConfig(routerAppId, RoutingService.CONFIG_CLASS);
 
         if (config == null) {
@@ -113,11 +125,12 @@
             log.debug("Start to set up BGP paths for BGP speaker: {}",
                     bgpSpeaker);
 
-            intents.addAll(buildSpeakerIntents(bgpSpeaker));
-        }
+            buildSpeakerIntents(bgpSpeaker).forEach(i -> {
+                peerIntents.put(bgpSpeaker, i);
+                intentSynchronizer.submit(i);
+            });
 
-        // Submit all the intents.
-        intentSynchronizer.submitPeerIntents(intents);
+        }
     }
 
     private Collection<PointToPointIntent> buildSpeakerIntents(BgpConfig.BgpSpeakerConfig speaker) {
@@ -167,8 +180,8 @@
         List<PointToPointIntent> intents = new ArrayList<>();
 
         TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
-
         TrafficSelector selector;
+        Key key;
 
         byte tcpProtocol;
         byte icmpProtocol;
@@ -188,8 +201,11 @@
                 null,
                 BGP_PORT);
 
+        key = buildKey(ipOne, ipTwo, SUFFIX_DST);
+
         intents.add(PointToPointIntent.builder()
                 .appId(appId)
+                .key(key)
                 .selector(selector)
                 .treatment(treatment)
                 .ingressPoint(portOne)
@@ -204,8 +220,11 @@
                 BGP_PORT,
                 null);
 
+        key = buildKey(ipOne, ipTwo, SUFFIX_SRC);
+
         intents.add(PointToPointIntent.builder()
                 .appId(appId)
+                .key(key)
                 .selector(selector)
                 .treatment(treatment)
                 .ingressPoint(portOne)
@@ -220,8 +239,11 @@
                 null,
                 BGP_PORT);
 
+        key = buildKey(ipTwo, ipOne, SUFFIX_DST);
+
         intents.add(PointToPointIntent.builder()
                 .appId(appId)
+                .key(key)
                 .selector(selector)
                 .treatment(treatment)
                 .ingressPoint(portTwo)
@@ -236,8 +258,11 @@
                 BGP_PORT,
                 null);
 
+        key = buildKey(ipTwo, ipOne, SUFFIX_SRC);
+
         intents.add(PointToPointIntent.builder()
                 .appId(appId)
+                .key(key)
                 .selector(selector)
                 .treatment(treatment)
                 .ingressPoint(portTwo)
@@ -252,8 +277,11 @@
                 null,
                 null);
 
+        key = buildKey(ipOne, ipTwo, SUFFIX_ICMP);
+
         intents.add(PointToPointIntent.builder()
                 .appId(appId)
+                .key(key)
                 .selector(selector)
                 .treatment(treatment)
                 .ingressPoint(portOne)
@@ -268,8 +296,11 @@
                 null,
                 null);
 
+        key = buildKey(ipTwo, ipOne, SUFFIX_ICMP);
+
         intents.add(PointToPointIntent.builder()
                 .appId(appId)
+                .key(key)
                 .selector(selector)
                 .treatment(treatment)
                 .ingressPoint(portTwo)
@@ -316,4 +347,27 @@
         return builder.build();
     }
 
+    /**
+     * Builds an intent Key for a point-to-point intent based off the source
+     * and destination IP address, as well as a suffix String to distinguish
+     * between different types of intents between the same source and
+     * destination.
+     *
+     * @param srcIp source IP address
+     * @param dstIp destination IP address
+     * @param suffix suffix string
+     * @return
+     */
+    private Key buildKey(IpAddress srcIp, IpAddress dstIp, String suffix) {
+        String keyString = new StringBuilder()
+                .append(srcIp.toString())
+                .append("-")
+                .append(dstIp.toString())
+                .append("-")
+                .append(suffix)
+                .toString();
+
+        return Key.of(keyString, appId);
+    }
+
 }
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java
index 3d1fe65..1b3eda9 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java
@@ -32,7 +32,9 @@
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.host.HostService;
 import org.onosproject.net.intent.IntentService;
+import org.onosproject.routing.IntentSynchronizationService;
 import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.SdnIpService;
 import org.onosproject.routing.config.RoutingConfigurationService;
 import org.slf4j.Logger;
 
@@ -79,6 +81,7 @@
 
     private IntentSynchronizer intentSynchronizer;
     private PeerConnectivityManager peerConnectivity;
+    private SdnIpFib fib;
 
     private LeadershipEventListener leadershipEventListener =
         new InnerLeadershipEventListener();
@@ -93,10 +96,7 @@
 
         localControllerNode = clusterService.getLocalNode();
 
-        intentSynchronizer = new IntentSynchronizer(appId, intentService,
-                                                    hostService,
-                                                    config,
-                                                    interfaceService);
+        intentSynchronizer = new IntentSynchronizer(appId, intentService);
         intentSynchronizer.start();
 
         peerConnectivity = new PeerConnectivityManager(appId,
@@ -106,8 +106,9 @@
                                                        interfaceService);
         peerConnectivity.start();
 
-        routingService.addFibListener(intentSynchronizer);
-        routingService.addIntentRequestListener(intentSynchronizer);
+        fib = new SdnIpFib(appId, interfaceService, intentSynchronizer);
+
+        routingService.addFibListener(fib);
         routingService.start();
 
         leadershipService.addListener(leadershipEventListener);
@@ -131,6 +132,11 @@
         intentSynchronizer.leaderChanged(isPrimary);
     }
 
+    @Override
+    public IntentSynchronizationService getIntentSynchronizationService() {
+        return intentSynchronizer;
+    }
+
     /**
      * Converts DPIDs of the form xx:xx:xx:xx:xx:xx:xx to OpenFlow provider
      * device URIs.
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java
new file mode 100644
index 0000000..c0001bd
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.sdnip;
+
+import com.google.common.collect.ImmutableList;
+import org.onlab.packet.Ethernet;
+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.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+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.intent.Constraint;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.IntentSynchronizationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * FIB component of SDN-IP.
+ */
+public class SdnIpFib implements FibListener {
+    private Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final int PRIORITY_OFFSET = 100;
+    private static final int PRIORITY_MULTIPLIER = 5;
+    protected static final ImmutableList<Constraint> CONSTRAINTS
+            = ImmutableList.of(new PartialFailureConstraint());
+
+    private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents;
+
+    private final ApplicationId appId;
+    private final InterfaceService interfaceService;
+    private final IntentSynchronizationService intentSynchronizer;
+
+    /**
+     * Class constructor.
+     *
+     * @param appId application ID to use when generating intents
+     * @param interfaceService interface service
+     * @param intentSynchronizer intent synchronizer
+     */
+    public SdnIpFib(ApplicationId appId, InterfaceService interfaceService,
+                    IntentSynchronizationService intentSynchronizer) {
+        routeIntents = new ConcurrentHashMap<>();
+
+        this.appId = appId;
+        this.interfaceService = interfaceService;
+        this.intentSynchronizer = intentSynchronizer;
+    }
+
+
+    @Override
+    public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
+        int submitCount = 0, withdrawCount = 0;
+        //
+        // NOTE: Semantically, we MUST withdraw existing intents before
+        // submitting new intents.
+        //
+        synchronized (this) {
+            MultiPointToSinglePointIntent intent;
+
+            //
+            // Prepare the Intent batch operations for the intents to withdraw
+            //
+            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 " +
+                            "for prefix: {}", prefix);
+                    continue;
+                }
+                intentSynchronizer.withdraw(intent);
+                withdrawCount++;
+            }
+
+            //
+            // Prepare the Intent batch operations for the intents to submit
+            //
+            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;
+                }
+
+                routeIntents.put(prefix, intent);
+                intentSynchronizer.submit(intent);
+                submitCount++;
+            }
+
+            log.debug("SDN-IP submitted {}/{}, withdrew = {}/{}", submitCount,
+                    updates.size(), withdrawCount, withdraws.size());
+        }
+    }
+
+    /**
+     * 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 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
+     */
+    private MultiPointToSinglePointIntent generateRouteIntent(
+            IpPrefix prefix,
+            IpAddress nextHopIpAddress,
+            MacAddress nextHopMacAddress) {
+
+        // Find the attachment point (egress interface) of the next hop
+        Interface 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()) {
+            // TODO this should be only peering interfaces
+            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.isIp4()) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+            selector.matchIPDst(prefix);
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+            selector.matchIPv6Dst(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);
+        }
+
+        int priority =
+                prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
+        Key key = Key.of(prefix.toString(), appId);
+        return MultiPointToSinglePointIntent.builder()
+                .appId(appId)
+                .key(key)
+                .selector(selector.build())
+                .treatment(treatment.build())
+                .ingressPoints(ingressPorts)
+                .egressPoint(egressPort)
+                .priority(priority)
+                .constraints(CONSTRAINTS)
+                .build();
+    }
+
+}
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/PrimaryChangeCommand.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/PrimaryChangeCommand.java
index 72cc112..7a17cfe 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/PrimaryChangeCommand.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/PrimaryChangeCommand.java
@@ -18,7 +18,7 @@
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
-import org.onosproject.sdnip.SdnIpService;
+import org.onosproject.routing.SdnIpService;
 
 /**
  * Command to change whether this SDNIP instance is primary or not.
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java
index fc5782e..6dc3ce1 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java
@@ -16,6 +16,7 @@
 package org.onosproject.sdnip;
 
 import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.MoreExecutors;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.junit.TestUtils;
@@ -27,10 +28,9 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
+import org.onosproject.TestApplicationId;
 import org.onosproject.core.ApplicationId;
-import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.incubator.net.intf.Interface;
-import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
@@ -43,20 +43,13 @@
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
 import org.onosproject.net.intent.MultiPointToSinglePointIntent;
-import org.onosproject.routing.FibEntry;
-import org.onosproject.routing.FibUpdate;
 import org.onosproject.routing.RouteEntry;
-import org.onosproject.routing.config.BgpPeer;
-import org.onosproject.routing.config.RoutingConfigurationService;
-import org.onosproject.sdnip.IntentSynchronizer.IntentKey;
 
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
@@ -64,11 +57,8 @@
 import static org.easymock.EasyMock.reset;
 import static org.easymock.EasyMock.verify;
 import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId;
 
 /**
  * This class tests the intent synchronization function in the
@@ -76,10 +66,7 @@
  */
 public class IntentSyncTest extends AbstractIntentTest {
 
-    private RoutingConfigurationService routingConfig;
-    private InterfaceService interfaceService;
     private IntentService intentService;
-    private NetworkConfigService configService;
 
     private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
             DeviceId.deviceId("of:0000000000000001"),
@@ -100,65 +87,18 @@
     private IntentSynchronizer intentSynchronizer;
     private final Set<Interface> interfaces = Sets.newHashSet();
 
-    private static final ApplicationId APPID = new ApplicationId() {
-        @Override
-        public short id() {
-            return 1;
-        }
-
-        @Override
-        public String name() {
-            return "SDNIP";
-        }
-    };
+    private static final ApplicationId APPID = TestApplicationId.create("SDNIP");
 
     @Before
     public void setUp() throws Exception {
         super.setUp();
 
-        routingConfig = createMock(RoutingConfigurationService.class);
-        interfaceService = createMock(InterfaceService.class);
-        configService = createMock(NetworkConfigService.class);
-
-        // These will set expectations on routingConfig
         setUpInterfaceService();
-        setUpBgpPeers();
-
-        replay(routingConfig);
-        replay(interfaceService);
 
         intentService = createMock(IntentService.class);
 
         intentSynchronizer = new IntentSynchronizer(APPID, intentService,
-                                                    null, routingConfig,
-                                                    interfaceService);
-    }
-
-    /**
-     * Sets up BGP peers in external networks.
-     */
-    private void setUpBgpPeers() {
-
-        Map<IpAddress, BgpPeer> peers = new HashMap<>();
-
-        String peerSw1Eth1 = "192.168.10.1";
-        peers.put(IpAddress.valueOf(peerSw1Eth1),
-                  new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
-
-        // Two BGP peers are connected to switch 2 port 1.
-        String peer1Sw2Eth1 = "192.168.20.1";
-        peers.put(IpAddress.valueOf(peer1Sw2Eth1),
-                  new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
-
-        String peer2Sw2Eth1 = "192.168.20.2";
-        peers.put(IpAddress.valueOf(peer2Sw2Eth1),
-                  new BgpPeer("00:00:00:00:00:00:00:02", 1, peer2Sw2Eth1));
-
-        String peer1Sw4Eth1 = "192.168.40.1";
-        peers.put(IpAddress.valueOf(peer1Sw4Eth1),
-                  new BgpPeer("00:00:00:00:00:00:00:04", 1, peer1Sw4Eth1));
-
-        expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
+                MoreExecutors.newDirectExecutorService());
     }
 
     /**
@@ -200,267 +140,13 @@
                                           MacAddress.valueOf("00:00:00:00:00:04"),
                                           VlanId.vlanId((short) 1));
 
-        expect(interfaceService.getInterfacesByPort(SW4_ETH1)).andReturn(
-                Collections.singleton(sw4Eth1)).anyTimes();
-        expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.40.1")))
-                .andReturn(sw4Eth1).anyTimes();
-
         interfaces.add(sw4Eth1);
-
-        expect(interfaceService.getInterfacesByPort(SW1_ETH1)).andReturn(
-                Collections.singleton(sw1Eth1)).anyTimes();
-        expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.10.1")))
-                .andReturn(sw1Eth1).anyTimes();
-        expect(interfaceService.getInterfacesByPort(SW2_ETH1)).andReturn(
-                Collections.singleton(sw2Eth1)).anyTimes();
-        expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.20.1")))
-                .andReturn(sw2Eth1).anyTimes();
-        expect(interfaceService.getInterfacesByPort(SW3_ETH1)).andReturn(
-                Collections.singleton(sw3Eth1)).anyTimes();
-        expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.30.1")))
-                .andReturn(sw3Eth1).anyTimes();
-        expect(interfaceService.getInterfaces()).andReturn(interfaces).anyTimes();
     }
 
     /**
-     * Tests adding a FIB entry to the IntentSynchronizer.
-     *
-     * We verify that the synchronizer records the correct state and that the
-     * correct intent is submitted to the IntentService.
-     *
-     * @throws TestUtilsException
-     */
-    @Test
-    public void testFibAdd() throws TestUtilsException {
-        FibEntry fibEntry = new FibEntry(
-                Ip4Prefix.valueOf("1.1.1.0/24"),
-                Ip4Address.valueOf("192.168.10.1"),
-                MacAddress.valueOf("00:00:00:00:00:01"));
-
-        // Construct a MultiPointToSinglePointIntent intent
-        TrafficSelector.Builder selectorBuilder =
-                DefaultTrafficSelector.builder();
-        selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
-                fibEntry.prefix());
-
-        TrafficTreatment.Builder treatmentBuilder =
-                DefaultTrafficTreatment.builder();
-        treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
-
-        Set<ConnectPoint> ingressPoints = new HashSet<>();
-        ingressPoints.add(SW2_ETH1);
-        ingressPoints.add(SW3_ETH1);
-        ingressPoints.add(SW4_ETH1);
-
-        MultiPointToSinglePointIntent intent =
-                MultiPointToSinglePointIntent.builder()
-                        .appId(APPID)
-                        .selector(selectorBuilder.build())
-                        .treatment(treatmentBuilder.build())
-                        .ingressPoints(ingressPoints)
-                        .egressPoint(SW1_ETH1)
-                        .constraints(IntentSynchronizer.CONSTRAINTS)
-                        .build();
-
-        // Setup the expected intents
-        intentService.submit(eqExceptId(intent));
-        replay(intentService);
-
-        intentSynchronizer.leaderChanged(true);
-        TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
-
-        FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE,
-                                            fibEntry);
-        intentSynchronizer.update(Collections.singleton(fibUpdate),
-                                  Collections.emptyList());
-
-        assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
-        Intent firstIntent =
-                intentSynchronizer.getRouteIntents().iterator().next();
-        IntentKey firstIntentKey = new IntentKey(firstIntent);
-        IntentKey intentKey = new IntentKey(intent);
-        assertTrue(firstIntentKey.equals(intentKey));
-        verify(intentService);
-    }
-
-    /**
-     * Tests adding a FIB entry with to a next hop in a VLAN.
-     *
-     * We verify that the synchronizer records the correct state and that the
-     * correct intent is submitted to the IntentService.
-     *
-     * @throws TestUtilsException
-     */
-    @Test
-    public void testFibAddWithVlan() throws TestUtilsException {
-        FibEntry fibEntry = new FibEntry(
-                Ip4Prefix.valueOf("3.3.3.0/24"),
-                Ip4Address.valueOf("192.168.40.1"),
-                MacAddress.valueOf("00:00:00:00:00:04"));
-
-        // Construct a MultiPointToSinglePointIntent intent
-        TrafficSelector.Builder selectorBuilder =
-                DefaultTrafficSelector.builder();
-        selectorBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                       .matchIPDst(fibEntry.prefix())
-                       .matchVlanId(VlanId.ANY);
-
-        TrafficTreatment.Builder treatmentBuilder =
-                DefaultTrafficTreatment.builder();
-        treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:04"))
-                        .setVlanId(VlanId.vlanId((short) 1));
-
-        Set<ConnectPoint> ingressPoints = new HashSet<>();
-        ingressPoints.add(SW1_ETH1);
-        ingressPoints.add(SW2_ETH1);
-        ingressPoints.add(SW3_ETH1);
-
-        MultiPointToSinglePointIntent intent =
-                MultiPointToSinglePointIntent.builder()
-                        .appId(APPID)
-                        .selector(selectorBuilder.build())
-                        .treatment(treatmentBuilder.build())
-                        .ingressPoints(ingressPoints)
-                        .egressPoint(SW4_ETH1)
-                        .constraints(IntentSynchronizer.CONSTRAINTS)
-                        .build();
-
-        // Setup the expected intents
-        intentService.submit(eqExceptId(intent));
-
-        replay(intentService);
-
-        // Run the test
-        intentSynchronizer.leaderChanged(true);
-        TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
-        FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry);
-
-        intentSynchronizer.update(Collections.singleton(fibUpdate),
-                                  Collections.emptyList());
-
-        // Verify
-        assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
-        Intent firstIntent =
-            intentSynchronizer.getRouteIntents().iterator().next();
-        IntentKey firstIntentKey = new IntentKey(firstIntent);
-        IntentKey intentKey = new IntentKey(intent);
-        assertTrue(firstIntentKey.equals(intentKey));
-        verify(intentService);
-    }
-
-    /**
-     * Tests updating a FIB entry.
-     *
-     * We verify that the synchronizer records the correct state and that the
-     * correct intent is submitted to the IntentService.
-     *
-     * @throws TestUtilsException
-     */
-    @Test
-    public void testFibUpdate() throws TestUtilsException {
-        // Firstly add a route
-        testFibAdd();
-
-        Intent addedIntent =
-                intentSynchronizer.getRouteIntents().iterator().next();
-
-        // Start to construct a new route entry and new intent
-        FibEntry fibEntryUpdate = new FibEntry(
-                Ip4Prefix.valueOf("1.1.1.0/24"),
-                Ip4Address.valueOf("192.168.20.1"),
-                MacAddress.valueOf("00:00:00:00:00:02"));
-
-        // Construct a new MultiPointToSinglePointIntent intent
-        TrafficSelector.Builder selectorBuilderNew =
-                DefaultTrafficSelector.builder();
-        selectorBuilderNew.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
-                fibEntryUpdate.prefix());
-
-        TrafficTreatment.Builder treatmentBuilderNew =
-                DefaultTrafficTreatment.builder();
-        treatmentBuilderNew.setEthDst(MacAddress.valueOf("00:00:00:00:00:02"));
-
-
-        Set<ConnectPoint> ingressPointsNew = new HashSet<>();
-        ingressPointsNew.add(SW1_ETH1);
-        ingressPointsNew.add(SW3_ETH1);
-        ingressPointsNew.add(SW4_ETH1);
-
-        MultiPointToSinglePointIntent intentNew =
-                MultiPointToSinglePointIntent.builder()
-                        .appId(APPID)
-                        .selector(selectorBuilderNew.build())
-                        .treatment(treatmentBuilderNew.build())
-                        .ingressPoints(ingressPointsNew)
-                        .egressPoint(SW2_ETH1)
-                        .constraints(IntentSynchronizer.CONSTRAINTS)
-                        .build();
-
-        // Set up test expectation
-        reset(intentService);
-        // Setup the expected intents
-        intentService.withdraw(eqExceptId(addedIntent));
-        intentService.submit(eqExceptId(intentNew));
-        replay(intentService);
-
-        // Call the update() method in IntentSynchronizer class
-        intentSynchronizer.leaderChanged(true);
-        TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
-        FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE,
-                                                  fibEntryUpdate);
-        intentSynchronizer.update(Collections.singletonList(fibUpdate),
-                                  Collections.emptyList());
-
-        // Verify
-        assertEquals(intentSynchronizer.getRouteIntents().size(), 1);
-        Intent firstIntent =
-                intentSynchronizer.getRouteIntents().iterator().next();
-        IntentKey firstIntentKey = new IntentKey(firstIntent);
-        IntentKey intentNewKey = new IntentKey(intentNew);
-        assertTrue(firstIntentKey.equals(intentNewKey));
-        verify(intentService);
-    }
-
-    /**
-     * Tests deleting a FIB entry.
-     *
-     * We verify that the synchronizer records the correct state and that the
-     * correct intent is withdrawn from the IntentService.
-     *
-     * @throws TestUtilsException
-     */
-    @Test
-    public void testFibDelete() throws TestUtilsException {
-        // Firstly add a route
-        testFibAdd();
-
-        Intent addedIntent =
-                intentSynchronizer.getRouteIntents().iterator().next();
-
-        // Construct the existing route entry
-        FibEntry fibEntry = new FibEntry(
-                Ip4Prefix.valueOf("1.1.1.0/24"), null, null);
-
-        // Set up expectation
-        reset(intentService);
-        // Setup the expected intents
-        intentService.withdraw(eqExceptId(addedIntent));
-        replay(intentService);
-
-        // Call the update() method in IntentSynchronizer class
-        intentSynchronizer.leaderChanged(true);
-        TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
-        FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.DELETE, fibEntry);
-        intentSynchronizer.update(Collections.emptyList(),
-                                  Collections.singletonList(fibUpdate));
-
-        // Verify
-        assertEquals(intentSynchronizer.getRouteIntents().size(), 0);
-        verify(intentService);
-    }
-
-    /**
-     * This method tests the behavior of intent Synchronizer.
+     * Tests the synchronization behavior of intent synchronizer. We set up
+     * a discrepancy between the intent service state and the intent
+     * synchronizer's state and ensure that this is reconciled correctly.
      *
      * @throws TestUtilsException
      */
@@ -529,27 +215,13 @@
         // Compose a intent, which is equal to intent5 but the id is different.
         MultiPointToSinglePointIntent intent5New =
                 staticIntentBuilder(intent5, routeEntry5, "00:00:00:00:00:01");
-        assertThat(IntentSynchronizer.IntentKey.equalIntents(
-                        intent5, intent5New),
-                   is(true));
+        assertThat(IntentUtils.equals(intent5, intent5New), is(true));
         assertFalse(intent5.equals(intent5New));
 
         MultiPointToSinglePointIntent intent6 = intentBuilder(
                 routeEntry6.prefix(), "00:00:00:00:00:01",  SW1_ETH1);
 
-        // Set up the routeIntents field in IntentSynchronizer class
-        ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent>
-            routeIntents =  new ConcurrentHashMap<>();
-        routeIntents.put(routeEntry1.prefix(), intent1);
-        routeIntents.put(routeEntry3.prefix(), intent3);
-        routeIntents.put(routeEntry4Update.prefix(), intent4Update);
-        routeIntents.put(routeEntry5.prefix(), intent5New);
-        routeIntents.put(routeEntry6.prefix(), intent6);
-        routeIntents.put(routeEntry7.prefix(), intent7);
-        TestUtils.setField(intentSynchronizer, "routeIntents", routeIntents);
-
         // Set up expectation
-        reset(intentService);
         Set<Intent> intents = new HashSet<>();
         intents.add(intent1);
         expect(intentService.getIntentState(intent1.key()))
@@ -568,9 +240,9 @@
                 .andReturn(IntentState.WITHDRAWING).anyTimes();
         expect(intentService.getIntents()).andReturn(intents).anyTimes();
 
+        // These are the operations that should be done to the intentService
+        // during synchronization
         intentService.withdraw(intent2);
-        intentService.withdraw(intent4);
-
         intentService.submit(intent3);
         intentService.submit(intent4Update);
         intentService.submit(intent6);
@@ -578,16 +250,101 @@
         replay(intentService);
 
         // Start the test
-        intentSynchronizer.leaderChanged(true);
-        intentSynchronizer.synchronizeIntents();
 
-        // Verify
-        assertEquals(intentSynchronizer.getRouteIntents().size(), 6);
-        assertTrue(intentSynchronizer.getRouteIntents().contains(intent1));
-        assertTrue(intentSynchronizer.getRouteIntents().contains(intent3));
-        assertTrue(intentSynchronizer.getRouteIntents().contains(intent4Update));
-        assertTrue(intentSynchronizer.getRouteIntents().contains(intent5));
-        assertTrue(intentSynchronizer.getRouteIntents().contains(intent6));
+        // Simulate some input from the clients. The intent synchronizer has not
+        // gained the global leadership yet, but it will remember this input for
+        // when it does.
+        intentSynchronizer.submit(intent1);
+        intentSynchronizer.submit(intent2);
+        intentSynchronizer.withdraw(intent2);
+        intentSynchronizer.submit(intent3);
+        intentSynchronizer.submit(intent4);
+        intentSynchronizer.submit(intent4Update);
+        intentSynchronizer.submit(intent5);
+        intentSynchronizer.submit(intent6);
+        intentSynchronizer.submit(intent7);
+
+        // Give the leadership to the intent synchronizer. It will now attempt
+        // to synchronize the intents in the store with the intents it has
+        // recorded based on the earlier user input.
+        intentSynchronizer.leaderChanged(true);
+
+        verify(intentService);
+    }
+
+    /**
+     * Tests the behavior of the submit API, both when the synchronizer has
+     * leadership and when it does not.
+     */
+    @Test
+    public void testSubmit() {
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+        Intent intent = intentBuilder(prefix, "00:00:00:00:00:01", SW1_ETH1);
+
+        // Set up expectations
+        intentService.submit(intent);
+        expect(intentService.getIntents()).andReturn(Collections.emptyList())
+                .anyTimes();
+        replay(intentService);
+
+        // Give the intent synchronizer leadership so it will submit intents
+        // to the intent service
+        intentSynchronizer.leaderChanged(true);
+
+        // Test the submit
+        intentSynchronizer.submit(intent);
+
+        verify(intentService);
+
+        // Now we'll remove leadership from the intent synchronizer and verify
+        // that it does not submit any intents to the intent service when we
+        // call the submit API
+        reset(intentService);
+        replay(intentService);
+
+        intentSynchronizer.leaderChanged(false);
+
+        intentSynchronizer.submit(intent);
+
+        verify(intentService);
+    }
+
+    /**
+     * Tests the behavior of the withdraw API, both when the synchronizer has
+     * leadership and when it does not.
+     */
+    @Test
+    public void testWithdraw() {
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+        Intent intent = intentBuilder(prefix, "00:00:00:00:00:01", SW1_ETH1);
+
+        // Submit an intent first so we can withdraw it later
+        intentService.submit(intent);
+        intentService.withdraw(intent);
+        expect(intentService.getIntents()).andReturn(Collections.emptyList())
+                .anyTimes();
+        replay(intentService);
+
+        // Give the intent synchronizer leadership so it will submit intents
+        // to the intent service
+        intentSynchronizer.leaderChanged(true);
+
+        // Test the submit then withdraw
+        intentSynchronizer.submit(intent);
+        intentSynchronizer.withdraw(intent);
+
+        verify(intentService);
+
+        // Now we'll remove leadership from the intent synchronizer and verify
+        // that it does not withdraw any intents to the intent service when we
+        // call the withdraw API
+        reset(intentService);
+        replay(intentService);
+
+        intentSynchronizer.leaderChanged(false);
+
+        intentSynchronizer.submit(intent);
+        intentSynchronizer.withdraw(intent);
 
         verify(intentService);
     }
@@ -607,10 +364,10 @@
         TrafficSelector.Builder selectorBuilder =
                 DefaultTrafficSelector.builder();
         if (ipPrefix.isIp4()) {
-            selectorBuilder.matchEthType(Ethernet.TYPE_IPV4);   // IPv4
+            selectorBuilder.matchEthType(Ethernet.TYPE_IPV4);
             selectorBuilder.matchIPDst(ipPrefix);
         } else {
-            selectorBuilder.matchEthType(Ethernet.TYPE_IPV6);   // IPv6
+            selectorBuilder.matchEthType(Ethernet.TYPE_IPV6);
             selectorBuilder.matchIPv6Dst(ipPrefix);
         }
 
@@ -628,11 +385,12 @@
         MultiPointToSinglePointIntent intent =
                 MultiPointToSinglePointIntent.builder()
                         .appId(APPID)
+                        .key(Key.of(ipPrefix.toString(), APPID))
                         .selector(selectorBuilder.build())
                         .treatment(treatmentBuilder.build())
                         .ingressPoints(ingressPoints)
                         .egressPoint(egressPoint)
-                        .constraints(IntentSynchronizer.CONSTRAINTS)
+                        .constraints(SdnIpFib.CONSTRAINTS)
                         .build();
         return intent;
     }
@@ -646,7 +404,7 @@
      * @return the newly constructed MultiPointToSinglePointIntent
      * @throws TestUtilsException
      */
-    private  MultiPointToSinglePointIntent staticIntentBuilder(
+    private MultiPointToSinglePointIntent staticIntentBuilder(
             MultiPointToSinglePointIntent intent, RouteEntry routeEntry,
             String nextHopMacAddress) throws TestUtilsException {
 
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
index d89c3c2..c4b2daa 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
@@ -19,7 +19,6 @@
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
-import org.onlab.junit.TestUtils;
 import org.onlab.junit.TestUtils.TestUtilsException;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
@@ -28,13 +27,14 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
 import org.onlab.packet.VlanId;
+import org.onosproject.TestApplicationId;
 import org.onosproject.core.ApplicationId;
-import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigService;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
@@ -42,8 +42,9 @@
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.intent.AbstractIntentTest;
 import org.onosproject.net.intent.Intent;
-import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.Key;
 import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.routing.IntentSynchronizationService;
 import org.onosproject.routing.config.BgpConfig;
 import org.onosproject.routing.config.BgpPeer;
 import org.onosproject.routing.config.BgpSpeaker;
@@ -71,26 +72,15 @@
  */
 public class PeerConnectivityManagerTest extends AbstractIntentTest {
 
-    private static final ApplicationId APPID = new ApplicationId() {
-        @Override
-        public short id() {
-            return 0;
-        }
-
-        @Override
-        public String name() {
-            return "foo";
-        }
-    };
+    private static final ApplicationId APPID = TestApplicationId.create("foo");
 
     private static final ApplicationId CONFIG_APP_ID = APPID;
 
     private PeerConnectivityManager peerConnectivityManager;
-    private IntentSynchronizer intentSynchronizer;
+    private IntentSynchronizationService intentSynchronizer;
     private RoutingConfigurationService routingConfig;
     private InterfaceService interfaceService;
     private NetworkConfigService networkConfigService;
-    private IntentService intentService;
 
     private Set<BgpConfig.BgpSpeakerConfig> bgpSpeakers;
     private Map<String, Interface> interfaces;
@@ -98,8 +88,6 @@
 
     private BgpConfig bgpConfig;
 
-    private Map<String, Interface> configuredInterfaces;
-    private Map<IpAddress, BgpPeer> configuredPeers;
     private List<PointToPointIntent> intentList;
 
     private final String dpid1 = "00:00:00:00:00:00:00:01";
@@ -136,7 +124,7 @@
         // These will set expectations on routingConfig and interfaceService
         bgpSpeakers = setUpBgpSpeakers();
         interfaces = Collections.unmodifiableMap(setUpInterfaces());
-        peers = Collections.unmodifiableMap(setUpPeers());
+        peers = setUpPeers();
 
         initPeerConnectivity();
         intentList = setUpIntentList();
@@ -169,11 +157,11 @@
      * Sets up logical interfaces, which emulate the configured interfaces
      * in SDN-IP application.
      *
-     * @return configured interfaces as a MAP from Interface name to Interface
+     * @return configured interfaces as a map from interface name to Interface
      */
     private Map<String, Interface> setUpInterfaces() {
 
-        configuredInterfaces = new HashMap<>();
+        Map<String, Interface> configuredInterfaces = new HashMap<>();
 
         String interfaceSw1Eth1 = "s1-eth1";
         InterfaceIpAddress ia1 =
@@ -242,7 +230,7 @@
      */
     private Map<IpAddress, BgpPeer> setUpPeers() {
 
-        configuredPeers = new HashMap<>();
+        Map<IpAddress, BgpPeer> configuredPeers = new HashMap<>();
 
         String peerSw1Eth1 = "192.168.10.1";
         configuredPeers.put(IpAddress.valueOf(peerSw1Eth1),
@@ -266,14 +254,12 @@
      * @return point to point intent list
      */
     private List<PointToPointIntent> setUpIntentList() {
-
         intentList = new ArrayList<>();
 
         setUpBgpIntents();
         setUpIcmpIntents();
 
         return intentList;
-
     }
 
     /**
@@ -306,8 +292,12 @@
             builder.matchTcpDst(TpPort.tpPort(dstTcpPort));
         }
 
+        Key key = Key.of(srcPrefix.split("/")[0] + "-" + dstPrefix.split("/")[0]
+                + "-" + ((srcTcpPort == null) ? "dst" : "src"), APPID);
+
         PointToPointIntent intent = PointToPointIntent.builder()
                 .appId(APPID)
+                .key(key)
                 .selector(builder.build())
                 .treatment(noTreatment)
                 .ingressPoint(srcConnectPoint)
@@ -392,8 +382,12 @@
                 .matchIPDst(IpPrefix.valueOf(dstPrefix))
                 .build();
 
+        Key key = Key.of(srcPrefix.split("/")[0] + "-" + dstPrefix.split("/")[0]
+                + "-" + "icmp", APPID);
+
         PointToPointIntent intent = PointToPointIntent.builder()
                 .appId(APPID)
+                .key(key)
                 .selector(selector)
                 .treatment(noTreatment)
                 .ingressPoint(srcConnectPoint)
@@ -434,19 +428,14 @@
         expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
         expect(bgpConfig.bgpSpeakers()).andReturn(bgpSpeakers).anyTimes();
         replay(bgpConfig);
-        expect(networkConfigService.getConfig(APPID, BgpConfig.class)).andReturn(bgpConfig).anyTimes();
+        expect(networkConfigService.getConfig(APPID, BgpConfig.class))
+                .andReturn(bgpConfig).anyTimes();
         replay(networkConfigService);
         replay(routingConfig);
         replay(interfaceService);
 
-        intentService = createMock(IntentService.class);
-        replay(intentService);
-
-        intentSynchronizer = new IntentSynchronizer(APPID, intentService,
-                                                    null, routingConfig,
-                                                    interfaceService);
-        intentSynchronizer.leaderChanged(true);
-        TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
+        intentSynchronizer = createMock(IntentSynchronizationService.class);
+        replay(intentSynchronizer);
 
         peerConnectivityManager =
             new PeerConnectivityManager(APPID, intentSynchronizer,
@@ -464,20 +453,18 @@
      */
     @Test
     public void testConnectionSetup() {
-
-        reset(intentService);
+        reset(intentSynchronizer);
 
         // Setup the expected intents
         for (Intent intent : intentList) {
-            intentService.submit(eqExceptId(intent));
+            intentSynchronizer.submit(eqExceptId(intent));
         }
-        replay(intentService);
+        replay(intentSynchronizer);
 
         // Running the interface to be tested.
         peerConnectivityManager.start();
 
-        verify(intentService);
-
+        verify(intentSynchronizer);
     }
 
     /**
@@ -488,7 +475,7 @@
         reset(interfaceService);
 
         expect(interfaceService.getInterfaces()).andReturn(
-                Sets.<Interface>newHashSet()).anyTimes();
+                Sets.newHashSet()).anyTimes();
         expect(interfaceService.getInterfacesByPort(s2Eth1))
                 .andReturn(Collections.emptySet()).anyTimes();
         expect(interfaceService.getInterfacesByPort(s1Eth1))
@@ -508,10 +495,10 @@
 
         replay(interfaceService);
 
-        reset(intentService);
-        replay(intentService);
+        reset(intentSynchronizer);
+        replay(intentSynchronizer);
         peerConnectivityManager.start();
-        verify(intentService);
+        verify(intentSynchronizer);
     }
 
     /**
@@ -527,10 +514,10 @@
         expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
         replay(routingConfig);
 
-        reset(intentService);
-        replay(intentService);
+        reset(intentSynchronizer);
+        replay(intentSynchronizer);
         peerConnectivityManager.start();
-        verify(intentService);
+        verify(intentSynchronizer);
     }
 
     /**
@@ -540,7 +527,7 @@
     @Test
     public void testNoPeerInterface() {
         String peerSw100Eth1 = "192.168.200.1";
-        configuredPeers.put(IpAddress.valueOf(peerSw100Eth1),
+        peers.put(IpAddress.valueOf(peerSw100Eth1),
                 new BgpPeer("00:00:00:00:00:00:01:00", 1, peerSw100Eth1));
         testConnectionSetup();
     }
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
new file mode 100644
index 0000000..5466d52
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.sdnip;
+
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+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.host.InterfaceIpAddress;
+import org.onosproject.net.intent.AbstractIntentTest;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.routing.FibEntry;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.IntentSynchronizationService;
+import org.onosproject.routing.config.BgpPeer;
+import org.onosproject.routing.config.RoutingConfigurationService;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.onosproject.sdnip.TestIntentServiceHelper.eqExceptId;
+
+/**
+ * Unit tests for SdnIpFib.
+ */
+public class SdnIpFibTest extends AbstractIntentTest {
+
+    private RoutingConfigurationService routingConfig;
+    private InterfaceService interfaceService;
+
+    private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000001"),
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW2_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000002"),
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW3_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000003"),
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW4_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000004"),
+            PortNumber.portNumber(1));
+
+    private SdnIpFib sdnipFib;
+    private IntentSynchronizationService intentSynchronizer;
+    private final Set<Interface> interfaces = Sets.newHashSet();
+
+    private static final ApplicationId APPID = TestApplicationId.create("SDNIP");
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        routingConfig = createMock(RoutingConfigurationService.class);
+        interfaceService = createMock(InterfaceService.class);
+
+        // These will set expectations on routingConfig and interfaceService
+        setUpInterfaceService();
+        setUpBgpPeers();
+
+        replay(routingConfig);
+        replay(interfaceService);
+
+        intentSynchronizer = createMock(IntentSynchronizationService.class);
+
+        sdnipFib = new SdnIpFib(APPID, interfaceService, intentSynchronizer);
+    }
+
+    /**
+     * Sets up BGP peers in external networks.
+     */
+    private void setUpBgpPeers() {
+
+        Map<IpAddress, BgpPeer> peers = new HashMap<>();
+
+        String peerSw1Eth1 = "192.168.10.1";
+        peers.put(IpAddress.valueOf(peerSw1Eth1),
+                new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
+
+        // Two BGP peers are connected to switch 2 port 1.
+        String peer1Sw2Eth1 = "192.168.20.1";
+        peers.put(IpAddress.valueOf(peer1Sw2Eth1),
+                new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
+
+        String peer2Sw2Eth1 = "192.168.20.2";
+        peers.put(IpAddress.valueOf(peer2Sw2Eth1),
+                new BgpPeer("00:00:00:00:00:00:00:02", 1, peer2Sw2Eth1));
+
+        String peer1Sw4Eth1 = "192.168.40.1";
+        peers.put(IpAddress.valueOf(peer1Sw4Eth1),
+                new BgpPeer("00:00:00:00:00:00:00:04", 1, peer1Sw4Eth1));
+
+        expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
+    }
+
+    /**
+     * Sets up InterfaceService.
+     */
+    private void setUpInterfaceService() {
+        Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
+        interfaceIpAddresses1.add(new InterfaceIpAddress(
+                IpAddress.valueOf("192.168.10.101"),
+                IpPrefix.valueOf("192.168.10.0/24")));
+        Interface sw1Eth1 = new Interface(SW1_ETH1,
+                interfaceIpAddresses1, MacAddress.valueOf("00:00:00:00:00:01"),
+                VlanId.NONE);
+        interfaces.add(sw1Eth1);
+
+        Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
+        interfaceIpAddresses2.add(
+                new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
+                        IpPrefix.valueOf("192.168.20.0/24")));
+        Interface sw2Eth1 = new Interface(SW2_ETH1,
+                interfaceIpAddresses2, MacAddress.valueOf("00:00:00:00:00:02"),
+                VlanId.NONE);
+        interfaces.add(sw2Eth1);
+
+        Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
+        interfaceIpAddresses3.add(
+                new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
+                        IpPrefix.valueOf("192.168.30.0/24")));
+        Interface sw3Eth1 = new Interface(SW3_ETH1,
+                interfaceIpAddresses3, MacAddress.valueOf("00:00:00:00:00:03"),
+                VlanId.NONE);
+        interfaces.add(sw3Eth1);
+
+        InterfaceIpAddress interfaceIpAddress4 =
+                new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"),
+                        IpPrefix.valueOf("192.168.40.0/24"));
+        Interface sw4Eth1 = new Interface(SW4_ETH1,
+                Sets.newHashSet(interfaceIpAddress4),
+                MacAddress.valueOf("00:00:00:00:00:04"),
+                VlanId.vlanId((short) 1));
+
+        expect(interfaceService.getInterfacesByPort(SW4_ETH1)).andReturn(
+                Collections.singleton(sw4Eth1)).anyTimes();
+        expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.40.1")))
+                .andReturn(sw4Eth1).anyTimes();
+
+        interfaces.add(sw4Eth1);
+
+        expect(interfaceService.getInterfacesByPort(SW1_ETH1)).andReturn(
+                Collections.singleton(sw1Eth1)).anyTimes();
+        expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.10.1")))
+                .andReturn(sw1Eth1).anyTimes();
+        expect(interfaceService.getInterfacesByPort(SW2_ETH1)).andReturn(
+                Collections.singleton(sw2Eth1)).anyTimes();
+        expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.20.1")))
+                .andReturn(sw2Eth1).anyTimes();
+        expect(interfaceService.getInterfacesByPort(SW3_ETH1)).andReturn(
+                Collections.singleton(sw3Eth1)).anyTimes();
+        expect(interfaceService.getMatchingInterface(Ip4Address.valueOf("192.168.30.1")))
+                .andReturn(sw3Eth1).anyTimes();
+        expect(interfaceService.getInterfaces()).andReturn(interfaces).anyTimes();
+    }
+
+    /**
+     * Tests adding a FIB entry to the IntentSynchronizer.
+     *
+     * We verify that the synchronizer records the correct state and that the
+     * correct intent is submitted to the IntentService.
+     */
+    @Test
+    public void testFibAdd() {
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+        FibEntry fibEntry = new FibEntry(prefix,
+                Ip4Address.valueOf("192.168.10.1"),
+                MacAddress.valueOf("00:00:00:00:00:01"));
+
+        // Construct a MultiPointToSinglePointIntent intent
+        TrafficSelector.Builder selectorBuilder =
+                DefaultTrafficSelector.builder();
+        selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+                fibEntry.prefix());
+
+        TrafficTreatment.Builder treatmentBuilder =
+                DefaultTrafficTreatment.builder();
+        treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
+
+        Set<ConnectPoint> ingressPoints = new HashSet<>();
+        ingressPoints.add(SW2_ETH1);
+        ingressPoints.add(SW3_ETH1);
+        ingressPoints.add(SW4_ETH1);
+
+        MultiPointToSinglePointIntent intent =
+                MultiPointToSinglePointIntent.builder()
+                        .appId(APPID)
+                        .key(Key.of(prefix.toString(), APPID))
+                        .selector(selectorBuilder.build())
+                        .treatment(treatmentBuilder.build())
+                        .ingressPoints(ingressPoints)
+                        .egressPoint(SW1_ETH1)
+                        .constraints(SdnIpFib.CONSTRAINTS)
+                        .build();
+
+        // Setup the expected intents
+        intentSynchronizer.submit(eqExceptId(intent));
+        replay(intentSynchronizer);
+
+        // Send in the UPDATE FibUpdate
+        FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry);
+        sdnipFib.update(Collections.singleton(fibUpdate), Collections.emptyList());
+
+        verify(intentSynchronizer);
+    }
+
+    /**
+     * Tests adding a FIB entry with to a next hop in a VLAN.
+     *
+     * We verify that the synchronizer records the correct state and that the
+     * correct intent is submitted to the IntentService.
+     */
+    @Test
+    public void testFibAddWithVlan() {
+        IpPrefix prefix = Ip4Prefix.valueOf("3.3.3.0/24");
+        FibEntry fibEntry = new FibEntry(prefix,
+                Ip4Address.valueOf("192.168.40.1"),
+                MacAddress.valueOf("00:00:00:00:00:04"));
+
+        // Construct a MultiPointToSinglePointIntent intent
+        TrafficSelector.Builder selectorBuilder =
+                DefaultTrafficSelector.builder();
+        selectorBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(fibEntry.prefix())
+                .matchVlanId(VlanId.ANY);
+
+        TrafficTreatment.Builder treatmentBuilder =
+                DefaultTrafficTreatment.builder();
+        treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:04"))
+                .setVlanId(VlanId.vlanId((short) 1));
+
+        Set<ConnectPoint> ingressPoints = new HashSet<>();
+        ingressPoints.add(SW1_ETH1);
+        ingressPoints.add(SW2_ETH1);
+        ingressPoints.add(SW3_ETH1);
+
+        MultiPointToSinglePointIntent intent =
+                MultiPointToSinglePointIntent.builder()
+                        .appId(APPID)
+                        .key(Key.of(prefix.toString(), APPID))
+                        .selector(selectorBuilder.build())
+                        .treatment(treatmentBuilder.build())
+                        .ingressPoints(ingressPoints)
+                        .egressPoint(SW4_ETH1)
+                        .constraints(SdnIpFib.CONSTRAINTS)
+                        .build();
+
+        // Setup the expected intents
+        intentSynchronizer.submit(eqExceptId(intent));
+
+        replay(intentSynchronizer);
+
+        // Send in the UPDATE FibUpdate
+        FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry);
+        sdnipFib.update(Collections.singleton(fibUpdate), Collections.emptyList());
+
+        verify(intentSynchronizer);
+    }
+
+    /**
+     * Tests updating a FIB entry.
+     *
+     * We verify that the synchronizer records the correct state and that the
+     * correct intent is submitted to the IntentService.
+     */
+    @Test
+    public void testFibUpdate() {
+        // Firstly add a route
+        testFibAdd();
+
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+
+        // Start to construct a new route entry and new intent
+        FibEntry fibEntryUpdate = new FibEntry(prefix,
+                Ip4Address.valueOf("192.168.20.1"),
+                MacAddress.valueOf("00:00:00:00:00:02"));
+
+        // Construct a new MultiPointToSinglePointIntent intent
+        TrafficSelector.Builder selectorBuilderNew =
+                DefaultTrafficSelector.builder();
+        selectorBuilderNew.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+                fibEntryUpdate.prefix());
+
+        TrafficTreatment.Builder treatmentBuilderNew =
+                DefaultTrafficTreatment.builder();
+        treatmentBuilderNew.setEthDst(MacAddress.valueOf("00:00:00:00:00:02"));
+
+        Set<ConnectPoint> ingressPointsNew = new HashSet<>();
+        ingressPointsNew.add(SW1_ETH1);
+        ingressPointsNew.add(SW3_ETH1);
+        ingressPointsNew.add(SW4_ETH1);
+
+        MultiPointToSinglePointIntent intentNew =
+                MultiPointToSinglePointIntent.builder()
+                        .appId(APPID)
+                        .key(Key.of(prefix.toString(), APPID))
+                        .selector(selectorBuilderNew.build())
+                        .treatment(treatmentBuilderNew.build())
+                        .ingressPoints(ingressPointsNew)
+                        .egressPoint(SW2_ETH1)
+                        .constraints(SdnIpFib.CONSTRAINTS)
+                        .build();
+
+        // Set up test expectation
+        reset(intentSynchronizer);
+
+        // Setup the expected intents
+        intentSynchronizer.submit(eqExceptId(intentNew));
+        replay(intentSynchronizer);
+
+        // Send in the UPDATE FibUpdate
+        FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE,
+                fibEntryUpdate);
+        sdnipFib.update(Collections.singletonList(fibUpdate),
+                Collections.emptyList());
+
+        verify(intentSynchronizer);
+    }
+
+    /**
+     * Tests deleting a FIB entry.
+     *
+     * We verify that the synchronizer records the correct state and that the
+     * correct intent is withdrawn from the IntentService.
+     */
+    @Test
+    public void testFibDelete() {
+        // Firstly add a route
+        testFibAdd();
+
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+
+        // Construct the existing route entry
+        FibEntry fibEntry = new FibEntry(prefix, null, null);
+
+        // Construct the existing MultiPointToSinglePoint intent
+        TrafficSelector.Builder selectorBuilder =
+                DefaultTrafficSelector.builder();
+        selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+                fibEntry.prefix());
+
+        TrafficTreatment.Builder treatmentBuilder =
+                DefaultTrafficTreatment.builder();
+        treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
+
+        Set<ConnectPoint> ingressPoints = new HashSet<>();
+        ingressPoints.add(SW2_ETH1);
+        ingressPoints.add(SW3_ETH1);
+        ingressPoints.add(SW4_ETH1);
+
+        MultiPointToSinglePointIntent addedIntent =
+                MultiPointToSinglePointIntent.builder()
+                        .appId(APPID)
+                        .key(Key.of(prefix.toString(), APPID))
+                        .selector(selectorBuilder.build())
+                        .treatment(treatmentBuilder.build())
+                        .ingressPoints(ingressPoints)
+                        .egressPoint(SW1_ETH1)
+                        .constraints(SdnIpFib.CONSTRAINTS)
+                        .build();
+
+        // Set up expectation
+        reset(intentSynchronizer);
+        // Setup the expected intents
+        intentSynchronizer.withdraw(eqExceptId(addedIntent));
+        replay(intentSynchronizer);
+
+        // Send in the DELETE FibUpdate
+        FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.DELETE, fibEntry);
+        sdnipFib.update(Collections.emptyList(), Collections.singletonList(fibUpdate));
+
+        verify(intentSynchronizer);
+    }
+}
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/TestIntentServiceHelper.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/TestIntentServiceHelper.java
index 69b18aa..7f825e8 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/TestIntentServiceHelper.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/TestIntentServiceHelper.java
@@ -17,7 +17,6 @@
 
 import org.easymock.IArgumentMatcher;
 import org.onosproject.net.intent.Intent;
-import org.onosproject.sdnip.IntentSynchronizer.IntentKey;
 
 import static org.easymock.EasyMock.reportMatcher;
 
@@ -53,8 +52,6 @@
      * the solution is to use an EasyMock matcher that verifies that all the
      * value properties of the provided intent match the expected values, but
      * ignores the intent ID when testing equality.
-     *
-     * FIXME this currently does not take key into account
      */
     private static final class IdAgnosticIntentMatcher implements
                 IArgumentMatcher {
@@ -86,9 +83,7 @@
             Intent providedIntent = (Intent) object;
             providedString = providedIntent.toString();
 
-            IntentKey thisIntentKey = new IntentKey(intent);
-            IntentKey providedIntentKey = new IntentKey(providedIntent);
-            return thisIntentKey.equals(providedIntentKey);
+            return IntentUtils.equals(intent, providedIntent);
         }
     }
 
diff --git a/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapImpl.java b/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapImpl.java
index 7835f3e..f1e0dbd 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapImpl.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/ecmap/EventuallyConsistentMapImpl.java
@@ -521,7 +521,7 @@
             return;
         }
         peers.forEach(node ->
-            senderPending.computeIfAbsent(node, unusedKey -> new EventAccumulator(node)).add(event)
+                        senderPending.computeIfAbsent(node, unusedKey -> new EventAccumulator(node)).add(event)
         );
     }
 
@@ -574,8 +574,10 @@
             return;
         }
         try {
-            log.debug("Received anti-entropy advertisement from {} for {} with {} entries in it",
-                    mapName, ad.sender(), ad.digest().size());
+            if (log.isTraceEnabled()) {
+                log.trace("Received anti-entropy advertisement from {} for {} with {} entries in it",
+                        mapName, ad.sender(), ad.digest().size());
+            }
             antiEntropyCheckLocalItems(ad).forEach(this::notifyListeners);
 
             if (!lightweightAntiEntropy) {