Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/config/src/main/java/org/onlab/onos/config/AddressEntry.java b/apps/config/src/main/java/org/onlab/onos/config/AddressEntry.java
index 318aebd..081efed 100644
--- a/apps/config/src/main/java/org/onlab/onos/config/AddressEntry.java
+++ b/apps/config/src/main/java/org/onlab/onos/config/AddressEntry.java
@@ -3,8 +3,6 @@
 import java.util.List;
 
 import org.codehaus.jackson.annotate.JsonProperty;
-import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MacAddress;
 
 /**
  * Represents a set of addresses bound to a port.
@@ -12,8 +10,8 @@
 public class AddressEntry {
     private String dpid;
     private short portNumber;
-    private List<IpPrefix> ipAddresses;
-    private MacAddress macAddress;
+    private List<String> ipAddresses;
+    private String macAddress;
 
     public String getDpid() {
         return dpid;
@@ -33,21 +31,21 @@
         this.portNumber = portNumber;
     }
 
-    public List<IpPrefix> getIpAddresses() {
+    public List<String> getIpAddresses() {
         return ipAddresses;
     }
 
     @JsonProperty("ips")
-    public void setIpAddresses(List<IpPrefix> ipAddresses) {
-        this.ipAddresses = ipAddresses;
+    public void setIpAddresses(List<String> strIps) {
+        this.ipAddresses = strIps;
     }
 
-    public MacAddress getMacAddress() {
+    public String getMacAddress() {
         return macAddress;
     }
 
     @JsonProperty("mac")
-    public void setMacAddress(MacAddress macAddress) {
+    public void setMacAddress(String macAddress) {
         this.macAddress = macAddress;
     }
 }
diff --git a/apps/config/src/main/java/org/onlab/onos/config/NetworkConfigReader.java b/apps/config/src/main/java/org/onlab/onos/config/NetworkConfigReader.java
index 985c4a2..4f1a48a 100644
--- a/apps/config/src/main/java/org/onlab/onos/config/NetworkConfigReader.java
+++ b/apps/config/src/main/java/org/onlab/onos/config/NetworkConfigReader.java
@@ -5,6 +5,8 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -17,10 +19,10 @@
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.host.HostAdminService;
 import org.onlab.onos.net.host.PortAddresses;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.slf4j.Logger;
 
-import com.google.common.collect.Sets;
-
 /**
  * Simple configuration module to read in supplementary network configuration
  * from a file.
@@ -51,9 +53,29 @@
                         DeviceId.deviceId(dpidToUri(entry.getDpid())),
                         PortNumber.portNumber(entry.getPortNumber()));
 
+                Set<IpPrefix> ipAddresses = new HashSet<IpPrefix>();
+
+                for (String strIp : entry.getIpAddresses()) {
+                    try {
+                        IpPrefix address = IpPrefix.valueOf(strIp);
+                        ipAddresses.add(address);
+                    } catch (IllegalArgumentException e) {
+                        log.warn("Bad format for IP address in config: {}", strIp);
+                    }
+                }
+
+                MacAddress macAddress = null;
+                if (entry.getMacAddress() != null) {
+                    try {
+                        macAddress = MacAddress.valueOf(entry.getMacAddress());
+                    } catch (IllegalArgumentException e) {
+                        log.warn("Bad format for MAC address in config: {}",
+                                entry.getMacAddress());
+                    }
+                }
+
                 PortAddresses addresses = new PortAddresses(cp,
-                        Sets.newHashSet(entry.getIpAddresses()),
-                        entry.getMacAddress());
+                        ipAddresses, macAddress);
 
                 hostAdminService.bindAddressesToPort(addresses);
             }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
index 12bd02b..ad34a2c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
@@ -1,4 +1,5 @@
 package org.onlab.onos.net.intent;
+//TODO is this the right package?
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
index 781e3d1..b5dfa88 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
@@ -1,4 +1,5 @@
 package org.onlab.onos.net.intent;
+//TODO is this the right package?
 
 import java.util.Objects;
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java
index 5583240..c678f31 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java
@@ -1,4 +1,5 @@
 package org.onlab.onos.net.intent;
+//TODO is this the right package?
 
 /**
  * An interface of the class which is assigned to BatchOperation.
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IdGenerator.java b/core/api/src/main/java/org/onlab/onos/net/intent/IdGenerator.java
new file mode 100644
index 0000000..0bba622
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IdGenerator.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * A generalized interface for ID generation
+ *
+ * {@link #getNewId()} generates a globally unique ID instance on
+ * each invocation.
+ *
+ * @param <T> the type of ID
+ */
+// TODO: do we need to define a base marker interface for ID,
+// then changed the type parameter to <T extends BaseId> something
+// like that?
+public interface IdGenerator<T> {
+    /**
+     * Returns a globally unique ID instance.
+     *
+     * @return globally unique ID instance
+     */
+    T getNewId();
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java
new file mode 100644
index 0000000..fb1efee
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java
@@ -0,0 +1,28 @@
+package org.onlab.onos.net.intent;
+
+import java.util.Set;
+
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+
+/**
+ * Base facilities to test various connectivity tests.
+ */
+public abstract class ConnectivityIntentTest extends IntentTest {
+
+    public static final IntentId IID = new IntentId(123);
+    public static final TrafficSelector MATCH = (new DefaultTrafficSelector.Builder()).build();
+    public static final TrafficTreatment NOP = (new DefaultTrafficTreatment.Builder()).build();
+
+    public static final ConnectPoint P1 = new ConnectPoint(DeviceId.deviceId("111"), PortNumber.portNumber(0x1));
+    public static final ConnectPoint P2 = new ConnectPoint(DeviceId.deviceId("222"), PortNumber.portNumber(0x2));
+    public static final ConnectPoint P3 = new ConnectPoint(DeviceId.deviceId("333"), PortNumber.portNumber(0x3));
+
+    public static final Set<ConnectPoint> PS1 = itemSet(new ConnectPoint[]{P1, P3});
+    public static final Set<ConnectPoint> PS2 = itemSet(new ConnectPoint[]{P2, P3});
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
new file mode 100644
index 0000000..df46ec5
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
@@ -0,0 +1,268 @@
+package org.onlab.onos.net.intent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Fake implementation of the intent service to assist in developing tests
+ * of the interface contract.
+ */
+public class FakeIntentManager implements TestableIntentService {
+
+    private final Map<IntentId, Intent> intents = new HashMap<>();
+    private final Map<IntentId, IntentState> intentStates = new HashMap<>();
+    private final Map<IntentId, List<InstallableIntent>> installables = new HashMap<>();
+    private final Set<IntentEventListener> listeners = new HashSet<>();
+
+    private final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers = new HashMap<>();
+    private final Map<Class<? extends InstallableIntent>,
+            IntentInstaller<? extends InstallableIntent>> installers = new HashMap<>();
+
+    private final ExecutorService executor = Executors.newSingleThreadExecutor();
+    private final List<IntentException> exceptions = new ArrayList<>();
+
+    @Override
+    public List<IntentException> getExceptions() {
+        return exceptions;
+    }
+
+    // Provides an out-of-thread simulation of intent submit life-cycle
+    private void executeSubmit(final Intent intent) {
+        registerSubclassCompilerIfNeeded(intent);
+        executor.execute(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    List<InstallableIntent> installable = compileIntent(intent);
+                    installIntents(intent, installable);
+                } catch (IntentException e) {
+                    exceptions.add(e);
+                }
+            }
+        });
+    }
+
+    // Provides an out-of-thread simulation of intent withdraw life-cycle
+    private void executeWithdraw(final Intent intent) {
+        executor.execute(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    List<InstallableIntent> installable = getInstallable(intent.getId());
+                    uninstallIntents(intent, installable);
+                } catch (IntentException e) {
+                    exceptions.add(e);
+                }
+
+            }
+        });
+    }
+
+    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;
+    }
+
+    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;
+    }
+
+    private <T extends Intent> List<InstallableIntent> compileIntent(T intent) {
+        try {
+            // For the fake, we compile using a single level pass
+            List<InstallableIntent> installable = new ArrayList<>();
+            for (Intent compiled : getCompiler(intent).compile(intent)) {
+                installable.add((InstallableIntent) compiled);
+            }
+            setState(intent, IntentState.COMPILED);
+            return installable;
+        } catch (IntentException e) {
+            setState(intent, IntentState.FAILED);
+            throw e;
+        }
+    }
+
+    private void installIntents(Intent intent, List<InstallableIntent> installable) {
+        try {
+            for (InstallableIntent ii : installable) {
+                registerSubclassInstallerIfNeeded(ii);
+                getInstaller(ii).install(ii);
+            }
+            setState(intent, IntentState.INSTALLED);
+            putInstallable(intent.getId(), installable);
+        } catch (IntentException e) {
+            setState(intent, IntentState.FAILED);
+            throw e;
+        }
+    }
+
+    private void uninstallIntents(Intent intent, List<InstallableIntent> installable) {
+        try {
+            for (InstallableIntent ii : installable) {
+                getInstaller(ii).uninstall(ii);
+            }
+            setState(intent, IntentState.WITHDRAWN);
+            removeInstallable(intent.getId());
+        } catch (IntentException e) {
+            setState(intent, IntentState.FAILED);
+            throw e;
+        }
+    }
+
+
+    // Sets the internal state for the given intent and dispatches an event
+    private void setState(Intent intent, IntentState state) {
+        IntentState previous = intentStates.get(intent.getId());
+        intentStates.put(intent.getId(), state);
+        dispatch(new IntentEvent(intent, state, previous, System.currentTimeMillis()));
+    }
+
+    private void putInstallable(IntentId id, List<InstallableIntent> installable) {
+        installables.put(id, installable);
+    }
+
+    private void removeInstallable(IntentId id) {
+        installables.remove(id);
+    }
+
+    private List<InstallableIntent> getInstallable(IntentId id) {
+        List<InstallableIntent> installable = installables.get(id);
+        if (installable != null) {
+            return installable;
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public void submit(Intent intent) {
+        intents.put(intent.getId(), intent);
+        setState(intent, IntentState.SUBMITTED);
+        executeSubmit(intent);
+    }
+
+    @Override
+    public void withdraw(Intent intent) {
+        intents.remove(intent.getId());
+        setState(intent, IntentState.WITHDRAWING);
+        executeWithdraw(intent);
+    }
+
+    @Override
+    public void execute(IntentOperations operations) {
+        // TODO: implement later
+    }
+
+    @Override
+    public Set<Intent> getIntents() {
+        return Collections.unmodifiableSet(new HashSet<>(intents.values()));
+    }
+
+    @Override
+    public Intent getIntent(IntentId id) {
+        return intents.get(id);
+    }
+
+    @Override
+    public IntentState getIntentState(IntentId id) {
+        return intentStates.get(id);
+    }
+
+    @Override
+    public void addListener(IntentEventListener listener) {
+        listeners.add(listener);
+    }
+
+    @Override
+    public void removeListener(IntentEventListener listener) {
+        listeners.remove(listener);
+    }
+
+    private void dispatch(IntentEvent event) {
+        for (IntentEventListener listener : listeners) {
+            listener.event(event);
+        }
+    }
+
+    @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 Collections.unmodifiableMap(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 Collections.unmodifiableMap(installers);
+    }
+
+    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();
+            }
+        }
+    }
+
+    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();
+            }
+        }
+    }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/ImmutableClassChecker.java b/core/api/src/test/java/org/onlab/onos/net/intent/ImmutableClassChecker.java
new file mode 100644
index 0000000..0e63af9
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/ImmutableClassChecker.java
@@ -0,0 +1,125 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+import org.hamcrest.Description;
+import org.hamcrest.StringDescription;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Hamcrest style class for verifying that a class follows the
+ * accepted rules for immutable classes.
+ *
+ * The rules that are enforced for immutable classes:
+ *    - the class must be declared final
+ *    - all data members of the class must be declared private and final
+ *    - the class must not define any setter methods
+ */
+
+public class ImmutableClassChecker {
+
+    private String failureReason = "";
+
+    /**
+     * Method to determine if a given class is a properly specified
+     * immutable class.
+     *
+     * @param clazz the class to check
+     * @return true if the given class is a properly specified immutable class.
+     */
+    private boolean isImmutableClass(Class<?> clazz) {
+        // class must be declared final
+        if (!Modifier.isFinal(clazz.getModifiers())) {
+            failureReason = "a class that is not final";
+            return false;
+        }
+
+        // class must have only final and private data members
+        for (final Field field : clazz.getDeclaredFields()) {
+            if (field.getName().startsWith("__cobertura")) {
+                //  cobertura sticks these fields into classes - ignore them
+                continue;
+            }
+            if (!Modifier.isFinal(field.getModifiers())) {
+                failureReason = "a field named '" + field.getName() +
+                                "' that is not final";
+                return false;
+            }
+            if (!Modifier.isPrivate(field.getModifiers())) {
+                //
+                // NOTE: We relax the recommended rules for defining immutable
+                // objects and allow "static final" fields that are not
+                // private. The "final" check was already done above so we
+                // don't repeat it here.
+                //
+                if (!Modifier.isStatic(field.getModifiers())) {
+                    failureReason = "a field named '" + field.getName() +
+                                "' that is not private and is not static";
+                    return false;
+                }
+            }
+        }
+
+        //  class must not define any setters
+        for (final Method method : clazz.getMethods()) {
+            if (method.getDeclaringClass().equals(clazz)) {
+                if (method.getName().startsWith("set")) {
+                    failureReason = "a class with a setter named '" + method.getName() + "'";
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Describe why an error was reported.  Uses Hamcrest style Description
+     * interfaces.
+     *
+     * @param description the Description object to use for reporting the
+     *                    mismatch
+     */
+    public void describeMismatch(Description description) {
+        description.appendText(failureReason);
+    }
+
+    /**
+     * Describe the source object that caused an error, using a Hamcrest
+     * Matcher style interface.  In this case, it always returns
+     * that we are looking for a properly defined utility class.
+     *
+     * @param description the Description object to use to report the "to"
+     *                    object
+     */
+    public void describeTo(Description description) {
+        description.appendText("a properly defined immutable class");
+    }
+
+    /**
+     * Assert that the given class adheres to the utility class rules.
+     *
+     * @param clazz the class to check
+     *
+     * @throws java.lang.AssertionError if the class is not a valid
+     *         utility class
+     */
+    public static void assertThatClassIsImmutable(Class<?> clazz) {
+        final ImmutableClassChecker checker = new ImmutableClassChecker();
+        if (!checker.isImmutableClass(clazz)) {
+            final Description toDescription = new StringDescription();
+            final Description mismatchDescription = new StringDescription();
+
+            checker.describeTo(toDescription);
+            checker.describeMismatch(mismatchDescription);
+            final String reason =
+                    "\n" +
+                    "Expected: is \"" + toDescription.toString() + "\"\n" +
+                    "    but : was \"" + mismatchDescription.toString() + "\"";
+
+            throw new AssertionError(reason);
+        }
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentExceptionTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentExceptionTest.java
new file mode 100644
index 0000000..02564e6
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentExceptionTest.java
@@ -0,0 +1,33 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the intent exception.
+ */
+public class IntentExceptionTest {
+
+    @Test
+    public void basics() {
+        validate(new IntentException(), null, null);
+        validate(new IntentException("foo"), "foo", null);
+
+        Throwable cause = new NullPointerException("bar");
+        validate(new IntentException("foo", cause), "foo", cause);
+    }
+
+    /**
+     * Validates that the specified exception has the correct message and cause.
+     *
+     * @param e       exception to test
+     * @param message expected message
+     * @param cause   expected cause
+     */
+    protected void validate(RuntimeException e, String message, Throwable cause) {
+        assertEquals("incorrect message", message, e.getMessage());
+        assertEquals("incorrect cause", cause, e.getCause());
+    }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdGenerator.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdGenerator.java
new file mode 100644
index 0000000..0ca669b
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdGenerator.java
@@ -0,0 +1,14 @@
+package org.onlab.onos.net.intent;
+
+/**
+ * This interface is for generator of IntentId. It is defined only for
+ * testing purpose to keep type safety on mock creation.
+ *
+ * <p>
+ * {@link #getNewId()} generates a globally unique {@link IntentId} instance
+ * on each invocation. Application developers should not generate IntentId
+ * by themselves. Instead use an implementation of this interface.
+ * </p>
+ */
+public interface IntentIdGenerator extends IdGenerator<IntentId> {
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdTest.java
new file mode 100644
index 0000000..2a0824c
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentIdTest.java
@@ -0,0 +1,57 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * This class tests the immutability, equality, and non-equality of
+ * {@link IntentId}.
+ */
+public class IntentIdTest {
+    /**
+     * Tests the immutability of {@link IntentId}.
+     */
+    @Test
+    public void intentIdFollowsGuidelineForImmutableObject() {
+        ImmutableClassChecker.assertThatClassIsImmutable(IntentId.class);
+    }
+
+    /**
+     * Tests equality of {@link IntentId}.
+     */
+    @Test
+    public void testEquality() {
+        IntentId id1 = new IntentId(1L);
+        IntentId id2 = new IntentId(1L);
+
+        assertThat(id1, is(id2));
+    }
+
+    /**
+     * Tests non-equality of {@link IntentId}.
+     */
+    @Test
+    public void testNonEquality() {
+        IntentId id1 = new IntentId(1L);
+        IntentId id2 = new IntentId(2L);
+
+        assertThat(id1, is(not(id2)));
+    }
+
+    @Test
+    public void valueOf() {
+        IntentId id = new IntentId(12345);
+        assertEquals("incorrect valueOf", id, IntentId.valueOf("12345"));
+    }
+
+    @Test
+    public void valueOfHex() {
+        IntentId id = new IntentId(0xdeadbeefL);
+        assertEquals("incorrect valueOf", id, IntentId.valueOf(id.toString()));
+    }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
new file mode 100644
index 0000000..c7682b1
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
@@ -0,0 +1,310 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.onlab.onos.net.intent.IntentState.*;
+import static org.junit.Assert.*;
+
+// TODO: consider make it categorized as integration test when it become
+//   slow test or fragile test
+/**
+ * Suite of tests for the intent service contract.
+ */
+public class IntentServiceTest {
+
+    public static final IntentId IID = new IntentId(123);
+    public static final IntentId INSTALLABLE_IID = new IntentId(234);
+
+    protected static final int GRACE_MS = 500; // millis
+
+    protected TestableIntentService service;
+    protected TestListener listener = new TestListener();
+
+    @Before
+    public void setUp() {
+        service = createIntentService();
+        service.addListener(listener);
+    }
+
+    @After
+    public void tearDown() {
+        service.removeListener(listener);
+    }
+
+    /**
+     * Creates a service instance appropriately instrumented for testing.
+     *
+     * @return testable intent service
+     */
+    protected TestableIntentService createIntentService() {
+        return new FakeIntentManager();
+    }
+
+    @Test
+    public void basics() {
+        // Make sure there are no intents
+        assertEquals("incorrect intent count", 0, service.getIntents().size());
+
+        // Register a compiler and an installer both setup for success.
+        service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID)));
+        service.registerInstaller(TestInstallableIntent.class, new TestInstaller(false));
+
+        final Intent intent = new TestIntent(IID);
+        service.submit(intent);
+
+        // Allow a small window of time until the intent is in the expected state
+        TestTools.assertAfter(GRACE_MS, new Runnable() {
+            @Override
+            public void run() {
+                assertEquals("incorrect intent state", INSTALLED,
+                             service.getIntentState(intent.getId()));
+            }
+        });
+
+        // Make sure that all expected events have been emitted
+        validateEvents(intent, SUBMITTED, COMPILED, INSTALLED);
+
+        // Make sure there is just one intent (and is ours)
+        assertEquals("incorrect intent count", 1, service.getIntents().size());
+        assertEquals("incorrect intent", intent, service.getIntent(intent.getId()));
+
+        // Reset the listener events
+        listener.events.clear();
+
+        // Now withdraw the intent
+        service.withdraw(intent);
+
+        // Allow a small window of time until the event is in the expected state
+        TestTools.assertAfter(GRACE_MS, new Runnable() {
+            @Override
+            public void run() {
+                assertEquals("incorrect intent state", WITHDRAWN,
+                             service.getIntentState(intent.getId()));
+            }
+        });
+
+        // Make sure that all expected events have been emitted
+        validateEvents(intent, WITHDRAWING, WITHDRAWN);
+
+        // TODO: discuss what is the fate of intents after they have been withdrawn
+        // Make sure that the intent is no longer in the system
+//        assertEquals("incorrect intent count", 0, service.getIntents().size());
+//        assertNull("intent should not be found", service.getIntent(intent.getId()));
+//        assertNull("intent state should not be found", service.getIntentState(intent.getId()));
+    }
+
+    @Test
+    public void failedCompilation() {
+        // Register a compiler programmed for success
+        service.registerCompiler(TestIntent.class, new TestCompiler(true));
+
+        // Submit an intent
+        final Intent intent = new TestIntent(IID);
+        service.submit(intent);
+
+        // Allow a small window of time until the intent is in the expected state
+        TestTools.assertAfter(GRACE_MS, new Runnable() {
+            @Override
+            public void run() {
+                assertEquals("incorrect intent state", FAILED,
+                             service.getIntentState(intent.getId()));
+            }
+        });
+
+        // Make sure that all expected events have been emitted
+        validateEvents(intent, SUBMITTED, FAILED);
+    }
+
+    @Test
+    public void failedInstallation() {
+        // Register a compiler programmed for success and installer for failure
+        service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID)));
+        service.registerInstaller(TestInstallableIntent.class, new TestInstaller(true));
+
+        // Submit an intent
+        final Intent intent = new TestIntent(IID);
+        service.submit(intent);
+
+        // Allow a small window of time until the intent is in the expected state
+        TestTools.assertAfter(GRACE_MS, new Runnable() {
+            @Override
+            public void run() {
+                assertEquals("incorrect intent state", FAILED,
+                             service.getIntentState(intent.getId()));
+            }
+        });
+
+        // Make sure that all expected events have been emitted
+        validateEvents(intent, SUBMITTED, COMPILED, FAILED);
+    }
+
+    /**
+     * Validates that the test event listener has received the following events
+     * for the specified intent. Events received for other intents will not be
+     * considered.
+     *
+     * @param intent intent subject
+     * @param states list of states for which events are expected
+     */
+    protected void validateEvents(Intent intent, IntentState... states) {
+        Iterator<IntentEvent> events = listener.events.iterator();
+        for (IntentState state : states) {
+            IntentEvent event = events.hasNext() ? events.next() : null;
+            if (event == null) {
+                fail("expected event not found: " + state);
+            } else if (intent.equals(event.getIntent())) {
+                assertEquals("incorrect state", state, event.getState());
+            }
+        }
+
+        // Remainder of events should not apply to this intent; make sure.
+        while (events.hasNext()) {
+            assertFalse("unexpected event for intent",
+                        intent.equals(events.next().getIntent()));
+        }
+    }
+
+    @Test
+    public void compilerBasics() {
+        // Make sure there are no compilers
+        assertEquals("incorrect compiler count", 0, service.getCompilers().size());
+
+        // Add a compiler and make sure that it appears in the map
+        IntentCompiler<TestIntent> compiler = new TestCompiler(false);
+        service.registerCompiler(TestIntent.class, compiler);
+        assertEquals("incorrect compiler", compiler,
+                     service.getCompilers().get(TestIntent.class));
+
+        // Remove the same and make sure that it no longer appears in the map
+        service.unregisterCompiler(TestIntent.class);
+        assertNull("compiler should not be registered",
+                   service.getCompilers().get(TestIntent.class));
+    }
+
+    @Test
+    public void installerBasics() {
+        // Make sure there are no installers
+        assertEquals("incorrect installer count", 0, service.getInstallers().size());
+
+        // Add an installer and make sure that it appears in the map
+        IntentInstaller<TestInstallableIntent> installer = new TestInstaller(false);
+        service.registerInstaller(TestInstallableIntent.class, installer);
+        assertEquals("incorrect installer", installer,
+                     service.getInstallers().get(TestInstallableIntent.class));
+
+        // Remove the same and make sure that it no longer appears in the map
+        service.unregisterInstaller(TestInstallableIntent.class);
+        assertNull("installer should not be registered",
+                   service.getInstallers().get(TestInstallableIntent.class));
+    }
+
+    @Test
+    public void implicitRegistration() {
+        // Add a compiler and make sure that it appears in the map
+        IntentCompiler<TestIntent> compiler = new TestCompiler(new TestSubclassInstallableIntent(INSTALLABLE_IID));
+        service.registerCompiler(TestIntent.class, compiler);
+        assertEquals("incorrect compiler", compiler,
+                     service.getCompilers().get(TestIntent.class));
+
+        // Add a installer and make sure that it appears in the map
+        IntentInstaller<TestInstallableIntent> installer = new TestInstaller(false);
+        service.registerInstaller(TestInstallableIntent.class, installer);
+        assertEquals("incorrect installer", installer,
+                     service.getInstallers().get(TestInstallableIntent.class));
+
+
+        // Submit an intent which is a subclass of the one we registered
+        final Intent intent = new TestSubclassIntent(IID);
+        service.submit(intent);
+
+        // Allow some time for the intent to be compiled and installed
+        TestTools.assertAfter(GRACE_MS, new Runnable() {
+            @Override
+            public void run() {
+                assertEquals("incorrect intent state", INSTALLED,
+                             service.getIntentState(intent.getId()));
+            }
+        });
+
+        // Make sure that now we have an implicit registration of the compiler
+        // under the intent subclass
+        assertEquals("incorrect compiler", compiler,
+                     service.getCompilers().get(TestSubclassIntent.class));
+
+        // Make sure that now we have an implicit registration of the installer
+        // under the intent subclass
+        assertEquals("incorrect installer", installer,
+                     service.getInstallers().get(TestSubclassInstallableIntent.class));
+
+        // TODO: discuss whether or if implicit registration should require implicit unregistration
+        // perhaps unregister by compiler or installer itself, rather than by class would be better
+    }
+
+
+    // Fixture to track emitted intent events
+    protected class TestListener implements IntentEventListener {
+        final List<IntentEvent> events = new ArrayList<>();
+
+        @Override
+        public void event(IntentEvent event) {
+            events.add(event);
+        }
+    }
+
+    // Controllable compiler
+    private class TestCompiler implements IntentCompiler<TestIntent> {
+        private final boolean fail;
+        private final List<Intent> result;
+
+        TestCompiler(boolean fail) {
+            this.fail = fail;
+            this.result = Collections.emptyList();
+        }
+
+        TestCompiler(Intent... result) {
+            this.fail = false;
+            this.result = Arrays.asList(result);
+        }
+
+        @Override
+        public List<Intent> compile(TestIntent intent) {
+            if (fail) {
+                throw new IntentException("compile failed by design");
+            }
+            List<Intent> compiled = new ArrayList<>(result);
+            return compiled;
+        }
+    }
+
+    // Controllable installer
+    private class TestInstaller implements IntentInstaller<TestInstallableIntent> {
+        private final boolean fail;
+
+        TestInstaller(boolean fail) {
+            this.fail = fail;
+        }
+
+        @Override
+        public void install(TestInstallableIntent intent) {
+            if (fail) {
+                throw new IntentException("install failed by design");
+            }
+        }
+
+        @Override
+        public void uninstall(TestInstallableIntent intent) {
+            if (fail) {
+                throw new IntentException("remove failed by design");
+            }
+        }
+    }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentTest.java
new file mode 100644
index 0000000..a6cedf9
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentTest.java
@@ -0,0 +1,65 @@
+package org.onlab.onos.net.intent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+/**
+ * Base facilities to test various intent tests.
+ */
+public abstract class IntentTest {
+    /**
+     * Produces a set of items from the supplied items.
+     *
+     * @param items items to be placed in set
+     * @param <T>   item type
+     * @return set of items
+     */
+    protected static <T> Set<T> itemSet(T[] items) {
+        return new HashSet<>(Arrays.asList(items));
+    }
+
+    @Test
+    public void equalsAndHashCode() {
+        Intent one = createOne();
+        Intent like = createOne();
+        Intent another = createAnother();
+
+        assertTrue("should be equal", one.equals(like));
+        assertEquals("incorrect hashCode", one.hashCode(), like.hashCode());
+
+        assertFalse("should not be equal", one.equals(another));
+
+        assertFalse("should not be equal", one.equals(null));
+        assertFalse("should not be equal", one.equals("foo"));
+    }
+
+    @Test
+    public void testToString() {
+        Intent one = createOne();
+        Intent like = createOne();
+        assertEquals("incorrect toString", one.toString(), like.toString());
+    }
+
+    /**
+     * Creates a new intent, but always a like intent, i.e. all instances will
+     * be equal, but should not be the same.
+     *
+     * @return intent
+     */
+    protected abstract Intent createOne();
+
+    /**
+     * Creates another intent, not equals to the one created by
+     * {@link #createOne()} and with a different hash code.
+     *
+     * @return another intent
+     */
+    protected abstract Intent createAnother();
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java
new file mode 100644
index 0000000..d971ba2
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Suite of tests of the multi-to-single point intent descriptor.
+ */
+public class MultiPointToSinglePointIntentTest extends ConnectivityIntentTest {
+
+    @Test
+    public void basics() {
+        MultiPointToSinglePointIntent intent = createOne();
+        assertEquals("incorrect id", IID, intent.getId());
+        assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
+        assertEquals("incorrect ingress", PS1, intent.getIngressPorts());
+        assertEquals("incorrect egress", P2, intent.getEgressPort());
+    }
+
+    @Override
+    protected MultiPointToSinglePointIntent createOne() {
+        return new MultiPointToSinglePointIntent(IID, MATCH, NOP, PS1, P2);
+    }
+
+    @Override
+    protected MultiPointToSinglePointIntent createAnother() {
+        return new MultiPointToSinglePointIntent(IID, MATCH, NOP, PS2, P1);
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
new file mode 100644
index 0000000..bd8dc08
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
@@ -0,0 +1,36 @@
+package org.onlab.onos.net.intent;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.onlab.onos.net.NetTestTools;
+import org.onlab.onos.net.Path;
+
+public class PathIntentTest extends ConnectivityIntentTest {
+    // 111:11 --> 222:22
+    private static final Path PATH1 = NetTestTools.createPath("111", "222");
+
+    // 111:11 --> 333:33
+    private static final Path PATH2 = NetTestTools.createPath("222", "333");
+
+    @Test
+    public void basics() {
+        PathIntent intent = createOne();
+        assertEquals("incorrect id", IID, intent.getId());
+        assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
+        assertEquals("incorrect action", NOP, intent.getTrafficTreatment());
+        assertEquals("incorrect ingress", P1, intent.getIngressPort());
+        assertEquals("incorrect egress", P2, intent.getEgressPort());
+        assertEquals("incorrect path", PATH1, intent.getPath());
+    }
+
+    @Override
+    protected PathIntent createOne() {
+        return new PathIntent(IID, MATCH, NOP, P1, P2, PATH1);
+    }
+
+    @Override
+    protected PathIntent createAnother() {
+        return new PathIntent(IID, MATCH, NOP, P1, P3, PATH2);
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java
new file mode 100644
index 0000000..426a3d9
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Suite of tests of the point-to-point intent descriptor.
+ */
+public class PointToPointIntentTest extends ConnectivityIntentTest {
+
+    @Test
+    public void basics() {
+        PointToPointIntent intent = createOne();
+        assertEquals("incorrect id", IID, intent.getId());
+        assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
+        assertEquals("incorrect ingress", P1, intent.getIngressPort());
+        assertEquals("incorrect egress", P2, intent.getEgressPort());
+    }
+
+    @Override
+    protected PointToPointIntent createOne() {
+        return new PointToPointIntent(IID, MATCH, NOP, P1, P2);
+    }
+
+    @Override
+    protected PointToPointIntent createAnother() {
+        return new PointToPointIntent(IID, MATCH, NOP, P2, P1);
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java
new file mode 100644
index 0000000..0561a87
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java
@@ -0,0 +1,30 @@
+package org.onlab.onos.net.intent;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Suite of tests of the single-to-multi point intent descriptor.
+ */
+public class SinglePointToMultiPointIntentTest extends ConnectivityIntentTest {
+
+    @Test
+    public void basics() {
+        SinglePointToMultiPointIntent intent = createOne();
+        assertEquals("incorrect id", IID, intent.getId());
+        assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
+        assertEquals("incorrect ingress", P1, intent.getIngressPort());
+        assertEquals("incorrect egress", PS2, intent.getEgressPorts());
+    }
+
+    @Override
+    protected SinglePointToMultiPointIntent createOne() {
+        return new SinglePointToMultiPointIntent(IID, MATCH, NOP, P1, PS2);
+    }
+
+    @Override
+    protected SinglePointToMultiPointIntent createAnother() {
+        return new SinglePointToMultiPointIntent(IID, MATCH, NOP, P2, PS1);
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java
new file mode 100644
index 0000000..a6ce52e
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java
@@ -0,0 +1,28 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An installable intent used in the unit test.
+ *
+ * FIXME: we don't want to expose this class publicly, but the current Kryo
+ * serialization mechanism does not allow this class to be private and placed
+ * on testing directory.
+ */
+public class TestInstallableIntent extends AbstractIntent implements InstallableIntent {
+    /**
+     * Constructs an instance with the specified intent ID.
+     *
+     * @param id intent ID
+     */
+    public TestInstallableIntent(IntentId id) {
+        super(id);
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    protected TestInstallableIntent() {
+        super();
+    }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestIntent.java
new file mode 100644
index 0000000..2f30727
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestIntent.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An intent used in the unit test.
+ *
+ * FIXME: we don't want to expose this class publicly, but the current Kryo
+ * serialization mechanism does not allow this class to be private and placed
+ * on testing directory.
+ */
+public class TestIntent extends AbstractIntent {
+    /**
+     * Constructs an instance with the specified intent ID.
+     *
+     * @param id intent ID
+     */
+    public TestIntent(IntentId id) {
+        super(id);
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    protected TestIntent() {
+        super();
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassInstallableIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassInstallableIntent.java
new file mode 100644
index 0000000..40765c2
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassInstallableIntent.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An intent used in the unit test.
+ *
+ * FIXME: we don't want to expose this class publicly, but the current Kryo
+ * serialization mechanism does not allow this class to be private and placed
+ * on testing directory.
+ */
+public class TestSubclassInstallableIntent extends TestInstallableIntent implements InstallableIntent {
+    /**
+     * Constructs an instance with the specified intent ID.
+     *
+     * @param id intent ID
+     */
+    public TestSubclassInstallableIntent(IntentId id) {
+        super(id);
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    protected TestSubclassInstallableIntent() {
+        super();
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassIntent.java
new file mode 100644
index 0000000..43bb0dd
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestSubclassIntent.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.net.intent;
+//TODO is this the right package?
+
+/**
+ * An intent used in the unit test.
+ *
+ * FIXME: we don't want to expose this class publicly, but the current Kryo
+ * serialization mechanism does not allow this class to be private and placed
+ * on testing directory.
+ */
+public class TestSubclassIntent extends TestIntent {
+    /**
+     * Constructs an instance with the specified intent ID.
+     *
+     * @param id intent ID
+     */
+    public TestSubclassIntent(IntentId id) {
+        super(id);
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    protected TestSubclassIntent() {
+        super();
+    }
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestTools.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestTools.java
new file mode 100644
index 0000000..f22585e
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestTools.java
@@ -0,0 +1,126 @@
+package org.onlab.onos.net.intent;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Set of test tools.
+ */
+public final class TestTools {
+
+    // Disallow construction
+    private TestTools() {
+    }
+
+    /**
+     * Utility method to pause the current thread for the specified number of
+     * milliseconds.
+     *
+     * @param ms number of milliseconds to pause
+     */
+    public static void delay(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+            fail("unexpected interrupt");
+        }
+    }
+
+    /**
+     * Periodically runs the given runnable, which should contain a series of
+     * test assertions until all the assertions succeed, in which case it will
+     * return, or until the the time expires, in which case it will throw the
+     * first failed assertion error.
+     *
+     * @param start start time, in millis since start of epoch from which the
+     *        duration will be measured
+     * @param delay initial delay (in milliseconds) before the first assertion
+     *        attempt
+     * @param step delay (in milliseconds) between successive assertion
+     *        attempts
+     * @param duration number of milliseconds beyond the given start time,
+     *        after which the failed assertions will be propagated and allowed
+     *        to fail the test
+     * @param assertions runnable housing the test assertions
+     */
+    public static void assertAfter(long start, int delay, int step,
+                                   int duration, Runnable assertions) {
+        delay(delay);
+        while (true) {
+            try {
+                assertions.run();
+                break;
+            } catch (AssertionError e) {
+                if (System.currentTimeMillis() - start > duration) {
+                    throw e;
+                }
+            }
+            delay(step);
+        }
+    }
+
+    /**
+     * Periodically runs the given runnable, which should contain a series of
+     * test assertions until all the assertions succeed, in which case it will
+     * return, or until the the time expires, in which case it will throw the
+     * first failed assertion error.
+     * <p>
+     * The start of the period is the current time.
+     *
+     * @param delay initial delay (in milliseconds) before the first assertion
+     *        attempt
+     * @param step delay (in milliseconds) between successive assertion
+     *        attempts
+     * @param duration number of milliseconds beyond the current time time,
+     *        after which the failed assertions will be propagated and allowed
+     *        to fail the test
+     * @param assertions runnable housing the test assertions
+     */
+    public static void assertAfter(int delay, int step, int duration,
+                                   Runnable assertions) {
+        assertAfter(System.currentTimeMillis(), delay, step, duration,
+                    assertions);
+    }
+
+    /**
+     * Periodically runs the given runnable, which should contain a series of
+     * test assertions until all the assertions succeed, in which case it will
+     * return, or until the the time expires, in which case it will throw the
+     * first failed assertion error.
+     * <p>
+     * The start of the period is the current time and the first assertion
+     * attempt is delayed by the value of {@code step} parameter.
+     *
+     * @param step delay (in milliseconds) between successive assertion
+     *        attempts
+     * @param duration number of milliseconds beyond the current time time,
+     *        after which the failed assertions will be propagated and allowed
+     *        to fail the test
+     * @param assertions runnable housing the test assertions
+     */
+    public static void assertAfter(int step, int duration,
+                                   Runnable assertions) {
+        assertAfter(step, step, duration, assertions);
+    }
+
+    /**
+     * Periodically runs the given runnable, which should contain a series of
+     * test assertions until all the assertions succeed, in which case it will
+     * return, or until the the time expires, in which case it will throw the
+     * first failed assertion error.
+     * <p>
+     * The start of the period is the current time and each successive
+     * assertion attempt is delayed by at least 10 milliseconds unless the
+     * {@code duration} is less than that, in which case the one and only
+     * assertion is made after that delay.
+     *
+     * @param duration number of milliseconds beyond the current time,
+     *        after which the failed assertions will be propagated and allowed
+     *        to fail the test
+     * @param assertions runnable housing the test assertions
+     */
+    public static void assertAfter(int duration, Runnable assertions) {
+        int step = Math.min(duration, Math.max(10, duration / 10));
+        assertAfter(step, duration, assertions);
+    }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestableIntentService.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestableIntentService.java
new file mode 100644
index 0000000..95502d3
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestableIntentService.java
@@ -0,0 +1,12 @@
+package org.onlab.onos.net.intent;
+
+import java.util.List;
+
+/**
+ * Abstraction of an extensible intent service enabled for unit tests.
+ */
+public interface TestableIntentService extends IntentService, IntentExtensionService {
+
+    List<IntentException> getExceptions();
+
+}
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 e3f53fe..88b6923 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
@@ -1,5 +1,10 @@
 package org.onlab.onos.net.host.impl;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+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;
@@ -12,6 +17,7 @@
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Host;
 import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.device.DeviceService;
 import org.onlab.onos.net.host.HostAdminService;
 import org.onlab.onos.net.host.HostDescription;
 import org.onlab.onos.net.host.HostEvent;
@@ -23,6 +29,7 @@
 import org.onlab.onos.net.host.HostStore;
 import org.onlab.onos.net.host.HostStoreDelegate;
 import org.onlab.onos.net.host.PortAddresses;
+import org.onlab.onos.net.packet.PacketService;
 import org.onlab.onos.net.provider.AbstractProviderRegistry;
 import org.onlab.onos.net.provider.AbstractProviderService;
 import org.onlab.packet.IpAddress;
@@ -31,11 +38,6 @@
 import org.onlab.packet.VlanId;
 import org.slf4j.Logger;
 
-import java.util.Set;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.slf4j.LoggerFactory.getLogger;
-
 /**
  * Provides basic implementation of the host SB &amp; NB APIs.
  */
@@ -59,12 +61,22 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected EventDeliveryService eventDispatcher;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    private HostMonitor monitor;
 
     @Activate
     public void activate() {
+        log.info("Started");
         store.setDelegate(delegate);
         eventDispatcher.addSink(HostEvent.class, listenerRegistry);
-        log.info("Started");
+
+        monitor = new HostMonitor(deviceService,  packetService, this);
+
     }
 
     @Deactivate
@@ -76,6 +88,8 @@
 
     @Override
     protected HostProviderService createProviderService(HostProvider provider) {
+        monitor.registerHostProvider(provider);
+
         return new InternalHostProviderService(provider);
     }
 
@@ -126,12 +140,12 @@
 
     @Override
     public void startMonitoringIp(IpAddress ip) {
-        // TODO pass through to HostMonitor
+        monitor.addMonitoringFor(ip);
     }
 
     @Override
     public void stopMonitoringIp(IpAddress ip) {
-        // TODO pass through to HostMonitor
+        monitor.stopMonitoring(ip);
     }
 
     @Override
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 a5aa13e..9f8dd48 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,10 +2,11 @@
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
 import org.jboss.netty.util.Timeout;
@@ -21,19 +22,19 @@
 import org.onlab.onos.net.flow.instructions.Instruction;
 import org.onlab.onos.net.flow.instructions.Instructions;
 import org.onlab.onos.net.host.HostProvider;
-import org.onlab.onos.net.host.HostService;
-import org.onlab.onos.net.host.HostStore;
 import org.onlab.onos.net.host.PortAddresses;
 import org.onlab.onos.net.packet.DefaultOutboundPacket;
 import org.onlab.onos.net.packet.OutboundPacket;
 import org.onlab.onos.net.packet.PacketService;
-import org.onlab.onos.net.topology.TopologyService;
+import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.packet.ARP;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpAddress;
 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.
@@ -43,9 +44,7 @@
  * probe for hosts that have not yet been detected (specified by IP address).
  */
 public class HostMonitor implements TimerTask {
-
-    private static final byte[] DEFAULT_MAC_ADDRESS =
-            MacAddress.valueOf("00:00:00:00:00:01").getAddress();
+    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();
@@ -54,59 +53,77 @@
     private static final byte[] BROADCAST_MAC =
             MacAddress.valueOf("ff:ff:ff:ff:ff:ff").getAddress();
 
-    private final HostService hostService;
-    private final TopologyService topologyService;
-    private final DeviceService deviceService;
-    private final HostProvider hostProvider;
-    private final PacketService packetService;
-    private final HostStore hostStore;
+    private DeviceService deviceService;
+    private PacketService packetService;
+    private HostManager hostManager;
 
     private final Set<IpAddress> monitoredAddresses;
 
+    private final Map<ProviderId, HostProvider> hostProviders;
+
     private final long probeRate;
 
     private final Timeout timeout;
 
-    public HostMonitor(HostService hostService, TopologyService topologyService,
+    public HostMonitor(
             DeviceService deviceService,
-            HostProvider hostProvider, PacketService packetService,
-            HostStore hostStore) {
-        this.hostService = hostService;
-        this.topologyService = topologyService;
+            PacketService packetService,
+            HostManager hostService) {
+
         this.deviceService = deviceService;
-        this.hostProvider = hostProvider;
         this.packetService = packetService;
-        this.hostStore = hostStore;
+        this.hostManager = hostService;
 
         monitoredAddresses = new HashSet<>();
+        hostProviders = new ConcurrentHashMap<>();
 
         probeRate = 30000; // milliseconds
 
         timeout = Timer.getTimer().newTimeout(this, 0, TimeUnit.MILLISECONDS);
+
+        addDefaultAddresses();
     }
 
-    public void addMonitoringFor(IpAddress ip) {
+    private void addDefaultAddresses() {
+        //monitoredAddresses.add(IpAddress.valueOf("10.0.0.1"));
+    }
+
+    void addMonitoringFor(IpAddress ip) {
         monitoredAddresses.add(ip);
     }
 
-    public void stopMonitoring(IpAddress ip) {
+    void stopMonitoring(IpAddress ip) {
         monitoredAddresses.remove(ip);
     }
 
-    public void shutdown() {
+    void shutdown() {
         timeout.cancel();
     }
 
+    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) {
-            Set<Host> hosts = Collections.emptySet(); //TODO hostService.getHostsByIp(ip);
+            // TODO have to convert right now because the HostService API uses IpPrefix
+            IpPrefix prefix = IpPrefix.valueOf(ip.toOctets());
+
+            Set<Host> hosts = hostManager.getHostsByIp(prefix);
 
             if (hosts.isEmpty()) {
                 sendArpRequest(ip);
             } else {
                 for (Host host : hosts) {
-                    hostProvider.triggerProbe(host);
+                    HostProvider provider = hostProviders.get(host.providerId());
+                    if (provider != null) {
+                        provider.triggerProbe(host);
+                    }
                 }
             }
         }
@@ -120,29 +137,26 @@
      * @param targetIp IP address to ARP for
      */
     private void sendArpRequest(IpAddress targetIp) {
-
         // Find ports with an IP address in the target's subnet and sent ARP
         // probes out those ports.
         for (Device device : deviceService.getDevices()) {
             for (Port port : deviceService.getPorts(device.id())) {
                 ConnectPoint cp = new ConnectPoint(device.id(), port.number());
-                PortAddresses addresses = hostStore.getAddressBindingsForPort(cp);
+                PortAddresses addresses = hostManager.getAddressBindingsForPort(cp);
 
-                /*for (IpPrefix prefix : addresses.ips()) {
+                for (IpPrefix prefix : addresses.ips()) {
                     if (prefix.contains(targetIp)) {
-                        sendProbe(device.id(), port, addresses, targetIp);
+                        sendProbe(device.id(), port, targetIp,
+                                prefix.toIpAddress(), addresses.mac());
                     }
-                }*/
+                }
             }
         }
-
-        // TODO case where no address was found.
-        // Broadcast out internal edge ports?
     }
 
-    private void sendProbe(DeviceId deviceId, Port port, PortAddresses portAddresses,
-            IpAddress targetIp) {
-        Ethernet arpPacket = createArpFor(targetIp, portAddresses);
+    private void sendProbe(DeviceId deviceId, Port port, IpAddress targetIp,
+            IpAddress sourceIp, MacAddress sourceMac) {
+        Ethernet arpPacket = buildArpRequest(targetIp, sourceIp, sourceMac);
 
         List<Instruction> instructions = new ArrayList<>();
         instructions.add(Instructions.createOutput(port.number()));
@@ -158,31 +172,26 @@
         packetService.emit(outboundPacket);
     }
 
-    private Ethernet createArpFor(IpAddress targetIp, PortAddresses portAddresses) {
+    private Ethernet buildArpRequest(IpAddress targetIp, IpAddress sourceIp,
+            MacAddress sourceMac) {
 
         ARP arp = new ARP();
         arp.setHardwareType(ARP.HW_TYPE_ETHERNET)
-        .setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
-        .setProtocolType(ARP.PROTO_TYPE_IP)
-        .setProtocolAddressLength((byte) IpPrefix.INET_LEN);
+           .setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+           .setProtocolType(ARP.PROTO_TYPE_IP)
+           .setProtocolAddressLength((byte) IpPrefix.INET_LEN)
+           .setOpCode(ARP.OP_REQUEST);
 
-        byte[] sourceMacAddress;
-        if (portAddresses.mac() == null) {
-            sourceMacAddress = DEFAULT_MAC_ADDRESS;
-        } else {
-            sourceMacAddress = portAddresses.mac().getAddress();
-        }
-
-        arp.setSenderHardwareAddress(sourceMacAddress)
-        //TODO .setSenderProtocolAddress(portAddresses.ips().toOctets())
-        .setTargetHardwareAddress(ZERO_MAC_ADDRESS)
-        .setTargetProtocolAddress(targetIp.toOctets());
+        arp.setSenderHardwareAddress(sourceMac.getAddress())
+           .setSenderProtocolAddress(sourceIp.toOctets())
+           .setTargetHardwareAddress(ZERO_MAC_ADDRESS)
+           .setTargetProtocolAddress(targetIp.toOctets());
 
         Ethernet ethernet = new Ethernet();
         ethernet.setEtherType(Ethernet.TYPE_ARP)
-        .setDestinationMACAddress(BROADCAST_MAC)
-        .setSourceMACAddress(sourceMacAddress)
-        .setPayload(arp);
+                .setDestinationMACAddress(BROADCAST_MAC)
+                .setSourceMACAddress(sourceMac.getAddress())
+                .setPayload(arp);
 
         return ethernet;
     }
diff --git a/core/store/dist/pom.xml b/core/store/dist/pom.xml
index 2451955..1faab74 100644
--- a/core/store/dist/pom.xml
+++ b/core/store/dist/pom.xml
@@ -33,6 +33,12 @@
             <artifactId>onlab-nio</artifactId>
             <version>${project.version}</version>
         </dependency>
+        
+        <dependency>
+            <groupId>org.onlab.onos</groupId>
+            <artifactId>onlab-netty</artifactId>
+            <version>${project.version}</version>
+        </dependency>
 
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
@@ -51,15 +57,6 @@
           <groupId>de.javakaffee</groupId>
           <artifactId>kryo-serializers</artifactId>
         </dependency>
-        <dependency>
-          <groupId>io.netty</groupId>
-          <artifactId>netty-all</artifactId>
-        </dependency>
-        <dependency>
-          <groupId>commons-pool</groupId>
-          <artifactId>commons-pool</artifactId>
-          <version>1.6</version>
-        </dependency>
     </dependencies>
 
     <build>
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
index e25c964..9408cc9 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
@@ -20,7 +20,7 @@
 import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.store.AbstractStore;
 import org.onlab.onos.store.cluster.messaging.ClusterCommunicationAdminService;
-import org.onlab.onos.store.cluster.messaging.impl.OnosClusterCommunicationManager;
+import org.onlab.onos.store.cluster.messaging.impl.ClusterCommunicationManager;
 import org.onlab.packet.IpPrefix;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -50,7 +50,7 @@
     private final Map<NodeId, State> states = new ConcurrentHashMap<>();
     private final Cache<NodeId, ControllerNode> livenessCache = CacheBuilder.newBuilder()
             .maximumSize(1000)
-            .expireAfterWrite(OnosClusterCommunicationManager.HEART_BEAT_INTERVAL_MILLIS * 3, TimeUnit.MILLISECONDS)
+            .expireAfterWrite(ClusterCommunicationManager.HEART_BEAT_INTERVAL_MILLIS * 3, TimeUnit.MILLISECONDS)
             .removalListener(new LivenessCacheRemovalListener()).build();
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageHandler.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageHandler.java
index 15e756d..7ec27ec 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageHandler.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageHandler.java
@@ -1,5 +1,13 @@
 package org.onlab.onos.store.cluster.messaging;
 
+/**
+ * Interface for handling cluster messages.
+ */
 public interface ClusterMessageHandler {
+
+    /**
+     * Handles/Processes the cluster message.
+     * @param message cluster message.
+     */
     public void handle(ClusterMessage message);
 }
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubject.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubject.java
index 4c9eefa..ee8d9c1 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubject.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubject.java
@@ -2,6 +2,8 @@
 
 /**
  * Representation of a message subject.
+ * Cluster messages have associated subjects that dictate how they get handled
+ * on the receiving side.
  */
 public class MessageSubject {
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubscriber.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubscriber.java
deleted file mode 100644
index 666ac6d..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubscriber.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.onlab.onos.store.cluster.messaging;
-
-import org.onlab.onos.cluster.NodeId;
-
-/**
- * Represents a message consumer.
- */
-public interface MessageSubscriber {
-
-    /**
-     * Receives the specified cluster message.
-     *
-     * @param message    message to be received
-     * @param fromNodeId node from which the message was received
-     */
-    void receive(Object messagePayload, NodeId fromNodeId);
-
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/OnosClusterCommunicationManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
similarity index 95%
rename from core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/OnosClusterCommunicationManager.java
rename to core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
index 9bd25b4..d4fd9c0 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/OnosClusterCommunicationManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
@@ -23,16 +23,16 @@
 import org.onlab.onos.store.cluster.messaging.ClusterMessage;
 import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
 import org.onlab.onos.store.cluster.messaging.MessageSubject;
-import org.onlab.onos.store.messaging.Endpoint;
-import org.onlab.onos.store.messaging.Message;
-import org.onlab.onos.store.messaging.MessageHandler;
-import org.onlab.onos.store.messaging.MessagingService;
+import org.onlab.netty.Endpoint;
+import org.onlab.netty.Message;
+import org.onlab.netty.MessageHandler;
+import org.onlab.netty.MessagingService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Component(immediate = true)
 @Service
-public class OnosClusterCommunicationManager
+public class ClusterCommunicationManager
         implements ClusterCommunicationService, ClusterCommunicationAdminService {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
index 3d87fb1..bba12f2 100644
--- a/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
@@ -6,8 +6,8 @@
 import org.junit.Test;
 import org.onlab.onos.cluster.DefaultControllerNode;
 import org.onlab.onos.cluster.NodeId;
-import org.onlab.onos.store.cluster.messaging.impl.OnosClusterCommunicationManager;
-import org.onlab.onos.store.messaging.impl.NettyMessagingService;
+import org.onlab.onos.store.cluster.messaging.impl.ClusterCommunicationManager;
+import org.onlab.netty.NettyMessagingService;
 import org.onlab.packet.IpPrefix;
 
 import java.util.concurrent.CountDownLatch;
@@ -29,8 +29,8 @@
 
     private static final IpPrefix IP = IpPrefix.valueOf("127.0.0.1");
 
-    private OnosClusterCommunicationManager ccm1;
-    private OnosClusterCommunicationManager ccm2;
+    private ClusterCommunicationManager ccm1;
+    private ClusterCommunicationManager ccm2;
 
     private TestDelegate cnd1 = new TestDelegate();
     private TestDelegate cnd2 = new TestDelegate();
@@ -46,11 +46,11 @@
         NettyMessagingService messagingService = new NettyMessagingService();
         messagingService.activate();
 
-        ccm1 = new OnosClusterCommunicationManager();
+        ccm1 = new ClusterCommunicationManager();
 //        ccm1.serializationService = messageSerializer;
         ccm1.activate();
 
-        ccm2 = new OnosClusterCommunicationManager();
+        ccm2 = new ClusterCommunicationManager();
 //        ccm2.serializationService = messageSerializer;
         ccm2.activate();
 
diff --git a/features/features.xml b/features/features.xml
index f008c14..68fa8c3 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -11,7 +11,7 @@
         <bundle>mvn:io.netty/netty/3.9.2.Final</bundle>
 
         <bundle>mvn:com.hazelcast/hazelcast/3.3</bundle>
-        <bundle>mvn:com.codahale.metrics/metrics-core/3.0.2</bundle>
+        <bundle>mvn:io.dropwizard.metrics/metrics-core/3.1.0</bundle>
         <bundle>mvn:com.eclipsesource.minimal-json/minimal-json/0.9.1</bundle>
 
         <bundle>mvn:com.esotericsoftware.kryo/kryo/2.24.0</bundle>
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
index eb12286..e8ebcd1 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
@@ -169,7 +169,12 @@
 
     @Override
     public void setRole(Dpid dpid, RoleState role) {
-        getSwitch(dpid).setRole(role);
+        final OpenFlowSwitch sw = getSwitch(dpid);
+        if (sw == null) {
+            log.debug("Switch not connected. Ignoring setRole({}, {})", dpid, role);
+            return;
+        }
+        sw.setRole(role);
     }
 
     /**
diff --git a/pom.xml b/pom.xml
index 3124467..cb00f32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,19 @@
             </dependency>
 
             <dependency>
+                <groupId>org.hamcrest</groupId>
+                <artifactId>hamcrest-core</artifactId>
+                <version>1.3</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.hamcrest</groupId>
+                <artifactId>hamcrest-library</artifactId>
+                <version>1.3</version>
+                <scope>test</scope>
+            </dependency>
+
+            <dependency>
                 <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-api</artifactId>
                 <version>1.7.6</version>
@@ -98,16 +111,16 @@
                 <version>3.3.2</version>
             </dependency>
 
-	    <dependency>
-	      <groupId>org.codehaus.jackson</groupId>
-	      <artifactId>jackson-core-asl</artifactId>
-	      <version>1.9.13</version>
-	    </dependency>
-	    <dependency>
-	      <groupId>org.codehaus.jackson</groupId>
-	      <artifactId>jackson-mapper-asl</artifactId>
-	      <version>1.9.13</version>
-	    </dependency>
+            <dependency>
+                <groupId>org.codehaus.jackson</groupId>
+                <artifactId>jackson-core-asl</artifactId>
+                <version>1.9.13</version>
+            </dependency>
+            <dependency>
+                <groupId>org.codehaus.jackson</groupId>
+                <artifactId>jackson-mapper-asl</artifactId>
+                <version>1.9.13</version>
+            </dependency>
 
 
             <!-- Web related -->
@@ -235,6 +248,11 @@
                 <classifier>tests</classifier>
                 <scope>test</scope>
             </dependency>
+            <dependency>
+              <groupId>commons-pool</groupId>
+              <artifactId>commons-pool</artifactId>
+              <version>1.6</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
@@ -244,6 +262,14 @@
             <artifactId>junit</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-jdk14</artifactId>
         </dependency>
@@ -320,6 +346,35 @@
                 </plugin>
 
                 <!-- TODO: add findbugs plugin for static code analysis; for explicit invocation only -->
+                <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+                <plugin>
+                        <groupId>org.eclipse.m2e</groupId>
+                        <artifactId>lifecycle-mapping</artifactId>
+                        <version>1.0.0</version>
+                        <configuration>
+                                <lifecycleMappingMetadata>
+                                        <pluginExecutions>
+                                                <pluginExecution>
+                                                        <pluginExecutionFilter>
+                                                                <groupId>org.jacoco</groupId>
+                                                                <artifactId>
+                                                                        jacoco-maven-plugin
+                                                                </artifactId>
+                                                                <versionRange>
+                                                                        [0.7.1.201405082137,)
+                                                                </versionRange>
+                                                                <goals>
+                                                                        <goal>prepare-agent</goal>
+                                                                </goals>
+                                                        </pluginExecutionFilter>
+                                                        <action>
+                                                                <ignore></ignore>
+                                                        </action>
+                                                </pluginExecution>
+                                        </pluginExecutions>
+                                </lifecycleMappingMetadata>
+                        </configuration>
+                </plugin>
             </plugins>
         </pluginManagement>
 
diff --git a/tools/build/conf/pom.xml b/tools/build/conf/pom.xml
index c2ad09c..4865705 100644
--- a/tools/build/conf/pom.xml
+++ b/tools/build/conf/pom.xml
@@ -6,5 +6,10 @@
   <groupId>org.onlab.tools</groupId>
   <artifactId>onos-build-conf</artifactId>
   <version>1.0</version>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
 </project>
 
diff --git a/utils/misc/pom.xml b/utils/misc/pom.xml
index bb25635..bd3cc08 100644
--- a/utils/misc/pom.xml
+++ b/utils/misc/pom.xml
@@ -56,9 +56,13 @@
             <artifactId>objenesis</artifactId>
         </dependency>
         <dependency>
-            <groupId>com.codahale.metrics</groupId>
+            <groupId>io.dropwizard.metrics</groupId>
             <artifactId>metrics-core</artifactId>
-            <version>3.0.2</version>
+            <version>3.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
         </dependency>
     </dependencies>
 
diff --git a/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java b/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java
index 2b13efb..e07d3f9 100644
--- a/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java
+++ b/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java
@@ -1,10 +1,18 @@
 package org.onlab.metrics;
 
+import java.io.File;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
 
 import com.codahale.metrics.Counter;
+import com.codahale.metrics.CsvReporter;
 import com.codahale.metrics.Gauge;
 import com.codahale.metrics.Histogram;
 import com.codahale.metrics.Meter;
@@ -45,24 +53,44 @@
  *   </code>
  * </pre>
  */
+@Component(immediate = true)
 public final class MetricsManager implements MetricsService {
 
     /**
      * Registry to hold the Components defined in the system.
      */
-    private ConcurrentMap<String, MetricsComponent> componentsRegistry =
-            new ConcurrentHashMap<>();
+    private ConcurrentMap<String, MetricsComponent> componentsRegistry;
 
     /**
      * Registry for the Metrics objects created in the system.
      */
-    private final MetricRegistry metricsRegistry = new MetricRegistry();
+    private final MetricRegistry metricsRegistry;
 
     /**
-     * Hide constructor.  The only way to get the registry is through the
-     * singleton getter.
+     * Default Reporter for this metrics manager.
      */
-    private MetricsManager() {}
+    private final CsvReporter reporter;
+
+    public MetricsManager() {
+        this.componentsRegistry = new ConcurrentHashMap<>();
+        this.metricsRegistry = new MetricRegistry();
+
+        this.reporter = CsvReporter.forRegistry(metricsRegistry)
+                .formatFor(Locale.US)
+                .convertRatesTo(TimeUnit.SECONDS)
+                .convertDurationsTo(TimeUnit.MICROSECONDS)
+                .build(new File("/tmp/"));
+
+        reporter.start(10, TimeUnit.SECONDS);
+    }
+
+    @Activate
+    public void activate() {
+    }
+
+    @Deactivate
+    public void deactivate() {
+    }
 
     /**
      * Registers a component.
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
index b205f90..84acb82 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
@@ -250,6 +250,17 @@
         return new IpPrefix(version, host, netmask);
     }
 
+    /**
+     * Returns an IpAddress of the bytes contained in this prefix.
+     * FIXME this is a hack for now and only works because IpPrefix doesn't
+     * mask the input bytes on creation.
+     *
+     * @return the IpAddress
+     */
+    public IpAddress toIpAddress() {
+        return IpAddress.valueOf(octets);
+    }
+
     public boolean isMasked() {
         return mask() != 0;
     }
@@ -278,6 +289,17 @@
         return false;
     }
 
+    public boolean contains(IpAddress address) {
+        // Need to get the network address because prefixes aren't automatically
+        // masked on creation
+        IpPrefix meMasked = network();
+
+        IpPrefix otherMasked =
+                IpPrefix.valueOf(address.octets, netmask).network();
+
+        return Arrays.equals(meMasked.octets, otherMasked.octets);
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -303,6 +325,7 @@
         if (netmask != other.netmask) {
             return false;
         }
+        // TODO not quite right until we mask the input
         if (!Arrays.equals(octets, other.octets)) {
             return false;
         }
diff --git a/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java b/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
index f6bf6f1..297a0f3 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
@@ -76,7 +76,7 @@
     }
 
     @Test
-    public void testContains() {
+    public void testContainsIpPrefix() {
         IpPrefix slash31 = IpPrefix.valueOf(BYTES1, 31);
         IpPrefix slash32 = IpPrefix.valueOf(BYTES1, 32);
         IpPrefix differentSlash32 = IpPrefix.valueOf(BYTES2, 32);
@@ -96,4 +96,17 @@
         assertTrue(slash8.contains(slash31));
         assertFalse(slash31.contains(slash8));
     }
+
+    @Test
+    public void testContainsIpAddress() {
+        IpPrefix slash31 = IpPrefix.valueOf(BYTES1, 31);
+        IpAddress slash32 = IpAddress.valueOf(BYTES1, 32);
+
+        assertTrue(slash31.contains(slash32));
+
+        IpPrefix intf = IpPrefix.valueOf("192.168.10.101/24");
+        IpAddress addr = IpAddress.valueOf("192.168.10.1");
+
+        assertTrue(intf.contains(addr));
+    }
 }
diff --git a/utils/netty/pom.xml b/utils/netty/pom.xml
new file mode 100644
index 0000000..a980d1d
--- /dev/null
+++ b/utils/netty/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onlab.onos</groupId>
+        <artifactId>onlab-utils</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onlab-netty</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>Network I/O using Netty framework</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava-testlib</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onlab.onos</groupId>
+            <artifactId>onlab-misc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onlab.onos</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>de.javakaffee</groupId>
+            <artifactId>kryo-serializers</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-pool</groupId>
+            <artifactId>commons-pool</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/AsyncResponse.java b/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java
similarity index 95%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/AsyncResponse.java
rename to utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java
index ac2337d..b2b490e 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/AsyncResponse.java
+++ b/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java
@@ -1,10 +1,8 @@
-package org.onlab.onos.store.messaging.impl;
+package org.onlab.netty;
 
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import org.onlab.onos.store.messaging.Response;
-
 /**
  * An asynchronous response.
  * This class provides a base implementation of Response, with methods to retrieve the
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/EchoHandler.java b/utils/netty/src/main/java/org/onlab/netty/EchoHandler.java
similarity index 71%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/EchoHandler.java
rename to utils/netty/src/main/java/org/onlab/netty/EchoHandler.java
index 7891c5c..313a448 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/EchoHandler.java
+++ b/utils/netty/src/main/java/org/onlab/netty/EchoHandler.java
@@ -1,10 +1,7 @@
-package org.onlab.onos.store.messaging.impl;
+package org.onlab.netty;
 
 import java.io.IOException;
 
-import org.onlab.onos.store.messaging.Message;
-import org.onlab.onos.store.messaging.MessageHandler;
-
 /**
  * Message handler that echos the message back to the sender.
  */
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Endpoint.java b/utils/netty/src/main/java/org/onlab/netty/Endpoint.java
similarity index 96%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/Endpoint.java
rename to utils/netty/src/main/java/org/onlab/netty/Endpoint.java
index bd6d45f..8681093 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Endpoint.java
+++ b/utils/netty/src/main/java/org/onlab/netty/Endpoint.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.messaging;
+package org.onlab.netty;
 
 /**
  * Representation of a TCP/UDP communication end point.
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/InternalMessage.java b/utils/netty/src/main/java/org/onlab/netty/InternalMessage.java
similarity index 93%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/InternalMessage.java
rename to utils/netty/src/main/java/org/onlab/netty/InternalMessage.java
index 8a87a3e..bcf6f52 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/InternalMessage.java
+++ b/utils/netty/src/main/java/org/onlab/netty/InternalMessage.java
@@ -1,10 +1,7 @@
-package org.onlab.onos.store.messaging.impl;
+package org.onlab.netty;
 
 import java.io.IOException;
 
-import org.onlab.onos.store.messaging.Endpoint;
-import org.onlab.onos.store.messaging.Message;
-
 /**
  * Internal message representation with additional attributes
  * for supporting, synchronous request/reply behavior.
diff --git a/utils/netty/src/main/java/org/onlab/netty/KryoSerializer.java b/utils/netty/src/main/java/org/onlab/netty/KryoSerializer.java
new file mode 100644
index 0000000..73c01a0
--- /dev/null
+++ b/utils/netty/src/main/java/org/onlab/netty/KryoSerializer.java
@@ -0,0 +1,47 @@
+package org.onlab.netty;
+
+import org.onlab.util.KryoPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Kryo Serializer.
+ */
+public class KryoSerializer implements Serializer {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private KryoPool serializerPool;
+
+    public KryoSerializer() {
+        setupKryoPool();
+    }
+
+    /**
+     * Sets up the common serialzers pool.
+     */
+    protected void setupKryoPool() {
+        // FIXME Slice out types used in common to separate pool/namespace.
+        serializerPool = KryoPool.newBuilder()
+                .register(ArrayList.class,
+                          HashMap.class,
+                          ArrayList.class
+                )
+                .build()
+                .populate(1);
+    }
+
+
+    @Override
+    public Object decode(byte[] data) {
+        return serializerPool.deserialize(data);
+    }
+
+    @Override
+    public byte[] encode(Object payload) {
+        return serializerPool.serialize(payload);
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/LoggingHandler.java b/utils/netty/src/main/java/org/onlab/netty/LoggingHandler.java
similarity index 62%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/LoggingHandler.java
rename to utils/netty/src/main/java/org/onlab/netty/LoggingHandler.java
index bf871f8..ed6cdb4 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/LoggingHandler.java
+++ b/utils/netty/src/main/java/org/onlab/netty/LoggingHandler.java
@@ -1,7 +1,4 @@
-package org.onlab.onos.store.messaging.impl;
-
-import org.onlab.onos.store.messaging.Message;
-import org.onlab.onos.store.messaging.MessageHandler;
+package org.onlab.netty;
 
 /**
  * A MessageHandler that simply logs the information.
@@ -12,4 +9,4 @@
     public void handle(Message message) {
         System.out.println("Received: " + message.payload());
     }
-}
\ No newline at end of file
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Message.java b/utils/netty/src/main/java/org/onlab/netty/Message.java
similarity index 92%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/Message.java
rename to utils/netty/src/main/java/org/onlab/netty/Message.java
index d814927..54b9526 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Message.java
+++ b/utils/netty/src/main/java/org/onlab/netty/Message.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.messaging;
+package org.onlab.netty;
 
 import java.io.IOException;
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageDecoder.java b/utils/netty/src/main/java/org/onlab/netty/MessageDecoder.java
similarity index 77%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageDecoder.java
rename to utils/netty/src/main/java/org/onlab/netty/MessageDecoder.java
index 7f94015..ecf2d62 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageDecoder.java
+++ b/utils/netty/src/main/java/org/onlab/netty/MessageDecoder.java
@@ -1,28 +1,25 @@
-package org.onlab.onos.store.messaging.impl;
+package org.onlab.netty;
 
 import java.util.Arrays;
 import java.util.List;
 
 import static com.google.common.base.Preconditions.checkState;
 
-import org.onlab.onos.store.cluster.messaging.SerializationService;
-import org.onlab.onos.store.messaging.Endpoint;
-
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.ByteToMessageDecoder;
 
 /**
- * Decode bytes into a InrenalMessage.
+ * Decode bytes into a InternalMessage.
  */
 public class MessageDecoder extends ByteToMessageDecoder {
 
     private final NettyMessagingService messagingService;
-    private final SerializationService serializationService;
+    private final Serializer serializer;
 
-    public MessageDecoder(NettyMessagingService messagingService, SerializationService serializationService) {
+    public MessageDecoder(NettyMessagingService messagingService, Serializer serializer) {
         this.messagingService = messagingService;
-        this.serializationService = serializationService;
+        this.serializer = serializer;
     }
 
     @Override
@@ -47,7 +44,7 @@
         Endpoint sender = new Endpoint(host, port);
 
         // read message payload; first read size and then bytes.
-        Object payload = serializationService.decode(in.readBytes(in.readInt()).array());
+        Object payload = serializer.decode(in.readBytes(in.readInt()).array());
 
         InternalMessage message = new InternalMessage.Builder(messagingService)
                 .withId(id)
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageEncoder.java b/utils/netty/src/main/java/org/onlab/netty/MessageEncoder.java
similarity index 74%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageEncoder.java
rename to utils/netty/src/main/java/org/onlab/netty/MessageEncoder.java
index b1c660c..1b52a0f 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/MessageEncoder.java
+++ b/utils/netty/src/main/java/org/onlab/netty/MessageEncoder.java
@@ -1,6 +1,4 @@
-package org.onlab.onos.store.messaging.impl;
-
-import org.onlab.onos.store.cluster.messaging.SerializationService;
+package org.onlab.netty;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
@@ -14,10 +12,10 @@
     // onosiscool in ascii
     public static final byte[] PREAMBLE = "onosiscool".getBytes();
 
-    private final SerializationService serializationService;
+    private final Serializer serializer;
 
-    public MessageEncoder(SerializationService serializationService) {
-        this.serializationService = serializationService;
+    public MessageEncoder(Serializer serializer) {
+        this.serializer = serializer;
     }
 
     @Override
@@ -46,12 +44,12 @@
         out.writeInt(message.sender().port());
 
         try {
-            serializationService.encode(message.payload());
+            serializer.encode(message.payload());
         } catch (Exception e) {
             e.printStackTrace();
         }
 
-        byte[] payload = serializationService.encode(message.payload());
+        byte[] payload = serializer.encode(message.payload());
 
         // write payload length.
         out.writeInt(payload.length);
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessageHandler.java b/utils/netty/src/main/java/org/onlab/netty/MessageHandler.java
similarity index 86%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessageHandler.java
rename to utils/netty/src/main/java/org/onlab/netty/MessageHandler.java
index 8eaef1e..7bd5a7f 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessageHandler.java
+++ b/utils/netty/src/main/java/org/onlab/netty/MessageHandler.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.messaging;
+package org.onlab.netty;
 
 import java.io.IOException;
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessagingService.java b/utils/netty/src/main/java/org/onlab/netty/MessagingService.java
similarity index 96%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessagingService.java
rename to utils/netty/src/main/java/org/onlab/netty/MessagingService.java
index 4aa32cb..ebad442 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/MessagingService.java
+++ b/utils/netty/src/main/java/org/onlab/netty/MessagingService.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.messaging;
+package org.onlab.netty;
 
 import java.io.IOException;
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/NettyMessagingService.java b/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
similarity index 89%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/NettyMessagingService.java
rename to utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
index b6b3857..54da8cc 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/NettyMessagingService.java
+++ b/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.messaging.impl;
+package org.onlab.netty;
 
 import java.io.IOException;
 import java.net.UnknownHostException;
@@ -25,17 +25,6 @@
 import org.apache.commons.pool.KeyedObjectPool;
 import org.apache.commons.pool.KeyedPoolableObjectFactory;
 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
-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.store.cluster.messaging.SerializationService;
-import org.onlab.onos.store.messaging.Endpoint;
-import org.onlab.onos.store.messaging.MessageHandler;
-import org.onlab.onos.store.messaging.MessagingService;
-import org.onlab.onos.store.messaging.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,8 +34,6 @@
 /**
  * A Netty based implementation of MessagingService.
  */
-@Component(immediate = true)
-@Service
 public class NettyMessagingService implements MessagingService {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
@@ -60,8 +47,7 @@
     private Cache<Long, AsyncResponse<?>> responseFutures;
     private final Endpoint localEp;
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected SerializationService serializationService;
+    protected Serializer serializer;
 
     public NettyMessagingService() {
         // TODO: Default port should be configurable.
@@ -79,7 +65,6 @@
         }
     }
 
-    @Activate
     public void activate() throws Exception {
         responseFutures = CacheBuilder.newBuilder()
                 .maximumSize(100000)
@@ -90,7 +75,6 @@
         startAcceptingConnections();
     }
 
-    @Deactivate
     public void deactivate() throws Exception {
         channels.close();
         bossGroup.shutdownGracefully();
@@ -213,8 +197,8 @@
         @Override
         protected void initChannel(SocketChannel channel) throws Exception {
             channel.pipeline()
-                .addLast(new MessageEncoder(serializationService))
-                .addLast(new MessageDecoder(NettyMessagingService.this, serializationService))
+                .addLast(new MessageEncoder(serializer))
+                .addLast(new MessageDecoder(NettyMessagingService.this, serializer))
                 .addLast(new NettyMessagingService.InboundMessageDispatcher());
         }
     }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Response.java b/utils/netty/src/main/java/org/onlab/netty/Response.java
similarity index 95%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/Response.java
rename to utils/netty/src/main/java/org/onlab/netty/Response.java
index ff0d84f..04675ce 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/Response.java
+++ b/utils/netty/src/main/java/org/onlab/netty/Response.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.messaging;
+package org.onlab.netty;
 
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
diff --git a/utils/netty/src/main/java/org/onlab/netty/Serializer.java b/utils/netty/src/main/java/org/onlab/netty/Serializer.java
new file mode 100644
index 0000000..ac55f5a
--- /dev/null
+++ b/utils/netty/src/main/java/org/onlab/netty/Serializer.java
@@ -0,0 +1,24 @@
+package org.onlab.netty;
+
+/**
+ * Interface for encoding/decoding message payloads.
+ */
+public interface Serializer {
+
+    /**
+     * Decodes the specified byte array to a POJO.
+     *
+     * @param data byte array.
+     * @return POJO
+     */
+    Object decode(byte[] data);
+
+    /**
+     * Encodes the specified POJO into a byte array.
+     *
+     * @param data POJO to be encoded
+     * @return byte array.
+     */
+    byte[] encode(Object message);
+
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleClient.java b/utils/netty/src/main/java/org/onlab/netty/SimpleClient.java
similarity index 69%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleClient.java
rename to utils/netty/src/main/java/org/onlab/netty/SimpleClient.java
index 95753e7..1573780 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleClient.java
+++ b/utils/netty/src/main/java/org/onlab/netty/SimpleClient.java
@@ -1,11 +1,7 @@
-package org.onlab.onos.store.messaging.impl;
+package org.onlab.netty;
 
 import java.util.concurrent.TimeUnit;
 
-import org.onlab.onos.store.cluster.impl.MessageSerializer;
-import org.onlab.onos.store.messaging.Endpoint;
-import org.onlab.onos.store.messaging.Response;
-
 public final class SimpleClient {
     private SimpleClient() {}
 
@@ -21,9 +17,8 @@
     public static class TestNettyMessagingService extends NettyMessagingService {
         public TestNettyMessagingService(int port) throws Exception {
             super(port);
-            MessageSerializer mgr = new MessageSerializer();
-            mgr.activate();
-            this.serializationService = mgr;
+            Serializer serializer = new KryoSerializer();
+            this.serializer = serializer;
         }
     }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleServer.java b/utils/netty/src/main/java/org/onlab/netty/SimpleServer.java
similarity index 67%
rename from core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleServer.java
rename to utils/netty/src/main/java/org/onlab/netty/SimpleServer.java
index 1b331ba..12fa025 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/messaging/impl/SimpleServer.java
+++ b/utils/netty/src/main/java/org/onlab/netty/SimpleServer.java
@@ -1,6 +1,4 @@
-package org.onlab.onos.store.messaging.impl;
-
-import org.onlab.onos.store.cluster.impl.MessageSerializer;
+package org.onlab.netty;
 
 public final class SimpleServer {
     private SimpleServer() {}
@@ -14,9 +12,8 @@
 
     public static class TestNettyMessagingService extends NettyMessagingService {
         protected TestNettyMessagingService() {
-            MessageSerializer mgr = new MessageSerializer();
-            mgr.activate();
-            this.serializationService = mgr;
+            Serializer serializer = new KryoSerializer();
+            this.serializer = serializer;
         }
     }
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/package-info.java b/utils/netty/src/main/java/org/onlab/netty/package-info.java
new file mode 100644
index 0000000..b1b90a3
--- /dev/null
+++ b/utils/netty/src/main/java/org/onlab/netty/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Asynchronous messaging APIs implemented using the Netty framework.
+ */
+package org.onlab.netty;
\ No newline at end of file
diff --git a/utils/pom.xml b/utils/pom.xml
index 2beeba8..feb60e9 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -19,6 +19,7 @@
     <modules>
         <module>junit</module>
         <module>misc</module>
+        <module>netty</module>
         <module>nio</module>
         <module>osgi</module>
         <module>rest</module>