Sketching out what link-state addition would look like; quite easy until we get to the distributed store.
Added unit tests to provide durable-nondurable transitions.
FIxed issue where link could be accidentally activated.
Renamed parameter.

Change-Id: I8aa19a6583ec50dbf28769995f0a8ea9be9a4daa
diff --git a/core/api/src/main/java/org/onlab/onos/net/AnnotationKeys.java b/core/api/src/main/java/org/onlab/onos/net/AnnotationKeys.java
index af74043..de18fc0 100644
--- a/core/api/src/main/java/org/onlab/onos/net/AnnotationKeys.java
+++ b/core/api/src/main/java/org/onlab/onos/net/AnnotationKeys.java
@@ -25,6 +25,17 @@
     private AnnotationKeys() {}
 
     /**
+     * Annotation key for durable links.
+     */
+    public static final String DURABLE = "durable";
+
+    /**
+     * Annotation key for active/inactive links. Links are implicitly
+     * considered active unless explicitly marked otherwise.
+     */
+    public static final String INACTIVE = "inactive";
+
+    /**
      * Annotation key for latency.
      */
     public static final String LATENCY = "latency";
diff --git a/core/api/src/main/java/org/onlab/onos/net/DefaultLink.java b/core/api/src/main/java/org/onlab/onos/net/DefaultLink.java
index a63de3b..6ebd52d 100644
--- a/core/api/src/main/java/org/onlab/onos/net/DefaultLink.java
+++ b/core/api/src/main/java/org/onlab/onos/net/DefaultLink.java
@@ -20,6 +20,7 @@
 import java.util.Objects;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onlab.onos.net.Link.State.ACTIVE;
 
 /**
  * Default infrastructure link model implementation.
@@ -29,22 +30,45 @@
     private final ConnectPoint src;
     private final ConnectPoint dst;
     private final Type type;
+    private final State state;
+    private final boolean isDurable;
 
     /**
-     * Creates an infrastructure link using the supplied information.
+     * Creates an active infrastructure link using the supplied information.
      *
-     * @param providerId provider identity
-     * @param src        link source
-     * @param dst        link destination
-     * @param type       link type
+     * @param providerId  provider identity
+     * @param src         link source
+     * @param dst         link destination
+     * @param type        link type
      * @param annotations optional key/value annotations
      */
     public DefaultLink(ProviderId providerId, ConnectPoint src, ConnectPoint dst,
                        Type type, Annotations... annotations) {
+        this(providerId, src, dst, type, ACTIVE, false, annotations);
+    }
+
+    /**
+     * Creates an infrastructure link using the supplied information.
+     * Links marked as durable will remain in the inventory when a vanish
+     * message is received and instead will be marked as inactive.
+     *
+     * @param providerId  provider identity
+     * @param src         link source
+     * @param dst         link destination
+     * @param type        link type
+     * @param state       link state
+     * @param isDurable   indicates if the link is to be considered durable
+     * @param annotations optional key/value annotations
+     */
+    public DefaultLink(ProviderId providerId, ConnectPoint src, ConnectPoint dst,
+                       Type type, State state,
+                       boolean isDurable, Annotations... annotations) {
         super(providerId, annotations);
         this.src = src;
         this.dst = dst;
         this.type = type;
+        this.state = state;
+        this.isDurable = isDurable;
     }
 
     @Override
@@ -63,6 +87,18 @@
     }
 
     @Override
+    public State state() {
+        return state;
+    }
+
+    @Override
+    public boolean isDurable() {
+        return isDurable;
+    }
+
+    // Note: Durability & state are purposefully omitted form equality & hashCode.
+
+    @Override
     public int hashCode() {
         return Objects.hash(src, dst, type);
     }
@@ -87,6 +123,8 @@
                 .add("src", src)
                 .add("dst", dst)
                 .add("type", type)
+                .add("state", state)
+                .add("durable", isDurable)
                 .toString();
     }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/Link.java b/core/api/src/main/java/org/onlab/onos/net/Link.java
