Link config enhancements:

o Links can be injected using configs
o fix for AllowedEntity check on links
o config for link durability
o Link operator methods for creating Link descriptions

Change-Id: I8a1fabc688a2e7eeb579feced451a6c1ba7e3232
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 b6068ee..e962110 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
@@ -17,6 +17,7 @@
 
 import org.onosproject.net.Link;
 import org.onosproject.net.LinkKey;
+import com.fasterxml.jackson.databind.JsonNode;
 
 import java.time.Duration;
 
@@ -28,6 +29,7 @@
     public static final String TYPE = "type";
     public static final String LATENCY = "latency";
     public static final String BANDWIDTH = "bandwidth";
+    public static final String IS_DURABLE = "durable";
 
     /**
      * Returns the link type.
@@ -87,4 +89,26 @@
         return (BasicLinkConfig) setOrClear(BANDWIDTH, bandwidth);
     }
 
+    /**
+     * Returns if link is durable in the network model or not.
+     *
+     * @return true for durable, false otherwise
+     */
+    public Boolean isDurable() {
+        JsonNode res = object.path(IS_DURABLE);
+        if (res.isMissingNode()) {
+            return null;
+        }
+        return res.asBoolean();
+    }
+
+    /**
+     * Sets durability for this link.
+     *
+     * @param isDurable true for durable, false otherwise
+     * @return this BasicLinkConfig
+     */
+    public BasicLinkConfig isDurable(Boolean isDurable) {
+        return (BasicLinkConfig) setOrClear(IS_DURABLE, isDurable);
+    }
 }
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 a6b08f6..801092f 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
@@ -16,12 +16,14 @@
 package org.onosproject.net.link.impl;
 
 import static org.slf4j.LoggerFactory.getLogger;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.time.Duration;
 
 import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.config.ConfigOperator;
 import org.onosproject.net.config.basics.BasicLinkConfig;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.Link;
 import org.onosproject.net.SparseAnnotations;
@@ -81,6 +83,46 @@
         if (cfg.bandwidth() != DEF_BANDWIDTH) {
             b.set(AnnotationKeys.BANDWIDTH, String.valueOf(cfg.bandwidth()));
         }
+        if (cfg.isDurable() != null) {
+            b.set(AnnotationKeys.DURABLE, String.valueOf(cfg.isDurable()));
+        }
         return DefaultAnnotations.union(an, b.build());
     }
+
+    /**
+     * Generates a link description from a link description entity. The endpoints
+     * must be specified to indicate directionality.
+     *
+     * @param src the source ConnectPoint
+     * @param dst the destination ConnectPoint
+     * @param link the link config entity
+     * @return a linkDescription based on the config
+     */
+    public static LinkDescription descriptionOf(
+                ConnectPoint src, ConnectPoint dst, Link link) {
+        checkNotNull(src, "Must supply a source endpoint");
+        checkNotNull(dst, "Must supply a destination endpoint");
+        checkNotNull(link, "Must supply a link");
+        return new DefaultLinkDescription(
+                src, dst, link.type(), (SparseAnnotations) link.annotations());
+    }
+
+    /**
+     * Generates a link description from a link config entity. This is for
+     * links that cannot be discovered and has to be injected. The endpoints
+     * must be specified to indicate directionality.
+     *
+     * @param src the source ConnectPoint
+     * @param dst the destination ConnectPoint
+     * @param link the link config entity
+     * @return a linkDescription based on the config
+     */
+    public static LinkDescription descriptionOf(
+                ConnectPoint src, ConnectPoint dst, BasicLinkConfig link) {
+        checkNotNull(src, "Must supply a source endpoint");
+        checkNotNull(dst, "Must supply a destination endpoint");
+        checkNotNull(link, "Must supply a link config");
+        return new DefaultLinkDescription(
+                src, dst, link.type(), combine(link, DefaultAnnotations.EMPTY));
+    }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/link/impl/LinkManager.java b/core/net/src/main/java/org/onosproject/net/link/impl/LinkManager.java
index 157288a..7bdeb4f 100644
--- a/core/net/src/main/java/org/onosproject/net/link/impl/LinkManager.java
+++ b/core/net/src/main/java/org/onosproject/net/link/impl/LinkManager.java
@@ -18,6 +18,7 @@
 import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Sets;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -25,6 +26,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onosproject.net.provider.AbstractListenerProviderRegistry;
+import org.onosproject.net.provider.ProviderId;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
 import org.onosproject.net.config.NetworkConfigService;
@@ -54,7 +56,6 @@
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
 import static org.onosproject.net.LinkKey.linkKey;
 import static org.onosproject.security.AppGuard.checkPermission;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -207,6 +208,10 @@
         post(store.removeLink(src, dst));
     }
 
