2 * Copyright (c) 2014 Pacnet and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.openflowplugin.applications.lldpspeaker;
11 import static org.opendaylight.infrautils.utils.concurrent.LoggingFutures.addErrorLogging;
13 import com.google.common.base.Preconditions;
14 import com.google.common.util.concurrent.ThreadFactoryBuilder;
16 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.concurrent.Executors;
18 import java.util.concurrent.Future;
19 import java.util.concurrent.ScheduledExecutorService;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.ThreadFactory;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.atomic.AtomicInteger;
24 import org.opendaylight.infrautils.utils.concurrent.JdkFutures;
25 import org.opendaylight.openflowplugin.applications.deviceownershipservice.DeviceOwnershipService;
26 import org.opendaylight.openflowplugin.libraries.liblldp.PacketException;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketOutput;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.openflow.applications.lldp.speaker.config.rev160512.LldpSpeakerConfig;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.openflow.applications.lldp.speaker.rev141023.OperStatus;
41 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
42 import org.opendaylight.yangtools.yang.common.RpcResult;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * Objects of this class send LLDP frames over all flow-capable ports that can
48 * be discovered through inventory.
50 public class LLDPSpeaker implements NodeConnectorEventsObserver, Runnable, AutoCloseable {
51 private static final Logger LOG = LoggerFactory.getLogger(LLDPSpeaker.class);
53 private static final long LLDP_FLOOD_PERIOD = 5;
54 private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder()
55 .setNameFormat("lldp-speaker-%d").setDaemon(true).build();
56 private final PacketProcessingService packetProcessingService;
57 private final ScheduledExecutorService scheduledExecutorService;
58 private final DeviceOwnershipService deviceOwnershipService;
59 private final Map<InstanceIdentifier<NodeConnector>, TransmitPacketInput> nodeConnectorMap =
60 new ConcurrentHashMap<>();
61 private final MacAddress addressDestination;
62 private long currentFloodPeriod = LLDP_FLOOD_PERIOD;
63 private ScheduledFuture<?> scheduledSpeakerTask;
64 private volatile OperStatus operationalStatus = OperStatus.RUN;
66 public LLDPSpeaker(final PacketProcessingService packetProcessingService, final LldpSpeakerConfig lldpSpeakerConfig,
67 final DeviceOwnershipService deviceOwnershipService) {
68 this(packetProcessingService, Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY), lldpSpeakerConfig,
69 deviceOwnershipService);
72 public LLDPSpeaker(final PacketProcessingService packetProcessingService,
73 final ScheduledExecutorService scheduledExecutorService,
74 final LldpSpeakerConfig lldpSpeakerConfig,
75 final DeviceOwnershipService deviceOwnershipStatusService) {
76 this.addressDestination = lldpSpeakerConfig.getAddressDestination();
77 this.scheduledExecutorService = scheduledExecutorService;
78 this.deviceOwnershipService = deviceOwnershipStatusService;
79 scheduledSpeakerTask = this.scheduledExecutorService
80 .scheduleAtFixedRate(this, LLDP_FLOOD_PERIOD,LLDP_FLOOD_PERIOD, TimeUnit.SECONDS);
81 this.packetProcessingService = packetProcessingService;
82 LOG.info("LLDPSpeaker started, it will send LLDP frames each {} seconds", LLDP_FLOOD_PERIOD);
85 public void setOperationalStatus(final OperStatus operationalStatus) {
86 LOG.info("LLDP speaker operational status set to {}", operationalStatus);
87 this.operationalStatus = operationalStatus;
88 if (operationalStatus.equals(OperStatus.STANDBY)) {
89 nodeConnectorMap.clear();
93 public OperStatus getOperationalStatus() {
94 return operationalStatus;
97 public void setLldpFloodInterval(long time) {
98 this.currentFloodPeriod = time;
99 scheduledSpeakerTask.cancel(false);
100 scheduledSpeakerTask = this.scheduledExecutorService
101 .scheduleAtFixedRate(this, time, time, TimeUnit.SECONDS);
102 LOG.info("LLDPSpeaker restarted, it will send LLDP frames each {} seconds", time);
105 public long getLldpFloodInterval() {
106 return currentFloodPeriod;
110 * Closes this resource, relinquishing any underlying resources.
113 public void close() {
114 nodeConnectorMap.clear();
115 if (scheduledExecutorService != null) {
116 scheduledExecutorService.shutdown();
118 if (scheduledSpeakerTask != null) {
119 scheduledSpeakerTask.cancel(true);
121 LOG.trace("LLDPSpeaker stopped sending LLDP frames.");
125 * Send LLDPDU frames to all known openflow switch ports.
129 if (OperStatus.RUN.equals(operationalStatus)) {
130 LOG.debug("Sending LLDP frames to total {} ports", getOwnedPorts());
131 nodeConnectorMap.keySet().forEach(ncIID -> {
132 NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(ncIID).getId();
133 NodeId nodeId = ncIID.firstKeyOf(Node.class).getId();
134 if (deviceOwnershipService.isEntityOwned(nodeId.getValue())) {
135 LOG.debug("Node is owned by this controller, sending LLDP packet through port {}",
136 nodeConnectorId.getValue());
137 addErrorLogging(packetProcessingService.transmitPacket(nodeConnectorMap.get(ncIID)), LOG,
138 "transmitPacket() failed");
140 LOG.debug("Node {} is not owned by this controller, so skip sending LLDP packet on port {}",
141 nodeId.getValue(), nodeConnectorId.getValue());
148 public void nodeConnectorAdded(final InstanceIdentifier<NodeConnector> nodeConnectorInstanceId,
149 final FlowCapableNodeConnector flowConnector) {
150 NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(nodeConnectorInstanceId).getId();
152 // nodeConnectorAdded can be called even if we already sending LLDP
154 // port, so first we check if we actually need to perform any action
155 if (nodeConnectorMap.containsKey(nodeConnectorInstanceId)) {
156 LOG.debug("Port {} already in LLDPSpeaker.nodeConnectorMap, no need for additional processing",
157 nodeConnectorId.getValue());
160 // Prepare to build LLDP payload
161 InstanceIdentifier<Node> nodeInstanceId = nodeConnectorInstanceId.firstIdentifierOf(Node.class);
162 NodeId nodeId = InstanceIdentifier.keyOf(nodeInstanceId).getId();
163 if (!deviceOwnershipService.isEntityOwned(nodeId.getValue())) {
164 LOG.debug("Node {} is not owned by this controller, so skip sending LLDP packet on port {}",
165 nodeId.getValue(), nodeConnectorId.getValue());
168 MacAddress srcMacAddress = flowConnector.getHardwareAddress();
169 Long outputPortNo = flowConnector.getPortNumber().getUint32();
171 // No need to send LLDP frames on local ports
172 if (outputPortNo == null) {
173 LOG.debug("Port {} is local, not sending LLDP frames through it", nodeConnectorId.getValue());
177 // Generate packet with destination switch and port
178 TransmitPacketInput packet;
180 packet = new TransmitPacketInputBuilder()
181 .setEgress(new NodeConnectorRef(nodeConnectorInstanceId))
182 .setNode(new NodeRef(nodeInstanceId)).setPayload(LLDPUtil.buildLldpFrame(nodeId,
183 nodeConnectorId, srcMacAddress, outputPortNo, addressDestination)).build();
184 } catch (PacketException e) {
185 LOG.error("Error building LLDP frame", e);
189 // Save packet to node connector id -> packet map to transmit it periodically on the configured interval.
190 nodeConnectorMap.put(nodeConnectorInstanceId, packet);
191 LOG.debug("Port {} added to LLDPSpeaker.nodeConnectorMap", nodeConnectorId.getValue());
193 // Transmit packet for first time immediately
194 final Future<RpcResult<TransmitPacketOutput>> resultFuture = packetProcessingService.transmitPacket(packet);
195 JdkFutures.addErrorLogging(resultFuture, LOG, "transmitPacket");
199 public void nodeConnectorRemoved(final InstanceIdentifier<NodeConnector> nodeConnectorInstanceId) {
200 Preconditions.checkNotNull(nodeConnectorInstanceId);
202 nodeConnectorMap.remove(nodeConnectorInstanceId);
203 NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(nodeConnectorInstanceId).getId();
204 LOG.trace("Port removed from node-connector map : {}", nodeConnectorId.getValue());
207 private int getOwnedPorts() {
208 AtomicInteger ownedPorts = new AtomicInteger();
209 nodeConnectorMap.keySet().forEach(ncIID -> {
210 NodeId nodeId = ncIID.firstKeyOf(Node.class).getId();
211 if (deviceOwnershipService.isEntityOwned(nodeId.getValue())) {
212 ownedPorts.incrementAndGet();
215 return ownedPorts.get();