[Falcon] link discovery -

- Support for TLV containing cluster fingerprint info
- Config for enabling extra TLV at device level
- Refactored ONOSLLDP constructor for ease of use

Change-Id: I93abe6c0ed8b7e37c80af5920649272faad8856e
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/DiscoveryContext.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/DiscoveryContext.java
index a9da92a..04729e4 100644
--- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/DiscoveryContext.java
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/DiscoveryContext.java
@@ -66,4 +66,11 @@
      * @param key link key
      */
     void touchLink(LinkKey key);
+
+    /**
+     * Returns the cluster-wide unique identifier.
+     *
+     * @return the cluster identifier
+     */
+    String fingerprint();
 }
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/FingerprintProbeFromDevice.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/FingerprintProbeFromDevice.java
new file mode 100644
index 0000000..f6e3c2d
--- /dev/null
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/FingerprintProbeFromDevice.java
@@ -0,0 +1,18 @@
+package org.onosproject.provider.lldp.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.basics.BasicFeatureConfig;
+
+/**
+ * A feature to send and receive probes carrying a cluster-unique fingerprint.
+ * Note that, as it leverages LinkDiscovery, disabling linkDiscovery will disable
+ * this function.
+ */
+public class FingerprintProbeFromDevice extends BasicFeatureConfig<DeviceId> {
+
+    protected FingerprintProbeFromDevice() {
+        // default:disabled
+        super(false);
+    }
+
+}
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java
index 4b962ae..11b8cd3 100644
--- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java
@@ -16,6 +16,7 @@
 package org.onosproject.provider.lldp.impl;
 
 import com.google.common.collect.Sets;
+
 import org.jboss.netty.util.Timeout;
 import org.jboss.netty.util.TimerTask;
 import org.onlab.packet.Ethernet;
@@ -59,13 +60,13 @@
     private final Device device;
     private final DiscoveryContext context;
 
-    private final ONOSLLDP lldpPacket;
     private final Ethernet ethPacket;
     private final Ethernet bddpEth;
 
     private Timeout timeout;
     private volatile boolean isStopped;
-
+    // This LinkDiscovery can handle remote link probes (default false).
+    private volatile boolean fingerprinted;
     // Set of ports to be probed
     private final Set<Long> ports = Sets.newConcurrentHashSet();
 
@@ -81,22 +82,17 @@
         this.device = device;
         this.context = context;
 
-        lldpPacket = new ONOSLLDP();
-        lldpPacket.setChassisId(device.chassisId());
-        lldpPacket.setDevice(device.id().toString());
-
         ethPacket = new Ethernet();
         ethPacket.setEtherType(Ethernet.TYPE_LLDP);
         ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
-        ethPacket.setPayload(this.lldpPacket);
         ethPacket.setPad(true);
 
         bddpEth = new Ethernet();
-        bddpEth.setPayload(lldpPacket);
         bddpEth.setEtherType(Ethernet.TYPE_BSN);
         bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
         bddpEth.setPad(true);
 
+        fingerprinted = false;
         isStopped = true;
         start();
         log.debug("Started discovery manager for switch {}", device.id());
@@ -220,8 +216,8 @@
         if (port == null) {
             return null;
         }
-        lldpPacket.setPortId(port.intValue());
-        ethPacket.setSourceMACAddress(SRC_MAC);
+        ONOSLLDP lldp = getLinkProbe(port);
+        ethPacket.setSourceMACAddress(SRC_MAC).setPayload(lldp);
         return new DefaultOutboundPacket(device.id(),
                                          builder().setOutput(portNumber(port)).build(),
                                          ByteBuffer.wrap(ethPacket.serialize()));
@@ -237,13 +233,21 @@
         if (port == null) {
             return null;
         }
-        lldpPacket.setPortId(port.intValue());
-        bddpEth.setSourceMACAddress(SRC_MAC);
+        ONOSLLDP lldp = getLinkProbe(port);
+        bddpEth.setSourceMACAddress(SRC_MAC).setPayload(lldp);
         return new DefaultOutboundPacket(device.id(),
                                          builder().setOutput(portNumber(port)).build(),
                                          ByteBuffer.wrap(bddpEth.serialize()));
     }
 
+    private ONOSLLDP getLinkProbe(Long port) {
+        return fingerprinted
+                ? ONOSLLDP.fingerprintedLLDP(device.id().toString(), device.chassisId(),
+                                             port.intValue(), context.fingerprint())
+                : ONOSLLDP.onosLLDP(device.id().toString(), device.chassisId(),
+                                    port.intValue());
+    }
+
     private void sendProbes(Long portNumber) {
         log.trace("Sending probes out to {}@{}", portNumber, device.id());
         OutboundPacket pkt = createOutBoundLldp(portNumber);
@@ -258,4 +262,11 @@
         return ports.contains(portNumber);
     }
 
