Devices,hosts, and links can be blocked and kicked off with the network configuration api

Change-Id: I68d427f4886a7b63475df8d35383e2e347946946
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 75134f3..e6fa300 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
@@ -27,14 +27,22 @@
 import org.onosproject.core.Permission;
 import org.onosproject.event.EventDeliveryService;
 import org.onosproject.event.ListenerRegistry;
+import org.onosproject.incubator.net.config.NetworkConfigEvent;
+import org.onosproject.incubator.net.config.NetworkConfigListener;
+import org.onosproject.incubator.net.config.NetworkConfigService;
+import org.onosproject.incubator.net.config.basics.BasicLinkConfig;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Link;
 import org.onosproject.net.Link.State;
+import org.onosproject.net.LinkKey;
 import org.onosproject.net.MastershipRole;
+import org.onosproject.net.SparseAnnotations;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.link.DefaultLinkDescription;
 import org.onosproject.net.link.LinkAdminService;
 import org.onosproject.net.link.LinkDescription;
 import org.onosproject.net.link.LinkEvent;
@@ -49,9 +57,12 @@
 import org.onosproject.net.provider.AbstractProviderService;
 import org.slf4j.Logger;
 
+import java.time.Duration;
 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.slf4j.LoggerFactory.getLogger;
 import static org.onosproject.security.AppGuard.checkPermission;
 
@@ -78,6 +89,8 @@
 
     private final DeviceListener deviceListener = new InternalDeviceListener();
 
+    private final NetworkConfigListener networkConfigListener = new InternalNetworkConfigListener();
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected LinkStore store;
 
@@ -87,11 +100,15 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected EventDeliveryService eventDispatcher;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigService networkConfigService;
+
     @Activate
     public void activate() {
         store.setDelegate(delegate);
         eventDispatcher.addSink(LinkEvent.class, listenerRegistry);
         deviceService.addListener(deviceListener);
+        networkConfigService.addListener(networkConfigListener);
         log.info("Started");
     }
 
@@ -100,6 +117,7 @@
         store.unsetDelegate(delegate);
         eventDispatcher.removeSink(LinkEvent.class);
         deviceService.removeListener(deviceListener);
+        networkConfigService.removeListener(networkConfigListener);
         log.info("Stopped");
     }
 
@@ -206,17 +224,19 @@
         removeLinks(getDeviceLinks(deviceId), false);
     }
 
+    public void removeLink(ConnectPoint src, ConnectPoint dst) {
+        post(store.removeLink(src, dst));
+    }
+
     @Override
     public void addListener(LinkListener listener) {
         checkPermission(Permission.LINK_EVENT);
-
         listenerRegistry.addListener(listener);
     }
 
     @Override
     public void removeListener(LinkListener listener) {
         checkPermission(Permission.LINK_EVENT);
-
         listenerRegistry.removeListener(listener);
     }
 
@@ -229,7 +249,7 @@
                 removeLinks(event.subject().id());
             } else if (event.type() == DeviceEvent.Type.PORT_REMOVED) {
                 removeLinks(new ConnectPoint(event.subject().id(),
-                                             event.port().number()));
+                        event.port().number()));
             }
         }
     }
@@ -252,15 +272,62 @@
         public void linkDetected(LinkDescription linkDescription) {
             checkNotNull(linkDescription, LINK_DESC_NULL);
             checkValidity();
-
+            linkDescription = validateLink(linkDescription);
             LinkEvent event = store.createOrUpdateLink(provider().id(),
-                                                       linkDescription);
+                    linkDescription);
             if (event != null) {
                 log.info("Link {} detected", linkDescription);
                 post(event);
             }
         }
 
+        // returns a LinkDescription made from the union of the BasicLinkConfig
+        // annotations if it exists
+        private LinkDescription validateLink(LinkDescription linkDescription) {
+            // TODO Investigate whether this can be made more efficient
+            BasicLinkConfig cfg = networkConfigService.getConfig(linkKey(linkDescription.src(),
+                                                                         linkDescription.dst()),
+                                                                 BasicLinkConfig.class);
+            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");
+            if (cfg != null) {
+                SparseAnnotations finalSparse = processAnnotations(cfg, linkDescription);
+                // check whether config has a specified type
+                if (cfg.type() != Link.Type.DIRECT) {
+                    linkDescription = new DefaultLinkDescription(linkDescription.src(),
+                                                                 linkDescription.dst(),
+                                                                 cfg.type(), finalSparse);
+                } else {
+                    linkDescription = new DefaultLinkDescription(linkDescription.src(),
+                                                                 linkDescription.dst(),
+                                                                 linkDescription.type(), finalSparse);
+                }
+            }
+            return linkDescription;
+        }
+
+        // supplements or replaces linkDescriptions's annotations with BasicLinkConfig's
+        // annotations
+        private SparseAnnotations processAnnotations(BasicLinkConfig cfg, LinkDescription linkDescription) {
+            SparseAnnotations originalAnnotations = linkDescription.annotations();
+            DefaultAnnotations.Builder newBuilder = DefaultAnnotations.builder();
+            if (cfg.type() != Link.Type.DIRECT) {
+                newBuilder.set(cfg.TYPE, cfg.type().toString());
+            }
+            if (cfg.latency() != Duration.ofNanos(-1)) {
+                newBuilder.set(cfg.LATENCY, cfg.latency().toString());
+            }
+            if (cfg.bandwidth() != -1) {
+                newBuilder.set(cfg.BANDWIDTH, String.valueOf(cfg.bandwidth()));
+            }
+            DefaultAnnotations newAnnotations = newBuilder.build();
+            return DefaultAnnotations.union(originalAnnotations, newAnnotations);
+        }
+
         @Override
         public void linkVanished(LinkDescription linkDescription) {
             checkNotNull(linkDescription, LINK_DESC_NULL);
@@ -297,7 +364,7 @@
     }
 
     // Removes all links in the specified set and emits appropriate events.
-    private void  removeLinks(Set<Link> links, boolean isSoftRemove) {
+    private void removeLinks(Set<Link> links, boolean isSoftRemove) {
         for (Link link : links) {
             LinkEvent event = isSoftRemove ?
                     store.removeOrDownLink(link.src(), link.dst()) :
@@ -323,4 +390,24 @@
             post(event);
         }
     }
+
+    // listens for NetworkConfigEvents of type BasicLinkConfig and removes
+    // links that the config does not allow
+    private class InternalNetworkConfigListener implements NetworkConfigListener {
+        @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());
+                }
+            }
+        }
+    }
 }