Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/net/api/src/main/java/org/onlab/onos/event/AbstractListenerRegistry.java b/net/api/src/main/java/org/onlab/onos/event/AbstractListenerRegistry.java
index 5e6011a..9710306 100644
--- a/net/api/src/main/java/org/onlab/onos/event/AbstractListenerRegistry.java
+++ b/net/api/src/main/java/org/onlab/onos/event/AbstractListenerRegistry.java
@@ -45,7 +45,7 @@
         for (L listener : listeners) {
             try {
                 listener.event(event);
-            } catch (Throwable error) {
+            } catch (Exception error) {
                 reportProblem(event, error);
             }
         }
diff --git a/net/core/trivial/src/main/java/org/onlab/onos/event/impl/SimpleEventDispatcher.java b/net/core/trivial/src/main/java/org/onlab/onos/event/impl/SimpleEventDispatcher.java
index da919e4..3834676 100644
--- a/net/core/trivial/src/main/java/org/onlab/onos/event/impl/SimpleEventDispatcher.java
+++ b/net/core/trivial/src/main/java/org/onlab/onos/event/impl/SimpleEventDispatcher.java
@@ -82,7 +82,7 @@
                         log.warn("No sink registered for event class {}",
                                  event.getClass());
                     }
-                } catch (Throwable e) {
+                } catch (Exception e) {
                     log.warn("Error encountered while dispatching event:", e);
                 }
             }
diff --git a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
index b713f00..9df00b0 100644
--- a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
+++ b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
@@ -141,7 +141,6 @@
         public void updatePorts(DeviceId deviceId, List<PortDescription> portDescriptions) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(portDescriptions, "Port descriptions list cannot be null");