+    protected void enableFingerprint() {
+        fingerprinted = true;
+    }
+
+    protected void disableFingerprint() {
+        fingerprinted = false;
+    }
 }
diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java
index ce2826c..a87b6a7 100644
--- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java
+++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java
@@ -47,6 +47,7 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterMetadataService;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
@@ -133,6 +134,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected NetworkConfigRegistry cfgRegistry;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterMetadataService clusterMetadataService;
+
     private LinkProviderService providerService;
 
     private ScheduledExecutorService executor;
@@ -185,6 +189,7 @@
 
     public static final String CONFIG_KEY = "suppression";
     public static final String FEATURE_NAME = "linkDiscovery";
+    public static final String FINGERPRINT_FEATURE_NAME = "fingerprint";
 
     private final Set<ConfigFactory<?, ?>> factories = ImmutableSet.of(
             new ConfigFactory<ApplicationId, SuppressionConfig>(APP_SUBJECT_FACTORY,
@@ -208,6 +213,13 @@
                 public LinkDiscoveryFromPort createConfig() {
                     return new LinkDiscoveryFromPort();
                 }
+            },
+            new ConfigFactory<DeviceId, FingerprintProbeFromDevice>(DEVICE_SUBJECT_FACTORY,
+                    FingerprintProbeFromDevice.class, FINGERPRINT_FEATURE_NAME) {
+                @Override
+                public FingerprintProbeFromDevice createConfig() {
+                    return new FingerprintProbeFromDevice();
+                }
             }
     );
 
@@ -385,6 +397,14 @@
         return isBlacklisted(new ConnectPoint(port.element().id(), port.number()));
     }
 
+    private boolean isFingerprinted(DeviceId did) {
+        FingerprintProbeFromDevice cfg = cfgRegistry.getConfig(did, FingerprintProbeFromDevice.class);
+        if (cfg == null) {
+            return false;
+        }
+        return cfg.enabled();
+    }
+
     /**
      * Updates discovery helper for specified device.
      *
@@ -405,6 +425,11 @@
         }
         LinkDiscovery ld = discoverers.computeIfAbsent(device.id(),
                                      did -> new LinkDiscovery(device, context));
+        if (isFingerprinted(device.id())) {
+            ld.enableFingerprint();
+        } else {
+            ld.disableFingerprint();
+        }
         if (ld.isStopped()) {
             ld.start();
         }
@@ -715,6 +740,11 @@
         public void touchLink(LinkKey key) {
             linkTimes.put(key, System.currentTimeMillis());
         }
+
+        @Override
+        public String fingerprint() {
+            return clusterMetadataService.getClusterMetadata().getName();
+        }
     }
 
     static final EnumSet<NetworkConfigEvent.Type> CONFIG_CHANGED
@@ -760,6 +790,15 @@
                     }
                 }
 
+            } else if (event.configClass() == FingerprintProbeFromDevice.class &&
+                    CONFIG_CHANGED.contains(event.type())) {
+
+                if (event.subject() instanceof DeviceId) {
+                    final DeviceId did = (DeviceId) event.subject();
+                    Device device = deviceService.getDevice(did);
+                    updateDevice(device);
+                }
+
             } else if (event.configClass().equals(SuppressionConfig.class) &&
                 (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
                  event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)) {
diff --git a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java
index 758c34e..28b08ea 100644
--- a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java
+++ b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java
@@ -21,14 +21,22 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.packet.ChassisId;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.ONOSLLDP;
 import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.cluster.ClusterMetadata;
+import org.onosproject.cluster.ClusterMetadataService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
 import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.Partition;
 import org.onosproject.cluster.RoleInfo;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
@@ -80,7 +88,6 @@
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
-
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertEquals;
@@ -143,7 +150,7 @@
         provider.packetService = packetService;
         provider.providerRegistry = linkRegistry;
         provider.masterService = masterService;
-
+        provider.clusterMetadataService = new TestMetadataService();
         provider.activate(null);
     }
 
@@ -696,11 +703,9 @@
 
         @Override
         public InboundPacket inPacket() {
-            ONOSLLDP lldp = new ONOSLLDP();
-            lldp.setChassisId(device.chassisId());
-            lldp.setPortId((int) pd1.number().toLong());
-            lldp.setDevice(deviceService.getDevice(DID1).id().toString());
-
+            ONOSLLDP lldp = ONOSLLDP.onosLLDP(deviceService.getDevice(DID1).id().toString(),
+                                              device.chassisId(),
+                                              (int) pd1.number().toLong());
 
             Ethernet ethPacket = new Ethernet();
             ethPacket.setEtherType(Ethernet.TYPE_LLDP);
@@ -708,8 +713,6 @@
             ethPacket.setPayload(lldp);
             ethPacket.setPad(true);
 
-
-
             ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11");
 
             ConnectPoint cp = new ConnectPoint(device.id(), pd3.number());
@@ -941,4 +944,26 @@
             return this;
         }
     }
+
+    private final class TestMetadataService implements ClusterMetadataService {
+        @Override
+        public ClusterMetadata getClusterMetadata() {
+            final NodeId nid = new NodeId("test-node");
+            final IpAddress addr = IpAddress.valueOf(0);
+            final Partition p = new Partition("test-pt", Sets.newHashSet(nid));
+            return ClusterMetadata.builder()
+                    .withName("test-cluster")
+                    .withControllerNodes(Sets.newHashSet(new DefaultControllerNode(nid, addr)))
+                    .withPartitions(Sets.newHashSet(p)).build();
+        }
+
+        @Override
+        public void setClusterMetadata(ClusterMetadata metadata) {
+        }
+
+        @Override
+        public ControllerNode getLocalNode() {
+            return null;
+        }
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java b/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java
index 4d5d58b..78a6ad3 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java
@@ -15,14 +15,21 @@
  */
 package org.onlab.packet;
 
