Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Conflicts:
	core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
	core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStore.java

Change-Id: Ia1274657b27e01366a4a87196a13068d7104ee80
diff --git a/core/net/src/main/java/org/onlab/onos/cluster/impl/CoreManager.java b/core/net/src/main/java/org/onlab/onos/cluster/impl/CoreManager.java
new file mode 100644
index 0000000..4b1191f
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/cluster/impl/CoreManager.java
@@ -0,0 +1,38 @@
+package org.onlab.onos.cluster.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.CoreService;
+import org.onlab.onos.Version;
+import org.onlab.util.Tools;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Core service implementation.
+ */
+@Component
+@Service
+public class CoreManager implements CoreService {
+
+    private static final File VERSION_FILE = new File("../VERSION");
+    private static Version version = Version.version("1.0.0-SNAPSHOT");
+
+    // TODO: work in progress
+
+    @Activate
+    public void activate() {
+        List<String> versionLines = Tools.slurp(VERSION_FILE);
+        if (versionLines != null && !versionLines.isEmpty()) {
+            version = Version.version(versionLines.get(0));
+        }
+    }
+
+    @Override
+    public Version version() {
+        return version;
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
index 5400fb0..a8d63c1 100644
--- a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
@@ -18,6 +18,7 @@
 import org.onlab.onos.cluster.MastershipService;
 import org.onlab.onos.cluster.MastershipTermService;
 import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.event.AbstractListenerRegistry;
 import org.onlab.onos.event.EventDeliveryService;
 import org.onlab.onos.net.Device;
@@ -38,7 +39,7 @@
 import org.onlab.onos.net.device.PortDescription;
 import org.onlab.onos.net.provider.AbstractProviderRegistry;
 import org.onlab.onos.net.provider.AbstractProviderService;
-import org.onlab.onos.store.ClockService;
+import org.onlab.onos.store.ClockProviderService;
 import org.slf4j.Logger;
 
 /**
@@ -80,7 +81,7 @@
     protected MastershipTermService termService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected ClockService clockService;
+    protected ClockProviderService clockProviderService;
 
     @Activate
     public void activate() {
@@ -144,6 +145,10 @@
     private void applyRole(DeviceId deviceId, MastershipRole newRole) {
         if (newRole.equals(MastershipRole.NONE)) {
             Device device = store.getDevice(deviceId);
+            // FIXME: Device might not be there yet. (eventual consistent)
+            if (device == null) {
+                return;
+            }
             DeviceProvider provider = getProvider(device.providerId());
             if (provider != null) {
                 provider.roleChanged(device, newRole);
@@ -193,13 +198,50 @@
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(deviceDescription, DEVICE_DESCRIPTION_NULL);
             checkValidity();
+
+            log.info("Device {} connected", deviceId);
+            // check my Role
+            MastershipRole role = mastershipService.requestRoleFor(deviceId);
+
+            if (role != MastershipRole.MASTER) {
+                // TODO: Do we need to explicitly tell the Provider that
+                // this instance is no longer the MASTER? probably not
+                return;
+            }
+
+            MastershipTerm term = mastershipService.requestTermService()
+                    .getMastershipTerm(deviceId);
+            if (!term.master().equals(clusterService.getLocalNode().id())) {
+                // lost mastership after requestRole told this instance was MASTER.
+                return;
+            }
+            // tell clock provider if this instance is the master
+            clockProviderService.setMastershipTerm(deviceId, term);
+
             DeviceEvent event = store.createOrUpdateDevice(provider().id(),
                     deviceId, deviceDescription);
 
+            // If there was a change of any kind, tell the provider
+            // that this instance is the master.
+            // Note: event can be null, if mastership was lost between
+            // roleRequest and store update calls.
             if (event != null) {
-                log.info("Device {} connected", deviceId);
-                provider().roleChanged(event.subject(),
-                        mastershipService.requestRoleFor(deviceId));
+                // TODO: Check switch reconnected case. Is it assured that
+                //       event will never be null?
+                //       Could there be a situation MastershipService told this
+                //       instance is the new Master, but
+                //       event returned from the store is null?
+
+                // TODO: Confirm: Mastership could be lost after requestRole
+                //       and createOrUpdateDevice call.
+                //       In that case STANDBY node can
+                //       claim itself to be master against the Device.
+                //       Will the Node, chosen by the MastershipService, retry
+                //       to get the MASTER role when that happen?
+
+                // FIXME: 1st argument should be deviceId, to allow setting
+                //        certain roles even if the store returned null.
+                provider().roleChanged(event.subject(), role);
                 post(event);
             }
         }
@@ -208,6 +250,15 @@
         public void deviceDisconnected(DeviceId deviceId) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkValidity();
+
+            // FIXME: only the MASTER should be marking off-line in normal cases,
+            // but if I was the last STANDBY connection, etc. and no one else
+            // was there to mark the device offline, this instance may need to
+            // temporarily request for Master Role and mark offline.
+            if (!mastershipService.getLocalRole(deviceId).equals(MastershipRole.MASTER)) {
+                log.debug("Device {} disconnected, but I am not the master", deviceId);
+                return;
+            }
             DeviceEvent event = store.markOffline(deviceId);
             //we're no longer capable of being master or a candidate.
             mastershipService.relinquishMastership(deviceId);
@@ -253,6 +304,9 @@
             // FIXME: implement response to this notification
             log.warn("Failed to assert role [{}] onto Device {}", role,
                     deviceId);
+            if (role == MastershipRole.MASTER) {
+                mastershipService.relinquishMastership(deviceId);
+            }
         }
     }
 
@@ -268,11 +322,17 @@
 
         @Override
         public void event(MastershipEvent event) {
-            DeviceId did = event.subject();
+            final DeviceId did = event.subject();
             if (isAvailable(did)) {
-                if (event.master().equals(clusterService.getLocalNode().id())) {
+                final NodeId myNodeId = clusterService.getLocalNode().id();
+
+                if (myNodeId.equals(event.master())) {
                     MastershipTerm term = termService.getMastershipTerm(did);
-                    clockService.setMastershipTerm(did, term);
+
+                    if (term.master().equals(myNodeId)) {
+                        // only set the new term if I am the master
+                        clockProviderService.setMastershipTerm(did, term);
+                    }
                     applyRole(did, MastershipRole.MASTER);
                 } else {
                     applyRole(did, MastershipRole.STANDBY);
diff --git a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
index 00619b3..a9eddd8 100644
--- a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
@@ -5,9 +5,10 @@
 
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -21,7 +22,11 @@
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.CompletedBatchOperation;
+import org.onlab.onos.net.flow.FlowEntry;
 import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleListener;
 import org.onlab.onos.net.flow.FlowRuleProvider;
@@ -34,7 +39,9 @@
 import org.onlab.onos.net.provider.AbstractProviderService;
 import org.slf4j.Logger;
 
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
 
 /**
  * Provides implementation of the flow NB &amp; SB APIs.
@@ -42,14 +49,14 @@
 @Component(immediate = true)
 @Service
 public class FlowRuleManager
-extends AbstractProviderRegistry<FlowRuleProvider, FlowRuleProviderService>
-implements FlowRuleService, FlowRuleProviderRegistry {
+        extends AbstractProviderRegistry<FlowRuleProvider, FlowRuleProviderService>
+        implements FlowRuleService, FlowRuleProviderRegistry {
 
     public static final String FLOW_RULE_NULL = "FlowRule cannot be null";
     private final Logger log = getLogger(getClass());
 
     private final AbstractListenerRegistry<FlowRuleEvent, FlowRuleListener>
-    listenerRegistry = new AbstractListenerRegistry<>();
+            listenerRegistry = new AbstractListenerRegistry<>();
 
     private final FlowRuleStoreDelegate delegate = new InternalStoreDelegate();
 
@@ -62,8 +69,6 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
 
-    private final Map<FlowRule, AtomicInteger> deadRounds = new ConcurrentHashMap<>();
-
     @Activate
     public void activate() {
         store.setDelegate(delegate);
@@ -79,7 +84,12 @@
     }
 
     @Override
-    public Iterable<FlowRule> getFlowEntries(DeviceId deviceId) {
+    public int getFlowRuleCount() {
+        return store.getFlowRuleCount();
+    }
+
+    @Override
+    public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
         return store.getFlowEntries(deviceId);
     }
 
@@ -89,7 +99,6 @@
             FlowRule f = flowRules[i];
             final Device device = deviceService.getDevice(f.deviceId());
             final FlowRuleProvider frp = getProvider(device.providerId());
-            deadRounds.put(f, new AtomicInteger(0));
             store.storeFlowRule(f);
             frp.applyFlowRule(f);
         }
@@ -103,16 +112,17 @@
         for (int i = 0; i < flowRules.length; i++) {
             f = flowRules[i];
             device = deviceService.getDevice(f.deviceId());
-            frp = getProvider(device.providerId());
-            deadRounds.remove(f);
             store.deleteFlowRule(f);
-            frp.removeFlowRule(f);
+            if (device != null) {
+                frp = getProvider(device.providerId());
+                frp.removeFlowRule(f);
+            }
         }
     }
 
     @Override
     public void removeFlowRulesById(ApplicationId id) {
-        Iterable<FlowRule> rules =  getFlowRulesById(id);
+        Iterable<FlowRule> rules = getFlowRulesById(id);
         FlowRuleProvider frp;
         Device device;
 
@@ -126,7 +136,39 @@
 
     @Override
     public Iterable<FlowRule> getFlowRulesById(ApplicationId id) {
-        return store.getFlowEntriesByAppId(id);
+        return store.getFlowRulesByAppId(id);
+    }
+
+    @Override
+    public Future<CompletedBatchOperation> applyBatch(
+            FlowRuleBatchOperation batch) {
+        Multimap<FlowRuleProvider, FlowRuleBatchEntry> batches =
+                ArrayListMultimap.create();
+        List<Future<Void>> futures = Lists.newArrayList();
+        for (FlowRuleBatchEntry fbe : batch.getOperations()) {
+            final FlowRule f = fbe.getTarget();
+            final Device device = deviceService.getDevice(f.deviceId());
+            final FlowRuleProvider frp = getProvider(device.providerId());
+            batches.put(frp, fbe);
+            switch (fbe.getOperator()) {
+                case ADD:
+                    store.storeFlowRule(f);
+                    break;
+                case REMOVE:
+                    store.deleteFlowRule(f);
+                    break;
+                case MODIFY:
+                default:
+                    log.error("Batch operation type {} unsupported.", fbe.getOperator());
+            }
+        }
+        for (FlowRuleProvider provider : batches.keySet()) {
+            FlowRuleBatchOperation b =
+                    new FlowRuleBatchOperation(batches.get(provider));
+            Future<Void> future = provider.executeBatch(b);
+            futures.add(future);
+        }
+        return new FlowRuleBatchFuture(futures);
     }
 
     @Override
@@ -146,63 +188,63 @@
     }
 
     private class InternalFlowRuleProviderService
-    extends AbstractProviderService<FlowRuleProvider>
-    implements FlowRuleProviderService {
+            extends AbstractProviderService<FlowRuleProvider>
+            implements FlowRuleProviderService {
 
         protected InternalFlowRuleProviderService(FlowRuleProvider provider) {
             super(provider);
         }
 
         @Override
-        public void flowRemoved(FlowRule flowRule) {
-            checkNotNull(flowRule, FLOW_RULE_NULL);
+        public void flowRemoved(FlowEntry flowEntry) {
+            checkNotNull(flowEntry, FLOW_RULE_NULL);
             checkValidity();
-            FlowRule stored = store.getFlowRule(flowRule);
+            FlowEntry stored = store.getFlowEntry(flowEntry);
             if (stored == null) {
-                log.debug("Rule already evicted from store: {}", flowRule);
+                log.info("Rule already evicted from store: {}", flowEntry);
                 return;
             }
-            Device device = deviceService.getDevice(flowRule.deviceId());
+            Device device = deviceService.getDevice(flowEntry.deviceId());
             FlowRuleProvider frp = getProvider(device.providerId());
             FlowRuleEvent event = null;
             switch (stored.state()) {
-            case ADDED:
-            case PENDING_ADD:
+                case ADDED:
+                case PENDING_ADD:
                     frp.applyFlowRule(stored);
-                break;
-            case PENDING_REMOVE:
-            case REMOVED:
-                event = store.removeFlowRule(flowRule);
-                break;
-            default:
-                break;
+                    break;
+                case PENDING_REMOVE:
+                case REMOVED:
+                    event = store.removeFlowRule(stored);
+                    break;
+                default:
+                    break;
 
             }
             if (event != null) {
-                log.debug("Flow {} removed", flowRule);
+                log.debug("Flow {} removed", flowEntry);
                 post(event);
             }
         }
 
 
-        private void flowMissing(FlowRule flowRule) {
+        private void flowMissing(FlowEntry flowRule) {
             checkNotNull(flowRule, FLOW_RULE_NULL);
             checkValidity();
             Device device = deviceService.getDevice(flowRule.deviceId());
             FlowRuleProvider frp = getProvider(device.providerId());
             FlowRuleEvent event = null;
             switch (flowRule.state()) {
-            case PENDING_REMOVE:
-            case REMOVED:
-                event = store.removeFlowRule(flowRule);
-                frp.removeFlowRule(flowRule);
-                break;
-            case ADDED:
-            case PENDING_ADD:
-                frp.applyFlowRule(flowRule);
-                break;
-            default:
-                log.debug("Flow {} has not been installed.", flowRule);
+                case PENDING_REMOVE:
+                case REMOVED:
+                    event = store.removeFlowRule(flowRule);
+                    frp.removeFlowRule(flowRule);
+                    break;
+                case ADDED:
+                case PENDING_ADD:
+                    frp.applyFlowRule(flowRule);
+                    break;
+                default:
+                    log.debug("Flow {} has not been installed.", flowRule);
             }
 
             if (event != null) {
@@ -221,36 +263,40 @@
         }
 
 
-        private void flowAdded(FlowRule flowRule) {
-            checkNotNull(flowRule, FLOW_RULE_NULL);
+        private void flowAdded(FlowEntry flowEntry) {
+            checkNotNull(flowEntry, FLOW_RULE_NULL);
             checkValidity();
 
-            if (deadRounds.containsKey(flowRule) &&
-                    checkRuleLiveness(flowRule, store.getFlowRule(flowRule))) {
+            if (checkRuleLiveness(flowEntry, store.getFlowEntry(flowEntry))) {
 
-                FlowRuleEvent event = store.addOrUpdateFlowRule(flowRule);
+                FlowRuleEvent event = store.addOrUpdateFlowRule(flowEntry);
                 if (event == null) {
                     log.debug("No flow store event generated.");
                 } else {
-                    log.debug("Flow {} {}", flowRule, event.type());
+                    log.debug("Flow {} {}", flowEntry, event.type());
                     post(event);
                 }
             } else {
-                removeFlowRules(flowRule);
+                removeFlowRules(flowEntry);
             }
 
         }
 
-        private boolean checkRuleLiveness(FlowRule swRule, FlowRule storedRule) {
-            int timeout = storedRule.timeout();
+        private boolean checkRuleLiveness(FlowEntry swRule, FlowEntry storedRule) {
+            if (storedRule == null) {
+                return false;
+            }
+            long timeout = storedRule.timeout() * 1000;
+            Long currentTime = System.currentTimeMillis();
             if (storedRule.packets() != swRule.packets()) {
-                deadRounds.get(swRule).set(0);
+                storedRule.setLastSeen();
                 return true;
             }
 
-            return (deadRounds.get(swRule).getAndIncrement() *
-                    FlowRuleProvider.POLL_INTERVAL) <= timeout;
-
+            if ((currentTime - storedRule.lastSeen()) <= timeout) {
+                return true;
+            }
+            return false;
         }
 
         // Posts the specified event to the local event dispatcher.
@@ -261,13 +307,13 @@
         }
 
         @Override
-        public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowRule> flowEntries) {
-            List<FlowRule> storedRules = Lists.newLinkedList(store.getFlowEntries(deviceId));
+        public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries) {
+            List<FlowEntry> storedRules = Lists.newLinkedList(store.getFlowEntries(deviceId));
 
-            Iterator<FlowRule> switchRulesIterator = flowEntries.iterator();
+            Iterator<FlowEntry> switchRulesIterator = flowEntries.iterator();
 
             while (switchRulesIterator.hasNext()) {
-                FlowRule rule = switchRulesIterator.next();
+                FlowEntry rule = switchRulesIterator.next();
                 if (storedRules.remove(rule)) {
                     // we both have the rule, let's update some info then.
                     flowAdded(rule);
@@ -276,7 +322,7 @@
                     extraneousFlow(rule);
                 }
             }
-            for (FlowRule rule : storedRules) {
+            for (FlowEntry rule : storedRules) {
                 // there are rules in the store that aren't on the switch
                 flowMissing(rule);
 
@@ -291,4 +337,63 @@
             eventDispatcher.post(event);
         }
     }
+
+    private class FlowRuleBatchFuture
+        implements Future<CompletedBatchOperation> {
+
+        private final List<Future<Void>> futures;
+
+        public FlowRuleBatchFuture(List<Future<Void>> futures) {
+            this.futures = futures;
+        }
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            boolean isDone = true;
+            for (Future<Void> future : futures) {
+                isDone &= future.isDone();
+            }
+            return isDone;
+        }
+
+        @Override
+        public CompletedBatchOperation get() throws InterruptedException,
+        ExecutionException {
+            // TODO Auto-generated method stub
+            for (Future<Void> future : futures) {
+                future.get();
+            }
+            return new CompletedBatchOperation();
+        }
+
+        @Override
+        public CompletedBatchOperation get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            // TODO we should decrement the timeout
+            long start = System.nanoTime();
+            long end = start + unit.toNanos(timeout);
+            for (Future<Void> future : futures) {
+                long now = System.nanoTime();
+                long thisTimeout = end - now;
+                future.get(thisTimeout, TimeUnit.NANOSECONDS);
+            }
+            return new CompletedBatchOperation();
+        }
+
+    }
+
+
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
index 88b6923..29a0f18 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
@@ -76,7 +76,7 @@
         eventDispatcher.addSink(HostEvent.class, listenerRegistry);
 
         monitor = new HostMonitor(deviceService,  packetService, this);
-
+        monitor.start();
     }
 
     @Deactivate
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
index 9f8dd48..e6e348f 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
@@ -2,11 +2,11 @@
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
 
 import org.jboss.netty.util.Timeout;
@@ -33,8 +33,6 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.util.Timer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Monitors hosts on the dataplane to detect changes in host data.
@@ -44,70 +42,91 @@
  * probe for hosts that have not yet been detected (specified by IP address).
  */
 public class HostMonitor implements TimerTask {
-    private static final Logger log = LoggerFactory.getLogger(HostMonitor.class);
-
-    private static final byte[] ZERO_MAC_ADDRESS =
-            MacAddress.valueOf("00:00:00:00:00:00").getAddress();
-
-    // TODO put on Ethernet
-    private static final byte[] BROADCAST_MAC =
-            MacAddress.valueOf("ff:ff:ff:ff:ff:ff").getAddress();
-
     private DeviceService deviceService;
     private PacketService packetService;
     private HostManager hostManager;
 
     private final Set<IpAddress> monitoredAddresses;
 
-    private final Map<ProviderId, HostProvider> hostProviders;
+    private final ConcurrentMap<ProviderId, HostProvider> hostProviders;
 
-    private final long probeRate;
+    private static final long DEFAULT_PROBE_RATE = 30000; // milliseconds
+    private long probeRate = DEFAULT_PROBE_RATE;
 
-    private final Timeout timeout;
+    private Timeout timeout;
 
-    public HostMonitor(
-            DeviceService deviceService,
-            PacketService packetService,
-            HostManager hostService) {
+    /**
+     * Creates a new host monitor.
+     *
+     * @param deviceService device service used to find edge ports
+     * @param packetService packet service used to send packets on the data plane
+     * @param hostManager host manager used to look up host information and
+     * probe existing hosts
+     */
+    public HostMonitor(DeviceService deviceService, PacketService packetService,
+            HostManager hostManager) {
 
         this.deviceService = deviceService;
         this.packetService = packetService;
-        this.hostManager = hostService;
+        this.hostManager = hostManager;
 
-        monitoredAddresses = new HashSet<>();
+        monitoredAddresses = Collections.newSetFromMap(
+                new ConcurrentHashMap<IpAddress, Boolean>());
         hostProviders = new ConcurrentHashMap<>();
 
-        probeRate = 30000; // milliseconds
-
         timeout = Timer.getTimer().newTimeout(this, 0, TimeUnit.MILLISECONDS);
-
-        addDefaultAddresses();
     }
 
-    private void addDefaultAddresses() {
-        //monitoredAddresses.add(IpAddress.valueOf("10.0.0.1"));
-    }
-
+    /**
+     * Adds an IP address to be monitored by the host monitor. The monitor will
+     * periodically probe the host to detect changes.
+     *
+     * @param ip IP address of the host to monitor
+     */
     void addMonitoringFor(IpAddress ip) {
         monitoredAddresses.add(ip);
     }
 
+    /**
+     * Stops monitoring the given IP address.
+     *
+     * @param ip IP address to stop monitoring on
+     */
     void stopMonitoring(IpAddress ip) {
         monitoredAddresses.remove(ip);
     }
 
-    void shutdown() {
-        timeout.cancel();
+    /**
+     * Starts the host monitor. Does nothing if the monitor is already running.
+     */
+    void start() {
+        synchronized (this) {
+            if (timeout == null) {
+                timeout = Timer.getTimer().newTimeout(this, 0, TimeUnit.MILLISECONDS);
+            }
+        }
     }
 
+    /**
+     * Stops the host monitor.
+     */
+    void shutdown() {
+        synchronized (this) {
+            timeout.cancel();
+            timeout = null;
+        }
+    }
+
+    /**
+     * Registers a host provider with the host monitor. The monitor can use the
+     * provider to probe hosts.
+     *
+     * @param provider the host provider to register
+     */
     void registerHostProvider(HostProvider provider) {
         hostProviders.put(provider.id(), provider);
     }
 
-    void unregisterHostProvider(HostProvider provider) {
-        // TODO find out how to call this
-    }
-
     @Override
     public void run(Timeout timeout) throws Exception {
         for (IpAddress ip : monitoredAddresses) {
@@ -121,14 +140,16 @@
             } else {
                 for (Host host : hosts) {
                     HostProvider provider = hostProviders.get(host.providerId());
-                    if (provider != null) {
+                    if (provider == null) {
+                        hostProviders.remove(host.providerId(), null);
+                    } else {
                         provider.triggerProbe(host);
                     }
                 }
             }
         }
 
-        timeout = Timer.getTimer().newTimeout(this, probeRate, TimeUnit.MILLISECONDS);
+        this.timeout = Timer.getTimer().newTimeout(this, probeRate, TimeUnit.MILLISECONDS);
     }
 
     /**
@@ -161,7 +182,7 @@
         List<Instruction> instructions = new ArrayList<>();
         instructions.add(Instructions.createOutput(port.number()));
 
-        TrafficTreatment treatment = new DefaultTrafficTreatment.Builder()
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
         .setOutput(port.number())
         .build();
 
@@ -184,12 +205,12 @@
 
         arp.setSenderHardwareAddress(sourceMac.getAddress())
            .setSenderProtocolAddress(sourceIp.toOctets())
-           .setTargetHardwareAddress(ZERO_MAC_ADDRESS)
+           .setTargetHardwareAddress(MacAddress.ZERO_MAC_ADDRESS)
            .setTargetProtocolAddress(targetIp.toOctets());
 
         Ethernet ethernet = new Ethernet();
         ethernet.setEtherType(Ethernet.TYPE_ARP)
-                .setDestinationMACAddress(BROADCAST_MAC)
+                .setDestinationMACAddress(MacAddress.BROADCAST_MAC)
                 .setSourceMACAddress(sourceMac.getAddress())
                 .setPayload(arp);
 
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/AbstractBlockAllocatorBasedIdGenerator.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/AbstractBlockAllocatorBasedIdGenerator.java
new file mode 100644
index 0000000..00b64da
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/AbstractBlockAllocatorBasedIdGenerator.java
@@ -0,0 +1,42 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IdGenerator;
+
+/**
+ * Base class of {@link IdGenerator} implementations which use {@link IdBlockAllocator} as
+ * backend.
+ *
+ * @param <T> the type of ID
+ */
+public abstract class AbstractBlockAllocatorBasedIdGenerator<T> implements IdGenerator<T> {
+    protected final IdBlockAllocator allocator;
+    protected IdBlock idBlock;
+
+    /**
+     * Constructs an ID generator which use {@link IdBlockAllocator} as backend.
+     *
+     * @param allocator
+     */
+    protected AbstractBlockAllocatorBasedIdGenerator(IdBlockAllocator allocator) {
+        this.allocator = allocator;
+        this.idBlock = allocator.allocateUniqueIdBlock();
+    }
+
+    @Override
+    public synchronized T getNewId() {
+        try {
+            return convertFrom(idBlock.getNextId());
+        } catch (UnavailableIdException e) {
+            idBlock = allocator.allocateUniqueIdBlock();
+            return convertFrom(idBlock.getNextId());
+        }
+    }
+
+    /**
+     * Returns an ID instance of {@code T} type from the long value.
+     *
+     * @param value original long value
+     * @return ID instance
+     */
+    protected abstract T convertFrom(long value);
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/DummyIdBlockAllocator.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/DummyIdBlockAllocator.java
new file mode 100644
index 0000000..f331aa2
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/DummyIdBlockAllocator.java
@@ -0,0 +1,31 @@
+package org.onlab.onos.net.intent.impl;
+
+public class DummyIdBlockAllocator implements IdBlockAllocator {
+    private long blockTop;
+    private static final long BLOCK_SIZE = 0x1000000L;
+
+    /**
+     * Returns a block of IDs which are unique and unused.
+     * Range of IDs is fixed size and is assigned incrementally as this method
+     * called.
+     *
+     * @return an IdBlock containing a set of unique IDs
+     */
+    @Override
+    public IdBlock allocateUniqueIdBlock() {
+        synchronized (this)  {
+            long blockHead = blockTop;
+            long blockTail = blockTop + BLOCK_SIZE;
+
+            IdBlock block = new IdBlock(blockHead, BLOCK_SIZE);
+            blockTop = blockTail;
+
+            return block;
+        }
+    }
+
+    @Override
+    public IdBlock allocateUniqueIdBlock(long range) {
+        throw new UnsupportedOperationException("Not supported yet");
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
new file mode 100644
index 0000000..de61e8e
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
@@ -0,0 +1,90 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.HostToHostIntent;
+import org.onlab.onos.net.intent.IdGenerator;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.topology.PathService;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import static org.onlab.onos.net.flow.DefaultTrafficSelector.builder;
+
+/**
+ * A intent compiler for {@link HostToHostIntent}.
+ */
+@Component(immediate = true)
+public class HostToHostIntentCompiler
+        implements IntentCompiler<HostToHostIntent> {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PathService pathService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    private IdGenerator<IntentId> intentIdGenerator;
+
+    @Activate
+    public void activate() {
+        IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator();
+        intentIdGenerator = new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator);
+        intentManager.registerCompiler(HostToHostIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterCompiler(HostToHostIntent.class);
+    }
+
+    @Override
+    public List<Intent> compile(HostToHostIntent intent) {
+        Path pathOne = getPath(intent.one(), intent.two());
+        Path pathTwo = getPath(intent.two(), intent.one());
+
+        Host one = hostService.getHost(intent.one());
+        Host two = hostService.getHost(intent.two());
+
+        return Arrays.asList(createPathIntent(pathOne, one, two, intent),
+                             createPathIntent(pathTwo, two, one, intent));
+    }
+
+    // Creates a path intent from the specified path and original connectivity intent.
+    private Intent createPathIntent(Path path, Host src, Host dst,
+                                    HostToHostIntent intent) {
+
+        TrafficSelector selector = builder(intent.selector())
+                .matchEthSrc(src.mac()).matchEthDst(dst.mac()).build();
+
+        return new PathIntent(intentIdGenerator.getNewId(),
+                              selector, intent.treatment(),
+                              path.src(), path.dst(), path);
+    }
+
+    private Path getPath(HostId one, HostId two) {
+        Set<Path> paths = pathService.getPaths(one, two);
+        if (paths.isEmpty()) {
+            throw new PathNotFoundException("No path from host " + one + " to " + two);
+        }
+        // TODO: let's be more intelligent about this eventually
+        return paths.iterator().next();
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlock.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlock.java
new file mode 100644
index 0000000..ce418ea
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlock.java
@@ -0,0 +1,111 @@
+package org.onlab.onos.net.intent.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * A class representing an ID space.
+ */
+public final class IdBlock {
+    private final long start;
+    private final long size;
+
+    private final AtomicLong currentId;
+
+    /**
+     * Constructs a new ID block with the specified size and initial value.
+     *
+     * @param start initial value of the block
+     * @param size size of the block
+     * @throws IllegalArgumentException if the size is less than or equal to 0
+     */
+    public IdBlock(long start, long size) {
+        checkArgument(size > 0, "size should be more than 0, but %s", size);
+
+        this.start = start;
+        this.size = size;
+
+        this.currentId = new AtomicLong(start);
+    }
+
+    // TODO: consider if this method is needed or not
+    /**
+     * Returns the initial value.
+     *
+     * @return initial value
+     */
+    public long getStart() {
+        return start;
+    }
+
+    // TODO: consider if this method is needed or not
+    /**
+     * Returns the last value.
+     *
+     * @return last value
+     */
+    public long getEnd() {
+        return start + size - 1;
+    }
+
+    /**
+     * Returns the block size.
+     *
+     * @return block size
+     */
+    public long getSize() {
+        return size;
+    }
+
+    /**
+     * Returns the next ID in the block.
+     *
+     * @return next ID
+     * @throws UnavailableIdException if there is no available ID in the block.
+     */
+    public long getNextId() {
+        final long id = currentId.getAndIncrement();
+        if (id > getEnd()) {
+            throw new UnavailableIdException(String.format(
+                    "used all IDs in allocated space (size: %d, end: %d, current: %d)",
+                    size, getEnd(), id
+            ));
+        }
+
+        return id;
+    }
+
+    // TODO: Do we really need equals and hashCode? Should it contain currentId?
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        IdBlock that = (IdBlock) o;
+        return Objects.equals(this.start, that.start)
+                && Objects.equals(this.size, that.size)
+                && Objects.equals(this.currentId.get(), that.currentId.get());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(start, size, currentId);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("start", start)
+                .add("size", size)
+                .add("currentId", currentId)
+                .toString();
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocator.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocator.java
new file mode 100644
index 0000000..1adac02
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocator.java
@@ -0,0 +1,21 @@
+package org.onlab.onos.net.intent.impl;
+
+/**
+ * An interface that gives unique ID spaces.
+ */
+public interface IdBlockAllocator {
+    /**
+     * Allocates a unique Id Block.
+     *
+     * @return Id Block.
+     */
+    IdBlock allocateUniqueIdBlock();
+
+    /**
+     * Allocates next unique id and retrieve a new range of ids if needed.
+     *
+     * @param range range to use for the identifier
+     * @return Id Block.
+     */
+    IdBlock allocateUniqueIdBlock(long range);
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocatorBasedIntentIdGenerator.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocatorBasedIntentIdGenerator.java
new file mode 100644
index 0000000..9620e59
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocatorBasedIntentIdGenerator.java
@@ -0,0 +1,25 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentId;
+
+/**
+ * An implementation of {@link org.onlab.onos.net.intent.IdGenerator} of intent ID,
+ * which uses {@link IdBlockAllocator}.
+ */
+public class IdBlockAllocatorBasedIntentIdGenerator extends AbstractBlockAllocatorBasedIdGenerator<IntentId> {
+
+    /**
+     * Constructs an intent ID generator, which uses the specified ID block allocator
+     * to generate a global unique intent ID.
+     *
+     * @param allocator the ID block allocator to use for generating intent IDs
+     */
+    public IdBlockAllocatorBasedIntentIdGenerator(IdBlockAllocator allocator) {
+        super(allocator);
+    }
+
+    @Override
+    protected IntentId convertFrom(long value) {
+        return new IntentId(value);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentCompilationException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentCompilationException.java
new file mode 100644
index 0000000..bf739df
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentCompilationException.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentException;
+
+/**
+ * An exception thrown when a intent compilation fails.
+ */
+public class IntentCompilationException extends IntentException {
+    private static final long serialVersionUID = 235237603018210810L;
+
+    public IntentCompilationException() {
+        super();
+    }
+
+    public IntentCompilationException(String message) {
+        super(message);
+    }
+
+    public IntentCompilationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentInstallationException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentInstallationException.java
new file mode 100644
index 0000000..3b17cf1
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentInstallationException.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentException;
+
+/**
+ * An exception thrown when intent installation fails.
+ */
+public class IntentInstallationException extends IntentException {
+    private static final long serialVersionUID = 3720268258616014168L;
+
+    public IntentInstallationException() {
+        super();
+    }
+
+    public IntentInstallationException(String message) {
+        super(message);
+    }
+
+    public IntentInstallationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
new file mode 100644
index 0000000..16b75f2
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
@@ -0,0 +1,463 @@
+package org.onlab.onos.net.intent.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.onos.net.intent.IntentState.COMPILING;
+import static org.onlab.onos.net.intent.IntentState.FAILED;
+import static org.onlab.onos.net.intent.IntentState.INSTALLED;
+import static org.onlab.onos.net.intent.IntentState.INSTALLING;
+import static org.onlab.onos.net.intent.IntentState.RECOMPILING;
+import static org.onlab.onos.net.intent.IntentState.WITHDRAWING;
+import static org.onlab.onos.net.intent.IntentState.WITHDRAWN;
+import static org.onlab.util.Tools.namedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.event.AbstractListenerRegistry;
+import org.onlab.onos.event.EventDeliveryService;
+import org.onlab.onos.net.intent.InstallableIntent;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentEvent;
+import org.onlab.onos.net.intent.IntentException;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentInstaller;
+import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.IntentOperations;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.IntentStore;
+import org.onlab.onos.net.intent.IntentStoreDelegate;
+import org.slf4j.Logger;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * An implementation of Intent Manager.
+ */
+@Component(immediate = true)
+@Service
+public class IntentManager
+        implements IntentService, IntentExtensionService {
+    private final Logger log = getLogger(getClass());
+
+    public static final String INTENT_NULL = "Intent cannot be null";
+    public static final String INTENT_ID_NULL = "Intent ID cannot be null";
+
+    // Collections for compiler, installer, and listener are ONOS instance local
+    private final ConcurrentMap<Class<? extends Intent>,
+            IntentCompiler<? extends Intent>> compilers = new ConcurrentHashMap<>();
+    private final ConcurrentMap<Class<? extends InstallableIntent>,
+            IntentInstaller<? extends InstallableIntent>> installers = new ConcurrentHashMap<>();
+
+    private final AbstractListenerRegistry<IntentEvent, IntentListener>
+            listenerRegistry = new AbstractListenerRegistry<>();
+
+    private final ExecutorService executor = newSingleThreadExecutor(namedThreads("onos-intents"));
+
+    private final IntentStoreDelegate delegate = new InternalStoreDelegate();
+    private final TopologyChangeDelegate topoDelegate = new InternalTopoChangeDelegate();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentStore store;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ObjectiveTrackerService trackerService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected EventDeliveryService eventDispatcher;
+
+    @Activate
+    public void activate() {
+        store.setDelegate(delegate);
+        trackerService.setDelegate(topoDelegate);
+        eventDispatcher.addSink(IntentEvent.class, listenerRegistry);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        store.unsetDelegate(delegate);
+        trackerService.unsetDelegate(topoDelegate);
+        eventDispatcher.removeSink(IntentEvent.class);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void submit(Intent intent) {
+        checkNotNull(intent, INTENT_NULL);
+        registerSubclassCompilerIfNeeded(intent);
+        IntentEvent event = store.createIntent(intent);
+        if (event != null) {
+            eventDispatcher.post(event);
+            executor.execute(new IntentTask(COMPILING, intent));
+        }
+    }
+
+    @Override
+    public void withdraw(Intent intent) {
+        checkNotNull(intent, INTENT_NULL);
+        executor.execute(new IntentTask(WITHDRAWING, intent));
+    }
+
+    // FIXME: implement this method
+    @Override
+    public void execute(IntentOperations operations) {
+        throw new UnsupportedOperationException("execute() is not implemented yet");
+    }
+
+    @Override
+    public Iterable<Intent> getIntents() {
+        return store.getIntents();
+    }
+
+    @Override
+    public long getIntentCount() {
+        return store.getIntentCount();
+    }
+
+    @Override
+    public Intent getIntent(IntentId id) {
+        checkNotNull(id, INTENT_ID_NULL);
+        return store.getIntent(id);
+    }
+
+    @Override
+    public IntentState getIntentState(IntentId id) {
+        checkNotNull(id, INTENT_ID_NULL);
+        return store.getIntentState(id);
+    }
+
+    @Override
+    public void addListener(IntentListener listener) {
+        listenerRegistry.addListener(listener);
+    }
+
+    @Override
+    public void removeListener(IntentListener listener) {
+        listenerRegistry.removeListener(listener);
+    }
+
+    @Override
+    public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) {
+        compilers.put(cls, compiler);
+    }
+
+    @Override
+    public <T extends Intent> void unregisterCompiler(Class<T> cls) {
+        compilers.remove(cls);
+    }
+
+    @Override
+    public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() {
+        return ImmutableMap.copyOf(compilers);
+    }
+
+    @Override
+    public <T extends InstallableIntent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
+        installers.put(cls, installer);
+    }
+
+    @Override
+    public <T extends InstallableIntent> void unregisterInstaller(Class<T> cls) {
+        installers.remove(cls);
+    }
+
+    @Override
+    public Map<Class<? extends InstallableIntent>, IntentInstaller<? extends InstallableIntent>> getInstallers() {
+        return ImmutableMap.copyOf(installers);
+    }
+
+    /**
+     * Returns the corresponding intent compiler to the specified intent.
+     *
+     * @param intent intent
+     * @param <T>    the type of intent
+     * @return intent compiler corresponding to the specified intent
+     */
+    private <T extends Intent> IntentCompiler<T> getCompiler(T intent) {
+        @SuppressWarnings("unchecked")
+        IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass());
+        if (compiler == null) {
+            throw new IntentException("no compiler for class " + intent.getClass());
+        }
+        return compiler;
+    }
+
+    /**
+     * Returns the corresponding intent installer to the specified installable intent.
+     *
+     * @param intent intent
+     * @param <T>    the type of installable intent
+     * @return intent installer corresponding to the specified installable intent
+     */
+    private <T extends InstallableIntent> IntentInstaller<T> getInstaller(T intent) {
+        @SuppressWarnings("unchecked")
+        IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass());
+        if (installer == null) {
+            throw new IntentException("no installer for class " + intent.getClass());
+        }
+        return installer;
+    }
+
+    /**
+     * Compiles the specified intent.
+     *
+     * @param intent intent to be compiled
+     */
+    private void executeCompilingPhase(Intent intent) {
+        // Indicate that the intent is entering the compiling phase.
+        store.setState(intent, COMPILING);
+
+        try {
+            // Compile the intent into installable derivatives.
+            List<InstallableIntent> installable = compileIntent(intent);
+
+            // If all went well, associate the resulting list of installable
+            // intents with the top-level intent and proceed to install.
+            store.addInstallableIntents(intent.id(), installable);
+            executeInstallingPhase(intent);
+
+        } catch (Exception e) {
+            log.warn("Unable to compile intent {} due to: {}", intent.id(), e);
+
+            // If compilation failed, mark the intent as failed.
+            store.setState(intent, FAILED);
+        }
+    }
+
+    // FIXME: To make SDN-IP workable ASAP, only single level compilation is implemented
+    // TODO: implement compilation traversing tree structure
+    private List<InstallableIntent> compileIntent(Intent intent) {
+        List<InstallableIntent> installable = new ArrayList<>();
+        for (Intent compiled : getCompiler(intent).compile(intent)) {
+            InstallableIntent installableIntent = (InstallableIntent) compiled;
+            installable.add(installableIntent);
+        }
+        return installable;
+    }
+
+    /**
+     * Installs all installable intents associated with the specified top-level
+     * intent.
+     *
+     * @param intent intent to be installed
+     */
+    private void executeInstallingPhase(Intent intent) {
+        // Indicate that the intent is entering the installing phase.
+        store.setState(intent, INSTALLING);
+
+        try {
+            List<InstallableIntent> installables = store.getInstallableIntents(intent.id());
+            if (installables != null) {
+                for (InstallableIntent installable : installables) {
+                    registerSubclassInstallerIfNeeded(installable);
+                    trackerService.addTrackedResources(intent.id(),
+                                                       installable.requiredLinks());
+                    getInstaller(installable).install(installable);
+                }
+            }
+            eventDispatcher.post(store.setState(intent, INSTALLED));
+
+        } catch (Exception e) {
+            log.warn("Unable to install intent {} due to: {}", intent.id(), e);
+            uninstallIntent(intent);
+
+            // If compilation failed, kick off the recompiling phase.
+            executeRecompilingPhase(intent);
+        }
+    }
+
+    /**
+     * Recompiles the specified intent.
+     *
+     * @param intent intent to be recompiled
+     */
+    private void executeRecompilingPhase(Intent intent) {
+        // Indicate that the intent is entering the recompiling phase.
+        store.setState(intent, RECOMPILING);
+
+        try {
+            // Compile the intent into installable derivatives.
+            List<InstallableIntent> installable = compileIntent(intent);
+
+            // If all went well, compare the existing list of installable
+            // intents with the newly compiled list. If they are the same,
+            // bail, out since the previous approach was determined not to
+            // be viable.
+            List<InstallableIntent> originalInstallable =
+                    store.getInstallableIntents(intent.id());
+
+            if (Objects.equals(originalInstallable, installable)) {
+                eventDispatcher.post(store.setState(intent, FAILED));
+            } else {
+                // Otherwise, re-associate the newly compiled installable intents
+                // with the top-level intent and kick off installing phase.
+                store.addInstallableIntents(intent.id(), installable);
+                executeInstallingPhase(intent);
+            }
+        } catch (Exception e) {
+            log.warn("Unable to recompile intent {} due to: {}", intent.id(), e);
+
+            // If compilation failed, mark the intent as failed.
+            eventDispatcher.post(store.setState(intent, FAILED));
+        }
+    }
+
+    /**
+     * Uninstalls the specified intent by uninstalling all of its associated
+     * installable derivatives.
+     *
+     * @param intent intent to be installed
+     */
+    private void executeWithdrawingPhase(Intent intent) {
+        // Indicate that the intent is being withdrawn.
+        store.setState(intent, WITHDRAWING);
+        uninstallIntent(intent);
+
+        // If all went well, disassociate the top-level intent with its
+        // installable derivatives and mark it as withdrawn.
+        store.removeInstalledIntents(intent.id());
+        eventDispatcher.post(store.setState(intent, WITHDRAWN));
+    }
+
+    /**
+     * Uninstalls all installable intents associated with the given intent.
+     *
+     * @param intent intent to be uninstalled
+     */
+    private void uninstallIntent(Intent intent) {
+        try {
+            List<InstallableIntent> installables = store.getInstallableIntents(intent.id());
+            if (installables != null) {
+                for (InstallableIntent installable : installables) {
+                    getInstaller(installable).uninstall(installable);
+                }
+            }
+        } catch (IntentException e) {
+            log.warn("Unable to uninstall intent {} due to: {}", intent.id(), e);
+        }
+    }
+
+    /**
+     * Registers an intent compiler of the specified intent if an intent compiler
+     * for the intent is not registered. This method traverses the class hierarchy of
+     * the intent. Once an intent compiler for a parent type is found, this method
+     * registers the found intent compiler.
+     *
+     * @param intent intent
+     */
+    private void registerSubclassCompilerIfNeeded(Intent intent) {
+        if (!compilers.containsKey(intent.getClass())) {
+            Class<?> cls = intent.getClass();
+            while (cls != Object.class) {
+                // As long as we're within the Intent class descendants
+                if (Intent.class.isAssignableFrom(cls)) {
+                    IntentCompiler<?> compiler = compilers.get(cls);
+                    if (compiler != null) {
+                        compilers.put(intent.getClass(), compiler);
+                        return;
+                    }
+                }
+                cls = cls.getSuperclass();
+            }
+        }
+    }
+
+    /**
+     * Registers an intent installer of the specified intent if an intent installer
+     * for the intent is not registered. This method traverses the class hierarchy of
+     * the intent. Once an intent installer for a parent type is found, this method
+     * registers the found intent installer.
+     *
+     * @param intent intent
+     */
+    private void registerSubclassInstallerIfNeeded(InstallableIntent intent) {
+        if (!installers.containsKey(intent.getClass())) {
+            Class<?> cls = intent.getClass();
+            while (cls != Object.class) {
+                // As long as we're within the InstallableIntent class descendants
+                if (InstallableIntent.class.isAssignableFrom(cls)) {
+                    IntentInstaller<?> installer = installers.get(cls);
+                    if (installer != null) {
+                        installers.put(intent.getClass(), installer);
+                        return;
+                    }
+                }
+                cls = cls.getSuperclass();
+            }
+        }
+    }
+
+    // Store delegate to re-post events emitted from the store.
+    private class InternalStoreDelegate implements IntentStoreDelegate {
+        @Override
+        public void notify(IntentEvent event) {
+            eventDispatcher.post(event);
+            if (event.type() == IntentEvent.Type.SUBMITTED) {
+                executor.execute(new IntentTask(COMPILING, event.subject()));
+            }
+        }
+    }
+
+    // Topology change delegate
+    private class InternalTopoChangeDelegate implements TopologyChangeDelegate {
+        @Override
+        public void triggerCompile(Iterable<IntentId> intentIds,
+                                   boolean compileAllFailed) {
+            // Attempt recompilation of the specified intents first.
+            for (IntentId intentId : intentIds) {
+                Intent intent = getIntent(intentId);
+                uninstallIntent(intent);
+
+                executeRecompilingPhase(intent);
+            }
+
+            if (compileAllFailed) {
+                // If required, compile all currently failed intents.
+                for (Intent intent : getIntents()) {
+                    if (getIntentState(intent.id()) == FAILED) {
+                        executeCompilingPhase(intent);
+                    }
+                }
+            }
+        }
+    }
+
+    // Auxiliary runnable to perform asynchronous tasks.
+    private class IntentTask implements Runnable {
+        private final IntentState state;
+        private final Intent intent;
+
+        public IntentTask(IntentState state, Intent intent) {
+            this.state = state;
+            this.intent = intent;
+        }
+
+        @Override
+        public void run() {
+            if (state == COMPILING) {
+                executeCompilingPhase(intent);
+            } else if (state == RECOMPILING) {
+                executeRecompilingPhase(intent);
+            } else if (state == WITHDRAWING) {
+                executeWithdrawingPhase(intent);
+            }
+        }
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentRemovalException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentRemovalException.java
new file mode 100644
index 0000000..5ee4ee4
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentRemovalException.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentException;
+
+/**
+ * An exception thrown when intent removal failed.
+ */
+public class IntentRemovalException extends IntentException {
+    private static final long serialVersionUID = -5259226322037891951L;
+
+    public IntentRemovalException() {
+        super();
+    }
+
+    public IntentRemovalException(String message) {
+        super(message);
+    }
+
+    public IntentRemovalException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTracker.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTracker.java
new file mode 100644
index 0000000..d84c367
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTracker.java
@@ -0,0 +1,140 @@
+package org.onlab.onos.net.intent.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.event.Event;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.LinkKey;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.link.LinkEvent;
+import org.onlab.onos.net.topology.TopologyEvent;
+import org.onlab.onos.net.topology.TopologyListener;
+import org.onlab.onos.net.topology.TopologyService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
+import static org.onlab.util.Tools.namedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Entity responsible for tracking installed flows and for monitoring topology
+ * events to determine what flows are affected by topology changes.
+ */
+@Component
+@Service
+public class ObjectiveTracker implements ObjectiveTrackerService {
+
+    private final Logger log = getLogger(getClass());
+
+    private final SetMultimap<LinkKey, IntentId> intentsByLink =
+            synchronizedSetMultimap(HashMultimap.<LinkKey, IntentId>create());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected TopologyService topologyService;
+
+    private ExecutorService executorService =
+            newSingleThreadExecutor(namedThreads("onos-flowtracker"));
+
+    private TopologyListener listener = new InternalTopologyListener();
+    private TopologyChangeDelegate delegate;
+
+    @Activate
+    public void activate() {
+        topologyService.addListener(listener);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        topologyService.removeListener(listener);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void setDelegate(TopologyChangeDelegate delegate) {
+        checkNotNull(delegate, "Delegate cannot be null");
+        checkArgument(this.delegate == null || this.delegate == delegate,
+                      "Another delegate already set");
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void unsetDelegate(TopologyChangeDelegate delegate) {
+        checkArgument(this.delegate == delegate, "Not the current delegate");
+        this.delegate = null;
+    }
+
+    @Override
+    public void addTrackedResources(IntentId intentId, Collection<Link> resources) {
+        for (Link link : resources) {
+            intentsByLink.put(new LinkKey(link), intentId);
+        }
+    }
+
+    @Override
+    public void removeTrackedResources(IntentId intentId, Collection<Link> resources) {
+        for (Link link : resources) {
+            intentsByLink.remove(new LinkKey(link), intentId);
+        }
+    }
+
+    // Internal re-actor to topology change events.
+    private class InternalTopologyListener implements TopologyListener {
+        @Override
+        public void event(TopologyEvent event) {
+            executorService.execute(new TopologyChangeHandler(event));
+        }
+    }
+
+    // Re-dispatcher of topology change events.
+    private class TopologyChangeHandler implements Runnable {
+
+        private final TopologyEvent event;
+
+        TopologyChangeHandler(TopologyEvent event) {
+            this.event = event;
+        }
+
+        @Override
+        public void run() {
+            if (event.reasons() == null) {
+                delegate.triggerCompile(new HashSet<IntentId>(), true);
+
+            } else {
+                Set<IntentId> toBeRecompiled = new HashSet<>();
+                boolean recompileOnly = true;
+
+                // Scan through the list of reasons and keep accruing all
+                // intents that need to be recompiled.
+                for (Event reason : event.reasons()) {
+                    if (reason instanceof LinkEvent) {
+                        LinkEvent linkEvent = (LinkEvent) reason;
+                        if (linkEvent.type() == LINK_REMOVED) {
+                            Set<IntentId> intentIds = intentsByLink.get(new LinkKey(linkEvent.subject()));
+                            toBeRecompiled.addAll(intentIds);
+                        }
+                        recompileOnly = recompileOnly && linkEvent.type() == LINK_REMOVED;
+                    }
+                }
+
+                delegate.triggerCompile(toBeRecompiled, !recompileOnly);
+            }
+        }
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTrackerService.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTrackerService.java
new file mode 100644
index 0000000..15496ff
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTrackerService.java
@@ -0,0 +1,44 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.intent.IntentId;
+
+import java.util.Collection;
+
+/**
+ * Auxiliary service for tracking intent path flows and for notifying the
+ * intent service of environment changes via topology change delegate.
+ */
+public interface ObjectiveTrackerService {
+
+    /**
+     * Sets a topology change delegate.
+     *
+     * @param delegate topology change delegate
+     */
+    void setDelegate(TopologyChangeDelegate delegate);
+
+    /**
+     * Unsets topology change delegate.
+     *
+     * @param delegate topology change delegate
+     */
+    void unsetDelegate(TopologyChangeDelegate delegate);
+
+    /**
+     * Adds a path flow to be tracked.
+     *
+     * @param intentId intent identity on whose behalf the path is being tracked
+     * @param resources resources to track
+     */
+    public void addTrackedResources(IntentId intentId, Collection<Link> resources);
+
+    /**
+     * Removes a path flow to be tracked.
+     *
+     * @param intentId intent identity on whose behalf the path is being tracked
+     * @param resources resources to stop tracking
+     */
+    public void removeTrackedResources(IntentId intentId, Collection<Link> resources);
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
new file mode 100644
index 0000000..0ca75c2
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
@@ -0,0 +1,117 @@
+package org.onlab.onos.net.intent.impl;
+
+import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.flow.DefaultFlowRule;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.flow.FlowRuleService;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentInstaller;
+import org.onlab.onos.net.intent.PathIntent;
+import org.slf4j.Logger;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Installer for {@link PathIntent path connectivity intents}.
+ */
+@Component(immediate = true)
+public class PathIntentInstaller implements IntentInstaller<PathIntent> {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    private final ApplicationId appId = ApplicationId.getAppId();
+
+    @Activate
+    public void activate() {
+        intentManager.registerInstaller(PathIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterInstaller(PathIntent.class);
+    }
+
+    @Override
+    public void install(PathIntent intent) {
+        TrafficSelector.Builder builder =
+                DefaultTrafficSelector.builder(intent.selector());
+        Iterator<Link> links = intent.path().links().iterator();
+        ConnectPoint prev = links.next().dst();
+        List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+        while (links.hasNext()) {
+            builder.matchInport(prev.port());
+            Link link = links.next();
+            TrafficTreatment treatment = builder()
+                    .setOutput(link.src().port()).build();
+
+            FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
+                    builder.build(), treatment,
+                    123, appId, 600);
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule));
+            //flowRuleService.applyFlowRules(rule);
+            prev = link.dst();
+        }
+        FlowRuleBatchOperation batch = new FlowRuleBatchOperation(rules);
+        try {
+            flowRuleService.applyBatch(batch).get();
+        } catch (InterruptedException | ExecutionException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void uninstall(PathIntent intent) {
+        TrafficSelector.Builder builder =
+                DefaultTrafficSelector.builder(intent.selector());
+        Iterator<Link> links = intent.path().links().iterator();
+        ConnectPoint prev = links.next().dst();
+        List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+
+        while (links.hasNext()) {
+            builder.matchInport(prev.port());
+            Link link = links.next();
+            TrafficTreatment treatment = builder()
+                    .setOutput(link.src().port()).build();
+            FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
+                    builder.build(), treatment,
+                    123, appId, 600);
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, rule));
+            //flowRuleService.removeFlowRules(rule);
+            prev = link.dst();
+        }
+        FlowRuleBatchOperation batch = new FlowRuleBatchOperation(rules);
+        try {
+            flowRuleService.applyBatch(batch).get();
+        } catch (InterruptedException | ExecutionException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathNotFoundException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathNotFoundException.java
new file mode 100644
index 0000000..a1fd63a
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathNotFoundException.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentException;
+
+/**
+ * An exception thrown when a path is not found.
+ */
+public class PathNotFoundException extends IntentException {
+    private static final long serialVersionUID = -2087045731049914733L;
+
+    public PathNotFoundException() {
+        super();
+    }
+
+    public PathNotFoundException(String message) {
+        super(message);
+    }
+
+    public PathNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
new file mode 100644
index 0000000..0bd1703
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
@@ -0,0 +1,105 @@
+package org.onlab.onos.net.intent.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultEdgeLink;
+import org.onlab.onos.net.DefaultPath;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.IdGenerator;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.intent.PointToPointIntent;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.net.topology.PathService;
+
+/**
+ * A intent compiler for {@link org.onlab.onos.net.intent.HostToHostIntent}.
+ */
+@Component(immediate = true)
+public class PointToPointIntentCompiler
+        implements IntentCompiler<PointToPointIntent> {
+
+    private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PathService pathService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    private IdGenerator<IntentId> intentIdGenerator;
+
+    @Activate
+    public void activate() {
+        IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator();
+        intentIdGenerator = new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator);
+        intentManager.registerCompiler(PointToPointIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterCompiler(PointToPointIntent.class);
+    }
+
+    @Override
+    public List<Intent> compile(PointToPointIntent intent) {
+        Path path = getPath(intent.ingressPoint(), intent.egressPoint());
+
+        List<Link> links = new ArrayList<>();
+        links.add(DefaultEdgeLink.createEdgeLink(intent.ingressPoint(), true));
+        links.addAll(path.links());
+        links.add(DefaultEdgeLink.createEdgeLink(intent.egressPoint(), false));
+
+        return Arrays.asList(createPathIntent(new DefaultPath(PID, links, path.cost() + 2,
+                                              path.annotations()),
+                             intent));
+    }
+
+    /**
+     * Creates a path intent from the specified path and original
+     * connectivity intent.
+     *
+     * @param path path to create an intent for
+     * @param intent original intent
+     */
+    private Intent createPathIntent(Path path,
+                                    PointToPointIntent intent) {
+
+        return new PathIntent(intentIdGenerator.getNewId(),
+                              intent.selector(), intent.treatment(),
+                              path.src(), path.dst(), path);
+    }
+
+    /**
+     * Computes a path between two ConnectPoints.
+     *
+     * @param one start of the path
+     * @param two end of the path
+     * @return Path between the two
+     * @throws PathNotFoundException if a path cannot be found
+     */
+    private Path getPath(ConnectPoint one, ConnectPoint two) {
+        Set<Path> paths = pathService.getPaths(one.deviceId(), two.deviceId());
+        if (paths.isEmpty()) {
+            throw new PathNotFoundException("No path from " + one + " to " + two);
+        }
+        // TODO: let's be more intelligent about this eventually
+        return paths.iterator().next();
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/TopologyChangeDelegate.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/TopologyChangeDelegate.java
new file mode 100644
index 0000000..30e6899
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/TopologyChangeDelegate.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentId;
+
+/**
+ * Auxiliary delegate for integration of intent manager and flow trackerService.
+ */
+public interface TopologyChangeDelegate {
+
+    /**
+     * Notifies that topology has changed in such a way that the specified
+     * intents should be recompiled. If the {@code compileAllFailed} parameter
+     * is true, then all intents in {@link org.onlab.onos.net.intent.IntentState#FAILED}
+     * state should be compiled as well.
+     *
+     * @param intentIds intents that should be recompiled
+     * @param compileAllFailed true implies full compile of all failed intents
+     *                         is required; false for selective recompile only
+     */
+    void triggerCompile(Iterable<IntentId> intentIds, boolean compileAllFailed);
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/UnavailableIdException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/UnavailableIdException.java
new file mode 100644
index 0000000..fd4f122
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/UnavailableIdException.java
@@ -0,0 +1,34 @@
+package org.onlab.onos.net.intent.impl;
+
+/**
+ * Represents that there is no available IDs.
+ */
+public class UnavailableIdException extends RuntimeException {
+
+    private static final long serialVersionUID = -2287403908433720122L;
+
+    /**
+     * Constructs an exception with no message and no underlying cause.
+     */
+    public UnavailableIdException() {
+    }
+
+    /**
+     * Constructs an exception with the specified message.
+     *
+     * @param message the message describing the specific nature of the error
+     */
+    public UnavailableIdException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs an exception with the specified message and the underlying cause.
+     *
+     * @param message the message describing the specific nature of the error
+     * @param cause the underlying cause of this error
+     */
+    public UnavailableIdException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/package-info.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/package-info.java
new file mode 100644
index 0000000..3f00271
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Core subsystem for tracking high-level intents for treatment of selected
+ * network traffic.
+ */
+package org.onlab.onos.net.intent.impl;
\ No newline at end of file
diff --git a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
index 6e07c3e..4933322 100644
--- a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
@@ -31,6 +31,8 @@
 import org.onlab.onos.net.link.LinkListener;
 import org.onlab.onos.net.link.LinkService;
 import org.onlab.onos.net.packet.DefaultOutboundPacket;
+import org.onlab.onos.net.packet.InboundPacket;
+import org.onlab.onos.net.packet.PacketContext;
 import org.onlab.onos.net.packet.PacketService;
 import org.onlab.onos.net.proxyarp.ProxyArpService;
 import org.onlab.packet.ARP;
@@ -43,7 +45,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 
-
 @Component(immediate = true)
 @Service
 public class ProxyArpManager implements ProxyArpService {
@@ -93,14 +94,14 @@
 
     @Override
     public boolean known(IpPrefix addr) {
-        checkNotNull(MAC_ADDR_NULL, addr);
+        checkNotNull(addr, MAC_ADDR_NULL);
         Set<Host> hosts = hostService.getHostsByIp(addr);
         return !hosts.isEmpty();
     }
 
     @Override
     public void reply(Ethernet eth) {
-        checkNotNull(REQUEST_NULL, eth);
+        checkNotNull(eth, REQUEST_NULL);
         checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
                 REQUEST_NOT_ARP);
         ARP arp = (ARP) eth.getPayload();
@@ -128,7 +129,7 @@
 
         Ethernet arpReply = buildArpReply(dst, eth);
         // TODO: check send status with host service.
-        TrafficTreatment.Builder builder = new DefaultTrafficTreatment.Builder();
+        TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
         builder.setOutput(src.location().port());
         packetService.emit(new DefaultOutboundPacket(src.location().deviceId(),
                 builder.build(), ByteBuffer.wrap(arpReply.serialize())));
@@ -136,7 +137,7 @@
 
     @Override
     public void forward(Ethernet eth) {
-        checkNotNull(REQUEST_NULL, eth);
+        checkNotNull(eth, REQUEST_NULL);
         checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
                 REQUEST_NOT_ARP);
         ARP arp = (ARP) eth.getPayload();
@@ -148,7 +149,7 @@
         if (h == null) {
             flood(eth);
         } else {
-            TrafficTreatment.Builder builder = new DefaultTrafficTreatment.Builder();
+            TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
             builder.setOutput(h.location().port());
             packetService.emit(new DefaultOutboundPacket(h.location().deviceId(),
                     builder.build(), ByteBuffer.wrap(eth.serialize())));
@@ -156,6 +157,23 @@
 
     }
 
+    @Override
+    public boolean handleArp(PacketContext context) {
+        InboundPacket pkt = context.inPacket();
+        Ethernet ethPkt = pkt.parsed();
+        if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
+            ARP arp = (ARP) ethPkt.getPayload();
+            if (arp.getOpCode() == ARP.OP_REPLY) {
+                forward(ethPkt);
+            } else if (arp.getOpCode() == ARP.OP_REQUEST) {
+                reply(ethPkt);
+            }
+            context.block();
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Flood the arp request at all edges in the network.
      * @param request the arp request.
@@ -166,7 +184,7 @@
 
         synchronized (externalPorts) {
             for (Entry<Device, PortNumber> entry : externalPorts.entries()) {
-                builder = new DefaultTrafficTreatment.Builder();
+                builder = DefaultTrafficTreatment.builder();
                 builder.setOutput(entry.getValue());
                 packetService.emit(new DefaultOutboundPacket(entry.getKey().id(),
                         builder.build(), buf));
@@ -188,12 +206,12 @@
             for (Link l : links) {
                 // for each link, mark the concerned ports as internal
                 // and the remaining ports are therefore external.
-                if (l.src().deviceId().equals(d)
+                if (l.src().deviceId().equals(d.id())
                         && ports.contains(l.src().port())) {
                     ports.remove(l.src().port());
                     internalPorts.put(d, l.src().port());
                 }
-                if (l.dst().deviceId().equals(d)
+                if (l.dst().deviceId().equals(d.id())
                         && ports.contains(l.dst().port())) {
                     ports.remove(l.dst().port());
                     internalPorts.put(d, l.dst().port());
@@ -322,7 +340,6 @@
 
         }
 
-}
-
+    }
 
 }