LinkDiscovery support for Arista.

Provide the method of link discovery for legacy network.
Openflow provide the link discovery information itself.
But at legacy network side, it can be done by LLDP functionality.

So, this patch implements the gethering of LLDP information from arista switch
and constructs the information of LinkDescription for onos.

So that, onos can recognize the link status and utilize it.

ONOS-7707

Change-Id: I049fe4c0a5d4bf3bf78e985800941d0e131647e1
diff --git a/drivers/arista/src/main/java/org/onosproject/drivers/arista/AristaUtils.java b/drivers/arista/src/main/java/org/onosproject/drivers/arista/AristaUtils.java
new file mode 100644
index 0000000..e569a27
--- /dev/null
+++ b/drivers/arista/src/main/java/org/onosproject/drivers/arista/AristaUtils.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.arista;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Lists;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.protocol.rest.RestSBController;
+import org.slf4j.Logger;
+
+import javax.ws.rs.core.MediaType;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+final class AristaUtils {
+
+    private static final String API_ENDPOINT = "/command-api/";
+    private static final String JSONRPC = "jsonrpc";
+    private static final String METHOD = "method";
+    private static final String RUN_CMDS = "runCmds";
+    private static final String VERSION = "version";
+    private static final String ID = "id";
+    private static final String PARAMS = "params";
+    private static final String FORMAT = "format";
+    private static final String TIMESTAMPS = "timestamps";
+    private static final String CMDS = "cmds";
+    private static final String ENABLE = "enable";
+    private static final String JSON = "json";
+    private static final String TWO_POINT_ZERO = "2.0";
+    private static final String ONOS_REST = "onos-rest";
+    private static final Boolean FALSE = false;
+    private static final int VERSION_1 = 1;
+    private static final String RESULT = "result";
+    private static final String ERROR = "error";
+
+    public static final int RESULT_START_INDEX = 1;
+
+    private static final Logger log = getLogger(AristaUtils.class);
+
+    private AristaUtils() {
+
+    }
+
+    public static Optional<JsonNode> retrieveCommandResult(DriverHandler handler, String cmd) {
+        List<String> cmds = Lists.newArrayList();
+
+        cmds.add(cmd);
+
+        return retrieveCommandResult(handler, cmds);
+    }
+
+    public static Optional<JsonNode> retrieveCommandResult(DriverHandler handler, List<String> cmds) {
+        RestSBController controller = checkNotNull(handler.get(RestSBController.class));
+        DeviceId deviceId = checkNotNull(handler.data()).deviceId();
+        String request = generate(cmds);
+
+        log.debug("request :{}", request);
+
+        String response = controller.post(deviceId, API_ENDPOINT, new ByteArrayInputStream(request.getBytes()),
+                MediaType.APPLICATION_JSON_TYPE, String.class);
+
+        log.debug("response :{}", response);
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode node = (ObjectNode) mapper.readTree(response);
+
+            if (node.has(ERROR)) {
+                log.error("Error {}", node.get(ERROR));
+                return Optional.empty();
+            } else {
+                return Optional.ofNullable(node.get(RESULT));
+            }
+        } catch (IOException e) {
+            log.warn("IO exception occurred because of ", e);
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Generates a ObjectNode from a list of commands in String format.
+     *
+     * @param commands a list of commands
+     * @return an ObjectNode generated from a list of commands in String format
+     */
+    private static String generate(List<String> commands) {
+        ObjectMapper om = new ObjectMapper();
+
+        ArrayNode cmds = om.createArrayNode();
+        cmds.add(ENABLE);
+        commands.stream().forEach(cmds::add); //commands here
+
+        ObjectNode parm = om.createObjectNode();
+        parm.put(FORMAT, JSON);
+        parm.put(TIMESTAMPS, FALSE);
+        parm.put(CMDS, cmds);
+        parm.put(VERSION, VERSION_1);
+
+        ObjectNode node = om.createObjectNode();
+        node.put(JSONRPC, TWO_POINT_ZERO);
+        node.put(METHOD, RUN_CMDS);
+
+        node.put(PARAMS, parm);
+        node.put(ID, ONOS_REST);
+
+        return node.toString();
+    }
+}
\ No newline at end of file
diff --git a/drivers/arista/src/main/java/org/onosproject/drivers/arista/ControllerConfigAristaImpl.java b/drivers/arista/src/main/java/org/onosproject/drivers/arista/ControllerConfigAristaImpl.java
index 4cccd23..c206696 100644
--- a/drivers/arista/src/main/java/org/onosproject/drivers/arista/ControllerConfigAristaImpl.java
+++ b/drivers/arista/src/main/java/org/onosproject/drivers/arista/ControllerConfigAristaImpl.java
@@ -16,27 +16,14 @@
 
 package org.onosproject.drivers.arista;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import org.onosproject.net.DeviceId;
+import com.google.common.collect.Lists;
 import org.onosproject.net.behaviour.ControllerConfig;
 import org.onosproject.net.behaviour.ControllerInfo;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
-import org.onosproject.net.driver.DriverHandler;
-import org.onosproject.protocol.rest.RestSBController;
 import org.slf4j.Logger;
 
-import javax.ws.rs.core.MediaType;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
 import java.util.List;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -47,23 +34,7 @@
     private static final String CONFIGURE_TERMINAL = "configure";
     private static final String OPENFLOW_CMD = "openflow";
     private static final String REMOVE_CONTROLLER_CMD = "no controller tcp:%s:%d";
-    private static final String API_ENDPOINT = "/command-api/";
-    private static final String JSON = "json";
-    private static final String JSONRPC = "jsonrpc";
-    private static final String METHOD = "method";
-    private static final String RUN_CMDS = "runCmds";
-    private static final String VERSION = "version";
-    private static final String ID = "id";
-    private static final String PARAMS = "params";
-    private static final String FORMAT = "format";
-    private static final String TIMESTAMPS = "timestamps";
-    private static final String CMDS = "cmds";
-    private static final String TWO_POINT_ZERO = "2.0";
-    private static final String REMOVE_CONTROLLERS = "removeControllers";
-    private static final String ENABLE = "enable";
     private static final int MAX_CONTROLLERS = 8;
-    private static final Boolean FALSE = false;
-    private static final int VERSION_1 = 1;
 
     private final Logger log = getLogger(getClass());
 
@@ -79,57 +50,13 @@
 
     @Override
     public void removeControllers(List<ControllerInfo> controllers) {
-        DriverHandler handler = handler();
-        RestSBController controller = checkNotNull(handler.get(RestSBController.class));
-        DeviceId deviceId = handler.data().deviceId();
+        List<String> cmds = Lists.newArrayList();
 
-        List<String> cmds = new ArrayList<>();
         cmds.add(CONFIGURE_TERMINAL);
         cmds.add(OPENFLOW_CMD);
         controllers.stream().limit(MAX_CONTROLLERS).forEach(c -> cmds
                 .add(String.format(REMOVE_CONTROLLER_CMD, c.ip().toString(), c.port())));
 
-        String request = generate(cmds);
-        log.info("request :{}", request);
-
-        InputStream stream = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
-        String response = controller.post(deviceId, API_ENDPOINT, stream,
-                MediaType.APPLICATION_JSON_TYPE, String.class);
-        log.info("response :{}", response);
-
-        try {
-            JsonNode json = new ObjectMapper().readTree(response);
-        } catch (IOException e) {
-            log.error("Cannot communicate with device {} , exception {}", deviceId, e);
-        }
-    }
-
-    /**
-     * Generates a ObjectNode from a list of commands in String format.
-     *
-     * @param commands a list of commands
-     * @return an ObjectNode generated from a list of commands in String format
-     */
-    private static String generate(List<String> commands) {
-        ObjectMapper om = new ObjectMapper();
-
-        ArrayNode cmds = om.createArrayNode();
-        cmds.add(ENABLE);
-        commands.stream().forEach(cmds::add); //commands here
-
-        ObjectNode parm = om.createObjectNode();
-        parm.put(FORMAT, JSON);
-        parm.put(TIMESTAMPS, FALSE);
-        parm.put(CMDS, cmds);
-        parm.put(VERSION, VERSION_1);
-
-        ObjectNode node = om.createObjectNode();
-        node.put(JSONRPC, TWO_POINT_ZERO);
-        node.put(METHOD, RUN_CMDS);
-
-        node.put(PARAMS, parm);
-        node.put(ID, REMOVE_CONTROLLERS);
-
-        return node.toString();
+        AristaUtils.retrieveCommandResult(handler(), cmds);
     }
 }
