ONOS-3515 Added ability to configure different link-weight functions as defaults; or inject custom ones.

ONOS-3516 Added ability to inject alternate graph path search algorithms.

Change-Id: If5831c198a831ae79a9933fc794eb7deab776e2f
diff --git a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
index 8c5fb79..4f81e92 100644
--- a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
+++ b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
@@ -25,7 +25,6 @@
  */
 public final class AnnotationKeys {
 
-
     // Prohibit instantiation
     private AnnotationKeys() {
     }
@@ -81,6 +80,12 @@
     public static final String DURABLE = "durable";
 
     /**
+     * Annotation key for link metric; used by
+     * {@link org.onosproject.net.topology.MetricLinkWeight} function.
+     */
+    public static final String METRIC = "metric";
+
+    /**
      * Annotation key for latency.
      *
      * @deprecated since Cardinal
@@ -112,8 +117,14 @@
      */
     public static final String ROUTER_ID = "routerId";
 
+    /**
+     * Annotation key for the static lambda.
+     */
     public static final String STATIC_LAMBDA = "staticLambda";
 
+    /**
+     * Annotation key for the static port.
+     */
     public static final String STATIC_PORT = "staticPort";
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicLinkConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicLinkConfig.java
index e962110..ed807b8 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicLinkConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicLinkConfig.java
@@ -15,22 +15,32 @@
  */
 package org.onosproject.net.config.basics;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import org.onosproject.net.Link;
 import org.onosproject.net.LinkKey;
-import com.fasterxml.jackson.databind.JsonNode;
 
 import java.time.Duration;
 
+import static org.onosproject.net.config.Config.FieldPresence.OPTIONAL;
+
 /**
  * Basic configuration for network infrastructure link.
  */
 public class BasicLinkConfig extends AllowedEntityConfig<LinkKey> {
 
     public static final String TYPE = "type";
+    public static final String METRIC = "metric";
     public static final String LATENCY = "latency";
     public static final String BANDWIDTH = "bandwidth";
     public static final String IS_DURABLE = "durable";
 
+    @Override
+    public boolean isValid() {
+        return hasOnlyFields(TYPE, METRIC, LATENCY, BANDWIDTH, IS_DURABLE) &&
+                isNumber(METRIC, OPTIONAL) && isNumber(LATENCY, OPTIONAL) &&
+                isNumber(BANDWIDTH, OPTIONAL);
+    }
+
     /**
      * Returns the link type.
      *
@@ -51,6 +61,27 @@
     }
 
     /**
+     * Returns link metric value for use by
+     * {@link org.onosproject.net.topology.MetricLinkWeight} function.
+     *
+     * @return link metric; -1 if not set
+     */
+    public double metric() {
+        return get(METRIC, -1);
+    }
+
+    /**
+     * Sets the link metric for use by
+     * {@link org.onosproject.net.topology.MetricLinkWeight} function.
+     *
+     * @param metric new metric; null to clear
+     * @return self
+     */
+    public BasicLinkConfig metric(Double metric) {
+        return (BasicLinkConfig) setOrClear(METRIC, metric);
+    }
+
+    /**
      * Returns link latency in terms of nanos.
      *
      * @return link latency; -1 if not set
diff --git a/core/api/src/main/java/org/onosproject/net/topology/GeoDistanceLinkWeight.java b/core/api/src/main/java/org/onosproject/net/topology/GeoDistanceLinkWeight.java
new file mode 100644
index 0000000..c966902
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/topology/GeoDistanceLinkWeight.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.topology;
+
+import org.onlab.util.GeoLocation;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+
+import static java.lang.Double.MAX_VALUE;
+
+/**
+ * Link weight for measuring link cost using the geo distance between link
+ * vertices as determined by the element longitude/latitude annotation.
+ */
+public class GeoDistanceLinkWeight implements LinkWeight {
+
+    private static final double MAX_KM = 40_075 / 2.0;
+
+    private final DeviceService deviceService;
+
+    /**
+     * Creates a new link-weight with access to the specified device service.
+     *
+     * @param deviceService device service reference
+     */
+    public GeoDistanceLinkWeight(DeviceService deviceService) {
+        this.deviceService = deviceService;
+    }
+
+    @Override
+    public double weight(TopologyEdge edge) {
+        GeoLocation src = getLocation(edge.link().src().deviceId());
+        GeoLocation dst = getLocation(edge.link().dst().deviceId());
+        return src != null && dst != null ? src.kilometersTo(dst) : MAX_KM;
+    }
+
+    private GeoLocation getLocation(DeviceId deviceId) {
+        Device d = deviceService.getDevice(deviceId);
+        Annotations a = d != null ? d.annotations() : null;
+        double latitude = getDouble(a, AnnotationKeys.LATITUDE);
+        double longitude = getDouble(a, AnnotationKeys.LONGITUDE);
+        return latitude == MAX_VALUE || longitude == MAX_VALUE ? null :
+                new GeoLocation(latitude, longitude);
+    }
+
+    private double getDouble(Annotations a, String key) {
+        String value = a != null ? a.value(key) : null;
+        try {
+            return value != null ? Double.parseDouble(value) : MAX_VALUE;
+        } catch (NumberFormatException e) {
+            return MAX_VALUE;
+        }
+    }
+}
+
diff --git a/core/api/src/main/java/org/onosproject/net/topology/HopCountLinkWeight.java b/core/api/src/main/java/org/onosproject/net/topology/HopCountLinkWeight.java
new file mode 100644
index 0000000..c557016
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/topology/HopCountLinkWeight.java
@@ -0,0 +1,36 @@
+package org.onosproject.net.topology;
+
+import static org.onosproject.net.Link.State.ACTIVE;
+import static org.onosproject.net.Link.Type.INDIRECT;
+
+/**
+ * Link weight for measuring link cost as hop count with indirect links
+ * being as expensive as traversing the entire graph to assume the worst.
+ */
+public class HopCountLinkWeight implements LinkWeight {
+    private final int indirectLinkCost;
+
+    /**
+     * Creates a new hop-count weight.
+     */
+    public HopCountLinkWeight() {
+        this.indirectLinkCost = Short.MAX_VALUE;
+    }
+
+    /**
+     * Creates a new hop-count weight with the specified cost of indirect links.
+     */
+    public HopCountLinkWeight(int indirectLinkCost) {
+        this.indirectLinkCost = indirectLinkCost;
+    }
+
+    @Override
+    public double weight(TopologyEdge edge) {
+        // To force preference to use direct paths first, make indirect
+        // links as expensive as the linear vertex traversal.
+        return edge.link().state() ==
+                ACTIVE ? (edge.link().type() ==
+                INDIRECT ? indirectLinkCost : 1) : -1;
+    }
+}
+
diff --git a/core/api/src/main/java/org/onosproject/net/topology/MetricLinkWeight.java b/core/api/src/main/java/org/onosproject/net/topology/MetricLinkWeight.java
new file mode 100644
index 0000000..8463e08
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/topology/MetricLinkWeight.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.topology;
+
+import org.onosproject.net.AnnotationKeys;
+
+/**
+ * Link weight for measuring link cost using the link metric annotation.
+ */
+public class MetricLinkWeight implements LinkWeight {
+
+    @Override
+    public double weight(TopologyEdge edge) {
+        String v = edge.link().annotations().value(AnnotationKeys.METRIC);
+        try {
+            return v != null ? Double.parseDouble(v) : 1;
+        } catch (NumberFormatException e) {
+            return 1;
+        }
+    }
+}
+
diff --git a/core/api/src/main/java/org/onosproject/net/topology/PathAdminService.java b/core/api/src/main/java/org/onosproject/net/topology/PathAdminService.java
new file mode 100644
index 0000000..9d077e1
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/topology/PathAdminService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.topology;
+
+import org.onlab.graph.GraphPathSearch;
+
+/**
+ * Provides administrative abilities to tailor the path service behaviours.
+ */
+public interface PathAdminService {
+
+    /**
+     * Sets the specified link-weight function to be used as a default.
+     * If null is specified, the builtin default hop-count link-weight will be
+     * used.
+     *
+     * @param linkWeight default link-weight function
+     */
+    void setDefaultLinkWeight(LinkWeight linkWeight);
+
+    /**
+     * Sets the specified graph path search algorightm to be used as a default.
+     * If null is specified, the builtin default all-shortest-paths Dijkstra
+     * algorithm will be used.
+     *
+     * @param graphPathSearch default graph path search algorithm
+     */
+    void setDefaultGraphPathSearch(GraphPathSearch<TopologyVertex, TopologyEdge> graphPathSearch);
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/topology/PathService.java b/core/api/src/main/java/org/onosproject/net/topology/PathService.java
index 0bd4d75..3895407 100644
--- a/core/api/src/main/java/org/onosproject/net/topology/PathService.java
+++ b/core/api/src/main/java/org/onosproject/net/topology/PathService.java
@@ -30,8 +30,9 @@
 public interface PathService {
 
     /**
-     * Returns the set of all shortest paths, precomputed in terms of hop-count,
-     * between the specified source and destination elements.
+     * Returns the set of all shortest paths between the specified source and
+     * destination elements. The path is computed using the default edge-weight
+     * function, which by default is hop-count.
      *
      * @param src source element
      * @param dst destination element
@@ -40,9 +41,9 @@
     Set<Path> getPaths(ElementId src, ElementId dst);
 
     /**
-     * Returns the set of all shortest paths, computed using the supplied
-     * edge-weight entity, between the specified source and destination
-     * network elements.
+     * Returns the set of all shortest paths between the specified source and
+     * destination network elements.  The path is computed using the supplied
+     * edge-weight function.
      *
      * @param src    source element
      * @param dst    destination element
@@ -52,8 +53,9 @@
     Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight);
 
     /**
-     * Returns the set of all disjoint shortest path pairs, precomputed in terms of hop-count,
-     * between the specified source and destination devices.
+     * Returns the set of all disjoint shortest path pairs between the
+     * specified source and destination elements. The path is computed using
+     * the default edge-weight function, which by default is hop-count.
      *
      * @param src source device
      * @param dst destination device
@@ -62,8 +64,9 @@
     Set<DisjointPath> getDisjointPaths(ElementId src, ElementId dst);
 
     /**
-     * Returns the set of all disjoint shortest path pairs, computed using the supplied
-     * edge-weight entity, between the specified source and destination devices.
+     * Returns the set of all disjoint shortest path pairs between the
+     * specified source and destination elements. The path is computed using
+     * the supplied edge-weight function.
      *
      * @param src    source device
      * @param dst    destination device
@@ -74,8 +77,10 @@
                                        LinkWeight weight);
 
     /**
-     * Returns the set of all disjoint shortest path pairs, precomputed in terms of hop-count,
-     * between the specified source and destination devices.
+     * Returns the set of all disjoint shortest path pairs between the
+     * specified source and destination elements and taking into consideration
+     * the provided risk profile. The path is computed using the default
+     * edge-weight function, which by default is hop-count.
      *
      * @param src         source device
      * @param dst         destination device
@@ -86,8 +91,10 @@
                                        Map<Link, Object> riskProfile);
 
     /**
-     * Returns the set of all disjoint shortest path pairs, precomputed in terms of hop-count,
-     * between the specified source and destination devices.
+     * Returns the set of all disjoint shortest path pairs between the
+     * specified source and destination elements and taking into consideration
+     * the provided risk profile. The path is computed using the supplied
+     * edge-weight function.
      *
      * @param src         source device
      * @param dst         destination device
@@ -96,6 +103,7 @@
      * @return set of all shortest paths between the two devices
      */
     Set<DisjointPath> getDisjointPaths(ElementId src, ElementId dst,
-                                       LinkWeight weight, Map<Link, Object> riskProfile);
+                                       LinkWeight weight,
+                                       Map<Link, Object> riskProfile);
 
 }
diff --git a/core/common/src/main/java/org/onosproject/common/DefaultTopology.java b/core/common/src/main/java/org/onosproject/common/DefaultTopology.java
index 84cde42..c5263ed 100644
--- a/core/common/src/main/java/org/onosproject/common/DefaultTopology.java
+++ b/core/common/src/main/java/org/onosproject/common/DefaultTopology.java
@@ -43,12 +43,15 @@
 import org.onosproject.net.topology.DefaultTopologyCluster;
 import org.onosproject.net.topology.DefaultTopologyVertex;
 import org.onosproject.net.topology.GraphDescription;
+import org.onosproject.net.topology.HopCountLinkWeight;
 import org.onosproject.net.topology.LinkWeight;
 import org.onosproject.net.topology.Topology;
 import org.onosproject.net.topology.TopologyCluster;
 import org.onosproject.net.topology.TopologyEdge;
 import org.onosproject.net.topology.TopologyGraph;
 import org.onosproject.net.topology.TopologyVertex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.HashMap;
 import java.util.List;
@@ -61,7 +64,6 @@
 import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
 import static org.onlab.util.Tools.isNullOrEmpty;
 import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
-import static org.onosproject.net.Link.State.ACTIVE;
 import static org.onosproject.net.Link.State.INACTIVE;
 import static org.onosproject.net.Link.Type.INDIRECT;
 
@@ -71,18 +73,22 @@
  */
 public class DefaultTopology extends AbstractModel implements Topology {
 
+    private static final Logger log = LoggerFactory.getLogger(DefaultTopology.class);
+
     private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA = new DijkstraGraphSearch<>();
     private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN = new TarjanGraphSearch<>();
-    private static final SuurballeGraphSearch<TopologyVertex, TopologyEdge> SUURBALLE =
-            new SuurballeGraphSearch<>();
+    private static final SuurballeGraphSearch<TopologyVertex, TopologyEdge> SUURBALLE = new SuurballeGraphSearch<>();
 
+    private static LinkWeight defaultLinkWeight = null;
+    private static GraphPathSearch<TopologyVertex, TopologyEdge> defaultGraphPathSearch = null;
 
     private final long time;
     private final long creationTime;
     private final long computeCost;
     private final TopologyGraph graph;
 
-    private final LinkWeight weight;
+    private final LinkWeight hopCountWeight;
+
     private final Supplier<SccResult<TopologyVertex, TopologyEdge>> clusterResults;
     private final Supplier<ImmutableMap<ClusterId, TopologyCluster>> clusters;
     private final Supplier<ImmutableSet<ConnectPoint>> infrastructurePoints;
@@ -91,6 +97,30 @@
     private final Supplier<ClusterIndexes> clusterIndexes;
 
     /**
+     * Sets the default link-weight to be used when computing paths. If null is
+     * specified, the builtin default link-weight measuring hop-counts will be
+     * used.
+     *
+     * @param linkWeight new default link-weight
+     */
+    public static void setDefaultLinkWeight(LinkWeight linkWeight) {
+        log.info("Setting new default link-weight function to {}", linkWeight);
+        defaultLinkWeight = linkWeight;
+    }
+
+    /**
+     * Sets the default lpath search algorighm to be used when computing paths.
+     * If null is specified, the builtin default Dijkstra will be used.
+     *
+     * @param graphPathSearch new default algorithm
+     */
+    public static void setDefaultGraphPathSearch(GraphPathSearch<TopologyVertex, TopologyEdge> graphPathSearch) {
+        log.info("Setting new default graph path algorithm to {}", graphPathSearch);
+        defaultGraphPathSearch = graphPathSearch;
+    }
+
+
+    /**
      * Creates a topology descriptor attributed to the specified provider.
      *
      * @param providerId        identity of the provider
@@ -113,7 +143,7 @@
 
         this.clusterIndexes = Suppliers.memoize(() -> buildIndexes());
 
-        this.weight = new HopCountLinkWeight(graph.getVertexes().size());
+        this.hopCountWeight = new HopCountLinkWeight(graph.getVertexes().size());
         this.broadcastSets = Suppliers.memoize(() -> buildBroadcastSets());
         this.infrastructurePoints = Suppliers.memoize(() -> findInfrastructurePoints());
         this.computeCost = Math.max(0, System.nanoTime() - time);
@@ -294,7 +324,7 @@
      * @return set of shortest paths
      */
     public Set<Path> getPaths(DeviceId src, DeviceId dst) {
-        return getPaths(src, dst, null);
+        return getPaths(src, dst, linkWeight());
     }
 
     /**
@@ -307,8 +337,8 @@
      * @return set of shortest paths
      */
     public Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
-        final DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
-        final DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
+        DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
+        DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
         Set<TopologyVertex> vertices = graph.getVertexes();
         if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
             // src or dst not part of the current graph
@@ -316,7 +346,7 @@
         }
 
         GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
-                DIJKSTRA.search(graph, srcV, dstV, weight, ALL_PATHS);
+                graphPathSearch().search(graph, srcV, dstV, weight, ALL_PATHS);
         ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
         for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
             builder.add(networkPath(path));
@@ -334,7 +364,7 @@
      * @return set of shortest disjoint path pairs
      */
     public Set<DisjointPath> getDisjointPaths(DeviceId src, DeviceId dst) {
-        return getDisjointPaths(src, dst, (LinkWeight) null);
+        return getDisjointPaths(src, dst, linkWeight());
     }
 
     /**
@@ -347,8 +377,8 @@
      * @return set of disjoint shortest path pairs
      */
     public Set<DisjointPath> getDisjointPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
-        final DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
-        final DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
+        DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
+        DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
         Set<TopologyVertex> vertices = graph.getVertexes();
         if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
             // src or dst not part of the current graph
@@ -375,7 +405,7 @@
      * @return set of shortest disjoint paths
      */
     private Set<DisjointPath> disjointPaths(DeviceId src, DeviceId dst, LinkWeight weight,
-                                                Map<TopologyEdge, Object> riskProfile) {
+                                            Map<TopologyEdge, Object> riskProfile) {
         DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
         DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
 
@@ -438,7 +468,7 @@
      * @return set of shortest disjoint paths
      */
     public Set<DisjointPath> getDisjointPaths(DeviceId src, DeviceId dst, Map<Link, Object> riskProfile) {
-        return getDisjointPaths(src, dst, null, riskProfile);
+        return getDisjointPaths(src, dst, linkWeight(), riskProfile);
     }
 
     // Converts graph path to a network path with the same cost.
@@ -499,8 +529,7 @@
 
     // Processes a map of broadcast sets for each cluster.
     private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
-        Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap
-                .builder();
+        Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap.builder();
         for (TopologyCluster cluster : clusters.get().values()) {
             addClusterBroadcastSet(cluster, builder);
         }
@@ -512,7 +541,7 @@
     // all other devices within the cluster.
     private void addClusterBroadcastSet(TopologyCluster cluster, Builder<ClusterId, ConnectPoint> builder) {
         // Use the graph root search results to build the broadcast set.
-        Result<TopologyVertex, TopologyEdge> result = DIJKSTRA.search(graph, cluster.root(), null, weight, 1);
+        Result<TopologyVertex, TopologyEdge> result = DIJKSTRA.search(graph, cluster.root(), null, hopCountWeight, 1);
         for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
             TopologyVertex vertex = entry.getKey();
 
@@ -577,23 +606,12 @@
                                   linksBuilder.build());
     }
 
-    // Link weight for measuring link cost as hop count with indirect links
-    // being as expensive as traversing the entire graph to assume the worst.
-    private static class HopCountLinkWeight implements LinkWeight {
-        private final int indirectLinkCost;
+    private GraphPathSearch<TopologyVertex, TopologyEdge> graphPathSearch() {
+        return defaultGraphPathSearch != null ? defaultGraphPathSearch : DIJKSTRA;
+    }
 
-        HopCountLinkWeight(int indirectLinkCost) {
-            this.indirectLinkCost = indirectLinkCost;
-        }
-
-        @Override
-        public double weight(TopologyEdge edge) {
-            // To force preference to use direct paths first, make indirect
-            // links as expensive as the linear vertex traversal.
-            return edge.link().state() ==
-                    ACTIVE ? (edge.link().type() ==
-                    INDIRECT ? indirectLinkCost : 1) : -1;
-        }
+    private LinkWeight linkWeight() {
+        return defaultLinkWeight != null ? defaultLinkWeight : hopCountWeight;
     }
 
     // Link weight for preventing traversal over indirect links.
diff --git a/core/net/src/main/java/org/onosproject/net/link/impl/BasicLinkOperator.java b/core/net/src/main/java/org/onosproject/net/link/impl/BasicLinkOperator.java
index 801092f..ff74dbd 100644
--- a/core/net/src/main/java/org/onosproject/net/link/impl/BasicLinkOperator.java
+++ b/core/net/src/main/java/org/onosproject/net/link/impl/BasicLinkOperator.java
@@ -38,6 +38,7 @@
 public final class BasicLinkOperator implements ConfigOperator {
 
     private static final long DEF_BANDWIDTH = -1L;
+    private static final double DEF_METRIC = -1;
     private static final Duration DEF_DURATION = Duration.ofNanos(-1L);
     private static final Logger log = getLogger(BasicLinkOperator.class);
 
@@ -77,6 +78,9 @@
      */
     public static SparseAnnotations combine(BasicLinkConfig cfg, SparseAnnotations an) {
         DefaultAnnotations.Builder b = DefaultAnnotations.builder();
+        if (cfg.metric() != DEF_METRIC) {
+            b.set(AnnotationKeys.METRIC, String.valueOf(cfg.metric()));
+        }
         if (cfg.latency() != DEF_DURATION) {
             b.set(AnnotationKeys.LATENCY, cfg.latency().toString());
         }
diff --git a/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DistributedTopologyStore.java b/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DistributedTopologyStore.java
index da4e3cc..2641635 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DistributedTopologyStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DistributedTopologyStore.java
@@ -15,44 +15,43 @@
  */
 package org.onosproject.store.topology.impl;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static org.onlab.util.Tools.isNullOrEmpty;
-import static org.onosproject.net.topology.TopologyEvent.Type.TOPOLOGY_CHANGED;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
 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.Modified;
+import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.graph.GraphPathSearch;
 import org.onlab.util.KryoNamespace;
+import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.common.DefaultTopology;
 import org.onosproject.event.Event;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
 import org.onosproject.net.Link;
 import org.onosproject.net.Path;
-import org.onosproject.net.DisjointPath;
+import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.net.topology.ClusterId;
 import org.onosproject.net.topology.DefaultGraphDescription;
+import org.onosproject.net.topology.GeoDistanceLinkWeight;
 import org.onosproject.net.topology.GraphDescription;
 import org.onosproject.net.topology.LinkWeight;
+import org.onosproject.net.topology.MetricLinkWeight;
+import org.onosproject.net.topology.PathAdminService;
 import org.onosproject.net.topology.Topology;
 import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.net.topology.TopologyEdge;
 import org.onosproject.net.topology.TopologyEvent;
 import org.onosproject.net.topology.TopologyGraph;
 import org.onosproject.net.topology.TopologyStore;
 import org.onosproject.net.topology.TopologyStoreDelegate;
+import org.onosproject.net.topology.TopologyVertex;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.EventuallyConsistentMap;
@@ -60,8 +59,23 @@
 import org.onosproject.store.service.EventuallyConsistentMapListener;
 import org.onosproject.store.service.LogicalClockService;
 import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.isNullOrEmpty;
+import static org.onosproject.net.topology.TopologyEvent.Type.TOPOLOGY_CHANGED;
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * Manages inventory of topology snapshots using trivial in-memory
  * structures implementation.
@@ -73,9 +87,12 @@
 @Service
 public class DistributedTopologyStore
         extends AbstractStore<TopologyEvent, TopologyStoreDelegate>
-        implements TopologyStore {
+        implements TopologyStore, PathAdminService {
 
     private final Logger log = getLogger(getClass());
+
+    private static final String FORMAT = "Settings: linkWeightFunction={}";
+
     private volatile DefaultTopology current =
             new DefaultTopology(ProviderId.NONE,
                                 new DefaultGraphDescription(0L, System.currentTimeMillis(),
@@ -91,6 +108,21 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected MastershipService mastershipService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    private static final String HOP_COUNT = "hopCount";
+    private static final String LINK_METRIC = "linkMetric";
+    private static final String GEO_DISTANCE = "geoDistance";
+
+    private static final String DEFAULT_LINK_WEIGHT_FUNCTION = "hopCount";
+    @Property(name = "linkWeightFunction", value = DEFAULT_LINK_WEIGHT_FUNCTION,
+            label = "Default link-weight function: hopCount, linkMetric, geoDistance")
+    private String linkWeightFunction = DEFAULT_LINK_WEIGHT_FUNCTION;
+
     // Cluster root to broadcast points bindings to allow convergence to
     // a shared broadcast tree; node that is the master of the cluster root
     // is the primary.
@@ -100,7 +132,8 @@
             new InternalBroadcastPointListener();
 
     @Activate
-    public void activate() {
+    protected void activate() {
+        configService.registerProperties(getClass());
         KryoNamespace.Builder hostSerializer = KryoNamespace.newBuilder()
                 .register(KryoNamespaces.API);
 
@@ -114,12 +147,30 @@
     }
 
     @Deactivate
-    public void deactivate() {
+    protected void deactivate() {
+        configService.unregisterProperties(getClass(), false);
         broadcastPoints.removeListener(listener);
         broadcastPoints.destroy();
         log.info("Stopped");
     }
 
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+
+        String newLinkWeightFunction = get(properties, "linkWeightFunction");
+        if (newLinkWeightFunction != null &&
+                !Objects.equals(newLinkWeightFunction, linkWeightFunction)) {
+            linkWeightFunction = newLinkWeightFunction;
+            LinkWeight weight = linkWeightFunction.equals(LINK_METRIC) ?
+                    new MetricLinkWeight() :
+                    linkWeightFunction.equals(GEO_DISTANCE) ?
+                            new GeoDistanceLinkWeight(deviceService) : null;
+            setDefaultLinkWeight(weight);
+        }
+        log.info(FORMAT, linkWeightFunction);
+    }
+
     @Override
     public Topology currentTopology() {
         return current;
@@ -263,6 +314,16 @@
         return (DefaultTopology) topology;
     }
 
+    @Override
+    public void setDefaultLinkWeight(LinkWeight linkWeight) {
+        DefaultTopology.setDefaultLinkWeight(linkWeight);
+    }
+
+    @Override
+    public void setDefaultGraphPathSearch(GraphPathSearch<TopologyVertex, TopologyEdge> graphPathSearch) {
+        DefaultTopology.setDefaultGraphPathSearch(graphPathSearch);
+    }
+
     private class InternalBroadcastPointListener
             implements EventuallyConsistentMapListener<DeviceId, Set<ConnectPoint>> {
         @Override
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java b/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java
index c568841..68c536b 100644
--- a/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java
@@ -168,7 +168,7 @@
 
 
     @Activate
-    public void activate(ComponentContext context) {
+    public void activate() {
         cfgService.registerProperties(getClass());
 
         deviceProviderService = deviceProviderRegistry.register(deviceProvider);
@@ -180,7 +180,7 @@
     }
 
     @Deactivate
-    public void deactivate(ComponentContext context) {
+    public void deactivate() {
         cfgService.unregisterProperties(getClass(), false);
         tearDown();
 
diff --git a/utils/misc/src/main/java/org/onlab/util/GeoLocation.java b/utils/misc/src/main/java/org/onlab/util/GeoLocation.java
new file mode 100644
index 0000000..7e54653
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/util/GeoLocation.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.util;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Geo location specified in terms of longitude and latitude.
+ */
+public class GeoLocation {
+
+    public static final double EARTH_RADIUS_KM = 6378.1370D;
+
+    private final double latitude;
+    private final double longitude;
+
+    /**
+     * Creates a new location using the specified coordinates.
+     *
+     * @param latitude  latitude line
+     * @param longitude longitude line
+     */
+    public GeoLocation(double latitude, double longitude) {
+        this.latitude = latitude;
+        this.longitude = longitude;
+    }
+
+    /**
+     * Returns the latitude of this location.
+     *
+     * @return latitude
+     */
+    public double latitude() {
+        return latitude;
+    }
+
+    /**
+     * Returns the longitude of this location.
+     *
+     * @return longitude
+     */
+    public double longitude() {
+        return longitude;
+    }
+
+    /**
+     * Returns the distance in kilometers, between this location and another.
+     *
+     * @param other other geo location
+     * @return distance in kilometers
+     */
+    public double kilometersTo(GeoLocation other) {
+        double hereLat = Math.toRadians(latitude);
+        double hereLon = Math.toRadians(longitude);
+        double thereLat = Math.toRadians(other.latitude);
+        double thereLon = Math.toRadians(other.longitude);
+
+        double cos = Math.sin(hereLat) * Math.sin(thereLat) +
+                Math.cos(hereLat) * Math.cos(thereLat) * Math.cos(hereLon - thereLon);
+        return Math.acos(cos) * EARTH_RADIUS_KM;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("latitude", latitude)
+                .add("longitude", longitude)
+                .toString();
+    }
+
+}
diff --git a/utils/misc/src/test/java/org/onlab/util/GeoLocationTest.java b/utils/misc/src/test/java/org/onlab/util/GeoLocationTest.java
new file mode 100644
index 0000000..a497bcc
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/util/GeoLocationTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test suite of the geo location.
+ */
+public class GeoLocationTest {
+
+    @Test
+    public void basics() {
+        GeoLocation nLoc = new GeoLocation(40.7127, -74.0059);
+        GeoLocation wLoc = new GeoLocation(38.9047, -77.0164);
+
+        assertEquals("incorrect latitude", 40.7127, nLoc.latitude(), 0.0001);
+        assertEquals("incorrect longitude", -74.00598, nLoc.longitude(), 0.0001);
+        assertEquals("incorrect distance", 326.74, nLoc.kilometersTo(wLoc), 0.01);
+    }
+
+}
\ No newline at end of file