<groupId>org.opendaylight.controller.model</groupId>
<artifactId>model-inventory</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller.model</groupId>
+ <artifactId>model-topology</artifactId>
+ </dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<artifactId>jsr305</artifactId>
<optional>true</optional>
</dependency>
-
+ <dependency>
+ <groupId>org.opendaylight.openflowplugin</groupId>
+ <artifactId>openflowplugin-common</artifactId>
+ </dependency>
</dependencies>
<build>
}
@Override
- public void onPacketReceived(PacketReceived lldp) {
+ public void onPacketReceived(final PacketReceived lldp) {
NodeConnectorRef src = LLDPDiscoveryUtils.lldpToNodeConnectorRef(lldp.getPayload(), true);
if (src != null) {
final NodeKey nodeKey = lldp.getIngress().getValue().firstKeyOf(Node.class);
LOG.debug("LLDP packet received for destination node {}", nodeKey);
if (nodeKey != null) {
- LinkDiscoveredBuilder ldb = new LinkDiscoveredBuilder();
+ final LinkDiscoveredBuilder ldb = new LinkDiscoveredBuilder();
ldb.setDestination(lldp.getIngress());
ldb.setSource(new NodeConnectorRef(src));
- LinkDiscovered ld = ldb.build();
-
+ final LinkDiscovered ld = ldb.build();
+ final boolean linkWasPresent = lldpLinkAger.isLinkPresent(ld);
lldpLinkAger.put(ld);
if (LLDPDiscoveryUtils.isEntityOwned(this.eos, nodeKey.getId().getValue())) {
- LOG.debug("Publish add event for link {}", ld);
- try {
- notificationService.putNotification(ld);
- } catch (InterruptedException e) {
- LOG.warn("Interrupted while publishing notification {}", ld, e);
+ if (linkWasPresent) {
+ LOG.trace("Link {} already present in the cache, skip publishing the notification.", ld);
+ } else {
+ LOG.debug("Publish add event for link {}", ld);
+ try {
+ notificationService.putNotification(ld);
+ } catch (InterruptedException e) {
+ LOG.warn("Interrupted while publishing notification {}", ld, e);
+ }
}
} else {
LOG.trace("Skip publishing the add event for link because controller is non-owner of the "
package org.opendaylight.openflowplugin.applications.topology.lldp;
import com.google.common.annotations.VisibleForTesting;
+
+import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.aries.blueprint.annotation.service.Reference;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataObjectModification;
+import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
import org.opendaylight.mdsal.binding.api.NotificationPublishService;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipService;
import org.opendaylight.openflowplugin.api.openflow.configuration.ConfigurationListener;
import org.opendaylight.openflowplugin.api.openflow.configuration.ConfigurationService;
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.params.xml.ns.yang.topology.lldp.discovery.config.rev160511.TopologyLldpDiscoveryConfig;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
-public class LLDPLinkAger implements ConfigurationListener, AutoCloseable {
+public class LLDPLinkAger implements ConfigurationListener, ClusteredDataTreeChangeListener<Link>, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(LLDPLinkAger.class);
+ static final String TOPOLOGY_ID = "flow:1";
+ static final InstanceIdentifier<Link> II_TO_LINK = InstanceIdentifier.create(NetworkTopology.class)
+ .child(Topology.class, new TopologyKey(new TopologyId(TOPOLOGY_ID))).child(Link.class);
+
private final long linkExpirationTime;
private final Map<LinkDiscovered, Date> linkToDate;
private final Timer timer;
private final NotificationPublishService notificationService;
private final AutoCloseable configurationServiceRegistration;
private final EntityOwnershipService eos;
+ private ListenerRegistration<DataTreeChangeListener> listenerRegistration;
/**
* default ctor - start timer.
*/
@Inject
+ @SuppressWarnings("checkstyle:IllegalCatch")
public LLDPLinkAger(final TopologyLldpDiscoveryConfig topologyLldpDiscoveryConfig,
- @Reference final NotificationPublishService notificationService,
- @Reference final ConfigurationService configurationService,
- @Reference final EntityOwnershipService entityOwnershipService) {
+ @Reference final NotificationPublishService notificationService,
+ @Reference final ConfigurationService configurationService,
+ @Reference final EntityOwnershipService entityOwnershipService,
+ @Reference final DataBroker dataBroker) {
this.linkExpirationTime = topologyLldpDiscoveryConfig.getTopologyLldpExpirationInterval().getValue();
this.notificationService = notificationService;
this.configurationServiceRegistration = configurationService.registerListener(this);
this.eos = entityOwnershipService;
linkToDate = new ConcurrentHashMap<>();
timer = new Timer();
+ final DataTreeIdentifier dtiToNodeConnector = DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL,
+ II_TO_LINK);
+ try {
+ listenerRegistration = dataBroker.registerDataTreeChangeListener(dtiToNodeConnector, LLDPLinkAger.this);
+ } catch (Exception e) {
+ LOG.error("DataTreeChangeListeners registration failed:", e);
+ throw new IllegalStateException("LLDPLinkAger startup failed!", e);
+ }
timer.schedule(new LLDPAgingTask(), 0, topologyLldpDiscoveryConfig.getTopologyLldpInterval().getValue());
}
@Override
@PreDestroy
public void close() throws Exception {
+ if (listenerRegistration != null) {
+ listenerRegistration.close();
+ listenerRegistration = null;
+ }
timer.cancel();
linkToDate.clear();
configurationServiceRegistration.close();
}
- private class LLDPAgingTask extends TimerTask {
+ @Override
+ public void onDataTreeChanged(@NonNull Collection<DataTreeModification<Link>> changes) {
+ for (DataTreeModification modification : changes) {
+ switch (modification.getRootNode().getModificationType()) {
+ case WRITE:
+ break;
+ case SUBTREE_MODIFIED:
+ // NOOP
+ break;
+ case DELETE:
+ processLinkDeleted(modification.getRootNode());
+ break;
+ default:
+ LOG.error("Unhandled modification type: {}", modification.getRootNode().getModificationType());
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public boolean isLinkToDateEmpty() {
+ return linkToDate.isEmpty();
+ }
+ @Override
+ public void onPropertyChanged(@Nonnull final String propertyName, @Nonnull final String propertyValue) {
+ final TopologyLLDPDiscoveryProperty lldpDiscoveryProperty = TopologyLLDPDiscoveryProperty.forValue(
+ propertyName);
+ if (lldpDiscoveryProperty != null) {
+ switch (lldpDiscoveryProperty) {
+ case LLDP_SECURE_KEY:
+ case TOPOLOGY_LLDP_INTERVAL:
+ case TOPOLOGY_LLDP_EXPIRATION_INTERVAL:
+ LOG.warn("Runtime update not supported for property {}", lldpDiscoveryProperty);
+ break;
+ default:
+ LOG.warn("No topology lldp discovery property found.");
+ break;
+ }
+ }
+ }
+
+ protected boolean isLinkPresent(final LinkDiscovered linkDiscovered) {
+ return linkToDate.containsKey(linkDiscovered);
+ }
+
+ private void processLinkDeleted(DataObjectModification rootNode) {
+ Link link = (Link) rootNode.getDataBefore();
+ LOG.trace("Removing link {} from linkToDate cache", link);
+ LinkDiscovered linkDiscovered = LLDPDiscoveryUtils.toLLDPLinkDiscovered(link);
+ linkToDate.remove(linkDiscovered);
+ }
+
+ private class LLDPAgingTask extends TimerTask {
@Override
public void run() {
for (Entry<LinkDiscovered, Date> entry : linkToDate.entrySet()) {
if (now.after(expires)) {
if (notificationService != null) {
LinkRemovedBuilder lrb = new LinkRemovedBuilder(link);
-
NodeKey nodeKey = link.getDestination().getValue().firstKeyOf(Node.class);
LOG.info("No update received for link {} from last {} milliseconds. Removing link from cache.",
link, linkExpirationTime);
}
}
}
-
- @VisibleForTesting
- public boolean isLinkToDateEmpty() {
- return linkToDate.isEmpty();
- }
-
- @Override
- public void onPropertyChanged(@Nonnull final String propertyName, @Nonnull final String propertyValue) {
- final TopologyLLDPDiscoveryProperty lldpDiscoveryProperty = TopologyLLDPDiscoveryProperty.forValue(
- propertyName);
- if (lldpDiscoveryProperty != null) {
- switch (lldpDiscoveryProperty) {
- case LLDP_SECURE_KEY:
- case TOPOLOGY_LLDP_INTERVAL:
- case TOPOLOGY_LLDP_EXPIRATION_INTERVAL:
- LOG.warn("Runtime update not supported for property {}", lldpDiscoveryProperty);
- break;
- default:
- LOG.warn("No topology lldp discovery property found.");
- break;
- }
- }
- }
}
import org.opendaylight.openflowplugin.libraries.liblldp.LLDPTLV;
import org.opendaylight.openflowplugin.libraries.liblldp.NetUtils;
import org.opendaylight.openflowplugin.libraries.liblldp.PacketException;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.topology.discovery.rev130819.LinkDiscoveredBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
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.tbd.params.xml.ns.yang.network.topology.rev131021.TpId;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
return hashedValue.asBytes();
}
- private static boolean checkExtraAuthenticator(LLDP lldp, NodeConnectorId srcNodeConnectorId) {
- final LLDPTLV hashLldptlv = lldp.getCustomTLV(LLDPTLV.createSecSubTypeCustomTLVKey());
- boolean secAuthenticatorOk = false;
- if (hashLldptlv != null) {
- byte[] rawTlvValue = hashLldptlv.getValue();
- byte[] lldpCustomSecurityHash = ArrayUtils.subarray(rawTlvValue, 4, rawTlvValue.length);
- byte[] calculatedHash = getValueForLLDPPacketIntegrityEnsuring(srcNodeConnectorId);
- secAuthenticatorOk = Arrays.equals(calculatedHash, lldpCustomSecurityHash);
+ public static boolean isEntityOwned(final EntityOwnershipService eos, final String nodeId) {
+ Preconditions.checkNotNull(eos, "Entity ownership service must not be null");
+
+ EntityOwnershipState state = null;
+ Optional<EntityOwnershipState> status = getCurrentOwnershipStatus(eos, nodeId);
+ if (status.isPresent()) {
+ state = status.get();
} else {
- LOG.debug("Custom security hint wasn't specified via Custom TLV in LLDP packet.");
+ LOG.error("Fetching ownership status failed for node {}", nodeId);
}
+ return state != null && state.equals(EntityOwnershipState.IS_OWNER);
+ }
- return secAuthenticatorOk;
+ public static org.opendaylight.yang.gen.v1.urn.opendaylight.flow.topology.discovery.rev130819
+ .LinkDiscovered toLLDPLinkDiscovered(Link link) {
+ return new LinkDiscoveredBuilder()
+ .setSource(getNodeConnectorRefFromLink(link.getSource().getSourceTp(),
+ link.getSource().getSourceNode()))
+ .setDestination(getNodeConnectorRefFromLink(link.getDestination().getDestTp(),
+ link.getDestination().getDestNode()))
+ .build();
}
private static boolean isLLDP(final byte[] packet) {
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;
- Optional<EntityOwnershipState> status = getCurrentOwnershipStatus(eos, nodeId);
- if (status.isPresent()) {
- state = status.get();
+ private static boolean checkExtraAuthenticator(LLDP lldp, NodeConnectorId srcNodeConnectorId) {
+ final LLDPTLV hashLldptlv = lldp.getCustomTLV(LLDPTLV.createSecSubTypeCustomTLVKey());
+ boolean secAuthenticatorOk = false;
+ if (hashLldptlv != null) {
+ byte[] rawTlvValue = hashLldptlv.getValue();
+ byte[] lldpCustomSecurityHash = ArrayUtils.subarray(rawTlvValue, 4, rawTlvValue.length);
+ byte[] calculatedHash = getValueForLLDPPacketIntegrityEnsuring(srcNodeConnectorId);
+ secAuthenticatorOk = Arrays.equals(calculatedHash, lldpCustomSecurityHash);
} else {
- LOG.error("Fetching ownership status failed for node {}", nodeId);
+ LOG.debug("Custom security hint wasn't specified via Custom TLV in LLDP packet.");
}
- return state != null && state.equals(EntityOwnershipState.IS_OWNER);
+
+ return secAuthenticatorOk;
}
private static Optional<EntityOwnershipState> getCurrentOwnershipStatus(final EntityOwnershipService eos,
private static Entity createNodeEntity(final String nodeId) {
return new Entity(SERVICE_ENTITY_TYPE, nodeId);
}
+
+ private static NodeConnectorRef getNodeConnectorRefFromLink(final TpId tpId, final org.opendaylight.yang.gen.v1.urn
+ .tbd.params.xml.ns.yang.network.topology.rev131021.NodeId nodeId) {
+ String nodeConnectorId = tpId.getValue();
+ InstanceIdentifier<NodeConnector> nciid
+ = InstanceIdentifier.builder(Nodes.class)
+ .child(
+ Node.class,
+ new NodeKey(new org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819
+ .NodeId(nodeId)))
+ .child(
+ NodeConnector.class,
+ new NodeConnectorKey(new NodeConnectorId(nodeConnectorId)))
+ .build();
+ return new NodeConnectorRef(nciid);
+ }
}
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
+import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.binding.api.NotificationPublishService;
import org.opendaylight.mdsal.eos.binding.api.Entity;
import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipService;
@Mock
private EntityOwnershipService eos;
@Mock
+ private DataBroker dataBroker;
+ @Mock
private LinkRemoved linkRemoved;
+
@Before
public void setUp() {
- lldpLinkAger = new LLDPLinkAger(getConfig(), notificationService, getConfigurationService(), eos);
+ lldpLinkAger = new LLDPLinkAger(getConfig(), notificationService, getConfigurationService(), eos, dataBroker);
Mockito.when(link.getDestination()).thenReturn(new NodeConnectorRef(
InstanceIdentifier.create(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")))));
Mockito.when(eos.getOwnershipState(any(Entity.class))).thenReturn(