84358e0bb049df9ecac197684706f7be44e04f6b
[openflowplugin.git] / applications / lldp-speaker / src / main / java / org / opendaylight / openflowplugin / applications / lldpspeaker / LLDPSpeaker.java
1 /*
2  * Copyright (c) 2014 Pacnet and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.openflowplugin.applications.lldpspeaker;
10
11 import static org.opendaylight.infrautils.utils.concurrent.LoggingFutures.addErrorLogging;
12
13 import com.google.common.base.Preconditions;
14 import com.google.common.util.concurrent.ThreadFactoryBuilder;
15 import java.security.NoSuchAlgorithmException;
16 import java.util.Map;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.concurrent.Executors;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.ScheduledExecutorService;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.ThreadFactory;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.atomic.AtomicInteger;
25 import org.opendaylight.infrautils.utils.concurrent.JdkFutures;
26 import org.opendaylight.openflowplugin.applications.deviceownershipservice.DeviceOwnershipService;
27 import org.opendaylight.openflowplugin.libraries.liblldp.PacketException;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketOutput;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.openflow.applications.lldp.speaker.config.rev160512.LldpSpeakerConfig;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.openflow.applications.lldp.speaker.rev141023.OperStatus;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.opendaylight.yangtools.yang.common.RpcResult;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * Objects of this class send LLDP frames over all flow-capable ports that can
49  * be discovered through inventory.
50  */
51 public class LLDPSpeaker implements NodeConnectorEventsObserver, Runnable, AutoCloseable {
52     private static final Logger LOG = LoggerFactory.getLogger(LLDPSpeaker.class);
53
54     private static final long LLDP_FLOOD_PERIOD = 5;
55     private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder()
56             .setNameFormat("lldp-speaker-%d").setDaemon(true).build();
57     private final PacketProcessingService packetProcessingService;
58     private final ScheduledExecutorService scheduledExecutorService;
59     private final DeviceOwnershipService deviceOwnershipService;
60     private final Map<InstanceIdentifier<NodeConnector>, TransmitPacketInput> nodeConnectorMap =
61             new ConcurrentHashMap<>();
62     private final MacAddress addressDestination;
63     private long currentFloodPeriod = LLDP_FLOOD_PERIOD;
64     private ScheduledFuture<?> scheduledSpeakerTask;
65     private volatile OperStatus operationalStatus = OperStatus.RUN;
66
67     public LLDPSpeaker(final PacketProcessingService packetProcessingService, final LldpSpeakerConfig lldpSpeakerConfig,
68                        final DeviceOwnershipService deviceOwnershipService) {
69         this(packetProcessingService, Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY), lldpSpeakerConfig,
70                 deviceOwnershipService);
71     }
72
73     public LLDPSpeaker(final PacketProcessingService packetProcessingService,
74                        final ScheduledExecutorService scheduledExecutorService,
75                        final LldpSpeakerConfig lldpSpeakerConfig,
76                        final DeviceOwnershipService deviceOwnershipStatusService) {
77         this.addressDestination = lldpSpeakerConfig.getAddressDestination();
78         this.scheduledExecutorService = scheduledExecutorService;
79         this.deviceOwnershipService = deviceOwnershipStatusService;
80         scheduledSpeakerTask = this.scheduledExecutorService
81                 .scheduleAtFixedRate(this, LLDP_FLOOD_PERIOD,LLDP_FLOOD_PERIOD, TimeUnit.SECONDS);
82         this.packetProcessingService = packetProcessingService;
83         LOG.info("LLDPSpeaker started, it will send LLDP frames each {} seconds", LLDP_FLOOD_PERIOD);
84     }
85
86     public void setOperationalStatus(final OperStatus operationalStatus) {
87         LOG.info("LLDP speaker operational status set to {}", operationalStatus);
88         this.operationalStatus = operationalStatus;
89         if (operationalStatus.equals(OperStatus.STANDBY)) {
90             nodeConnectorMap.clear();
91         }
92     }
93
94     public OperStatus getOperationalStatus() {
95         return operationalStatus;
96     }
97
98     public void setLldpFloodInterval(long time) {
99         this.currentFloodPeriod = time;
100         scheduledSpeakerTask.cancel(false);
101         scheduledSpeakerTask = this.scheduledExecutorService
102                      .scheduleAtFixedRate(this, time, time, TimeUnit.SECONDS);
103         LOG.info("LLDPSpeaker restarted, it will send LLDP frames each {} seconds", time);
104     }
105
106     public long getLldpFloodInterval() {
107         return currentFloodPeriod;
108     }
109
110     /**
111      * Closes this resource, relinquishing any underlying resources.
112      */
113     @Override
114     public void close() {
115         nodeConnectorMap.clear();
116         if (scheduledExecutorService != null) {
117             scheduledExecutorService.shutdown();
118         }
119         if (scheduledSpeakerTask != null) {
120             scheduledSpeakerTask.cancel(true);
121         }
122         LOG.trace("LLDPSpeaker stopped sending LLDP frames.");
123     }
124
125     /**
126      * Send LLDPDU frames to all known openflow switch ports.
127      */
128     @Override
129     public void run() {
130         if (OperStatus.RUN.equals(operationalStatus)) {
131             LOG.debug("Sending LLDP frames to total {} ports", getOwnedPorts());
132             nodeConnectorMap.keySet().forEach(ncIID -> {
133                 NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(ncIID).getId();
134                 NodeId nodeId = ncIID.firstKeyOf(Node.class).getId();
135                 if (deviceOwnershipService.isEntityOwned(nodeId.getValue())) {
136                     LOG.debug("Node is owned by this controller, sending LLDP packet through port {}",
137                             nodeConnectorId.getValue());
138                     addErrorLogging(packetProcessingService.transmitPacket(nodeConnectorMap.get(ncIID)), LOG,
139                             "transmitPacket() failed");
140                 } else {
141                     LOG.debug("Node {} is not owned by this controller, so skip sending LLDP packet on port {}",
142                             nodeId.getValue(), nodeConnectorId.getValue());
143                 }
144             });
145         }
146     }
147
148     @Override
149     public void nodeConnectorAdded(final InstanceIdentifier<NodeConnector> nodeConnectorInstanceId,
150                                    final FlowCapableNodeConnector flowConnector) {
151         NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(nodeConnectorInstanceId).getId();
152
153         // nodeConnectorAdded can be called even if we already sending LLDP
154         // frames to
155         // port, so first we check if we actually need to perform any action
156         if (nodeConnectorMap.containsKey(nodeConnectorInstanceId)) {
157             LOG.debug("Port {} already in LLDPSpeaker.nodeConnectorMap, no need for additional processing",
158                     nodeConnectorId.getValue());
159             return;
160         }
161         // Prepare to build LLDP payload
162         InstanceIdentifier<Node> nodeInstanceId = nodeConnectorInstanceId.firstIdentifierOf(Node.class);
163         NodeId nodeId = InstanceIdentifier.keyOf(nodeInstanceId).getId();
164         if (!deviceOwnershipService.isEntityOwned(nodeId.getValue())) {
165             LOG.debug("Node {} is not owned by this controller, so skip sending LLDP packet on port {}",
166                     nodeId.getValue(), nodeConnectorId.getValue());
167             return;
168         }
169         MacAddress srcMacAddress = flowConnector.getHardwareAddress();
170         Long outputPortNo = flowConnector.getPortNumber().getUint32();
171
172         // No need to send LLDP frames on local ports
173         if (outputPortNo == null) {
174             LOG.debug("Port {} is local, not sending LLDP frames through it", nodeConnectorId.getValue());
175             return;
176         }
177
178         // Generate packet with destination switch and port
179         TransmitPacketInput packet;
180         try {
181             packet = new TransmitPacketInputBuilder()
182                     .setEgress(new NodeConnectorRef(nodeConnectorInstanceId))
183                     .setNode(new NodeRef(nodeInstanceId)).setPayload(LLDPUtil.buildLldpFrame(nodeId,
184                             nodeConnectorId, srcMacAddress, outputPortNo, addressDestination)).build();
185         } catch (NoSuchAlgorithmException | PacketException e) {
186             LOG.error("Error building LLDP frame", e);
187             return;
188         }
189
190         // Save packet to node connector id -> packet map to transmit it periodically on the configured interval.
191         nodeConnectorMap.put(nodeConnectorInstanceId, packet);
192         LOG.debug("Port {} added to LLDPSpeaker.nodeConnectorMap", nodeConnectorId.getValue());
193
194         // Transmit packet for first time immediately
195         final Future<RpcResult<TransmitPacketOutput>> resultFuture = packetProcessingService.transmitPacket(packet);
196         JdkFutures.addErrorLogging(resultFuture, LOG, "transmitPacket");
197     }
198
199     @Override
200     public void nodeConnectorRemoved(final InstanceIdentifier<NodeConnector> nodeConnectorInstanceId) {
201         Preconditions.checkNotNull(nodeConnectorInstanceId);
202
203         nodeConnectorMap.remove(nodeConnectorInstanceId);
204         NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(nodeConnectorInstanceId).getId();
205         LOG.trace("Port removed from node-connector map : {}", nodeConnectorId.getValue());
206     }
207
208     private int getOwnedPorts() {
209         AtomicInteger ownedPorts = new AtomicInteger();
210         nodeConnectorMap.keySet().forEach(ncIID -> {
211             NodeId nodeId = ncIID.firstKeyOf(Node.class).getId();
212             if (deviceOwnershipService.isEntityOwned(nodeId.getValue())) {
213                 ownedPorts.incrementAndGet();
214             }
215         });
216         return ownedPorts.get();
217     }
218 }