+import java.util.HashMap;
+
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
 import org.apache.commons.lang.ArrayUtils;
 
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 
+import static org.onlab.packet.LLDPOrganizationalTLV.OUI_LENGTH;
+import static org.onlab.packet.LLDPOrganizationalTLV.SUBTYPE_LENGTH;
+
 /**
- *  ONOS LLDP containing organizational TLV for ONOS device dicovery.
+ *  ONOS LLDP containing organizational TLV for ONOS device discovery.
  */
 public class ONOSLLDP extends LLDP {
 
@@ -37,12 +44,16 @@
     public static final byte[] BDDP_MULTICAST = {(byte) 0xff, (byte) 0xff,
             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
 
-    private static final byte NAME_SUBTYPE = 1;
-    private static final byte DEVICE_SUBTYPE = 2;
-    private static final short NAME_LENGTH = 4; //1 for subtype + 3 for OUI
-    private static final short DEVICE_LENGTH = 4; //1 for subtype + 3 for OUI
-    private final LLDPOrganizationalTLV nameTLV = new LLDPOrganizationalTLV();
-    private final LLDPOrganizationalTLV deviceTLV =  new LLDPOrganizationalTLV();
+    protected static final byte NAME_SUBTYPE = 1;
+    protected static final byte DEVICE_SUBTYPE = 2;
+    protected static final byte DOMAIN_SUBTYPE = 3;
+
+    private static final short NAME_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH;
+    private static final short DEVICE_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH;
+    private static final short DOMAIN_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH;
+
+    private final HashMap<Byte, LLDPOrganizationalTLV> opttlvs =
+            Maps.<Byte, LLDPOrganizationalTLV>newHashMap();
 
     // TLV constants: type, size and subtype
     // Organizationally specific TLV also have packet offset and contents of TLV
@@ -57,18 +68,24 @@
 
     private static final byte TTL_TLV_TYPE = 3;
 
-
     private final byte[] ttlValue = new byte[] {0, 0x78};
 
-    public ONOSLLDP() {
+    // Only needs to be accessed from LinkProbeFactory.
+    protected ONOSLLDP(byte ... subtype) {
         super();
+        for (byte st : subtype) {
+            opttlvs.put(st, new LLDPOrganizationalTLV());
+        }
+        // guarantee the following (name and device) TLVs exist
+        opttlvs.putIfAbsent(NAME_SUBTYPE, new LLDPOrganizationalTLV());
+        opttlvs.putIfAbsent(DEVICE_SUBTYPE, new LLDPOrganizationalTLV());
         setName(DEFAULT_NAME);
         setDevice(DEFAULT_DEVICE);
-        setOptionalTLVList(Lists.<LLDPTLV>newArrayList(nameTLV, deviceTLV));
+
+        setOptionalTLVList(Lists.<LLDPTLV>newArrayList(opttlvs.values()));
         setTtl(new LLDPTLV().setType(TTL_TLV_TYPE)
                        .setLength((short) ttlValue.length)
                        .setValue(ttlValue));
-
     }
 
     private ONOSLLDP(LLDP lldp) {
@@ -79,17 +96,31 @@
     }
 
     public void setName(String name) {
-        nameTLV.setLength((short) (name.length() + NAME_LENGTH));
-        nameTLV.setInfoString(name);
-        nameTLV.setSubType(NAME_SUBTYPE);
-        nameTLV.setOUI(ONLAB_OUI);
+        LLDPOrganizationalTLV nametlv = opttlvs.get(NAME_SUBTYPE);
+        nametlv.setLength((short) (name.length() + NAME_LENGTH));
+        nametlv.setInfoString(name);
+        nametlv.setSubType(NAME_SUBTYPE);
+        nametlv.setOUI(ONLAB_OUI);
     }
 
     public void setDevice(String device) {
-        deviceTLV.setInfoString(device);
-        deviceTLV.setLength((short) (device.length() + DEVICE_LENGTH));
-        deviceTLV.setSubType(DEVICE_SUBTYPE);
-        deviceTLV.setOUI(ONLAB_OUI);
+        LLDPOrganizationalTLV devicetlv = opttlvs.get(DEVICE_SUBTYPE);
+        devicetlv.setInfoString(device);
+        devicetlv.setLength((short) (device.length() + DEVICE_LENGTH));
+        devicetlv.setSubType(DEVICE_SUBTYPE);
+        devicetlv.setOUI(ONLAB_OUI);
+    }
+
+    public void setDomainInfo(String domainId) {
+        LLDPOrganizationalTLV domaintlv = opttlvs.get(DOMAIN_SUBTYPE);
+        if (domaintlv == null) {
+            // maybe warn people not to set this if remote probes aren't.
+            return;
+        }
+        domaintlv.setInfoString(domainId);
+        domaintlv.setLength((short) (domainId.length() + DOMAIN_LENGTH));
+        domaintlv.setSubType(DOMAIN_SUBTYPE);
+        domaintlv.setOUI(ONLAB_OUI);
     }
 
     public void setChassisId(final ChassisId chassisId) {
@@ -139,6 +170,24 @@
         return null;
     }
 
+    /**
+     * Gets the TLV associated with remote probing. This TLV will be null if
+     * remote probing is disabled.
+     *
+     * @return A TLV containing domain ID, or null.
+     */
+    public LLDPOrganizationalTLV getDomainTLV() {
+        for (LLDPTLV tlv : this.getOptionalTLVList()) {
+            if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
+                LLDPOrganizationalTLV orgTLV =  (LLDPOrganizationalTLV) tlv;
+                if (orgTLV.getSubType() == DOMAIN_SUBTYPE) {
+                    return orgTLV;
+                }
+            }
+        }
+        return null;
+    }
+
     public String getNameString() {
         LLDPOrganizationalTLV tlv = getNameTLV();
         if (tlv != null) {
@@ -155,6 +204,14 @@
         return null;
     }
 
+    public String getDomainString() {
+        LLDPOrganizationalTLV tlv = getDomainTLV();
+        if (tlv != null) {
+            return new String(tlv.getInfoString(), StandardCharsets.UTF_8);
+        }
+        return null;
+    }
+
     public Integer getPort() {
         ByteBuffer portBB = ByteBuffer.wrap(this.getPortId().getValue());
         portBB.position(1);
@@ -177,4 +234,40 @@
         }
         return null;
     }
+
+    /**
+     * Creates a link probe for link discovery/verification.
+     *
+     * @param deviceId The device ID as a String
+     * @param chassisId The chassis ID of the device
+     * @param portNum Port number of port to send probe out of
+     * @return ONOSLLDP probe message
+     */
+    public static ONOSLLDP onosLLDP(String deviceId, ChassisId chassisId, int portNum) {
+        ONOSLLDP probe = new ONOSLLDP(NAME_SUBTYPE, DEVICE_SUBTYPE);
+        probe.setPortId(portNum);
+        probe.setDevice(deviceId);
+        probe.setChassisId(chassisId);
+        return probe;
+    }
+
+    /**
+     * Creates a link probe carrying a fingerprint unique to the ONOS cluster managing
+     * link discovery/verification.
+     *
+     * @param deviceId The device ID as a String
+     * @param chassisId The chassis ID of the device
+     * @param portNum Port number of port to send probe out of
+     * @param domainId The cluster's fingerprint
+     * @return ONOSLLDP probe message
+     */
+    public static ONOSLLDP fingerprintedLLDP(
+            String deviceId, ChassisId chassisId, int portNum, String domainId) {
+        ONOSLLDP probe = new ONOSLLDP(NAME_SUBTYPE, DEVICE_SUBTYPE, DOMAIN_SUBTYPE);
+        probe.setPortId(portNum);
+        probe.setDevice(deviceId);
+        probe.setChassisId(chassisId);
+        probe.setDomainInfo(domainId);
+        return probe;
+    }
 }