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