diff --git a/drivers/arista/src/main/java/org/onosproject/drivers/arista/DeviceDescriptionDiscoveryAristaImpl.java b/drivers/arista/src/main/java/org/onosproject/drivers/arista/DeviceDescriptionDiscoveryAristaImpl.java
index 34fef89..be2d6a0 100644
--- a/drivers/arista/src/main/java/org/onosproject/drivers/arista/DeviceDescriptionDiscoveryAristaImpl.java
+++ b/drivers/arista/src/main/java/org/onosproject/drivers/arista/DeviceDescriptionDiscoveryAristaImpl.java
@@ -16,12 +16,7 @@
 
 package org.onosproject.drivers.arista;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.Lists;
 import org.onlab.packet.ChassisId;
 import org.onlab.packet.MacAddress;
@@ -39,9 +34,6 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.device.PortDescription;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
-import org.onosproject.net.driver.DriverHandler;
-import org.onosproject.protocol.rest.RestSBController;
-import javax.ws.rs.core.MediaType;
 import org.slf4j.Logger;
 
 import java.util.HashMap;
@@ -59,8 +51,6 @@
         implements DeviceDescriptionDiscovery {
 
     private static final String UNKNOWN = "unknown";
-    private static final String JSON = "json";
-    private static final String RESULT = "result";
     private static final String INTERFACE_STATUSES = "interfaceStatuses";
     private static final String LINK_STATUS = "linkStatus";
     private static final String LINE_PROTOCOL_STATUS = "lineProtocolStatus";
@@ -76,43 +66,29 @@
     private static final String SERIAL_NUMBER = "serialNumber";
     private static final String SYSTEM_MAC_ADDRESS = "systemMacAddress";
     private static final int WEIGHTING_FACTOR_MANAGEMENT_INTERFACE = 10000;
-    private static final String JSONRPC = "jsonrpc";
-    private static final String METHOD = "method";
-    private static final String RUN_CMDS = "runCmds";
-    private static final String VERSION = "version";
-    private static final String ID = "id";
-    private static final String ONOS_REST = "onos-rest";
-    private static final String PARAMS = "params";
-    private static final String FORMAT = "format";
-    private static final String TIMESTAMPS = "timestamps";
-    private static final String CMDS = "cmds";
     private static final String MANUFACTURER = "Arista Networks";
     private static final String SHOW_INTERFACES_STATUS = "show interfaces status";
     private static final String SHOW_INTERFACES = "show interfaces";
     private static final String SHOW_VERSION = "show version";
-    private static final String TWO_POINT_ZERO = "2.0";
     private static final long MBPS = 1000000;
 
     private final Logger log = getLogger(getClass());
 
-    private static final String API_ENDPOINT = "/command-api/";
-
     @Override
     public DeviceDescription discoverDeviceDetails() {
         try {
-            Optional<JsonNode> result = retrieveCommandResult(SHOW_VERSION);
+            Optional<JsonNode> result = AristaUtils.retrieveCommandResult(handler(), SHOW_VERSION);
 
             if (!result.isPresent()) {
                 return null;
             }
 
-            ArrayNode arrayNode = (ArrayNode) result.get();
-            JsonNode jsonNode = arrayNode.iterator().next();
+            JsonNode jsonNode = result.get().get(AristaUtils.RESULT_START_INDEX);
             String hwVer = jsonNode.get(MODEL_NAME).asText(UNKNOWN);
             String swVer = jsonNode.get(SW_VERSION).asText(UNKNOWN);
             String serialNum = jsonNode.get(SERIAL_NUMBER).asText(UNKNOWN);
             String systemMacAddress = jsonNode.get(SYSTEM_MAC_ADDRESS).asText("").replace(":", "");
-            DeviceId deviceId = handler().data().deviceId();
+            DeviceId deviceId = checkNotNull(handler().data().deviceId());
             DeviceService deviceService = checkNotNull(handler().get(DeviceService.class));
             Device device = deviceService.getDevice(deviceId);
             ChassisId chassisId = systemMacAddress.isEmpty() ? new ChassisId() : new ChassisId(systemMacAddress);
@@ -133,15 +109,13 @@
         List<PortDescription> ports = Lists.newArrayList();
 
         try {
-            Optional<JsonNode> result = retrieveCommandResult(SHOW_INTERFACES_STATUS);
+            Optional<JsonNode> result = AristaUtils.retrieveCommandResult(handler(), SHOW_INTERFACES_STATUS);
 
             if (!result.isPresent()) {
                 return ports;
             }
 
-            ArrayNode arrayNode = (ArrayNode) result.get();
-
-            JsonNode jsonNode = arrayNode.iterator().next().get(INTERFACE_STATUSES);
+            JsonNode jsonNode = result.get().findValue(INTERFACE_STATUSES);
 
             jsonNode.fieldNames().forEachRemaining(name -> {
                 JsonNode interfaceNode = jsonNode.get(name);
@@ -199,14 +173,13 @@
         Map<String, MacAddress> macAddressMap = new HashMap();
 
         try {
-            Optional<JsonNode> result = retrieveCommandResult(SHOW_INTERFACES);
+            Optional<JsonNode> result = AristaUtils.retrieveCommandResult(handler(), SHOW_INTERFACES);
 
             if (!result.isPresent()) {
                 return macAddressMap;
             }
 
-            ArrayNode arrayNode = (ArrayNode) result.get();
-            JsonNode jsonNode = arrayNode.iterator().next().get(INTERFACES);
+            JsonNode jsonNode = result.get().findValue(INTERFACES);
 
             jsonNode.fieldNames().forEachRemaining(name -> {
                 JsonNode interfaceNode = jsonNode.get(name);
@@ -247,37 +220,5 @@
 
         return macAddressMap;
     }
-
-    private Optional<JsonNode> retrieveCommandResult(String cmd) {
-        DriverHandler handler = handler();
-        RestSBController controller = checkNotNull(handler.get(RestSBController.class));
-        DeviceId deviceId = handler.data().deviceId();
-
-        ObjectMapper mapper = new ObjectMapper();
-
-        ObjectNode sendObjNode = mapper.createObjectNode();
-
-        sendObjNode.put(JSONRPC, TWO_POINT_ZERO)
-                .put(METHOD, RUN_CMDS)
-                .put(ID, ONOS_REST)
-                .putObject(PARAMS)
-                .put(FORMAT, JSON)
-                .put(TIMESTAMPS, false)
-                .put(VERSION, 1)
-                .putArray(CMDS).add(cmd);
-
-        String response = controller.post(deviceId, API_ENDPOINT,
-                new ByteArrayInputStream(sendObjNode.toString().getBytes()),
-                MediaType.APPLICATION_JSON_TYPE, String.class);
-
-        try {
-            ObjectNode node = (ObjectNode) mapper.readTree(response);
-
-            return Optional.ofNullable(node.get(RESULT));
-        } catch (IOException e) {
-            log.warn("IO exception occurred because of ", e);
-        }
-        return Optional.empty();
-    }
 }
 
diff --git a/drivers/arista/src/main/java/org/onosproject/drivers/arista/LinkDiscoveryAristaImpl.java b/drivers/arista/src/main/java/org/onosproject/drivers/arista/LinkDiscoveryAristaImpl.java
new file mode 100644
index 0000000..49ed615
--- /dev/null
+++ b/drivers/arista/src/main/java/org/onosproject/drivers/arista/LinkDiscoveryAristaImpl.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.arista;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Sets;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.Tools;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.LinkDiscovery;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkDescription;
+import org.slf4j.Logger;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class LinkDiscoveryAristaImpl extends AbstractHandlerBehaviour implements LinkDiscovery {
+
+    private static final String SHOW_LLDP_NEIGHBOR_DETAIL_CMD = "show lldp neighbors detail";
+    private static final String LLDP_NEIGHBORS = "lldpNeighbors";
+    private static final String LLDP_NEIGHBOR_INFO = "lldpNeighborInfo";
+    private static final String CHASSIS_ID = "chassisId";
+    private static final String PORT_ID = "interfaceDescription";
+    private static final String CHASSIS_ID_TYPE = "chassisIdType";
+    private static final String CHASSIS_ID_TYPE_MAC = "macAddress";
+
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    public Set<LinkDescription> getLinks() {
+        return createLinksDescs(AristaUtils.retrieveCommandResult(handler(), SHOW_LLDP_NEIGHBOR_DETAIL_CMD));
+    }
+
+    private Set<LinkDescription> createLinksDescs(Optional<JsonNode> response) {
+        DriverHandler handler = checkNotNull(handler());
+        DeviceId localDeviceId = checkNotNull(handler.data().deviceId());
+        DeviceService deviceService = handler.get(DeviceService.class);
+        Set<LinkDescription> linkDescriptions = Sets.newHashSet();
+        List<Port> ports = deviceService.getPorts(localDeviceId);
+
+        if (ports.isEmpty() || Objects.isNull(response)) {
+            return linkDescriptions;
+        }
+
+        if (!response.isPresent()) {
+            return linkDescriptions;
+        }
+
+        log.debug("response: {}, {}", response, localDeviceId.toString());
+
+        JsonNode res = response.get();
+
+        if (res == null) {
+            log.warn("result is null");
+            return linkDescriptions;
+        }
+
+        JsonNode lldpNeighbors = res.findValue(LLDP_NEIGHBORS);
+
+        if (lldpNeighbors == null) {
+            log.warn("{} is null", LLDP_NEIGHBORS);
+            return linkDescriptions;
+        }
+
+        Iterator<Map.Entry<String, JsonNode>> lldpNeighborsIter = lldpNeighbors.fields();
+
+        while (lldpNeighborsIter.hasNext()) {
+            Map.Entry<String, JsonNode> neighbor = lldpNeighborsIter.next();
+            String lldpLocalPort = neighbor.getKey();
+            JsonNode neighborValue = neighbor.getValue();
+
+            log.debug("lldpLocalPort: {}", lldpLocalPort);
+            log.debug("neighborValue: {}", neighborValue.toString());
+
+            if (lldpLocalPort.isEmpty()) {
+                continue;
+            }
+
+            JsonNode neighborInfo = neighborValue.findValue(LLDP_NEIGHBOR_INFO);
+
+            if (neighborInfo == null) {
+                log.warn("{} is null", LLDP_NEIGHBOR_INFO);
+                continue;
+            }
+
+            Iterator<JsonNode> neighborInfoIter = neighborInfo.elements();
+
+            while (neighborInfoIter.hasNext()) {
+                JsonNode info = neighborInfoIter.next();
+                String chassisIdType = info.get(CHASSIS_ID_TYPE).asText("");
+
+                if (chassisIdType == null) {
+                    log.warn("{} is null", CHASSIS_ID_TYPE);
+                    continue;
+                }
+
+                if (!chassisIdType.equals(CHASSIS_ID_TYPE_MAC)) {
+                    log.warn("{} is not mac: {}", CHASSIS_ID_TYPE_MAC, chassisIdType);
+                    continue;
+                }
+
+                JsonNode remotePortNameNode = info.findValue(PORT_ID);
+
+                if (remotePortNameNode == null) {
+                    continue;
+                }
+
+                String remoteChassisId = info.get(CHASSIS_ID).asText("");
+                String remotePortName = remotePortNameNode.asText("");
+
+                log.debug("{}: {}, {}: {}", CHASSIS_ID, remoteChassisId, PORT_ID, remotePortName);
+
+                Optional<Port> localPort = findLocalPortByName(ports, lldpLocalPort);
+
+                if (!localPort.isPresent()) {
+                    log.warn("local port not found. lldpLocalPort value: {}", lldpLocalPort);
+                    continue;
+                }
+
+                Optional<Device> remoteDevice = findRemoteDeviceByChassisId(deviceService, remoteChassisId);
+
+                if (!remoteDevice.isPresent()) {
+                    log.warn("remote device not found. remoteChassisId value: {}", remoteChassisId);
+                    continue;
+                }
+
+                Optional<Port> remotePort = findDestinationPortByName(
+                        remotePortName,
+                        deviceService,
+                        remoteDevice.get());
+
+                if (!remotePort.isPresent()) {
+                    log.warn("remote port not found. remotePortName value: {}", remotePortName);
+                    continue;
+                }
+
+                linkDescriptions
+                        .addAll(buildLinkPair(localDeviceId, localPort.get(),
+                                remoteDevice.get().id(), remotePort.get()));
+            }
+        }
+
+        log.debug("returning linkDescriptions: {}", linkDescriptions);
+
+        return linkDescriptions;
+    }
+
+    private Optional<Port> findLocalPortByName(List<Port> ports, String lldpLocalPort) {
+        Optional<Port> localPort = ports.stream()
+                .filter(port -> lldpLocalPort.equalsIgnoreCase(port.annotations().value(AnnotationKeys.PORT_NAME)))
+                .findAny();
+
+        if (!localPort.isPresent()) {
+            localPort = ports.stream()
+                    .filter(port -> lldpLocalPort.equalsIgnoreCase(port.annotations().value(AnnotationKeys.NAME)))
+                    .findAny();
+
+            if (!localPort.isPresent()) {
+                return Optional.empty();
+            }
+        }
+
+        return localPort;
+    }
+
+    private Optional<Device> findRemoteDeviceByChassisId(DeviceService deviceService, String remoteChassisIdString) {
+        String forMacTmp = remoteChassisIdString
+                .replace(".", "")
+                .replaceAll("(.{2})", "$1:")
+                .trim()
+                .substring(0, 17);
+        MacAddress mac = MacAddress.valueOf(forMacTmp);
+        Supplier<Stream<Device>> deviceStream = () ->
+                StreamSupport.stream(deviceService.getAvailableDevices().spliterator(), false);
+        Optional<Device> remoteDeviceOptional = deviceStream.get()
+                .filter(device -> device.chassisId() != null
+                        && MacAddress.valueOf(device.chassisId().value()).equals(mac))
+                .findAny();
+
+        if (remoteDeviceOptional.isPresent()) {
+            log.debug("remoteDevice found by chassis id: {}", forMacTmp);
+            return remoteDeviceOptional;
+        } else {
+            remoteDeviceOptional = deviceStream.get().filter(device ->
+                Tools.stream(deviceService.getPorts(device.id()))
+                        .anyMatch(port -> port.annotations().keys().contains(AnnotationKeys.PORT_MAC)
+                                && MacAddress.valueOf(port.annotations().value(AnnotationKeys.PORT_MAC))
+                        .equals(mac)))
+                    .findAny();
+            if (remoteDeviceOptional.isPresent()) {
+                log.debug("remoteDevice found by port mac: {}", forMacTmp);
+                return remoteDeviceOptional;
+            } else {
+                return Optional.empty();
+            }
+        }
+    }
+
+    private Optional<Port> findDestinationPortByName(String remotePortName,
+                                           DeviceService deviceService,
+                                           Device remoteDevice) {
+        Optional<Port> remotePort = deviceService.getPorts(remoteDevice.id())
+                .stream().filter(port -> remotePortName.equals(port.annotations().value(AnnotationKeys.PORT_NAME)))
+                .findAny();
+
+        if (remotePort.isPresent()) {
+            return remotePort;
+        } else {
+            int portNumber = Integer.valueOf(remotePortName.replaceAll("\\D+", ""));
+            DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                    .set(AnnotationKeys.PORT_NAME, remotePortName);
+
+            return Optional.of(new DefaultPort(remoteDevice, PortNumber.portNumber(portNumber, remotePortName),
+                    true,
+                    annotations.build()));
+        }
+    }
+
+    private static Set<LinkDescription> buildLinkPair(DeviceId localDevId,
+                                                      Port localPort,
+                                                      DeviceId remoteDevId,
+                                                      Port remotePort) {
+
+        Set<LinkDescription> linkDescriptions = Sets.newHashSet();
+        ConnectPoint local = new ConnectPoint(localDevId, localPort.number());
+        ConnectPoint remote = new ConnectPoint(remoteDevId, remotePort.number());
+        DefaultAnnotations annotations = DefaultAnnotations.builder()
+                .set("layer", "ETHERNET")
+                .build();
+
+        linkDescriptions.add(new DefaultLinkDescription(
+                remote, local, Link.Type.DIRECT, true, annotations));
+
+        return linkDescriptions;
+    }
+}
diff --git a/drivers/arista/src/main/resources/arista-drivers.xml b/drivers/arista/src/main/resources/arista-drivers.xml
index 15b42be..91238a9 100644
--- a/drivers/arista/src/main/resources/arista-drivers.xml
+++ b/drivers/arista/src/main/resources/arista-drivers.xml
@@ -20,6 +20,8 @@
                    impl="org.onosproject.drivers.arista.DeviceDescriptionDiscoveryAristaImpl"/>
         <behaviour api="org.onosproject.net.behaviour.ControllerConfig"
                    impl="org.onosproject.drivers.arista.ControllerConfigAristaImpl"/>
+        <behaviour api="org.onosproject.net.behaviour.LinkDiscovery"
+                   impl="org.onosproject.drivers.arista.LinkDiscoveryAristaImpl"/>
     </driver>
 </drivers>