index cdd0f23..cce34fa 100644
--- a/core/api/src/main/java/org/onlab/onos/net/Link.java
+++ b/core/api/src/main/java/org/onlab/onos/net/Link.java
@@ -55,6 +55,23 @@
     }
 
     /**
+     * Representation of the link state, which applies primarily only to
+     * configured durable links, i.e. those that need to remain present,
+     * but instead be marked as inactive.
+     */
+    public enum State {
+        /**
+         * Signifies that a link is currently active.
+         */
+        ACTIVE,
+
+        /**
+         * Signifies that a link is currently active.
+         */
+        INACTIVE
+    }
+
+    /**
      * Returns the link source connection point.
      *
      * @return link source connection point
@@ -75,4 +92,16 @@
      */
     Type type();
 
+    /**
+     * Returns the link state.
+     */
+    State state();
+
+    /**
+     * Indicates if the link is to be considered durable.
+     *
+     * @return link state
+     */
+    boolean isDurable();
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/link/DefaultLinkDescription.java b/core/api/src/main/java/org/onlab/onos/net/link/DefaultLinkDescription.java
index fc3f335..986eb0a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/link/DefaultLinkDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/link/DefaultLinkDescription.java
@@ -34,9 +34,9 @@
     /**
      * Creates a link description using the supplied information.
      *
-     * @param src  link source
-     * @param dst  link destination
-     * @param type link type
+     * @param src         link source
+     * @param dst         link destination
+     * @param type        link type
      * @param annotations optional key/value annotations
      */
     public DefaultLinkDescription(ConnectPoint src, ConnectPoint dst,
@@ -64,9 +64,10 @@
 
     @Override
     public String toString() {
-        return MoreObjects.toStringHelper("Link").add("src", src())
-                                .add("dst", dst())
-                                .add("type", type()).toString();
+        return MoreObjects.toStringHelper(this)
+                .add("src", src())
+                .add("dst", dst())
+                .add("type", type()).toString();
     }
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java b/core/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
index 3343451..3078243 100644
--- a/core/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
@@ -45,6 +45,5 @@
      */
     Link.Type type();
 
-
     // Add further link attributes
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/link/LinkEvent.java b/core/api/src/main/java/org/onlab/onos/net/link/LinkEvent.java
index b164b9e..763e95e 100644
--- a/core/api/src/main/java/org/onlab/onos/net/link/LinkEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/link/LinkEvent.java
@@ -33,7 +33,7 @@
         LINK_ADDED,
 
         /**
-         * Signifies that a link has been updated.
+         * Signifies that a link has been updated or changed state.
          */
         LINK_UPDATED,
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/link/LinkStore.java b/core/api/src/main/java/org/onlab/onos/net/link/LinkStore.java
index 95962e7..3af4f9f 100644
--- a/core/api/src/main/java/org/onlab/onos/net/link/LinkStore.java
+++ b/core/api/src/main/java/org/onlab/onos/net/link/LinkStore.java
@@ -95,6 +95,16 @@
                                         LinkDescription linkDescription);
 
     /**
+     * Removes the link, or marks it as inactive if the link is durable,
+     * based on the specified information.
+     *
+     * @param src link source
+     * @param dst link destination
+     * @return remove or update link event, or null if no change resulted
+     */
+    LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst);
+
+    /**
      * Removes the link based on the specified information.
      *
      * @param src link source
@@ -103,4 +113,5 @@
      */
     LinkEvent removeLink(ConnectPoint src, ConnectPoint dst);
 
+
 }
diff --git a/core/api/src/test/java/org/onlab/onos/net/link/LinkServiceAdapter.java b/core/api/src/test/java/org/onlab/onos/net/link/LinkServiceAdapter.java
index 17a570a..0e25e2a 100644
--- a/core/api/src/test/java/org/onlab/onos/net/link/LinkServiceAdapter.java
+++ b/core/api/src/test/java/org/onlab/onos/net/link/LinkServiceAdapter.java
@@ -78,5 +78,4 @@
     public void removeListener(LinkListener listener) {
     }
 