+    private boolean isAllowed(BasicLinkConfig cfg) {
+        return (cfg == null || cfg.isAllowed());
+    }
+
     // Auxiliary interceptor for device remove events to prune links that
     // are associated with the removed device or its port.
     private class InternalDeviceListener implements DeviceListener {
@@ -240,11 +245,12 @@
             checkNotNull(linkDescription, LINK_DESC_NULL);
             checkValidity();
             linkDescription = validateLink(linkDescription);
-            LinkEvent event = store.createOrUpdateLink(provider().id(),
-                    linkDescription);
-            if (event != null) {
-                log.info("Link {} detected", linkDescription);
-                post(event);
+            if (linkDescription != null) {
+                LinkEvent event = store.createOrUpdateLink(provider().id(), linkDescription);
+                if (event != null) {
+                    log.info("Link {} detected", linkDescription);
+                    post(event);
+                }
             }
         }
 
@@ -258,11 +264,12 @@
             BasicLinkConfig cfgTwo = networkConfigService.getConfig(linkKey(linkDescription.dst(),
                                                                             linkDescription.src()),
                                                                     BasicLinkConfig.class);
-
-            checkState(cfg == null || cfg.isAllowed(), "Link " + linkDescription.toString() + " is not allowed");
-            checkState(cfgTwo == null || cfgTwo.isAllowed(), "Link " + linkDescription.toString() + " is not allowed");
-
-            return BasicLinkOperator.combine(cfg, linkDescription);
+            if (isAllowed(cfg) && isAllowed(cfgTwo)) {
+                return BasicLinkOperator.combine(cfg, linkDescription);
+            } else {
+                log.trace("Link " + linkDescription.toString() + " is not allowed");
+                return null;
+            }
         }
 
         @Override
@@ -324,20 +331,41 @@
     // listens for NetworkConfigEvents of type BasicLinkConfig and removes
     // links that the config does not allow
     private class InternalNetworkConfigListener implements NetworkConfigListener {
+
+        @Override
+        public boolean isRelevant(NetworkConfigEvent event) {
+            return event.configClass().equals(BasicLinkConfig.class)
+                    && (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED
+                        || event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
+        }
+
         @Override
         public void event(NetworkConfigEvent event) {
-            if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
-                    event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
-                    event.configClass().equals(BasicLinkConfig.class)) {
-                log.info("Detected Link network config event {}", event.type());
-                LinkKey lk = (LinkKey) event.subject();
-                BasicLinkConfig cfg = networkConfigService.getConfig(lk, BasicLinkConfig.class);
-                if (cfg != null && !cfg.isAllowed()) {
-                    log.info("Kicking out links between {} and {}", lk.src(), lk.dst());
-                    removeLink(lk.src(), lk.dst());
-                    removeLink(lk.dst(), lk.src());
-                }
+            LinkKey lk = (LinkKey) event.subject();
+            BasicLinkConfig cfg = networkConfigService.getConfig(lk, BasicLinkConfig.class);
+
+            if (!isAllowed(cfg)) {
+                log.info("Kicking out links between {} and {}", lk.src(), lk.dst());
+                removeLink(lk.src(), lk.dst());
+                removeLink(lk.dst(), lk.src());
+                return;
             }
+            Link link = getLink(lk.src(), lk.dst());
+            LinkDescription fldesc;
+            LinkDescription rldesc;
+            if (link == null) {
+                fldesc = BasicLinkOperator.descriptionOf(lk.src(), lk.dst(), cfg);
+                rldesc = BasicLinkOperator.descriptionOf(lk.dst(), lk.src(), cfg);
+            } else {
+                fldesc = BasicLinkOperator.combine(cfg,
+                            BasicLinkOperator.descriptionOf(lk.src(), lk.dst(), link));
+                rldesc = BasicLinkOperator.combine(cfg,
+                            BasicLinkOperator.descriptionOf(lk.dst(), lk.src(), link));
+            }
+            // XXX think of sane way to fetch the LinkProvider
+            store.createOrUpdateLink(ProviderId.NONE, fldesc);
+            store.createOrUpdateLink(ProviderId.NONE, rldesc);
         }
+
     }
 }
diff --git a/core/store/dist/src/main/java/org/onosproject/store/link/impl/ECLinkStore.java b/core/store/dist/src/main/java/org/onosproject/store/link/impl/ECLinkStore.java
index 4577086..243caf8 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/link/impl/ECLinkStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/link/impl/ECLinkStore.java
@@ -211,7 +211,10 @@
         // otherwise signal the actual master.
         if (clusterService.getLocalNode().id().equals(dstNodeId)) {
             LinkKey linkKey = linkKey(linkDescription.src(), linkDescription.dst());
-            Provided<LinkKey> internalLinkKey = new Provided<>(linkKey, providerId);
+            Provided<LinkKey> internalLinkKey = getProvided(linkKey, providerId);
+            if (internalLinkKey == null) {
+                return null;
+            }
             linkDescriptions.compute(internalLinkKey, (k, v) -> createOrUpdateLinkInternal(v  , linkDescription));
             return refreshLinkCache(linkKey);
         } else {
@@ -226,6 +229,18 @@
         }
     }
 
+    private Provided<LinkKey> getProvided(LinkKey linkKey, ProviderId provId) {
+        ProviderId bpid = getBaseProviderId(linkKey);
+        if (provId == null) {
+            // The LinkService didn't know who this LinkKey belongs to.
+            // A fix is to either modify the getProvider() in LinkService classes
+            // or expose the contents of linkDescriptions to the LinkService.
+            return (bpid == null) ? null : new Provided<>(linkKey, bpid);
+        } else {
+            return new Provided<>(linkKey, provId);
+        }
+    }
+
     private LinkDescription createOrUpdateLinkInternal(LinkDescription current, LinkDescription updated) {
         if (current != null) {
             // we only allow transition from INDIRECT -> DIRECT