-            // FIXME: fix the interface to accept DeviceId separately
             log.info("Device {} ports updated: {}", portDescriptions);
             List<DeviceEvent> events = store.updatePorts(deviceId, portDescriptions);
             for (DeviceEvent event : events) {
diff --git a/pom.xml b/pom.xml
index 26f2889..ca10782 100644
--- a/pom.xml
+++ b/pom.xml
@@ -306,7 +306,7 @@
                         <group>
                             <title>Utilities</title>
                             <packages>
-                                org.onlab.util:org.onlab.util.*
+                                org.onlab.*
                             </packages>
                         </group>
                         <group>
diff --git a/utils/misc/pom.xml b/utils/misc/pom.xml
index 5862727..79a0780 100644
--- a/utils/misc/pom.xml
+++ b/utils/misc/pom.xml
@@ -17,6 +17,12 @@
     <description>Miscellaneous ON.Lab utilities</description>
 
     <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava-testlib</artifactId>
+            <version>17.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java b/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java
new file mode 100644
index 0000000..7ee86f4
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/AbstractEdge.java
@@ -0,0 +1,57 @@
+package org.onlab.graph;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstract graph edge implementation.
+ */
+public abstract class AbstractEdge<V extends Vertex> implements Edge<V> {
+
+    private final V src;
+    private final V dst;
+
+    /**
+     * Creates a new edge between the specified source and destination vertexes.
+     *
+     * @param src source vertex
+     * @param dst destination vertex
+     */
+    public AbstractEdge(V src, V dst) {
+        this.src = checkNotNull(src, "Source vertex cannot be null");
+        this.dst = checkNotNull(dst, "Destination vertex cannot be null");
+    }
+
+    @Override
+    public V src() {
+        return src;
+    }
+
+    @Override
+    public V dst() {
+        return dst;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(src, dst);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof AbstractEdge) {
+            final AbstractEdge other = (AbstractEdge) obj;
+            return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return com.google.common.base.Objects.toStringHelper(this)
+                .add("src", src)
+                .add("dst", dst)
+                .toString();
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/AbstractGraphPathSearch.java b/utils/misc/src/main/java/org/onlab/graph/AbstractGraphPathSearch.java
new file mode 100644
index 0000000..7dad767
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/AbstractGraphPathSearch.java
@@ -0,0 +1,273 @@
+package org.onlab.graph;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Basis for various graph path search algorithm implementations.
+ *
+ * @param <V> vertex type
+ * @param <E> edge type
+ */
+public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V>>
+        implements GraphPathSearch<V, E> {
+
+    private double samenessThreshold = 0.000000001;
+
+    /**
+     * Sets a new sameness threshold for comparing cost values; default is
+     * is {@code 0.000000001}.
+     *
+     * @param threshold fractional double value
+     */
+    public void setSamenessThreshold(double threshold) {
+        samenessThreshold = threshold;
+    }
+
+    /**
+     * Returns the current sameness threshold for comparing cost values.
+     *
+     * @return current threshold
+     */
+    public double samenessThreshold() {
+        return samenessThreshold;
+    }
+
+    /**
+     * Default path search result that uses the DefaultPath to convey paths
+     * in a graph.
+     */
+    protected class DefaultResult implements Result<V, E> {
+
+        private final V src;
+        private final V dst;
+        protected final Set<Path<V, E>> paths = new HashSet<>();
+        protected final Map<V, Double> costs = new HashMap<>();
+        protected final Map<V, Set<E>> parents = new HashMap<>();
+
+        /**
+         * Creates the result of path search.
+         *
+         * @param src path source
+         * @param dst optional path destination
+         */
+        public DefaultResult(V src, V dst) {
+            checkNotNull(src, "Source cannot be null");
+            this.src = src;
+            this.dst = dst;
+        }
+
+        @Override
+        public V src() {
+            return src;
+        }
+
+        @Override
+        public V dst() {
+            return dst;
+        }
+
+        @Override
+        public Set<Path<V, E>> paths() {
+            return paths;
+        }
+
+        @Override
+        public Map<V, Double> costs() {
+            return costs;
+        }
+
+        @Override
+        public Map<V, Set<E>> parents() {
+            return parents;
+        }
+
+        /**
+         * Indicates whether or not the given vertex has a cost yet.
+         *
+         * @param v vertex to test
+         * @return true if the vertex has cost already
+         */
+        boolean hasCost(V v) {
+            return costs.get(v) != null;
+        }
+
+        /**
+         * Returns the current cost to reach the specified vertex.
+         *
+         * @return cost to reach the vertex
+         */
+        double cost(V v) {
+            Double c = costs.get(v);
+            return c == null ? Double.MAX_VALUE : c;
+        }
+
+        /**
+         * Updates the cost of the vertex using its existing cost plus the
+         * cost to traverse the specified edge.
+         *
+         * @param v       vertex
+         * @param edge    edge through which vertex is reached
+         * @param cost    current cost to reach the vertex from the source
+         * @param replace true to indicate that any accrued edges are to be
+         *                cleared; false to indicate that the edge should be
+         *                added to the previously accrued edges as they yield
+         *                the same cost
+         */
+        void updateVertex(V v, E edge, double cost, boolean replace) {
+            costs.put(v, cost);
+            if (edge != null) {
+                Set<E> edges = parents.get(v);
+                if (edges == null) {
+                    edges = new HashSet<>();
+                    parents.put(v, edges);
+                }
+                if (replace) {
+                    edges.clear();
+                }
+                edges.add(edge);
+            }
+        }
+
+        /**
+         * Removes the set of parent edges for the specified vertex.
+         *
+         * @param v vertex
+         */
+        void removeVertex(V v) {
+            parents.remove(v);
+        }
+
+        /**
+         * If possible, relax the specified edge using the supplied base cost
+         * and edge-weight function.
+         *
+         * @param e    edge to be relaxed
+         * @param cost base cost to reach the edge destination vertex
+         * @param ew   optional edge weight function
+         * @return true if the edge was relaxed; false otherwise
+         */
+        boolean relaxEdge(E e, double cost, EdgeWeight<V, E> ew) {
+            V v = e.dst();
+            double oldCost = cost(v);
+            double newCost = cost + (ew == null ? 1.0 : ew.weight(e));
+            boolean relaxed = newCost < oldCost;
+            boolean same = Math.abs(newCost - oldCost) < samenessThreshold;
+            if (same || relaxed) {
+                updateVertex(v, e, newCost, !same);
+            }
+            return relaxed;
+        }
+
+        /**
+         * Builds a set of paths for the specified src/dst vertex pair.
+         */
+        protected void buildPaths() {
+            Set<V> destinations = new HashSet<>();
+            if (dst == null) {
+                destinations.addAll(costs.keySet());
+            } else {
+                destinations.add(dst);
+            }
+
+            // Build all paths between the source and all requested destinations.
+            for (V v : destinations) {
+                // Ignore the source, if it is among the destinations.
+                if (!v.equals(src)) {
+                    buildAllPaths(this, src, v);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Builds a set of all paths between the source and destination using the
+     * graph search result by applying breadth-first search through the parent
+     * edges and vertex costs.
+     *
+     * @param result graph search result
+     * @param src    source vertex
+     * @param dst    destination vertex
+     */
+    private void buildAllPaths(DefaultResult result, V src, V dst) {
+        DefaultMutablePath<V, E> basePath = new DefaultMutablePath<>();
+        basePath.setCost(result.cost(dst));
+
+        Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>();
+        pendingPaths.add(basePath);
+
+        while (!pendingPaths.isEmpty()) {
+            Set<DefaultMutablePath<V, E>> frontier = new HashSet<>();
+
+            for (DefaultMutablePath<V, E> path : pendingPaths) {
+                // For each pending path, locate its first vertex since we
+                // will be moving backwards from it.
+                V firstVertex = firstVertex(path, dst);
+
+                // If the first vertex is our expected source, we have reached
+                // the beginning, so add the this path to the result paths.
+                if (firstVertex.equals(src)) {
+                    path.setCost(result.cost(dst));
+                    result.paths.add(new DefaultPath<>(path.edges(), path.cost()));
+
+                } else {
+                    // If we have not reached the beginning, i.e. the source,
+                    // fetch the set of edges leading to the first vertex of
+                    // this pending path; if there are none, abandon processing
+                    // this path for good.
+                    Set<E> firstVertexParents = result.parents.get(firstVertex);
+                    if (firstVertexParents == null || firstVertexParents.isEmpty()) {
+                        break;
+                    }
+
+                    // Now iterate over all the edges and for each of them
+                    // cloning the current path and then insert that edge to
+                    // the path and then add that path to the pending ones.
+                    // When processing the last edge, modify the current
+                    // pending path rather than cloning a new one.
+                    Iterator<E> edges = firstVertexParents.iterator();
+                    while (edges.hasNext()) {
+                        E edge = edges.next();
+                        boolean isLast = !edges.hasNext();
+                        DefaultMutablePath<V, E> pendingPath = isLast ? path : new DefaultMutablePath<>(path);
+                        pendingPath.insertEdge(edge);
+                        frontier.add(pendingPath);
+                    }
+                }
+            }
+
+            // All pending paths have been scanned so promote the next frontier
+            pendingPaths = frontier;
+        }
+    }
+
+    // Returns the first vertex of the specified path. This is either the source
+    // of the first edge or, if there are no edges yet, the given destination.
+    private V firstVertex(Path<V, E> path, V dst) {
+        return path.edges().isEmpty() ? dst : path.edges().get(0).src();
+    }
+
+    /**
+     * Checks the specified path search arguments for validity.
+     *
+     * @param graph graph; must not be null
+     * @param src   source vertex; must not be null and belong to graph
+     * @param dst   optional target vertex; must belong to graph
+     */
+    protected void checkArguments(Graph<V, E> graph, V src, V dst) {
+        checkNotNull(graph, "Graph cannot be null");
+        checkNotNull(src, "Source cannot be null");
+        Set<V> vertices = graph.getVertexes();
+        checkArgument(vertices.contains(src), "Source not in the graph");
+        checkArgument(dst == null || vertices.contains(dst),
+                      "Destination not in graph");
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java b/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java
new file mode 100644
index 0000000..e1e0524
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/AdjacencyListsGraph.java
@@ -0,0 +1,102 @@
+package org.onlab.graph;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Immutable graph implemented using adjacency lists.
+ *
+ * @param <V> vertex type
+ * @param <E> edge type
+ */
+public class AdjacencyListsGraph<V extends Vertex, E extends Edge<V>> implements Graph<V, E> {
+
+    private final Set<V> vertexes;
+    private final Set<E> edges;
+
+    private final ImmutableSetMultimap<V, E> sources;
+    private final ImmutableSetMultimap<V, E> destinations;
+
+    /**
+     * Creates a graph comprising of the specified vertexes and edges.
+     *
+     * @param vertexes set of graph vertexes
+     * @param edges    set of graph edges
+     */
+    public AdjacencyListsGraph(Set<V> vertexes, Set<E> edges) {
+        checkNotNull(vertexes, "Vertex set cannot be null");
+        checkNotNull(edges, "Edge set cannot be null");
+
+        // Record ingress/egress edges for each vertex.
+        ImmutableSetMultimap.Builder<V, E> srcMap = ImmutableSetMultimap.builder();
+        ImmutableSetMultimap.Builder<V, E> dstMap = ImmutableSetMultimap.builder();
+
+        // Also make sure that all edge end-points are added as vertexes
+        ImmutableSet.Builder<V> actualVertexes = ImmutableSet.builder();
+        actualVertexes.addAll(vertexes);
+
+        for (E edge : edges) {
+            srcMap.put(edge.src(), edge);
+            actualVertexes.add(edge.src());
+            dstMap.put(edge.dst(), edge);
+            actualVertexes.add(edge.dst());
+        }
+
+        // Make an immutable copy of the edge and vertex sets
+        this.edges = ImmutableSet.copyOf(edges);
+        this.vertexes = actualVertexes.build();
+
+        // Build immutable copies of sources and destinations edge maps
+        sources = srcMap.build();
+        destinations = dstMap.build();
+    }
+
+    @Override
+    public Set<V> getVertexes() {
+        return vertexes;
+    }
+
+    @Override
+    public Set<E> getEdges() {
+        return edges;
+    }
+
+    @Override
+    public Set<E> getEdgesFrom(V src) {
+        return sources.get(src);
+    }
+
+    @Override
+    public Set<E> getEdgesTo(V dst) {
+        return destinations.get(dst);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof AdjacencyListsGraph) {
+            AdjacencyListsGraph that = (AdjacencyListsGraph) obj;
+            return this.getClass() == that.getClass() &&
+                    Objects.equals(this.vertexes, that.vertexes) &&
+                    Objects.equals(this.edges, that.edges);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(vertexes, edges);
+    }
+
+    @Override
+    public String toString() {
+        return com.google.common.base.Objects.toStringHelper(this)
+                .add("vertexes", vertexes)
+                .add("edges", edges)
+                .toString();
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/BellmanFordGraphSearch.java b/utils/misc/src/main/java/org/onlab/graph/BellmanFordGraphSearch.java
new file mode 100644
index 0000000..513c686
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/BellmanFordGraphSearch.java
@@ -0,0 +1,45 @@
+package org.onlab.graph;
+
+/**
+ * Bellman-Ford graph search algorithm for locating shortest-paths in
+ * directed graphs that may contain negative cycles.
+ */
+public class BellmanFordGraphSearch<V extends Vertex, E extends Edge<V>>
+        extends AbstractGraphPathSearch<V, E> {
+
+    @Override
+    public Result<V, E> search(Graph<V, E> graph, V src, V dst,
+                               EdgeWeight<V, E> weight) {
+        checkArguments(graph, src, dst);
+
+        // Prepare the graph search result.
+        DefaultResult result = new DefaultResult(src, dst);
+
+        // The source vertex has cost 0, of course.
+        result.updateVertex(src, null, 0.0, true);
+
+        int max = graph.getVertexes().size() - 1;
+        for (int i = 0; i < max; i++) {
+            // Relax, if possible, all egress edges of the current vertex.
+            for (E edge : graph.getEdges()) {
+                if (result.hasCost(edge.src())) {
+                    result.relaxEdge(edge, result.cost(edge.src()), weight);
+                }
+            }
+        }
+
+        // Remove any vertexes reached by traversing edges with negative weights.
+        for (E edge : graph.getEdges()) {
+            if (result.hasCost(edge.src())) {
+                if (result.relaxEdge(edge, result.cost(edge.src()), weight)) {
+                    result.removeVertex(edge.dst());
+                }
+            }
+        }
+
+        // Finally, but the paths on the search result and return.
+        result.buildPaths();
+        return result;
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/BreadthFirstSearch.java b/utils/misc/src/main/java/org/onlab/graph/BreadthFirstSearch.java
new file mode 100644
index 0000000..daf8ca2
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/BreadthFirstSearch.java
@@ -0,0 +1,64 @@
+package org.onlab.graph;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Implementation of the BFS algorithm.
+ */
+public class BreadthFirstSearch<V extends Vertex, E extends Edge<V>>
+        extends AbstractGraphPathSearch<V, E> {
+
+    @Override
+    public Result<V, E> search(Graph<V, E> graph, V src, V dst,
+                               EdgeWeight<V, E> weight) {
+        checkArguments(graph, src, dst);
+
+        // Prepare the graph result.
+        DefaultResult result = new DefaultResult(src, dst);
+
+        // Setup the starting frontier with the source as the sole vertex.
+        Set<V> frontier = new HashSet<>();
+        result.updateVertex(src, null, 0.0, true);
+        frontier.add(src);
+
+        boolean reachedEnd = false;
+        while (!reachedEnd && !frontier.isEmpty()) {
+            // Prepare the next frontier.
+            Set<V> next = new HashSet<>();
+
+            // Visit all vertexes in the current frontier.
+            for (V vertex : frontier) {
+                double cost = result.cost(vertex);
+
+                // Visit all egress edges of the current frontier vertex.
+                for (E edge : graph.getEdgesFrom(vertex)) {
+                    V nextVertex = edge.dst();
+                    if (!result.hasCost(nextVertex)) {
+                        // If this vertex has not been visited yet, update it.
+                        double newCost = cost + (weight == null ? 1.0 : weight.weight(edge));
+                        result.updateVertex(nextVertex, edge, newCost, true);
+                        // If we have reached our intended destination, bail.
+                        if (nextVertex.equals(dst)) {
+                            reachedEnd = true;
+                            break;
+                        }
+                        next.add(nextVertex);
+                    }
+
+                    if (reachedEnd) {
+                        break;
+                    }
+                }
+            }
+
+            // Promote the next frontier.
+            frontier = next;
+        }
+
+        // Finally, but the paths on the search result and return.
+        result.buildPaths();
+        return result;
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java b/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java
new file mode 100644
index 0000000..be7ad18
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/DefaultMutablePath.java
@@ -0,0 +1,117 @@
+package org.onlab.graph;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Simple concrete implementation of a directed graph path.
+ */
+public class DefaultMutablePath<V extends Vertex, E extends Edge<V>> implements MutablePath<V, E> {
+
+    private final List<E> edges = new ArrayList<>();
+    private double cost = 0.0;
+
+    /**
+     * Creates a new empty path.
+     */
+    public DefaultMutablePath() {
+    }
+
+    /**
+     * Creates a new path as a copy of another path.
+     *
+     * @param path path to be copied
+     */
+    public DefaultMutablePath(Path<V, E> path) {
+        checkNotNull(path, "Path cannot be null");
+        this.cost = path.cost();
+        edges.addAll(path.edges());
+    }
+
+    @Override
+    public V src() {
+        return edges.isEmpty() ? null : edges.get(0).src();
+    }
+
+    @Override
+    public V dst() {
+        return edges.isEmpty() ? null : edges.get(edges.size() - 1).dst();
+    }
+
+    @Override
+    public double cost() {
+        return cost;
+    }
+
+    @Override
+    public List<E> edges() {
+        return ImmutableList.copyOf(edges);
+    }
+
+    @Override
+    public void setCost(double cost) {
+        this.cost = cost;
+    }
+
+    @Override
+    public Path<V, E> toImmutable() {
+        return new DefaultPath<>(edges, cost);
+    }
+
+    @Override
+    public void insertEdge(E edge) {
+        checkNotNull(edge, "Edge cannot be null");
+        checkArgument(edges.isEmpty() || src().equals(edge.dst()),
+                      "Edge destination must be the same as the current path source");
+        edges.add(0, edge);
+    }
+
+    @Override
+    public void appendEdge(E edge) {
+        checkNotNull(edge, "Edge cannot be null");
+        checkArgument(edges.isEmpty() || dst().equals(edge.src()),
+                      "Edge source must be the same as the current path destination");
+        edges.add(edge);
+    }
+
+    @Override
+    public void removeEdge(E edge) {
+        checkArgument(edge.src().equals(edge.dst()) ||
+                              edges.indexOf(edge) == 0 ||
+                              edges.lastIndexOf(edge) == edges.size() - 1,
+                      "Edge must be at start or end of path, or it must be a cyclic edge");
+        edges.remove(edge);
+    }
+
+    @Override
+    public String toString() {
+        return com.google.common.base.Objects.toStringHelper(this)
+                .add("src", src())
+                .add("dst", dst())
+                .add("cost", cost)
+                .add("edges", edges)
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(edges, cost);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof DefaultMutablePath) {
+            final DefaultMutablePath other = (DefaultMutablePath) obj;
+            return Objects.equals(this.cost, other.cost) &&
+                    Objects.equals(this.edges, other.edges);
+        }
+        return false;
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java b/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java
new file mode 100644
index 0000000..d7fc9e8a
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/DefaultPath.java
@@ -0,0 +1,84 @@
+package org.onlab.graph;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Simple concrete implementation of a directed graph path.
+ */
+public class DefaultPath<V extends Vertex, E extends Edge<V>> implements Path<V, E> {
+
+    private final V src;
+    private final V dst;
+    private final List<E> edges;
+    private double cost = 0.0;
+
+    /**
+     * Creates a new path from the specified list of edges and cost.
+     *
+     * @param edges list of path edges
+     * @param cost  path cost as a unit-less number
+     */
+    public DefaultPath(List<E> edges, double cost) {
+        checkNotNull(edges, "Edges list must not be null");
+        checkArgument(!edges.isEmpty(), "There must be at least one edge");
+        this.edges = ImmutableList.copyOf(edges);
+        this.src = edges.get(0).src();
+        this.dst = edges.get(edges.size() - 1).dst();
+        this.cost = cost;
+    }
+
+    @Override
+    public V src() {
+        return src;
+    }
+
+    @Override
+    public V dst() {
+        return dst;
+    }
+
+    @Override
+    public double cost() {
+        return cost;
+    }
+
+    @Override
+    public List<E> edges() {
+        return Collections.unmodifiableList(edges);
+    }
+
+    @Override
+    public String toString() {
+        return com.google.common.base.Objects.toStringHelper(this)
+                .add("src", src)
+                .add("dst", dst)
+                .add("cost", cost)
+                .add("edges", edges)
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(src, dst, edges, cost);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof DefaultPath) {
+            final DefaultPath other = (DefaultPath) obj;
+            return Objects.equals(this.src, other.src) &&
+                    Objects.equals(this.dst, other.dst) &&
+                    Objects.equals(this.cost, other.cost) &&
+                    Objects.equals(this.edges, other.edges);
+        }
+        return false;
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/DepthFirstSearch.java b/utils/misc/src/main/java/org/onlab/graph/DepthFirstSearch.java
new file mode 100644
index 0000000..c4d3f83
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/DepthFirstSearch.java
@@ -0,0 +1,167 @@
+package org.onlab.graph;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * DFS graph search algorithm implemented via iteration rather than recursion.
+ */
+public class DepthFirstSearch<V extends Vertex, E extends Edge<V>>
+        extends AbstractGraphPathSearch<V, E> {
+
+    /**
+     * Graph edge types as classified by the DFS algorithm.
+     */
+    public static enum EdgeType {
+        TREE_EDGE, FORWARD_EDGE, BACK_EDGE, CROSS_EDGE
+    }
+
+    @Override
+    public SpanningTreeResult search(Graph<V, E> graph, V src, V dst,
+                                     EdgeWeight<V, E> weight) {
+        checkArguments(graph, src, dst);
+
+        // Prepare the search result.
+        SpanningTreeResult result = new SpanningTreeResult(src, dst);
+
+        // The source vertex has cost 0, of course.
+        result.updateVertex(src, null, 0.0, true);
+
+        // Track finished vertexes and keep a stack of vertexes that have been
+        // started; start this stack with the source on it.
+        Set<V> finished = new HashSet<>();
+        Stack<V> stack = new Stack<>();
+        stack.push(src);
+
+        while (!stack.isEmpty()) {
+            V vertex = stack.peek();
+            if (vertex.equals(dst)) {
+                // If we have reached our destination, bail.
+                break;
+            }
+
+            double cost = result.cost(vertex);
+            boolean tangent = false;
+
+            // Visit all egress edges of the current vertex.
+            for (E edge : graph.getEdgesFrom(vertex)) {
+                // If we have seen the edge already, skip it.
+                if (result.isEdgeMarked(edge)) {
+                    continue;
+                }
+
+                // Examine the destination of the current edge.
+                V nextVertex = edge.dst();
+                if (!result.hasCost(nextVertex)) {
+                    // If this vertex have not finished this vertex yet,
+                    // not started it, then start it as a tree-edge.
+                    result.markEdge(edge, EdgeType.TREE_EDGE);
+                    double newCost = cost + (weight == null ? 1.0 : weight.weight(edge));
+                    result.updateVertex(nextVertex, edge, newCost, true);
+                    stack.push(nextVertex);
+                    tangent = true;
+                    break;
+
+                } else if (!finished.contains(nextVertex)) {
+                    // We started the vertex, but did not yet finish it, so
+                    // it must be a back-edge.
+                    result.markEdge(edge, EdgeType.BACK_EDGE);
+                } else {
+                    // The target has been finished already, so what we have
+                    // here is either a forward-edge or a cross-edge.
+                    result.markEdge(edge, isForwardEdge(result, edge) ?
+                            EdgeType.FORWARD_EDGE : EdgeType.CROSS_EDGE);
+                }
+            }
+
+            // If we have not been sent on a tangent search and reached the
+            // end of the current scan normally, mark the node as finished
+            // and pop it off the vertex stack.
+            if (!tangent) {
+                finished.add(vertex);
+                stack.pop();
+            }
+        }
+
+        // Finally, but the paths on the search result and return.
+        result.buildPaths();
+        return result;
+    }
+
+    /**
+     * Determines whether the specified edge is a forward edge using the
+     * accumulated set of parent edges for each vertex.
+     *
+     * @param result search result
+     * @param edge   edge to be classified
+     * @return true if the edge is a forward edge
+     */
+    protected boolean isForwardEdge(DefaultResult result, E edge) {
+        // Follow the parent edges until we hit the edge source vertex
+        V target = edge.src();
+        V vertex = edge.dst();
+        Set<E> parentEdges;
+        while ((parentEdges = result.parents.get(vertex)) != null) {
+            for (E parentEdge : parentEdges) {
+                vertex = parentEdge.src();
+                if (vertex.equals(target)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Graph search result which includes edge classification for building
+     * a spanning tree.
+     */
+    public class SpanningTreeResult extends DefaultResult {
+
+        protected final Map<E, EdgeType> edges = new HashMap<>();
+
+        /**
+         * Creates a new spanning tree result.
+         *
+         * @param src search source
+         * @param dst optional search destination
+         */
+        public SpanningTreeResult(V src, V dst) {
+            super(src, dst);
+        }
+
+        /**
+         * Returns the map of edge type.
+         *
+         * @return edge to edge type bindings
+         */
+        public Map<E, EdgeType> edges() {
+            return edges;
+        }
+
+        /**
+         * Indicates whether or not the edge has been marked with type.
+         *
+         * @param edge edge to test
+         * @return true if the edge has been marked already
+         */
+        boolean isEdgeMarked(E edge) {
+            return edges.containsKey(edge);
+        }
+
+        /**
+         * Marks the edge with the specified type.
+         *
+         * @param edge edge to mark
+         * @param type edge type
+         */
+        void markEdge(E edge, EdgeType type) {
+            edges.put(edge, type);
+        }
+
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/DijkstraGraphSearch.java b/utils/misc/src/main/java/org/onlab/graph/DijkstraGraphSearch.java
new file mode 100644
index 0000000..535da09
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/DijkstraGraphSearch.java
@@ -0,0 +1,77 @@
+package org.onlab.graph;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Set;
+
+/**
+ * Dijkstra shortest-path graph search algorithm capable of finding not just
+ * one, but all shortest paths between the source and destinations.
+ */
+public class DijkstraGraphSearch<V extends Vertex, E extends Edge<V>>
+        extends AbstractGraphPathSearch<V, E> {
+
+    @Override
+    public Result<V, E> search(Graph<V, E> graph, V src, V dst,
+                               EdgeWeight<V, E> weight) {
+        checkArguments(graph, src, dst);
+
+        // Use the default result to remember cumulative costs and parent
+        // edges to each each respective vertex.
+        DefaultResult result = new DefaultResult(src, dst);
+
+        // Cost to reach the source vertex is 0 of course.
+        result.updateVertex(src, null, 0.0, false);
+
+        // Use the min priority queue to progressively find each nearest
+        // vertex until we reach the desired destination, if one was given,
+        // or until we reach all possible destinations.
+        Heap<V> minQueue = createMinQueue(graph.getVertexes(),
+                                          new PathCostComparator(result));
+        while (!minQueue.isEmpty()) {
+            // Get the nearest vertex
+            V nearest = minQueue.extractExtreme();
+            if (nearest.equals(dst)) {
+                break;
+            }
+
+            // Find its cost and use it to determine if the vertex is reachable.
+            double cost = result.cost(nearest);
+            if (cost < Double.MAX_VALUE) {
+                // If the vertex is reachable, relax all its egress edges.
+                for (E e : graph.getEdgesFrom(nearest)) {
+                    result.relaxEdge(e, cost, weight);
+                }
+            }
+
+            // Re-prioritize the min queue.
+            minQueue.heapify();
+        }
+
+        // Now construct a set of paths from the results.
+        result.buildPaths();
+        return result;
+    }
+
+    // Compares path weights using their accrued costs; used for sorting the
+    // min priority queue.
+    private final class PathCostComparator implements Comparator<V> {
+        private final DefaultResult result;
+
+        private PathCostComparator(DefaultResult result) {
+            this.result = result;
+        }
+
+        @Override
+        public int compare(V v1, V v2) {
+            double delta = result.cost(v2) - result.cost(v1);
+            return delta < 0 ? -1 : (delta > 0 ? 1 : 0);
+        }
+    }
+
+    // Creates a min priority queue from the specified vertexes and comparator.
+    private Heap<V> createMinQueue(Set<V> vertexes, Comparator<V> comparator) {
+        return new Heap<>(new ArrayList<>(vertexes), comparator);
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/Edge.java b/utils/misc/src/main/java/org/onlab/graph/Edge.java
new file mode 100644
index 0000000..d4031f5
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/Edge.java
@@ -0,0 +1,24 @@
+package org.onlab.graph;
+
+/**
+ * Representation of a graph edge.
+ *
+ * @param <V> vertex type
+ */
+public interface Edge<V extends Vertex> {
+
+    /**
+     * Returns the edge source vertex.
+     *
+     * @return source vertex
+     */
+    V src();
+
+    /**
+     * Returns the edge destination vertex.
+     *
+     * @return destination vertex
+     */
+    V dst();
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/EdgeWeight.java b/utils/misc/src/main/java/org/onlab/graph/EdgeWeight.java
new file mode 100644
index 0000000..b0630a0
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/EdgeWeight.java
@@ -0,0 +1,16 @@
+package org.onlab.graph;
+
+/**
+ * Abstraction of a graph edge weight function.
+ */
+public interface EdgeWeight<V extends Vertex, E extends Edge<V>> {
+
+    /**
+     * Returns the weight of the given edge as a unit-less number.
+     *
+     * @param edge edge to be weighed
+     * @return edge weight as a unit-less number
+     */
+    double weight(E edge);
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/Graph.java b/utils/misc/src/main/java/org/onlab/graph/Graph.java
new file mode 100644
index 0000000..137ec8d
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/Graph.java
@@ -0,0 +1,44 @@
+package org.onlab.graph;
+
+
+import java.util.Set;
+
+/**
+ * Abstraction of a directed graph structure.
+ *
+ * @param <V> vertex type
+ * @param <E> edge type
+ */
+public interface Graph<V extends Vertex, E extends Edge> {
+
+    /**
+     * Returns the set of vertexes comprising the graph.
+     *
+     * @return set of vertexes
+     */
+    Set<V> getVertexes();
+
+    /**
+     * Returns the set of edges comprising the graph.
+     *
+     * @return set of edges
+     */
+    Set<E> getEdges();
+
+    /**
+     * Returns all edges leading out from the specified source vertex.
+     *
+     * @param src source vertex
+     * @return set of egress edges; empty if no such edges
+     */
+    Set<E> getEdgesFrom(V src);
+
+    /**
+     * Returns all edges leading towards the specified destination vertex.
+     *
+     * @param dst destination vertex
+     * @return set of ingress vertexes; empty if no such edges
+     */
+    Set<E> getEdgesTo(V dst);
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/GraphPathSearch.java b/utils/misc/src/main/java/org/onlab/graph/GraphPathSearch.java
new file mode 100644
index 0000000..e8df185
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/GraphPathSearch.java
@@ -0,0 +1,68 @@
+package org.onlab.graph;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Representation of a graph path search algorithm.
+ *
+ * @param <V> vertex type
+ * @param <E> edge type
+ */
+public interface GraphPathSearch<V extends Vertex, E extends Edge<V>> {
+
+    /**
+     * Abstraction of a path search result.
+     */
+    public interface Result<V extends Vertex, E extends Edge<V>> {
+
+        /**
+         * Returns the search source.
+         *
+         * @return search source
+         */
+        public V src();
+
+        /**
+         * Returns the search destination, if was was given.
+         *
+         * @return optional search destination
+         */
+        public V dst();
+
+        /**
+         * Returns the set of paths produced as a result of the graph search.
+         *
+         * @return set of paths
+         */
+        Set<Path<V, E>> paths();
+
+        /**
+         * Returns bindings of each vertex to its parent edges in the path.
+         *
+         * @return map of vertex to its parent edge bindings
+         */
+        public Map<V, Set<E>> parents();
+
+        /**
+         * Return a bindings of each vertex to its cost in the path.
+         *
+         * @return map of vertex to path cost bindings
+         */
+        public Map<V, Double> costs();
+    }
+
+    /**
+     * Searches the specified graph.
+     *
+     * @param graph  graph to be searched
+     * @param src    optional source vertex
+     * @param dst    optional destination vertex; if null paths to all vertex
+     *               destinations will be searched
+     * @param weight optional edge-weight; if null cost of each edge will be
+     *               assumed to be 1.0
+     * @return search results
+     */
+    Result<V, E> search(Graph<V, E> graph, V src, V dst, EdgeWeight<V, E> weight);
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/GraphSearch.java b/utils/misc/src/main/java/org/onlab/graph/GraphSearch.java
new file mode 100644
index 0000000..4686851
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/GraphSearch.java
@@ -0,0 +1,28 @@
+package org.onlab.graph;
+
+/**
+ * Representation of a graph search algorithm and its outcome.
+ *
+ * @param <V>    vertex type
+ * @param <E>    edge type
+ */
+public interface GraphSearch<V extends Vertex, E extends Edge<V>> {
+
+    /**
+     * Notion of a graph search result.
+     */
+    public interface Result<V extends Vertex, E extends Edge<V>> {
+    }
+
+    /**
+     * Searches the specified graph.
+     *
+     * @param graph  graph to be searched
+     * @param weight optional edge-weight; if null cost of each edge will be
+     *               assumed to be 1.0
+     *
+     * @return search results
+     */
+    Result search(Graph<V, E> graph, EdgeWeight<V, E> weight);
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/Heap.java b/utils/misc/src/main/java/org/onlab/graph/Heap.java
new file mode 100644
index 0000000..21eeb85
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/Heap.java
@@ -0,0 +1,190 @@
+package org.onlab.graph;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of an array-backed heap structure whose sense of order is
+ * imposed by the provided comparator.
+ * <p/>
+ * While this provides similar functionality to {@link java.util.PriorityQueue}
+ * data structure, one key difference is that external entities can control
+ * when to restore the heap property, which is done through invocation of the
+ * {@link #heapify} method.
+ * <p/>
+ * This class is not thread-safe and care must be taken to prevent concurrent
+ * modifications.
+ *
+ * @param <T> type of the items on the heap
+ */
+public class Heap<T> {
+
+    private final List<T> data;
+    private final Comparator<T> comparator;
+
+    /**
+     * Creates a new heap backed by the specified list. In the interest of
+     * efficiency, the list should be array-backed. Also, for the same reason,
+     * the data is not copied and therefore, the caller must assure that the
+     * backing data is not altered in any way.
+     *
+     * @param data       backing data list
+     * @param comparator comparator for ordering the heap items
+     */
+    public Heap(List<T> data, Comparator<T> comparator) {
+        this.data = checkNotNull(data, "Data cannot be null");
+        this.comparator = checkNotNull(comparator, "Comparator cannot be null");
+        heapify();
+    }
+
+    /**
+     * Restores the heap property by re-arranging the elements in the backing
+     * array as necessary following any heap modifications.
+     */
+    public void heapify() {
+        for (int i = data.size() / 2; i >= 0; i--) {
+            heapify(i);
+        }
+    }
+
+    /**
+     * Returns the current size of the heap.
+     *
+     * @return number of items in the heap
+     */
+    public int size() {
+        return data.size();
+    }
+
+    /**
+     * Returns true if there are no items in the heap.
+     *
+     * @return true if heap is empty
+     */
+    public boolean isEmpty() {
+        return data.isEmpty();
+    }
+
+    /**
+     * Returns the most extreme item in the heap.
+     *
+     * @return heap extreme or null if the heap is empty
+     */
+    public T extreme() {
+        return data.isEmpty() ? null : data.get(0);
+    }
+
+    /**
+     * Extracts and returns the most extreme item from the heap.
+     *
+     * @return heap extreme or null if the heap is empty
+     */
+    public T extractExtreme() {
+        if (!isEmpty()) {
+            T extreme = extreme();
+
+            data.set(0, data.get(data.size() - 1));
+            data.remove(data.size() - 1);
+            heapify();
+            return extreme;
+        }
+        return null;
+    }
+
+    /**
+     * Inserts the specified item into the heap and returns the modified heap.
+     *
+     * @param item item to be inserted
+     * @return the heap self
+     * @throws IllegalArgumentException if the heap is already full
+     */
+    public Heap<T> insert(T item) {
+        data.add(item);
+        bubbleUp();
+        return this;
+    }
+
+    /**
+     * Returns iterator to traverse the heap level-by-level. This iterator
+     * does not permit removal of items.
+     *
+     * @return non-destructive heap iterator
+     */
+    public Iterator<T> iterator() {
+        return ImmutableList.copyOf(data).iterator();
+    }
+
+    // Bubbles up the last item in the heap to its proper position to restore
+    // the heap property.
+    private void bubbleUp() {
+        int child = data.size() - 1;
+        while (child > 0) {
+            int parent = child / 2;
+            if (comparator.compare(data.get(child), data.get(parent)) < 0) {
+                break;
+            }
+            swap(child, parent);
+            child = parent;
+        }
+    }
+
+    // Restores the heap property of the specified heap layer.
+    private void heapify(int i) {
+        int left = 2 * i + 1;
+        int right = 2 * i;
+        int extreme = i;
+
+        if (left < data.size() &&
+                comparator.compare(data.get(extreme), data.get(left)) < 0) {
+            extreme = left;
+        }
+
+        if (right < data.size() &&
+                comparator.compare(data.get(extreme), data.get(right)) < 0) {
+            extreme = right;
+        }
+
+        if (extreme != i) {
+            swap(i, extreme);
+            heapify(extreme);
+        }
+    }
+
+    // Swaps two heap items identified by their respective indexes.
+    private void swap(int i, int k) {
+        T aux = data.get(i);
+        data.set(i, data.get(k));
+        data.set(k, aux);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Heap) {
+            Heap that = (Heap) obj;
+            return this.getClass() == that.getClass() &&
+                    Objects.equals(this.comparator, that.comparator) &&
+                    Objects.deepEquals(this.data, that.data);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(comparator, data);
+    }
+
+    @Override
+    public String toString() {
+        return com.google.common.base.Objects.toStringHelper(this)
+                .add("data", data)
+                .add("comparator", comparator)
+                .toString();
+    }
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/MutableGraph.java b/utils/misc/src/main/java/org/onlab/graph/MutableGraph.java
new file mode 100644
index 0000000..2974a88
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/MutableGraph.java
@@ -0,0 +1,44 @@
+package org.onlab.graph;
+
+/**
+ * Abstraction of a mutable graph that can be constructed gradually.
+ */
+public interface MutableGraph<V extends Vertex, E extends Edge> extends Graph<V, E> {
+
+    /**
+     * Adds the specified vertex to this graph.
+     *
+     * @param vertex new vertex
+     */
+    void addVertex(V vertex);
+
+    /**
+     * Removes the specified vertex from the graph.
+     *
+     * @param vertex vertex to be removed
+     */
+    void removeVertex(V vertex);
+
+    /**
+     * Adds the specified edge to this graph. If the edge vertexes are not
+     * already in the graph, they will be added as well.
+     *
+     * @param edge new edge
+     */
+    void addEdge(E edge);
+
+    /**
+     * Removes the specified edge from the graph.
+     *
+     * @param edge edge to be removed
+     */
+    void removeEdge(E edge);
+
+    /**
+     * Returns an immutable copy of this graph.
+     *
+     * @return immutable copy
+     */
+    Graph<V, E> toImmutable();
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/MutablePath.java b/utils/misc/src/main/java/org/onlab/graph/MutablePath.java
new file mode 100644
index 0000000..2d8420d
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/MutablePath.java
@@ -0,0 +1,47 @@
+package org.onlab.graph;
+
+/**
+ * Abstraction of a mutable path that allows gradual construction.
+ */
+public interface MutablePath<V extends Vertex, E extends Edge<V>> extends Path<V, E> {
+
+    /**
+     * Inserts a new edge at the beginning of this path. The edge must be
+     * adjacent to the prior start of the path.
+     *
+     * @param edge edge to be inserted
+     */
+    void insertEdge(E edge);
+
+    /**
+     * Appends a new edge at the end of the this path. The edge must be
+     * adjacent to the prior end of the path.
+     *
+     * @param edge edge to be inserted
+     */
+    void appendEdge(E edge);
+
+    /**
+     * Removes the specified edge. This edge must be either at the start or
+     * at the end of the path, or it must be a cyclic edge in order not to
+     * violate the contiguous path property.
+     *
+     * @param edge edge to be removed
+     */
+    void removeEdge(E edge);
+
+    /**
+     * Sets the total path cost as a unit-less double.
+     *
+     * @param cost new path cost
+     */
+    void setCost(double cost);
+
+    /**
+     * Returns an immutable copy of this path.
+     *
+     * @return immutable copy
+     */
+    Path<V, E> toImmutable();
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/Path.java b/utils/misc/src/main/java/org/onlab/graph/Path.java
new file mode 100644
index 0000000..35f512b
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/Path.java
@@ -0,0 +1,30 @@
+package org.onlab.graph;
+
+import java.util.List;
+
+/**
+ * Representation of a path in a graph as a sequence of edges. Paths are
+ * assumed to be continuous, where adjacent edges must share a vertex.
+ *
+ * @param <V> vertex type
+ * @param <E> edge type
+ */
+public interface Path<V extends Vertex, E extends Edge<V>> extends Edge<V> {
+
+    /**
+     * Returns the list of edges comprising the path. Adjacent edges will
+     * share the same vertex, meaning that a source of one edge, will be the
+     * same as the destination of the prior edge.
+     *
+     * @return list of path edges
+     */
+    List<E> edges();
+
+    /**
+     * Returns the total cost of the path as a unit-less number.
+     *
+     * @return path cost as a unit-less number
+     */
+    double cost();
+
+}
diff --git a/utils/misc/src/main/java/org/onlab/graph/Vertex.java b/utils/misc/src/main/java/org/onlab/graph/Vertex.java
new file mode 100644
index 0000000..09d43b3
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/graph/Vertex.java
@@ -0,0 +1,7 @@
+package org.onlab.graph;
+
+/**
+ * Representation of a graph vertex.
+ */
+public interface Vertex {
+}
diff --git a/utils/misc/src/main/javadoc/org/onlab/graph/package.html b/utils/misc/src/main/javadoc/org/onlab/graph/package.html
new file mode 100644
index 0000000..fe266ac
--- /dev/null
+++ b/utils/misc/src/main/javadoc/org/onlab/graph/package.html
@@ -0,0 +1,3 @@
+<body>
+Graph abstractions and graph path finding algorithms.
+</body>
\ No newline at end of file
diff --git a/utils/misc/src/main/javadoc/org/onlab/util/package.html b/utils/misc/src/main/javadoc/org/onlab/util/package.html
new file mode 100644
index 0000000..f3de0e7
--- /dev/null
+++ b/utils/misc/src/main/javadoc/org/onlab/util/package.html
@@ -0,0 +1,3 @@
+<body>
+Miscellaneous domain-agnostic utilities.
+</body>
\ No newline at end of file
diff --git a/utils/misc/src/test/java/org/onlab/graph/AbstractEdgeTest.java b/utils/misc/src/test/java/org/onlab/graph/AbstractEdgeTest.java
new file mode 100644
index 0000000..4384a66
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/AbstractEdgeTest.java
@@ -0,0 +1,22 @@
+package org.onlab.graph;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+/**
+ * Test of the base edge implementation.
+ */
+public class AbstractEdgeTest {
+
+    @Test
+    public void equality() {
+        TestVertex v1 = new TestVertex("1");
+        TestVertex v2 = new TestVertex("2");
+        new EqualsTester()
+                .addEqualityGroup(new TestEdge(v1, v2, 1),
+                                  new TestEdge(v1, v2, 1))
+                .addEqualityGroup(new TestEdge(v2, v1, 1))
+                .testEquals();
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/AbstractGraphPathSearchTest.java b/utils/misc/src/test/java/org/onlab/graph/AbstractGraphPathSearchTest.java
new file mode 100644
index 0000000..d5b18fe
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/AbstractGraphPathSearchTest.java
@@ -0,0 +1,46 @@
+package org.onlab.graph;
+
+import org.junit.Test;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Base for all graph search tests.
+ */
+public abstract class AbstractGraphPathSearchTest extends GraphTest {
+
+    /**
+     * Creates a test-specific graph search to exercise.
+     *
+     * @return graph search
+     */
+    protected abstract AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch();
+
+    @Test(expected = IllegalArgumentException.class)
+    public void noSuchSourceArgument() {
+        graphSearch().search(new AdjacencyListsGraph<>(of(B, C),
+                                                       of(new TestEdge(B, C, 1))),
+                             A, H, weight);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullGraphArgument() {
+        graphSearch().search(null, A, H, weight);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullSourceArgument() {
+        graphSearch().search(new AdjacencyListsGraph<>(of(B, C),
+                                                       of(new TestEdge(B, C, 1))),
+                             null, H, weight);
+    }
+
+    @Test
+    public void samenessThreshold() {
+        AbstractGraphPathSearch<TestVertex, TestEdge> search = graphSearch();
+        search.setSamenessThreshold(0.3);
+        assertEquals("incorrect threshold", 0.3, search.samenessThreshold(), 0.01);
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/AdjacencyListsGraphTest.java b/utils/misc/src/test/java/org/onlab/graph/AdjacencyListsGraphTest.java
new file mode 100644
index 0000000..9105891
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/AdjacencyListsGraphTest.java
@@ -0,0 +1,57 @@
+package org.onlab.graph;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests of the graph implementation.
+ */
+public class AdjacencyListsGraphTest {
+
+    private static final TestVertex A = new TestVertex("A");
+    private static final TestVertex B = new TestVertex("B");
+    private static final TestVertex C = new TestVertex("C");
+    private static final TestVertex D = new TestVertex("D");
+    private static final TestVertex E = new TestVertex("E");
+    private static final TestVertex F = new TestVertex("F");
+    private static final TestVertex G = new TestVertex("G");
+
+    private final Set<TestEdge> edges =
+            ImmutableSet.of(new TestEdge(A, B, 1), new TestEdge(B, C, 1),
+                            new TestEdge(C, D, 1), new TestEdge(D, A, 1),
+                            new TestEdge(B, D, 1));
+
+    @Test
+    public void equality() {
+        Set<TestVertex> vertexes = ImmutableSet.of(A, B, C, D, E, F);
+        Set<TestVertex> vertexes2 = ImmutableSet.of(A, B, C, D, E, F, G);
+
+        AdjacencyListsGraph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(vertexes, edges);
+        AdjacencyListsGraph<TestVertex, TestEdge> same = new AdjacencyListsGraph<>(vertexes, edges);
+        AdjacencyListsGraph<TestVertex, TestEdge> different = new AdjacencyListsGraph<>(vertexes2, edges);
+
+        new EqualsTester()
+                .addEqualityGroup(graph, same)
+                .addEqualityGroup(different)
+                .testEquals();
+    }
+
+    @Test
+    public void basics() {
+        Set<TestVertex> vertexes = ImmutableSet.of(A, B, C, D, E, F);
+        AdjacencyListsGraph<TestVertex, TestEdge> graph = new AdjacencyListsGraph<>(vertexes, edges);
+        assertEquals("incorrect vertex count", 6, graph.getVertexes().size());
+        assertEquals("incorrect edge count", 5, graph.getEdges().size());
+
+        assertEquals("incorrect egress edge count", 1, graph.getEdgesFrom(A).size());
+        assertEquals("incorrect ingress edge count", 1, graph.getEdgesTo(A).size());
+        assertEquals("incorrect ingress edge count", 1, graph.getEdgesTo(C).size());
+        assertEquals("incorrect egress edge count", 2, graph.getEdgesFrom(B).size());
+        assertEquals("incorrect ingress edge count", 2, graph.getEdgesTo(D).size());
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/BellmanFordGraphSearchTest.java b/utils/misc/src/test/java/org/onlab/graph/BellmanFordGraphSearchTest.java
new file mode 100644
index 0000000..4ee9006
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/BellmanFordGraphSearchTest.java
@@ -0,0 +1,61 @@
+package org.onlab.graph;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the Bellman-Ford algorithm.
+ */
+public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest {
+
+    @Override
+    protected AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch() {
+        return new BellmanFordGraphSearch<>();
+    }
+
+    @Test
+    @Override
+    public void defaultGraphTest() {
+        executeDefaultTest(7, 5, 5.0);
+    }
+
+    @Test
+    public void defaultHopCountWeight() {
+        weight = null;
+        executeDefaultTest(10, 3, 3.0);
+    }
+
+    @Test
+    public void searchGraphWithNegativeCycles() {
+        Set<TestVertex> vertexes = new HashSet<>(vertices());
+        vertexes.add(Z);
+
+        Set<TestEdge> edges = new HashSet<>(edges());
+        edges.add(new TestEdge(G, Z, 1.0));
+        edges.add(new TestEdge(Z, G, -2.0));
+
+        g = new AdjacencyListsGraph<>(vertexes, edges);
+
+        GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
+        Set<Path<TestVertex, TestEdge>> paths = search.search(g, A, H, weight).paths();
+        assertEquals("incorrect paths count", 1, paths.size());
+
+        Path p = paths.iterator().next();
+        assertEquals("incorrect src", A, p.src());
+        assertEquals("incorrect dst", H, p.dst());
+        assertEquals("incorrect path length", 5, p.edges().size());
+        assertEquals("incorrect path cost", 5.0, p.cost(), 0.1);
+
+        paths = search.search(g, A, G, weight).paths();
+        assertEquals("incorrect paths count", 0, paths.size());
+
+        paths = search.search(g, A, null, weight).paths();
+        printPaths(paths);
+        assertEquals("incorrect paths count", 6, paths.size());
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/BreadthFirstSearchTest.java b/utils/misc/src/test/java/org/onlab/graph/BreadthFirstSearchTest.java
new file mode 100644
index 0000000..f9c752d
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/BreadthFirstSearchTest.java
@@ -0,0 +1,66 @@
+package org.onlab.graph;
+
+import org.junit.Test;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the BFS and similar path search algorithms.
+ */
+public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
+
+    @Override
+    protected AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch() {
+        return new BreadthFirstSearch<>();
+    }
+
+    @Test
+    public void defaultGraphTest() {
+        executeDefaultTest(7, 3, 8.0);
+    }
+
+    @Test
+    public void defaultHopCountWeight() {
+        weight = null;
+        executeDefaultTest(7, 3, 3.0);
+    }
+
+    // Executes the default test
+    protected void executeDefaultTest(int pathCount, int pathLength, double pathCost) {
+        g = new AdjacencyListsGraph<>(vertices(), edges());
+
+        GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
+        Set<Path<TestVertex, TestEdge>> paths = search.search(g, A, H, weight).paths();
+        assertEquals("incorrect paths count", 1, paths.size());
+
+        Path p = paths.iterator().next();
+        assertEquals("incorrect src", A, p.src());
+        assertEquals("incorrect dst", H, p.dst());
+        assertEquals("incorrect path length", pathLength, p.edges().size());
+        assertEquals("incorrect path cost", pathCost, p.cost(), 0.1);
+
+        paths = search.search(g, A, null, weight).paths();
+        printPaths(paths);
+        assertEquals("incorrect paths count", pathCount, paths.size());
+    }
+
+    // Executes the search and validates its results.
+    protected void executeSearch(GraphPathSearch<TestVertex, TestEdge> search,
+                                 Graph<TestVertex, TestEdge> graph,
+                                 TestVertex src, TestVertex dst,
+                                 EdgeWeight<TestVertex, TestEdge> weight,
+                                 int pathCount, double pathCost) {
+        GraphPathSearch.Result<TestVertex, TestEdge> result =
+                search.search(graph, src, dst, weight);
+        Set<Path<TestVertex, TestEdge>> paths = result.paths();
+        printPaths(paths);
+        assertEquals("incorrect paths count", pathCount, paths.size());
+        if (pathCount > 0) {
+            Path<TestVertex, TestEdge> path = paths.iterator().next();
+            assertEquals("incorrect path cost", pathCost, path.cost(), 0.1);
+        }
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/DefaultMutablePathTest.java b/utils/misc/src/test/java/org/onlab/graph/DefaultMutablePathTest.java
new file mode 100644
index 0000000..b3e82e8
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/DefaultMutablePathTest.java
@@ -0,0 +1,95 @@
+package org.onlab.graph;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static com.google.common.collect.ImmutableList.of;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Test of the default mutable path.
+ */
+public class DefaultMutablePathTest extends DefaultPathTest {
+
+    @Test
+    public void equality() {
+        DefaultPath<TestVertex, TestEdge> p1 =
+                new DefaultPath<>(of(new TestEdge(A, B, 1),
+                                     new TestEdge(B, C, 1)), 2.0);
+        DefaultPath<TestVertex, TestEdge> p2 =
+                new DefaultPath<>(of(new TestEdge(A, B, 1),
+                                     new TestEdge(B, D, 1)), 2.0);
+        new EqualsTester().addEqualityGroup(new DefaultMutablePath<>(p1),
+                                            new DefaultMutablePath<>(p1))
+                .addEqualityGroup(new DefaultMutablePath<>(p2))
+                .testEquals();
+    }
+
+    @Test
+    public void empty() {
+        MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>();
+        assertNull("src should be null", p.src());
+        assertNull("dst should be null", p.dst());
+        assertEquals("incorrect edge count", 0, p.edges().size());
+        assertEquals("incorrect path cost", 0.0, p.cost(), 0.1);
+    }
+
+    @Test
+    public void pathCost() {
+        MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>();
+        p.setCost(4);
+        assertEquals("incorrect path cost", 4.0, p.cost(), 0.1);
+    }
+
+    private void validatePath(Path<TestVertex, TestEdge> p,
+                              TestVertex src, TestVertex dst, int length) {
+        validatePath(p, src, dst, length, 0.0);
+    }
+
+    @Test
+    public void insertEdge() {
+        MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>();
+        p.insertEdge(new TestEdge(B, C, 1));
+        p.insertEdge(new TestEdge(A, B, 1));
+        validatePath(p, A, C, 2);
+    }
+
+    @Test
+    public void appendEdge() {
+        MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>();
+        p.appendEdge(new TestEdge(A, B, 1));
+        p.appendEdge(new TestEdge(B, C, 1));
+        validatePath(p, A, C, 2);
+    }
+
+    @Test
+    public void removeEdge() {
+        MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>();
+        p.appendEdge(new TestEdge(A, B, 1));
+        p.appendEdge(new TestEdge(B, C, 1));
+        p.appendEdge(new TestEdge(C, C, 2));
+        p.appendEdge(new TestEdge(C, D, 1));
+        validatePath(p, A, D, 4);
+
+        p.removeEdge(new TestEdge(A, B, 1));
+        validatePath(p, B, D, 3);
+
+        p.removeEdge(new TestEdge(C, C, 2));
+        validatePath(p, B, D, 2);
+
+        p.removeEdge(new TestEdge(C, D, 1));
+        validatePath(p, B, C, 1);
+    }
+
+    @Test
+    public void toImmutable() {
+        MutablePath<TestVertex, TestEdge> p = new DefaultMutablePath<>();
+        p.appendEdge(new TestEdge(A, B, 1));
+        p.appendEdge(new TestEdge(B, C, 1));
+        validatePath(p, A, C, 2);
+
+        assertEquals("immutables should equal", p.toImmutable(), p.toImmutable());
+        validatePath(p.toImmutable(), A, C, 2);
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/DefaultPathTest.java b/utils/misc/src/test/java/org/onlab/graph/DefaultPathTest.java
new file mode 100644
index 0000000..02f79fa
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/DefaultPathTest.java
@@ -0,0 +1,42 @@
+package org.onlab.graph;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import java.util.List;
+
+import static com.google.common.collect.ImmutableList.of;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the default path.
+ */
+public class DefaultPathTest extends GraphTest {
+
+    @Test
+    public void equality() {
+        List<TestEdge> edges = of(new TestEdge(A, B, 1), new TestEdge(B, C, 1));
+        new EqualsTester().addEqualityGroup(new DefaultPath<>(edges, 2.0),
+                                            new DefaultPath<>(edges, 2.0))
+                .addEqualityGroup(new DefaultPath<>(edges, 3.0))
+                .testEquals();
+    }
+
+    @Test
+    public void basics() {
+        Path<TestVertex, TestEdge> p = new DefaultPath<>(of(new TestEdge(A, B, 1),
+                                                            new TestEdge(B, C, 1)), 2.0);
+        validatePath(p, A, C, 2, 2.0);
+    }
+
+    // Validates the path against expected attributes
+    protected void validatePath(Path<TestVertex, TestEdge> p,
+                                TestVertex src, TestVertex dst,
+                                int length, double cost) {
+        assertEquals("incorrect path length", length, p.edges().size());
+        assertEquals("incorrect source", src, p.src());
+        assertEquals("incorrect destination", dst, p.dst());
+        assertEquals("incorrect path cost", cost, p.cost(), 0.1);
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/DepthFirstSearchTest.java b/utils/misc/src/test/java/org/onlab/graph/DepthFirstSearchTest.java
new file mode 100644
index 0000000..8881e84
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/DepthFirstSearchTest.java
@@ -0,0 +1,81 @@
+package org.onlab.graph;
+
+import org.junit.Test;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onlab.graph.DepthFirstSearch.EdgeType;
+
+/**
+ * Test of the DFS algorithm.
+ */
+public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
+
+    @Override
+    protected DepthFirstSearch<TestVertex, TestEdge> graphSearch() {
+        return new DepthFirstSearch<>();
+    }
+
+    @Test
+    public void defaultGraphTest() {
+        executeDefaultTest(3, 6, 5.0, 12.0);
+        executeBroadSearch();
+    }
+
+    @Test
+    public void defaultHopCountWeight() {
+        weight = null;
+        executeDefaultTest(3, 6, 3.0, 6.0);
+        executeBroadSearch();
+    }
+
+    protected void executeDefaultTest(int minLength, int maxLength,
+                                      double minCost, double maxCost) {
+        g = new AdjacencyListsGraph<>(vertices(), edges());
+        DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
+
+        DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
+                search.search(g, A, H, weight);
+        Set<Path<TestVertex, TestEdge>> paths = result.paths();
+        assertEquals("incorrect path count", 1, paths.size());
+
+        Path path = paths.iterator().next();
+        System.out.println(path);
+        assertEquals("incorrect src", A, path.src());
+        assertEquals("incorrect dst", H, path.dst());
+
+        int l = path.edges().size();
+        assertTrue("incorrect path length " + l,
+                   minLength <= l && l <= maxLength);
+        assertTrue("incorrect path cost " + path.cost(),
+                   minCost <= path.cost() && path.cost() <= maxCost);
+
+        System.out.println(result.edges());
+        printPaths(paths);
+    }
+
+    public void executeBroadSearch() {
+        g = new AdjacencyListsGraph<>(vertices(), edges());
+        DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
+
+        // Perform narrow path search to a specific destination.
+        DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
+                search.search(g, A, null, weight);
+        assertEquals("incorrect paths count", 7, result.paths().size());
+
+        int[] types = new int[]{0, 0, 0, 0};
+        for (EdgeType t : result.edges().values()) {
+            types[t.ordinal()] += 1;
+        }
+        assertEquals("incorrect tree-edge count", 7,
+                     types[EdgeType.TREE_EDGE.ordinal()]);
+        assertEquals("incorrect back-edge count", 1,
+                     types[EdgeType.BACK_EDGE.ordinal()]);
+        assertEquals("incorrect cross-edge & forward-edge count", 4,
+                     types[EdgeType.FORWARD_EDGE.ordinal()] +
+                             types[EdgeType.CROSS_EDGE.ordinal()]);
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/DijkstraGraphSearchTest.java b/utils/misc/src/test/java/org/onlab/graph/DijkstraGraphSearchTest.java
new file mode 100644
index 0000000..1731440
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/DijkstraGraphSearchTest.java
@@ -0,0 +1,94 @@
+package org.onlab.graph;
+
+import org.junit.Test;
+
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test of the Dijkstra algorithm.
+ */
+public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
+
+    @Override
+    protected AbstractGraphPathSearch<TestVertex, TestEdge> graphSearch() {
+        return new DijkstraGraphSearch<>();
+    }
+
+    @Test
+    @Override
+    public void defaultGraphTest() {
+        executeDefaultTest(7, 5, 5.0);
+    }
+
+    @Test
+    @Override
+    public void defaultHopCountWeight() {
+        weight = null;
+        executeDefaultTest(10, 3, 3.0);
+    }
+
+    @Test
+    public void noPath() {
+        g = new AdjacencyListsGraph<>(of(A, B, C, D),
+                                      of(new TestEdge(A, B, 1),
+                                         new TestEdge(B, A, 1),
+                                         new TestEdge(C, D, 1),
+                                         new TestEdge(D, C, 1)));
+        GraphPathSearch<TestVertex, TestEdge> gs = graphSearch();
+        Set<Path<TestVertex, TestEdge>> paths = gs.search(g, A, B, weight).paths();
+        printPaths(paths);
+        assertEquals("incorrect paths count", 1, paths.size());
+        assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
+
+        paths = gs.search(g, A, D, weight).paths();
+        printPaths(paths);
+        assertEquals("incorrect paths count", 0, paths.size());
+
+        paths = gs.search(g, A, null, weight).paths();
+        printPaths(paths);
+        assertEquals("incorrect paths count", 1, paths.size());
+        assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
+    }
+
+    @Test
+    public void simpleMultiplePath() {
+        g = new AdjacencyListsGraph<>(of(A, B, C, D),
+                                      of(new TestEdge(A, B, 1),
+                                         new TestEdge(A, C, 1),
+                                         new TestEdge(B, D, 1),
+                                         new TestEdge(C, D, 1)));
+        executeSearch(graphSearch(), g, A, D, weight, 2, 2.0);
+    }
+
+    @Test
+    public void denseMultiplePath() {
+        g = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G),
+                                      of(new TestEdge(A, B, 1),
+                                         new TestEdge(A, C, 1),
+                                         new TestEdge(B, D, 1),
+                                         new TestEdge(C, D, 1),
+                                         new TestEdge(D, E, 1),
+                                         new TestEdge(D, F, 1),
+                                         new TestEdge(E, G, 1),
+                                         new TestEdge(F, G, 1),
+                                         new TestEdge(A, G, 4)));
+        executeSearch(graphSearch(), g, A, G, weight, 5, 4.0);
+    }
+
+    @Test
+    public void dualEdgeMultiplePath() {
+        g = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H),
+                                      of(new TestEdge(A, B, 1), new TestEdge(A, C, 3),
+                                         new TestEdge(B, D, 2), new TestEdge(B, C, 1),
+                                         new TestEdge(B, E, 4), new TestEdge(C, E, 1),
+                                         new TestEdge(D, H, 5), new TestEdge(D, E, 1),
+                                         new TestEdge(E, F, 1), new TestEdge(F, D, 1),
+                                         new TestEdge(F, G, 1), new TestEdge(F, H, 1),
+                                         new TestEdge(A, E, 3), new TestEdge(B, D, 1)));
+        executeSearch(graphSearch(), g, A, E, weight, 3, 3.0);
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/GraphTest.java b/utils/misc/src/test/java/org/onlab/graph/GraphTest.java
new file mode 100644
index 0000000..44b1137
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/GraphTest.java
@@ -0,0 +1,51 @@
+package org.onlab.graph;
+
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableSet.of;
+
+/**
+ * Base class for various graph-related tests.
+ */
+public class GraphTest {
+
+    static final TestVertex A = new TestVertex("A");
+    static final TestVertex B = new TestVertex("B");
+    static final TestVertex C = new TestVertex("C");
+    static final TestVertex D = new TestVertex("D");
+    static final TestVertex E = new TestVertex("E");
+    static final TestVertex F = new TestVertex("F");
+    static final TestVertex G = new TestVertex("G");
+    static final TestVertex H = new TestVertex("H");
+    static final TestVertex Z = new TestVertex("Z");
+
+    protected Graph<TestVertex, TestEdge> g;
+
+    protected EdgeWeight<TestVertex, TestEdge> weight =
+            new EdgeWeight<TestVertex, TestEdge>() {
+                @Override
+                public double weight(TestEdge edge) {
+                    return edge.weight();
+                }
+            };
+
+    protected void printPaths(Set<Path<TestVertex, TestEdge>> paths) {
+        for (Path p : paths) {
+            System.out.println(p);
+        }
+    }
+
+    protected Set<TestVertex> vertices() {
+        return of(A, B, C, D, E, F, G, H);
+    }
+
+    protected Set<TestEdge> edges() {
+        return of(new TestEdge(A, B, 1), new TestEdge(A, C, 3),
+                  new TestEdge(B, D, 2), new TestEdge(B, C, 1),
+                  new TestEdge(B, E, 4), new TestEdge(C, E, 1),
+                  new TestEdge(D, H, 5), new TestEdge(D, E, 1),
+                  new TestEdge(E, F, 1), new TestEdge(F, D, 1),
+                  new TestEdge(F, G, 1), new TestEdge(F, H, 1));
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/HeapTest.java b/utils/misc/src/test/java/org/onlab/graph/HeapTest.java
new file mode 100644
index 0000000..068f52e
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/HeapTest.java
@@ -0,0 +1,82 @@
+package org.onlab.graph;
+
+import com.google.common.collect.Ordering;
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+
+import static com.google.common.collect.ImmutableList.of;
+import static org.junit.Assert.*;
+
+/**
+ * Heap data structure tests.
+ */
+public class HeapTest {
+
+    private ArrayList<Integer> data =
+            new ArrayList<>(of(6, 4, 5, 9, 8, 3, 2, 1, 7, 0));
+
+    private static final Comparator<Integer> MIN = Ordering.natural().reverse();
+    private static final Comparator<Integer> MAX = Ordering.natural();
+
+    @Test
+    public void equality() {
+        new EqualsTester()
+                .addEqualityGroup(new Heap<>(data, MIN),
+                                  new Heap<>(data, MIN))
+                .addEqualityGroup(new Heap<>(data, MAX))
+                .testEquals();
+    }
+
+    @Test
+    public void empty() {
+        Heap<Integer> h = new Heap<>(new ArrayList<Integer>(), MIN);
+        assertTrue("should be empty", h.isEmpty());
+        assertEquals("incorrect size", 0, h.size());
+        assertNull("no item expected", h.extreme());
+        assertNull("no item expected", h.extractExtreme());
+    }
+
+    @Test
+    public void insert() {
+        Heap<Integer> h = new Heap<>(data, MIN);
+        assertEquals("incorrect size", 10, h.size());
+        h.insert(3);
+        assertEquals("incorrect size", 11, h.size());
+    }
+
+    @Test
+    public void minQueue() {
+        Heap<Integer> h = new Heap<>(data, MIN);
+        assertFalse("should not be empty", h.isEmpty());
+        assertEquals("incorrect size", 10, h.size());
+        assertEquals("incorrect extreme", (Integer) 0, h.extreme());
+
+        for (int i = 0, n = h.size(); i < n; i++) {
+            assertEquals("incorrect element", (Integer) i, h.extractExtreme());
+        }
+        assertTrue("should be empty", h.isEmpty());
+    }
+
+    @Test
+    public void maxQueue() {
+        Heap<Integer> h = new Heap<>(data, MAX);
+        assertFalse("should not be empty", h.isEmpty());
+        assertEquals("incorrect size", 10, h.size());
+        assertEquals("incorrect extreme", (Integer) 9, h.extreme());
+
+        for (int i = h.size(); i > 0; i--) {
+            assertEquals("incorrect element", (Integer) (i - 1), h.extractExtreme());
+        }
+        assertTrue("should be empty", h.isEmpty());
+    }
+
+    @Test
+    public void iterator() {
+        Heap<Integer> h = new Heap<>(data, MIN);
+        assertTrue("should have next element", h.iterator().hasNext());
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/TestEdge.java b/utils/misc/src/test/java/org/onlab/graph/TestEdge.java
new file mode 100644
index 0000000..225690c
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/TestEdge.java
@@ -0,0 +1,55 @@
+package org.onlab.graph;
+
+import java.util.Objects;
+
+import static com.google.common.base.Objects.toStringHelper;
+
+/**
+ * Test edge.
+ */
+public class TestEdge extends AbstractEdge<TestVertex> {
+
+    private final double weight;
+
+    /**
+     * Creates a new edge between the specified source and destination vertexes.
+     *
+     * @param src    source vertex
+     * @param dst    destination vertex
+     * @param weight edge weight
+     */
+    public TestEdge(TestVertex src, TestVertex dst, double weight) {
+        super(src, dst);
+        this.weight = weight;
+    }
+
+    /**
+     * Returns the edge weight.
+     *
+     * @return edge weight
+     */
+    public double weight() {
+        return weight;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hash(weight);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof TestEdge) {
+            final TestEdge other = (TestEdge) obj;
+            return super.equals(obj) && Objects.equals(this.weight, other.weight);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this).add("src", src()).add("dst", dst()).
+                add("weight", weight).toString();
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/graph/TestVertex.java b/utils/misc/src/test/java/org/onlab/graph/TestVertex.java
new file mode 100644
index 0000000..2e960f3
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/graph/TestVertex.java
@@ -0,0 +1,35 @@
+package org.onlab.graph;
+
+import java.util.Objects;
+
+/**
+ * Test vertex.
+ */
+public class TestVertex implements Vertex {
+
+    private final String name;
+
+    public TestVertex(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof TestVertex) {
+            final TestVertex other = (TestVertex) obj;
+            return Objects.equals(this.name, other.name);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+
+}