-
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/link/impl/LinkManager.java b/core/net/src/main/java/org/onlab/onos/net/link/impl/LinkManager.java
index 23c7362..15fc4a1 100644
--- a/core/net/src/main/java/org/onlab/onos/net/link/impl/LinkManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/link/impl/LinkManager.java
@@ -158,7 +158,7 @@
         if (deviceService.getRole(connectPoint.deviceId()) != MastershipRole.MASTER) {
             return;
         }
-        removeLinks(getLinks(connectPoint));
+        removeLinks(getLinks(connectPoint), false);
     }
 
     @Override
@@ -166,7 +166,7 @@
         if (deviceService.getRole(deviceId) != MastershipRole.MASTER) {
             return;
         }
-        removeLinks(getDeviceLinks(deviceId));
+        removeLinks(getDeviceLinks(deviceId), false);
     }
 
     @Override
@@ -228,7 +228,7 @@
             ConnectPoint src = linkDescription.src();
             ConnectPoint dst = linkDescription.dst();
 
-            LinkEvent event = store.removeLink(src, dst);
+            LinkEvent event = store.removeOrDownLink(src, dst);
             if (event != null) {
                 log.info("Link {} vanished", linkDescription);
                 post(event);
@@ -242,7 +242,7 @@
 
             log.info("Links for connection point {} vanished", connectPoint);
             // FIXME: This will remove links registered by other providers
-            removeLinks(getLinks(connectPoint));
+            removeLinks(getLinks(connectPoint), true);
         }
 
         @Override
@@ -251,14 +251,16 @@
             checkValidity();
 
             log.info("Links for device {} vanished", deviceId);
-            removeLinks(getDeviceLinks(deviceId));
+            removeLinks(getDeviceLinks(deviceId), true);
         }
     }
 
     // Removes all links in the specified set and emits appropriate events.
