From f6a274e8e69a57f78feac3903d8d4b0b35b5ad90 Mon Sep 17 00:00:00 2001 From: Evan Zeller Date: Thu, 9 Nov 2017 17:19:28 -0800 Subject: [PATCH] OPNFLWPLUG-952: All links disappear from the topology In cluster split situations LLDP Speaker can get into a bad state, with incorrect number of node-connectors mapped locally. In some cases multiple controllers can start sending packet outs. The solution here is to use clustered DTCL to keep the node-connectors more consistent across all instances of LLDPSpeaker and use EOS to determine who should actually transmit the packet. Change-Id: I36df734dff23688e8ace27b42f85291aa62ac391 Signed-off-by: Evan Zeller --- .../DeviceOwnershipStatusService.java | 100 ++++++++++++++++ .../applications/lldpspeaker/LLDPSpeaker.java | 111 ++++++++++++------ .../applications/lldpspeaker/LLDPUtil.java | 9 +- ...NodeConnectorInventoryEventTranslator.java | 33 ++---- .../opendaylight/blueprint/lldp-speaker.xml | 2 + .../lldpspeaker/LLDPSpeakerTest.java | 27 ++--- applications/topology-lldp-discovery/pom.xml | 4 + .../topology/lldp/LLDPDiscoveryListener.java | 39 ++++-- .../topology/lldp/LLDPLinkAger.java | 26 +++- .../lldp/utils/LLDPDiscoveryUtils.java | 35 ++++++ .../blueprint/topology-lldp-discovery.xml | 3 + .../topology/lldp/LLDPLinkAgerTest.java | 19 ++- 12 files changed, 317 insertions(+), 91 deletions(-) create mode 100644 applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/DeviceOwnershipStatusService.java diff --git a/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/DeviceOwnershipStatusService.java b/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/DeviceOwnershipStatusService.java new file mode 100644 index 0000000000..1b2b71bd7c --- /dev/null +++ b/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/DeviceOwnershipStatusService.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017 Lumina Networks, Inc. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.openflowplugin.applications.lldpspeaker; + +import com.google.common.base.Optional; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; +import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipChange; +import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipListener; +import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipService; +import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.core.general.entity.rev150930.Entity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DeviceOwnershipStatusService implements EntityOwnershipListener { + private static final Logger LOG = LoggerFactory.getLogger(DeviceOwnershipStatusService.class); + private static final String SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.ServiceEntityType"; + private static final Pattern NODE_ID_PATTERN = Pattern.compile("^openflow:\\d+"); + + private final EntityOwnershipService eos; + private final ConcurrentMap ownershipStateCache = new ConcurrentHashMap<>(); + + public DeviceOwnershipStatusService(final EntityOwnershipService entityOwnershipService) { + this.eos = entityOwnershipService; + registerEntityOwnershipListener(); + } + + public boolean isEntityOwned(final String nodeId) { + EntityOwnershipState state = ownershipStateCache.get(nodeId); + if (state == null) { + java.util.Optional status = getCurrentOwnershipStatus(nodeId); + if (status.isPresent()) { + state = status.get(); + ownershipStateCache.put(nodeId, state); + } else { + LOG.warn("Fetching ownership status failed for node {}", nodeId); + } + } + return state != null && state.equals(EntityOwnershipState.IS_OWNER); + } + + public List getOwnedNodes() { + List nodes = new ArrayList<>(); + ownershipStateCache.forEach((node, change) -> { + if (isEntityOwned(node)) { + nodes.add(node); + } + }); + return nodes; + } + + @Override + public void ownershipChanged(final EntityOwnershipChange ownershipChange) { + final String entityName = ownershipChange.getEntity().getIdentifier().firstKeyOf(Entity.class).getName(); + if (entityName != null && isOpenFlowEntity(entityName)) { + LOG.info("Entity ownership change received for node : {} : {}", entityName, ownershipChange); + if (!ownershipChange.getState().isOwner() && !ownershipChange.getState().hasOwner() + && !ownershipChange.inJeopardy()) { + LOG.debug("Entity for node {} is unregistered.", entityName); + ownershipStateCache.remove(entityName); + } else if (!ownershipChange.getState().isOwner() && ownershipChange.getState().hasOwner()) { + ownershipStateCache.put(entityName, EntityOwnershipState.OWNED_BY_OTHER); + } else if (ownershipChange.getState().isOwner()) { + ownershipStateCache.put(entityName, EntityOwnershipState.IS_OWNER); + } + } + } + + private java.util.Optional getCurrentOwnershipStatus(final String nodeId) { + org.opendaylight.mdsal.eos.binding.api.Entity entity = createNodeEntity(nodeId); + Optional ownershipStatus = eos.getOwnershipState(entity); + + if (ownershipStatus.isPresent()) { + LOG.debug("Fetched ownership status for node {} is {}", nodeId, ownershipStatus.get()); + return java.util.Optional.of(ownershipStatus.get()); + } + return java.util.Optional.empty(); + } + + private org.opendaylight.mdsal.eos.binding.api.Entity createNodeEntity(final String nodeId) { + return new org.opendaylight.mdsal.eos.binding.api.Entity(SERVICE_ENTITY_TYPE, nodeId); + } + + private void registerEntityOwnershipListener() { + this.eos.registerListener(SERVICE_ENTITY_TYPE, this); + } + + private boolean isOpenFlowEntity(String entity) { + return NODE_ID_PATTERN.matcher(entity).matches(); + } +} diff --git a/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/LLDPSpeaker.java b/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/LLDPSpeaker.java index e068492374..ddc7481714 100644 --- a/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/LLDPSpeaker.java +++ b/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/LLDPSpeaker.java @@ -8,12 +8,18 @@ package org.opendaylight.openflowplugin.applications.lldpspeaker; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipService; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; @@ -22,6 +28,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService; import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput; import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder; @@ -34,25 +41,43 @@ import org.slf4j.LoggerFactory; * Objects of this class send LLDP frames over all flow-capable ports that can * be discovered through inventory. */ -public class LLDPSpeaker implements AutoCloseable, NodeConnectorEventsObserver, Runnable { +public class LLDPSpeaker implements NodeConnectorEventsObserver, Runnable, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(LLDPSpeaker.class); - private static final long LLDP_FLOOD_PERIOD = 5; - private long currentFloodPeriod = LLDP_FLOOD_PERIOD; + private static final long LLDP_FLOOD_PERIOD = 5; + private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder() + .setNameFormat("lldp-speaker-%d").setDaemon(true).build(); private final PacketProcessingService packetProcessingService; private final ScheduledExecutorService scheduledExecutorService; - private final Map, TransmitPacketInput> nodeConnectorMap = new - ConcurrentHashMap<>(); - private ScheduledFuture scheduledSpeakerTask; + private final DeviceOwnershipStatusService deviceOwnershipStatusService; + private final Map, TransmitPacketInput> nodeConnectorMap = + new ConcurrentHashMap<>(); private final MacAddress addressDestionation; + private long currentFloodPeriod = LLDP_FLOOD_PERIOD; + private ScheduledFuture scheduledSpeakerTask; private volatile OperStatus operationalStatus = OperStatus.RUN; - public LLDPSpeaker(final PacketProcessingService packetProcessingService, final MacAddress addressDestionation) { - this(packetProcessingService, Executors.newSingleThreadScheduledExecutor(), addressDestionation); + public LLDPSpeaker(final PacketProcessingService packetProcessingService, final MacAddress addressDestionation, + final EntityOwnershipService entityOwnershipService) { + this(packetProcessingService, Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY), addressDestionation, + entityOwnershipService); + } + + public LLDPSpeaker(final PacketProcessingService packetProcessingService, + final ScheduledExecutorService scheduledExecutorService, + final MacAddress addressDestionation, + final EntityOwnershipService entityOwnershipService) { + this.addressDestionation = addressDestionation; + this.scheduledExecutorService = scheduledExecutorService; + this.deviceOwnershipStatusService = new DeviceOwnershipStatusService(entityOwnershipService); + scheduledSpeakerTask = this.scheduledExecutorService + .scheduleAtFixedRate(this, LLDP_FLOOD_PERIOD,LLDP_FLOOD_PERIOD, TimeUnit.SECONDS); + this.packetProcessingService = packetProcessingService; + LOG.info("LLDPSpeaker started, it will send LLDP frames each {} seconds", LLDP_FLOOD_PERIOD); } public void setOperationalStatus(final OperStatus operationalStatus) { - LOG.info("Setting operational status to {}", operationalStatus); + LOG.info("LLDP speaker operational status set to {}", operationalStatus); this.operationalStatus = operationalStatus; if (operationalStatus.equals(OperStatus.STANDBY)) { nodeConnectorMap.clear(); @@ -66,7 +91,8 @@ public class LLDPSpeaker implements AutoCloseable, NodeConnectorEventsObserver, public void setLldpFloodInterval(long time) { this.currentFloodPeriod = time; scheduledSpeakerTask.cancel(false); - scheduledSpeakerTask = this.scheduledExecutorService.scheduleAtFixedRate(this, time, time, TimeUnit.SECONDS); + scheduledSpeakerTask = this.scheduledExecutorService + .scheduleAtFixedRate(this, time, time, TimeUnit.SECONDS); LOG.info("LLDPSpeaker restarted, it will send LLDP frames each {} seconds", time); } @@ -74,24 +100,18 @@ public class LLDPSpeaker implements AutoCloseable, NodeConnectorEventsObserver, return currentFloodPeriod; } - public LLDPSpeaker(final PacketProcessingService packetProcessingService, - final ScheduledExecutorService scheduledExecutorService, final MacAddress addressDestionation) { - this.addressDestionation = addressDestionation; - this.scheduledExecutorService = scheduledExecutorService; - scheduledSpeakerTask = this.scheduledExecutorService.scheduleAtFixedRate(this, LLDP_FLOOD_PERIOD, - LLDP_FLOOD_PERIOD, TimeUnit.SECONDS); - this.packetProcessingService = packetProcessingService; - LOG.info("LLDPSpeaker started, it will send LLDP frames each {} seconds", LLDP_FLOOD_PERIOD); - } - /** * Closes this resource, relinquishing any underlying resources. */ @Override public void close() { nodeConnectorMap.clear(); - scheduledExecutorService.shutdown(); - scheduledSpeakerTask.cancel(true); + if (scheduledExecutorService != null) { + scheduledExecutorService.shutdown(); + } + if (scheduledSpeakerTask != null) { + scheduledSpeakerTask.cancel(true); + } LOG.trace("LLDPSpeaker stopped sending LLDP frames."); } @@ -101,15 +121,27 @@ public class LLDPSpeaker implements AutoCloseable, NodeConnectorEventsObserver, @Override public void run() { if (OperStatus.RUN.equals(operationalStatus)) { - LOG.debug("Sending LLDP frames to {} ports...", nodeConnectorMap.keySet().size()); - for (InstanceIdentifier nodeConnectorInstanceId : nodeConnectorMap.keySet()) { - NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(nodeConnectorInstanceId).getId(); - LOG.trace("Sending LLDP through port {}", nodeConnectorId.getValue()); - packetProcessingService.transmitPacket(nodeConnectorMap.get(nodeConnectorInstanceId)); - } + LOG.debug("Sending LLDP frames to nodes {}", Arrays.toString(deviceOwnershipStatusService + .getOwnedNodes().toArray())); + LOG.debug("Sending LLDP frames to total {} ports", getOwnedPorts()); + nodeConnectorMap.keySet().forEach(ncIID -> { + NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(ncIID).getId(); + NodeId nodeId = ncIID.firstKeyOf(Node.class, NodeKey.class).getId(); + if (deviceOwnershipStatusService.isEntityOwned(nodeId.getValue())) { + LOG.debug("Node is owned by this controller, sending LLDP packet through port {}", + nodeConnectorId.getValue()); + packetProcessingService.transmitPacket(nodeConnectorMap.get(ncIID)); + } else { + LOG.trace("Node {} is not owned by this controller, so skip sending LLDP packet on port {}", + nodeId.getValue(), nodeConnectorId.getValue()); + } + }); } } + /** + * {@inheritDoc} + */ @Override public void nodeConnectorAdded(final InstanceIdentifier nodeConnectorInstanceId, final FlowCapableNodeConnector flowConnector) { @@ -119,7 +151,7 @@ public class LLDPSpeaker implements AutoCloseable, NodeConnectorEventsObserver, // frames to // port, so first we check if we actually need to perform any action if (nodeConnectorMap.containsKey(nodeConnectorInstanceId)) { - LOG.trace("Port {} already in LLDPSpeaker.nodeConnectorMap, no need for additional processing", + LOG.debug("Port {} already in LLDPSpeaker.nodeConnectorMap, no need for additional processing", nodeConnectorId.getValue()); return; } @@ -132,7 +164,7 @@ public class LLDPSpeaker implements AutoCloseable, NodeConnectorEventsObserver, // No need to send LLDP frames on local ports if (outputPortNo == null) { - LOG.trace("Port {} is local, not sending LLDP frames through it", nodeConnectorId.getValue()); + LOG.debug("Port {} is local, not sending LLDP frames through it", nodeConnectorId.getValue()); return; } @@ -142,10 +174,9 @@ public class LLDPSpeaker implements AutoCloseable, NodeConnectorEventsObserver, .setNode(new NodeRef(nodeInstanceId)).setPayload(LLDPUtil.buildLldpFrame(nodeId, nodeConnectorId, srcMacAddress, outputPortNo, addressDestionation)).build(); - // Save packet to node connector id -> packet map to transmit it every 5 - // seconds + // Save packet to node connector id -> packet map to transmit it periodically on the configured interval. nodeConnectorMap.put(nodeConnectorInstanceId, packet); - LOG.trace("Port {} added to LLDPSpeaker.nodeConnectorMap", nodeConnectorId.getValue()); + LOG.debug("Port {} added to LLDPSpeaker.nodeConnectorMap", nodeConnectorId.getValue()); // Transmit packet for first time immediately packetProcessingService.transmitPacket(packet); @@ -153,9 +184,21 @@ public class LLDPSpeaker implements AutoCloseable, NodeConnectorEventsObserver, @Override public void nodeConnectorRemoved(final InstanceIdentifier nodeConnectorInstanceId) { + Preconditions.checkNotNull(nodeConnectorInstanceId); + nodeConnectorMap.remove(nodeConnectorInstanceId); NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(nodeConnectorInstanceId).getId(); - LOG.trace("Port {} removed from LLDPSpeaker.nodeConnectorMap", nodeConnectorId.getValue()); + LOG.trace("Port removed from node-connector map : {}", nodeConnectorId.getValue()); } + private int getOwnedPorts() { + AtomicInteger ownedPorts = new AtomicInteger(); + nodeConnectorMap.keySet().forEach(ncIID -> { + NodeId nodeId = ncIID.firstKeyOf(Node.class, NodeKey.class).getId(); + if (deviceOwnershipStatusService.isEntityOwned(nodeId.getValue())) { + ownedPorts.incrementAndGet(); + } + }); + return ownedPorts.get(); + } } diff --git a/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/LLDPUtil.java b/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/LLDPUtil.java index c992017973..0bb7cea7e9 100644 --- a/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/LLDPUtil.java +++ b/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/LLDPUtil.java @@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory; */ public final class LLDPUtil { private static final Logger LOG = LoggerFactory.getLogger(LLDPUtil.class); + private static final String OF_URI_PREFIX = "openflow:"; private LLDPUtil() { @@ -88,9 +89,8 @@ public final class LLDPUtil { customSecTlv.setType(LLDPTLV.TLVType.Custom.getValue()).setLength((short) customSecValue.length) .setValue(customSecValue); discoveryPkt.addCustomTLV(customSecTlv); - } catch (NoSuchAlgorithmException e1) { - LOG.info("LLDP extra authenticator creation failed: {}", e1.getMessage()); - LOG.debug("Reason why LLDP extra authenticator creation failed: ", e1); + } catch (NoSuchAlgorithmException e) { + LOG.warn("LLDP extra authenticator creation failed.", e); } @@ -107,8 +107,7 @@ public final class LLDPUtil { try { return ethPkt.serialize(); } catch (PacketException e) { - LOG.warn("Error creating LLDP packet: {}", e.getMessage()); - LOG.debug("Error creating LLDP packet.. ", e); + LOG.warn("Error creating LLDP packet.", e); } return null; } diff --git a/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/NodeConnectorInventoryEventTranslator.java b/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/NodeConnectorInventoryEventTranslator.java index 09d09ec2cf..3e2e67c9a7 100644 --- a/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/NodeConnectorInventoryEventTranslator.java +++ b/applications/lldp-speaker/src/main/java/org/opendaylight/openflowplugin/applications/lldpspeaker/NodeConnectorInventoryEventTranslator.java @@ -14,8 +14,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener; import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; @@ -40,8 +40,9 @@ import org.slf4j.LoggerFactory; * NodeConnectorInventoryEventTranslator is listening for changes in inventory operational DOM tree * and update LLDPSpeaker and topology. */ -public class NodeConnectorInventoryEventTranslator implements DataTreeChangeListener, - AutoCloseable { +public class NodeConnectorInventoryEventTranslator + implements ClusteredDataTreeChangeListener, AutoCloseable { + private static final Logger LOG = LoggerFactory.getLogger(NodeConnectorInventoryEventTranslator.class); private static final InstanceIdentifier II_TO_STATE = InstanceIdentifier.builder(Nodes.class) .child(Node.class).child(NodeConnector.class).augmentation(FlowCapableNodeConnector.class) @@ -53,7 +54,6 @@ public class NodeConnectorInventoryEventTranslator impleme private static final long STARTUP_LOOP_TICK = 500L; private static final int STARTUP_LOOP_MAX_RETRIES = 8; - private static final Logger LOG = LoggerFactory.getLogger(NodeConnectorInventoryEventTranslator.class); private final ListenerRegistration listenerOnPortRegistration; private final ListenerRegistration listenerOnPortStateRegistration; @@ -70,24 +70,12 @@ public class NodeConnectorInventoryEventTranslator impleme II_TO_STATE); final SimpleTaskRetryLooper looper = new SimpleTaskRetryLooper(STARTUP_LOOP_TICK, STARTUP_LOOP_MAX_RETRIES); try { - listenerOnPortRegistration = looper - .loopUntilNoException(new Callable>() { - @Override - public ListenerRegistration call() throws Exception { - return dataBroker.registerDataTreeChangeListener(dtiToNodeConnector, - NodeConnectorInventoryEventTranslator - .this); - } - }); - listenerOnPortStateRegistration = looper - .loopUntilNoException(new Callable>() { - @Override - public ListenerRegistration call() throws Exception { - return dataBroker.registerDataTreeChangeListener(dtiToNodeConnectorState, - NodeConnectorInventoryEventTranslator - .this); - } - }); + listenerOnPortRegistration = looper.loopUntilNoException(() -> + dataBroker.registerDataTreeChangeListener(dtiToNodeConnector, + NodeConnectorInventoryEventTranslator.this)); + listenerOnPortStateRegistration = looper.loopUntilNoException(() -> + dataBroker.registerDataTreeChangeListener(dtiToNodeConnectorState, + NodeConnectorInventoryEventTranslator.this)); } catch (Exception e) { LOG.error("DataTreeChangeListeners registration failed: {}", e); throw new IllegalStateException("NodeConnectorInventoryEventTranslator startup failed!", e); @@ -197,5 +185,4 @@ public class NodeConnectorInventoryEventTranslator impleme observer.nodeConnectorRemoved(nodeConnectorInstanceId); } } - } diff --git a/applications/lldp-speaker/src/main/resources/org/opendaylight/blueprint/lldp-speaker.xml b/applications/lldp-speaker/src/main/resources/org/opendaylight/blueprint/lldp-speaker.xml index d07cc2d1ee..4290e3dcd0 100644 --- a/applications/lldp-speaker/src/main/resources/org/opendaylight/blueprint/lldp-speaker.xml +++ b/applications/lldp-speaker/src/main/resources/org/opendaylight/blueprint/lldp-speaker.xml @@ -4,6 +4,7 @@ odl:use-default-for-reference-types="true"> + @@ -23,6 +24,7 @@ + org.opendaylight.controller sal-binding-api + + org.opendaylight.mdsal + mdsal-eos-dom-api + org.opendaylight.openflowplugin.libraries liblldp diff --git a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java index 0205c4761e..f8132f87d3 100644 --- a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java +++ b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java @@ -8,33 +8,56 @@ package org.opendaylight.openflowplugin.applications.topology.lldp; import org.opendaylight.controller.sal.binding.api.NotificationProviderService; +import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipService; import org.opendaylight.openflowplugin.applications.topology.lldp.utils.LLDPDiscoveryUtils; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.topology.discovery.rev130819.LinkDiscovered; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.topology.discovery.rev130819.LinkDiscoveredBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener; import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LLDPDiscoveryListener implements PacketProcessingListener { + private static final Logger LOG = LoggerFactory.getLogger(LLDPDiscoveryListener.class); + private final LLDPLinkAger lldpLinkAger; private final NotificationProviderService notificationService; + private final EntityOwnershipService eos; + - public LLDPDiscoveryListener(NotificationProviderService notificationService, LLDPLinkAger lldpLinkAger) { + public LLDPDiscoveryListener(final NotificationProviderService notificationService, final LLDPLinkAger lldpLinkAger, + final EntityOwnershipService entityOwnershipService) { this.notificationService = notificationService; this.lldpLinkAger = lldpLinkAger; + this.eos = entityOwnershipService; } @Override public void onPacketReceived(PacketReceived lldp) { NodeConnectorRef src = LLDPDiscoveryUtils.lldpToNodeConnectorRef(lldp.getPayload(), true); if (src != null) { - LinkDiscoveredBuilder ldb = new LinkDiscoveredBuilder(); - ldb.setDestination(lldp.getIngress()); - ldb.setSource(new NodeConnectorRef(src)); - LinkDiscovered ld = ldb.build(); + NodeKey nodeKey = src.getValue().firstKeyOf(Node.class); + LOG.debug("LLDP packet received for node {}", nodeKey); + if (nodeKey != null) { + LinkDiscoveredBuilder ldb = new LinkDiscoveredBuilder(); + ldb.setDestination(lldp.getIngress()); + ldb.setSource(new NodeConnectorRef(src)); + LinkDiscovered ld = ldb.build(); - notificationService.publish(ld); - lldpLinkAger.put(ld); + lldpLinkAger.put(ld); + if (LLDPDiscoveryUtils.isEntityOwned(this.eos, nodeKey.getId().getValue())) { + LOG.debug("Publish add event for link {}", ld); + notificationService.publish(ld); + } else { + LOG.trace("Skip publishing the add event for link because controller is non-owner of the " + + "node {}. Link : {}", nodeKey.getId().getValue(), ld); + } + } else { + LOG.debug("LLDP packet ignored. Unable to extract node-key from source node-connector reference."); + } } } -} +} \ No newline at end of file diff --git a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPLinkAger.java b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPLinkAger.java index 75f0f26825..5c3cb23060 100644 --- a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPLinkAger.java +++ b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPLinkAger.java @@ -17,6 +17,8 @@ import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; import org.opendaylight.controller.sal.binding.api.NotificationProviderService; +import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipService; +import org.opendaylight.openflowplugin.applications.topology.lldp.utils.LLDPDiscoveryUtils; import org.opendaylight.openflowplugin.api.openflow.configuration.ConfigurationListener; import org.opendaylight.openflowplugin.api.openflow.configuration.ConfigurationService; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.topology.discovery.rev130819.LinkDiscovered; @@ -24,6 +26,8 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.topology.discovery.rev import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.lldp.discovery.config.rev160511.TopologyLldpDiscoveryConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; public class LLDPLinkAger implements ConfigurationListener, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(LLDPLinkAger.class); @@ -32,16 +36,18 @@ public class LLDPLinkAger implements ConfigurationListener, AutoCloseable { private final Timer timer; private final NotificationProviderService notificationService; private final AutoCloseable configurationServiceRegistration; + private final EntityOwnershipService eos; /** * default ctor - start timer. */ public LLDPLinkAger(final TopologyLldpDiscoveryConfig topologyLldpDiscoveryConfig, - final NotificationProviderService notificationService, - final ConfigurationService configurationService) { + final NotificationProviderService notificationService, + final ConfigurationService configurationService, final EntityOwnershipService entityOwnershipService) { this.linkExpirationTime = topologyLldpDiscoveryConfig.getTopologyLldpExpirationInterval().getValue(); this.notificationService = notificationService; this.configurationServiceRegistration = configurationService.registerListener(this); + this.eos = entityOwnershipService; linkToDate = new ConcurrentHashMap<>(); timer = new Timer(); timer.schedule(new LLDPAgingTask(), 0, topologyLldpDiscoveryConfig.getTopologyLldpInterval().getValue()); @@ -64,21 +70,29 @@ public class LLDPLinkAger implements ConfigurationListener, AutoCloseable { @Override public void run() { - for (Entry entry : linkToDate.entrySet()) { + for (Entry entry : linkToDate.entrySet()) { LinkDiscovered link = entry.getKey(); Date expires = entry.getValue(); Date now = new Date(); if (now.after(expires)) { if (notificationService != null) { LinkRemovedBuilder lrb = new LinkRemovedBuilder(link); - notificationService.publish(lrb.build()); + + NodeKey nodeKey = link.getDestination().getValue().firstKeyOf(Node.class); + LOG.info("No update received for link {} from last {} milliseconds. Removing link from cache.", + link, linkExpirationTime); linkToDate.remove(link); + if (nodeKey != null && LLDPDiscoveryUtils.isEntityOwned(eos, nodeKey.getId().getValue())) { + LOG.info("Publish Link Remove event for the link {}", link); + notificationService.publish(lrb.build()); + } else { + LOG.trace("Skip publishing Link Remove event for the link {} because link destination " + + "node is not owned by the controller", link); + } } } } - } - } @VisibleForTesting diff --git a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java index b90698a25c..5d39586995 100644 --- a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java +++ b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java @@ -7,6 +7,8 @@ */ package org.opendaylight.openflowplugin.applications.topology.lldp.utils; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; @@ -18,6 +20,9 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Objects; import org.apache.commons.lang3.ArrayUtils; +import org.opendaylight.mdsal.eos.binding.api.Entity; +import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipService; +import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState; import org.opendaylight.openflowplugin.applications.topology.lldp.LLDPActivator; import org.opendaylight.openflowplugin.libraries.liblldp.BitBufferHelper; import org.opendaylight.openflowplugin.libraries.liblldp.CustomTLVKey; @@ -46,6 +51,7 @@ public final class LLDPDiscoveryUtils { public static final short ETHERNET_TYPE_LLDP = (short) 0x88cc; private static final short ETHERNET_TYPE_OFFSET = 12; private static final short ETHERNET_VLAN_OFFSET = ETHERNET_TYPE_OFFSET + 4; + private static final String SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.ServiceEntityType"; private LLDPDiscoveryUtils() { } @@ -189,4 +195,33 @@ public final class LLDPDiscoveryUtils { return ethernetType == ETHERNET_TYPE_LLDP; } + + public static boolean isEntityOwned(final EntityOwnershipService eos, final String nodeId) { + Preconditions.checkNotNull(eos, "Entity ownership service must not be null"); + + EntityOwnershipState state = null; + java.util.Optional status = getCurrentOwnershipStatus(eos, nodeId); + if (status.isPresent()) { + state = status.get(); + } else { + LOG.error("Fetching ownership status failed for node {}", nodeId); + } + return state != null && state.equals(EntityOwnershipState.IS_OWNER); + } + + private static java.util.Optional getCurrentOwnershipStatus(final EntityOwnershipService eos, + final String nodeId) { + Entity entity = createNodeEntity(nodeId); + Optional ownershipStatus = eos.getOwnershipState(entity); + + if (ownershipStatus.isPresent()) { + LOG.debug("Fetched ownership status for node {} is {}", nodeId, ownershipStatus.get()); + return java.util.Optional.of(ownershipStatus.get()); + } + return java.util.Optional.empty(); + } + + private static Entity createNodeEntity(final String nodeId) { + return new Entity(SERVICE_ENTITY_TYPE, nodeId); + } } diff --git a/applications/topology-lldp-discovery/src/main/resources/org/opendaylight/blueprint/topology-lldp-discovery.xml b/applications/topology-lldp-discovery/src/main/resources/org/opendaylight/blueprint/topology-lldp-discovery.xml index 37749b9490..2d7ca120fb 100644 --- a/applications/topology-lldp-discovery/src/main/resources/org/opendaylight/blueprint/topology-lldp-discovery.xml +++ b/applications/topology-lldp-discovery/src/main/resources/org/opendaylight/blueprint/topology-lldp-discovery.xml @@ -6,6 +6,7 @@ + @@ -21,11 +22,13 @@ + +