-    private void removeLinks(Set<Link> links) {
+    private void removeLinks(Set<Link> links, boolean isSoftRemove) {
         for (Link link : links) {
-            LinkEvent event = store.removeLink(link.src(), link.dst());
+            LinkEvent event = isSoftRemove ?
+                    store.removeOrDownLink(link.src(), link.dst()) :
+                    store.removeLink(link.src(), link.dst());
             post(event);
         }
     }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/GossipLinkStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/GossipLinkStore.java
index 9810d7a..d496ebe1 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/GossipLinkStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/GossipLinkStore.java
@@ -21,7 +21,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import com.google.common.collect.SetMultimap;
-
 import org.apache.commons.lang3.RandomUtils;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -32,6 +31,7 @@
 import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.cluster.ControllerNode;
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.AnnotationKeys;
 import org.onlab.onos.net.AnnotationsUtil;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DefaultAnnotations;
@@ -65,28 +65,30 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
-import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
-import static org.onlab.onos.net.DefaultAnnotations.union;
 import static org.onlab.onos.net.DefaultAnnotations.merge;
+import static org.onlab.onos.net.DefaultAnnotations.union;
+import static org.onlab.onos.net.Link.State.ACTIVE;
+import static org.onlab.onos.net.Link.State.INACTIVE;
 import static org.onlab.onos.net.Link.Type.DIRECT;
 import static org.onlab.onos.net.Link.Type.INDIRECT;
 import static org.onlab.onos.net.LinkKey.linkKey;
 import static org.onlab.onos.net.link.LinkEvent.Type.*;
+import static org.onlab.onos.store.link.impl.GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT;
 import static org.onlab.util.Tools.namedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
-import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Predicates.notNull;
-import static org.onlab.onos.store.link.impl.GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT;
 
 /**
  * Manages inventory of infrastructure links in distributed data store
@@ -261,8 +263,6 @@
             mergedDesc = map.get(providerId);
         }
 
-
-
         if (event != null) {
             log.info("Notifying peers of a link update topology event from providerId: "
                     + "{}  between src: {} and dst: {}",
@@ -278,6 +278,26 @@
         return event;
     }
 
+    @Override
+    public LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst) {
+        Link link = getLink(src, dst);
+        if (link == null) {
+            return null;
+        }
+
+        if (link.isDurable()) {
+            // FIXME: this is not the right thing to call for the gossip store; will not sync link state!!!
+            return link.state() == INACTIVE ? null :
+                    updateLink(linkKey(link.src(), link.dst()), link,
+                               new DefaultLink(link.providerId(),
+                                               link.src(), link.dst(),
+                                               link.type(), INACTIVE,
+                                               link.isDurable(),
+                                               link.annotations()));
+        }
+        return removeLink(src, dst);
+    }
+
     private LinkEvent createOrUpdateLinkInternal(
             ProviderId providerId,
             Timestamped<LinkDescription> linkDescription) {
@@ -333,7 +353,7 @@
             }
             SparseAnnotations merged = union(existingLinkDescription.value().annotations(),
                     linkDescription.value().annotations());
-            newLinkDescription = new Timestamped<LinkDescription>(
+            newLinkDescription = new Timestamped<>(
                     new DefaultLinkDescription(
                         linkDescription.value().src(),
                         linkDescription.value().dst(),
@@ -357,7 +377,8 @@
     private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
         // Note: INDIRECT -> DIRECT transition only
         // so that BDDP discovered Link will not overwrite LDDP Link
-        if ((oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
+        if (oldLink.state() != newLink.state() ||
+            (oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
             !AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
 
             links.put(key, newLink);
@@ -449,12 +470,8 @@
                 // outdated remove request, ignore
                 return null;
             }
-            Link link = links.get(key);
-            if (isDurable(link)) {
-                return null;
-            }
             removedLinks.put(key, timestamp);
-            links.remove(key);
+            Link link = links.remove(key);
             linkDescriptions.clear();
             if (link != null) {
                 srcLinks.remove(link.src().deviceId(), key);
@@ -465,11 +482,6 @@
         }
     }
 
-    // Indicates if the link has been marked as durable via annotations.
-    private boolean isDurable(Link link) {
-        return link != null && Objects.equals(link.annotations().value("durable"), "true");
-    }
-
     private static <K, V> SetMultimap<K, V> createSynchronizedHashMultiMap() {
         return synchronizedSetMultimap(HashMultimap.<K, V>create());
     }
@@ -518,7 +530,10 @@
             annotations = merge(annotations, e.getValue().value().annotations());
         }
 
-        return new DefaultLink(baseProviderId, src, dst, type, annotations);
+        boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true");
+        boolean isActive = !Objects.equals(annotations.value(AnnotationKeys.INACTIVE), "true");
+        return new DefaultLink(baseProviderId, src, dst, type,
+                               isActive ? ACTIVE : INACTIVE, isDurable, annotations);
     }
 
     private Map<ProviderId, Timestamped<LinkDescription>> getOrCreateLinkDescriptions(LinkKey key) {
@@ -538,9 +553,11 @@
     }
 
     private final Function<LinkKey, Link> lookupLink = new LookupLink();
+
     /**
      * Returns a Function to lookup Link instance using LinkKey from cache.
-     * @return
+     *
+     * @return lookup link function
      */
     private Function<LinkKey, Link> lookupLink() {
         return lookupLink;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java b/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
index 1850fa4..4aafa35 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
@@ -49,6 +49,8 @@
 import static org.onlab.graph.GraphPathSearch.Result;
 import static org.onlab.graph.TarjanGraphSearch.SCCResult;
 import static org.onlab.onos.core.CoreService.CORE_PROVIDER_ID;
+import static org.onlab.onos.net.Link.State.ACTIVE;
+import static org.onlab.onos.net.Link.State.INACTIVE;
 import static org.onlab.onos.net.Link.Type.INDIRECT;
 
 /**
@@ -434,7 +436,8 @@
         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().type() == INDIRECT ? indirectLinkCost : 1;
+            return edge.link().state() == ACTIVE ?
+                    (edge.link().type() == INDIRECT ? indirectLinkCost : 1) : -1;
         }
     }
 
@@ -442,7 +445,7 @@
     private static class NoIndirectLinksWeight implements LinkWeight {
         @Override
         public double weight(TopologyEdge edge) {
-            return edge.link().type() == INDIRECT ? -1 : 1;
+            return edge.link().state() == INACTIVE || edge.link().type() == INDIRECT ? -1 : 1;
         }
     }
 
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultLinkSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultLinkSerializer.java
index a79e4d4..a787012 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultLinkSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultLinkSerializer.java
@@ -15,15 +15,15 @@
  */
 package org.onlab.onos.store.serializers;
 
-import org.onlab.onos.net.ConnectPoint;
-import org.onlab.onos.net.DefaultLink;
-import org.onlab.onos.net.Link.Type;
-import org.onlab.onos.net.provider.ProviderId;
-
 import com.esotericsoftware.kryo.Kryo;
 import com.esotericsoftware.kryo.Serializer;
 import com.esotericsoftware.kryo.io.Input;
 import com.esotericsoftware.kryo.io.Output;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultLink;
+import org.onlab.onos.net.Link.State;
+import org.onlab.onos.net.Link.Type;
+import org.onlab.onos.net.provider.ProviderId;
 
 /**
  * Kryo Serializer for {@link DefaultLink}.
@@ -44,6 +44,8 @@
         kryo.writeClassAndObject(output, object.src());
         kryo.writeClassAndObject(output, object.dst());
         kryo.writeClassAndObject(output, object.type());
+        kryo.writeClassAndObject(output, object.state());
+        kryo.writeClassAndObject(output, object.isDurable());
     }
 
     @Override
@@ -52,6 +54,8 @@
         ConnectPoint src = (ConnectPoint) kryo.readClassAndObject(input);
         ConnectPoint dst = (ConnectPoint) kryo.readClassAndObject(input);
         Type linkType = (Type) kryo.readClassAndObject(input);
-        return new DefaultLink(providerId, src, dst, linkType);
+        State state = (State) kryo.readClassAndObject(input);
+        boolean isDurable = (boolean) kryo.readClassAndObject(input);
+        return new DefaultLink(providerId, src, dst, linkType, state, isDurable);
     }
 }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
index 07db4a6..7043a61 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
@@ -164,6 +164,7 @@
                     DefaultPortDescription.class,
                     Element.class,
                     Link.Type.class,
+                    Link.State.class,
                     Timestamp.class,
                     HostId.class,
                     HostDescription.class,
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopology.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopology.java
index 2cae2c3..5478806 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopology.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopology.java
@@ -49,6 +49,8 @@
 import static org.onlab.graph.GraphPathSearch.Result;
 import static org.onlab.graph.TarjanGraphSearch.SCCResult;
 import static org.onlab.onos.core.CoreService.CORE_PROVIDER_ID;
+import static org.onlab.onos.net.Link.State.ACTIVE;
+import static org.onlab.onos.net.Link.State.INACTIVE;
 import static org.onlab.onos.net.Link.Type.INDIRECT;
 
 /**
@@ -434,7 +436,8 @@
         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().type() == INDIRECT ? indirectLinkCost : 1;
+            return edge.link().state() == ACTIVE ?
+                    (edge.link().type() == INDIRECT ? indirectLinkCost : 1) : -1;
         }
     }
 
@@ -442,7 +445,7 @@
     private static class NoIndirectLinksWeight implements LinkWeight {
         @Override
         public double weight(TopologyEdge edge) {
-            return edge.link().type() == INDIRECT ? -1 : 1;
+            return edge.link().state() == INACTIVE || edge.link().type() == INDIRECT ? -1 : 1;
         }
     }
 
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
index 50f4e06..a383167 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
@@ -19,20 +19,20 @@
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.SetMultimap;
-
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.net.AnnotationKeys;
 import org.onlab.onos.net.AnnotationsUtil;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DefaultAnnotations;
 import org.onlab.onos.net.DefaultLink;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Link;
-import org.onlab.onos.net.SparseAnnotations;
 import org.onlab.onos.net.Link.Type;
 import org.onlab.onos.net.LinkKey;
+import org.onlab.onos.net.SparseAnnotations;
 import org.onlab.onos.net.link.DefaultLinkDescription;
 import org.onlab.onos.net.link.LinkDescription;
 import org.onlab.onos.net.link.LinkEvent;
@@ -46,22 +46,24 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
-import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
-import static org.onlab.onos.net.DefaultAnnotations.union;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.base.Verify.verifyNotNull;
+import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
 import static org.onlab.onos.net.DefaultAnnotations.merge;
+import static org.onlab.onos.net.DefaultAnnotations.union;
+import static org.onlab.onos.net.Link.State.ACTIVE;
+import static org.onlab.onos.net.Link.State.INACTIVE;
 import static org.onlab.onos.net.Link.Type.DIRECT;
 import static org.onlab.onos.net.Link.Type.INDIRECT;
 import static org.onlab.onos.net.LinkKey.linkKey;
 import static org.onlab.onos.net.link.LinkEvent.Type.*;
 import static org.slf4j.LoggerFactory.getLogger;
-import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
-import static com.google.common.base.Predicates.notNull;
-import static com.google.common.base.Verify.verifyNotNull;
 
 /**
  * Manages inventory of infrastructure links using trivial in-memory structures
@@ -77,7 +79,7 @@
 
     // Link inventory
     private final ConcurrentMap<LinkKey, Map<ProviderId, LinkDescription>>
-                    linkDescs = new ConcurrentHashMap<>();
+            linkDescs = new ConcurrentHashMap<>();
 
     // Link instance cache
     private final ConcurrentMap<LinkKey, Link> links = new ConcurrentHashMap<>();
@@ -116,9 +118,9 @@
         // lock for iteration
         synchronized (srcLinks) {
             return FluentIterable.from(srcLinks.get(deviceId))
-            .transform(lookupLink())
-            .filter(notNull())
-            .toSet();
+                    .transform(lookupLink())
+                    .filter(notNull())
+                    .toSet();
         }
     }
 
@@ -127,9 +129,9 @@
         // lock for iteration
         synchronized (dstLinks) {
             return FluentIterable.from(dstLinks.get(deviceId))
-            .transform(lookupLink())
-            .filter(notNull())
-            .toSet();
+                    .transform(lookupLink())
+                    .filter(notNull())
+                    .toSet();
         }
     }
 
@@ -178,11 +180,30 @@
         }
     }
 
+    @Override
+    public LinkEvent removeOrDownLink(ConnectPoint src, ConnectPoint dst) {
+        Link link = getLink(src, dst);
+        if (link == null) {
+            return null;
+        }
+
+        if (link.isDurable()) {
+            return link.state() == INACTIVE ? null :
+                    updateLink(linkKey(link.src(), link.dst()), link,
+                               new DefaultLink(link.providerId(),
+                                               link.src(), link.dst(),
+                                               link.type(), INACTIVE,
+                                               link.isDurable(),
+                                               link.annotations()));
+        }
+        return removeLink(src, dst);
+    }
+
     // Guarded by linkDescs value (=locking each Link)
     private LinkDescription createOrUpdateLinkDescription(
-                             Map<ProviderId, LinkDescription> descs,
-                             ProviderId providerId,
-                             LinkDescription linkDescription) {
+            Map<ProviderId, LinkDescription> descs,
+            ProviderId providerId,
+            LinkDescription linkDescription) {
 
         // merge existing attributes and merge
         LinkDescription oldDesc = descs.get(providerId);
@@ -196,11 +217,10 @@
                 newType = linkDescription.type();
             }
             SparseAnnotations merged = union(oldDesc.annotations(),
-                    linkDescription.annotations());
-            newDesc = new DefaultLinkDescription(
-                        linkDescription.src(),
-                        linkDescription.dst(),
-                        newType, merged);
+                                             linkDescription.annotations());
+            newDesc = new DefaultLinkDescription(linkDescription.src(),
+                                                 linkDescription.dst(),
+                                                 newType, merged);
         }
         return descs.put(providerId, newDesc);
     }
@@ -217,8 +237,9 @@
     // Updates, if necessary the specified link and returns the appropriate event.
     // Guarded by linkDescs value (=locking each Link)
     private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
-        if ((oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
-            !AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
+        if (oldLink.state() != newLink.state() ||
+                (oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
+                !AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
 
             links.put(key, newLink);
             // strictly speaking following can be ommitted
@@ -234,11 +255,7 @@
         final LinkKey key = linkKey(src, dst);
         Map<ProviderId, LinkDescription> descs = getOrCreateLinkDescriptions(key);
         synchronized (descs) {
-            Link link = links.get(key);
-            if (isDurable(link)) {
-                return null;
-            }
-            links.remove(key);
+            Link link = links.remove(key);
             descs.clear();
             if (link != null) {
                 srcLinks.remove(link.src().deviceId(), key);
@@ -249,11 +266,6 @@
         }
     }
 
-    // Indicates if the link has been marked as durable via annotations.
-    private boolean isDurable(Link link) {
-        return link != null && Objects.equals(link.annotations().value("durable"), "true");
-    }
-
     private static <K, V> SetMultimap<K, V> createSynchronizedHashMultiMap() {
         return synchronizedSetMultimap(HashMultimap.<K, V>create());
     }
@@ -301,7 +313,10 @@
             annotations = merge(annotations, e.getValue().annotations());
         }
 
-        return new DefaultLink(primary , src, dst, type, annotations);
+        boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true");
+        boolean isActive = !Objects.equals(annotations.value(AnnotationKeys.INACTIVE), "true");
+        return new DefaultLink(primary, src, dst, type,
+                               isActive ? ACTIVE : INACTIVE, isDurable, annotations);
     }
 
     private Map<ProviderId, LinkDescription> getOrCreateLinkDescriptions(LinkKey key) {
@@ -321,6 +336,7 @@
     }
 
     private final Function<LinkKey, Link> lookupLink = new LookupLink();
+
     private Function<LinkKey, Link> lookupLink() {
         return lookupLink;
     }
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
index 735f99c..cfd9d24 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
@@ -23,6 +23,7 @@
 import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.onlab.onos.net.AnnotationKeys;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DefaultAnnotations;
 import org.onlab.onos.net.DeviceId;
@@ -80,6 +81,23 @@
             .set("B4", "b4")
             .build();
 
+    private static final SparseAnnotations DA1 = DefaultAnnotations.builder()
+            .set("A1", "a1")
+            .set("B1", "b1")
+            .set(AnnotationKeys.DURABLE, "true")
+            .build();
+    private static final SparseAnnotations DA2 = DefaultAnnotations.builder()
+            .set("A2", "a2")
+            .set("B2", "b2")
+            .set(AnnotationKeys.DURABLE, "true")
+            .build();
+    private static final SparseAnnotations NDA1 = DefaultAnnotations.builder()
+            .set("A1", "a1")
+            .set("B1", "b1")
+            .remove(AnnotationKeys.DURABLE)
+            .build();
+
+
 
     private SimpleLinkStore simpleLinkStore;
     private LinkStore linkStore;
@@ -105,17 +123,19 @@
     }
 
     private void putLink(DeviceId srcId, PortNumber srcNum,
-                         DeviceId dstId, PortNumber dstNum, Type type,
+                         DeviceId dstId, PortNumber dstNum,
+                         Type type, boolean isDurable,
                          SparseAnnotations... annotations) {
         ConnectPoint src = new ConnectPoint(srcId, srcNum);
         ConnectPoint dst = new ConnectPoint(dstId, dstNum);
-        linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type, annotations));
+        linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type,
+                                                                     annotations));
     }
 
     private void putLink(LinkKey key, Type type, SparseAnnotations... annotations) {
         putLink(key.src().deviceId(), key.src().port(),
                 key.dst().deviceId(), key.dst().port(),
-                type, annotations);
+                type, false, annotations);
     }
 
     private static void assertLink(DeviceId srcId, PortNumber srcNum,
@@ -138,9 +158,9 @@
     public final void testGetLinkCount() {
         assertEquals("initialy empty", 0, linkStore.getLinkCount());
 
-        putLink(DID1, P1, DID2, P2, DIRECT);
-        putLink(DID2, P2, DID1, P1, DIRECT);
-        putLink(DID1, P1, DID2, P2, DIRECT);
+        putLink(DID1, P1, DID2, P2, DIRECT, false);
+        putLink(DID2, P2, DID1, P1, DIRECT, false);
+        putLink(DID1, P1, DID2, P2, DIRECT, false);
 
         assertEquals("expecting 2 unique link", 2, linkStore.getLinkCount());
     }
@@ -360,6 +380,47 @@
 
 
     @Test
+    public final void testRemoveOrDownLink() {
+        removeOrDownLink(false);
+    }
+
+    @Test
+    public final void testRemoveOrDownLinkDurable() {
+        removeOrDownLink(true);
+    }
+
+    private void removeOrDownLink(boolean isDurable) {
+        final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+        final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+        LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+        LinkKey linkId2 = LinkKey.linkKey(d2P2, d1P1);
+
+        putLink(linkId1, DIRECT, isDurable ? DA1 : A1);
+        putLink(linkId2, DIRECT, isDurable ? DA2 : A2);
+
+        // DID1,P1 => DID2,P2
+        // DID2,P2 => DID1,P1
+        // DID1,P2 => DID2,P3
+
+        LinkEvent event = linkStore.removeOrDownLink(d1P1, d2P2);
+        assertEquals(isDurable ? LINK_UPDATED : LINK_REMOVED, event.type());
+        assertAnnotationsEquals(event.subject().annotations(), isDurable ? DA1 : A1);
+        LinkEvent event2 = linkStore.removeOrDownLink(d1P1, d2P2);
+        assertNull(event2);
+
+        assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
+        assertAnnotationsEquals(linkStore.getLink(d2P2, d1P1).annotations(),
+                                isDurable ? DA2 : A2);
+
+        // annotations, etc. should not survive remove
+        if (!isDurable) {
+            putLink(linkId1, DIRECT);
+            assertLink(linkId1, DIRECT, linkStore.getLink(d1P1, d2P2));
+            assertAnnotationsEquals(linkStore.getLink(d1P1, d2P2).annotations());
+        }
+    }
+
+    @Test
     public final void testRemoveLink() {
         final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
         final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
@@ -402,6 +463,30 @@
         assertNotNull(linkStore.getLink(src, dst));
     }
 
+    @Test
+    public void testDurableToNonDurable() {
+        final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+        final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+        LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+
+        putLink(linkId1, DIRECT, DA1);
+        assertTrue("should be be durable", linkStore.getLink(d1P1, d2P2).isDurable());
+        putLink(linkId1, DIRECT, NDA1);
+        assertFalse("should not be durable", linkStore.getLink(d1P1, d2P2).isDurable());
+    }
+
+    @Test
+    public void testNonDurableToDurable() {
+        final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
+        final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
+        LinkKey linkId1 = LinkKey.linkKey(d1P1, d2P2);
+
+        putLink(linkId1, DIRECT, A1);
+        assertFalse("should not be durable", linkStore.getLink(d1P1, d2P2).isDurable());
+        putLink(linkId1, DIRECT, DA1);
+        assertTrue("should be durable", linkStore.getLink(d1P1, d2P2).isDurable());
+    }
+
     // If Delegates should be called only on remote events,
     // then Simple* should never call them, thus not test required.
     @Ignore("Ignore until Delegate spec. is clear.")
@@ -451,7 +536,7 @@
 
         linkStore.unsetDelegate(checkUpdate);
         linkStore.setDelegate(checkRemove);
-        linkStore.removeLink(d1P1, d2P2);
+        linkStore.removeOrDownLink(d1P1, d2P2);
         assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
     }
 }