Update to work with Aluminium GA
[l2switch.git] / arphandler / src / main / java / org / opendaylight / l2switch / arphandler / core / ProactiveFloodFlowWriter.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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 package org.opendaylight.l2switch.arphandler.core;
9
10 import com.google.common.collect.ImmutableList;
11 import com.google.common.util.concurrent.FluentFuture;
12 import java.math.BigInteger;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Map;
16 import java.util.Optional;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.Executors;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.ScheduledExecutorService;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.atomic.AtomicLong;
23 import org.opendaylight.mdsal.binding.api.DataBroker;
24 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
25 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
26 import org.opendaylight.mdsal.binding.api.DataTreeModification;
27 import org.opendaylight.mdsal.binding.api.ReadTransaction;
28 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
29 import org.opendaylight.openflowplugin.api.OFConstants;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.OutputActionCaseBuilder;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.output.action._case.OutputActionBuilder;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionKey;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutput;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowTableRef;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowRef;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.OutputPortValues;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActions;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRemoved;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorUpdated;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRemoved;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeUpdated;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.OpendaylightInventoryListener;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.l2switch.loopremover.rev140714.StpStatus;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.l2switch.loopremover.rev140714.StpStatusAwareNodeConnector;
73 import org.opendaylight.yangtools.concepts.ListenerRegistration;
74 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
75 import org.opendaylight.yangtools.yang.common.RpcResult;
76 import org.opendaylight.yangtools.yang.common.Uint16;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80 /**
81  * ProactiveFloodFlowWriter is used for the proactive mode of L2Switch. In this
82  * mode, flood flows are automatically written to each switch and less traffic
83  * is sent to the controller.
84  */
85 public class ProactiveFloodFlowWriter implements DataTreeChangeListener<StpStatusAwareNodeConnector>,
86         OpendaylightInventoryListener {
87
88     private static final Logger LOG = LoggerFactory.getLogger(ProactiveFloodFlowWriter.class);
89
90     private static final String FLOW_ID_PREFIX = "L2switch-";
91
92     private final DataBroker dataBroker;
93     private final SalFlowService salFlowService;
94     private final ScheduledExecutorService stpStatusDataChangeEventProcessor = Executors.newScheduledThreadPool(1);
95     private volatile boolean flowRefreshScheduled = false;
96     private volatile boolean threadReschedule = false;
97     private long flowInstallationDelay;
98     private short flowTableId;
99     private int flowPriority;
100     private int flowIdleTimeout;
101     private int flowHardTimeout;
102     private final AtomicLong flowIdInc = new AtomicLong();
103     private final AtomicLong flowCookieInc = new AtomicLong(0x2b00000000000000L);
104
105     public ProactiveFloodFlowWriter(DataBroker dataBroker, SalFlowService salFlowService) {
106         this.dataBroker = dataBroker;
107         this.salFlowService = salFlowService;
108     }
109
110     public void setFlowInstallationDelay(long flowInstallationDelay) {
111         this.flowInstallationDelay = flowInstallationDelay;
112     }
113
114     public void setFlowTableId(short flowTableId) {
115         this.flowTableId = flowTableId;
116     }
117
118     public void setFlowPriority(int flowPriority) {
119         this.flowPriority = flowPriority;
120     }
121
122     public void setFlowIdleTimeout(int flowIdleTimeout) {
123         this.flowIdleTimeout = flowIdleTimeout;
124     }
125
126     public void setFlowHardTimeout(int flowHardTimeout) {
127         this.flowHardTimeout = flowHardTimeout;
128     }
129
130     @Override
131     public void onNodeConnectorRemoved(NodeConnectorRemoved notification) {
132         // do nothing
133     }
134
135     @Override
136     public void onNodeConnectorUpdated(NodeConnectorUpdated notification) {
137         // do nothing
138     }
139
140     @Override
141     public void onNodeRemoved(NodeRemoved notification) {
142         // do nothing
143     }
144
145     /**
146      * Install flood flows when a node comes up/down.
147      *
148      * @param notification
149      *            Notification for when a node comes up.
150      */
151     @Override
152     public void onNodeUpdated(NodeUpdated notification) {
153         if (!flowRefreshScheduled) {
154             synchronized (this) {
155                 if (!flowRefreshScheduled) {
156                     stpStatusDataChangeEventProcessor.schedule(new StpStatusDataChangeEventProcessor(),
157                             flowInstallationDelay, TimeUnit.MILLISECONDS);
158                     flowRefreshScheduled = true;
159                     LOG.debug("Scheduled Flows for refresh.");
160                 }
161             }
162         } else {
163             LOG.debug("Already scheduled for flow refresh.");
164             threadReschedule = true;
165         }
166     }
167
168     /**
169      * Registers as a data listener for Nodes.
170      */
171     public ListenerRegistration<ProactiveFloodFlowWriter> registerAsDataChangeListener() {
172         return dataBroker.registerDataTreeChangeListener(
173             DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(Nodes.class)
174                 .child(Node.class)
175                 .child(NodeConnector.class)
176                 .augmentation(StpStatusAwareNodeConnector.class)
177                 .build()), this);
178     }
179
180     /**
181      * Install flows when a link comes up/down.
182      */
183     @Override
184     public void onDataTreeChanged(Collection<DataTreeModification<StpStatusAwareNodeConnector>> changes) {
185         if (!flowRefreshScheduled) {
186             synchronized (this) {
187                 if (!flowRefreshScheduled) {
188                     stpStatusDataChangeEventProcessor.schedule(new StpStatusDataChangeEventProcessor(),
189                             flowInstallationDelay, TimeUnit.MILLISECONDS);
190                     flowRefreshScheduled = true;
191                     LOG.debug("Scheduled Flows for refresh.");
192                 }
193             }
194         } else {
195             LOG.debug("Already scheduled for flow refresh.");
196             threadReschedule = true;
197         }
198     }
199
200     private class StpStatusDataChangeEventProcessor implements Runnable {
201         @Override
202         public void run() {
203             LOG.debug("In flow refresh thread.");
204             if (threadReschedule) {
205                 LOG.debug("Rescheduling thread");
206                 stpStatusDataChangeEventProcessor.schedule(this, flowInstallationDelay, TimeUnit.MILLISECONDS);
207                 threadReschedule = false;
208                 return;
209             }
210
211             flowRefreshScheduled = false;
212             installFloodFlows();
213         }
214
215         /**
216          * Installs a FloodFlow on each node.
217          */
218         private void installFloodFlows() {
219             final FluentFuture<Optional<Nodes>> readFuture;
220             try (ReadTransaction readOnlyTransaction = dataBroker.newReadOnlyTransaction()) {
221                 readFuture = readOnlyTransaction.read(LogicalDatastoreType.OPERATIONAL,
222                     InstanceIdentifier.create(Nodes.class));
223             }
224
225             final Nodes nodes;
226             try {
227                 nodes = readFuture.get().orElse(null);
228             } catch (InterruptedException e) {
229                 LOG.error("Failed to read nodes from Operation data store.");
230                 throw new RuntimeException("Failed to read nodes from Operation data store.", e);
231             } catch (ExecutionException e) {
232                 LOG.error("Failed to read nodes from Operation data store.");
233                 throw new RuntimeException("Failed to read nodes from Operation data store.", e);
234             }
235
236             if (nodes == null) {
237                 // Reschedule thread when the data store read had errors
238                 LOG.debug("Rescheduling flow refresh thread because datastore read failed.");
239                 if (!flowRefreshScheduled) {
240                     flowRefreshScheduled = true;
241                     stpStatusDataChangeEventProcessor.schedule(this, flowInstallationDelay, TimeUnit.MILLISECONDS);
242                 }
243             } else {
244                 for (Node node : nodes.nonnullNode().values()) {
245                     // Install a FloodFlow on each node
246                     final Map<NodeConnectorKey, NodeConnector> nodeConnectors = node.getNodeConnector();
247                     if (nodeConnectors != null) {
248                         for (NodeConnector outerNodeConnector : nodeConnectors.values()) {
249                             StpStatusAwareNodeConnector outerSaNodeConnector = outerNodeConnector
250                                     .augmentation(StpStatusAwareNodeConnector.class);
251                             if (outerSaNodeConnector != null
252                                     && StpStatus.Discarding.equals(outerSaNodeConnector.getStatus())) {
253                                 continue;
254                             }
255                             if (!outerNodeConnector.getId().toString().contains("LOCAL")) {
256                                 int order = 0;
257                                 ArrayList<Action> outputActions = new ArrayList<>();
258                                 for (NodeConnector nodeConnector : nodeConnectors.values()) {
259                                     if (!nodeConnector.getId().toString().contains("LOCAL")
260                                             && !outerNodeConnector.equals(nodeConnector)) {
261                                         // NodeConnectors without STP status
262                                         // (external ports) and NodeConnectors
263                                         // that are "forwarding" will be flooded
264                                         // on
265                                         StpStatusAwareNodeConnector saNodeConnector = nodeConnector
266                                                 .augmentation(StpStatusAwareNodeConnector.class);
267                                         if (saNodeConnector == null
268                                                 || StpStatus.Forwarding.equals(saNodeConnector.getStatus())) {
269                                             outputActions.add(new ActionBuilder()
270                                                 .setOrder(order++)
271                                                 .setAction(
272                                                     new OutputActionCaseBuilder()
273                                                     .setOutputAction(new OutputActionBuilder()
274                                                         .setMaxLength(Uint16.MAX_VALUE)
275                                                         .setOutputNodeConnector(nodeConnector.getId())
276                                                         .build())
277                                                     .build())
278                                                 .build());
279                                         }
280                                     }
281                                 }
282
283                                 // Add controller port to outputActions for
284                                 // external ports only
285                                 if (outerSaNodeConnector == null) {
286                                     outputActions.add(new ActionBuilder().withKey(new ActionKey(order++))
287                                         .setAction(
288                                             new OutputActionCaseBuilder()
289                                             .setOutputAction(new OutputActionBuilder()
290                                                 .setMaxLength(Uint16.MAX_VALUE)
291                                                 .setOutputNodeConnector(new Uri(OutputPortValues.CONTROLLER.toString()))
292                                                 .build())
293                                             .build())
294                                         .build());
295                                 }
296
297                                 // Create an Apply Action
298                                 ApplyActions applyActions = new ApplyActionsBuilder() //
299                                         .setAction(ImmutableList.copyOf(outputActions)).build();
300
301                                 // Wrap our Apply Action in an Instruction
302                                 Instruction applyActionsInstruction = new InstructionBuilder() //
303                                         .setOrder(0)
304                                         .setInstruction(new ApplyActionsCaseBuilder()//
305                                                 .setApplyActions(applyActions) //
306                                                 .build()) //
307                                         .build();
308
309                                 FlowBuilder floodFlowBuilder = createBaseFlowForPortMatch(outerNodeConnector);
310                                 floodFlowBuilder.setInstructions(new InstructionsBuilder() //
311                                         .setInstruction(ImmutableList.of(applyActionsInstruction)) //
312                                         .build()); //
313
314                                 writeFlowToSwitch(node.getId(), floodFlowBuilder.build());
315                             }
316                         }
317                     }
318                 }
319             }
320         }
321
322         private FlowBuilder createBaseFlowForPortMatch(NodeConnector nc) {
323             FlowBuilder floodFlow = new FlowBuilder().setTableId(flowTableId).setFlowName("flood");
324             floodFlow.setId(new FlowId(Long.toString(floodFlow.hashCode())));
325
326             Match match = new MatchBuilder().setInPort(nc.getId()).build();
327
328             floodFlow.setMatch(match) //
329                     .setPriority(flowPriority) //
330                     .setBufferId(OFConstants.OFP_NO_BUFFER) //
331                     .setHardTimeout(flowHardTimeout) //
332                     .setIdleTimeout(flowIdleTimeout) //
333                     .setCookie(new FlowCookie(BigInteger.valueOf(flowCookieInc.getAndIncrement())))
334                     .setFlags(new FlowModFlags(false, false, false, false, false));
335             return floodFlow;
336         }
337
338         /**
339          * Starts and commits data change transaction which modifies provided
340          * flow path with supplied body.
341          */
342         private Future<RpcResult<AddFlowOutput>> writeFlowToSwitch(NodeId nodeId, Flow flow) {
343             InstanceIdentifier<Node> nodeInstanceId = InstanceIdentifier.<Nodes>builder(Nodes.class)
344                     .<Node, NodeKey>child(Node.class, new NodeKey(nodeId)).build();
345             InstanceIdentifier<Table> tableInstanceId = nodeInstanceId
346                     .<FlowCapableNode>augmentation(FlowCapableNode.class)
347                     .<Table, TableKey>child(Table.class, new TableKey(flowTableId));
348             InstanceIdentifier<Flow> flowPath = tableInstanceId.<Flow, FlowKey>child(Flow.class,
349                     new FlowKey(new FlowId(FLOW_ID_PREFIX + String.valueOf(flowIdInc.getAndIncrement()))));
350
351             final AddFlowInputBuilder builder = new AddFlowInputBuilder(flow).setNode(new NodeRef(nodeInstanceId))
352                     .setFlowTable(new FlowTableRef(tableInstanceId)).setFlowRef(new FlowRef(flowPath))
353                     .setTransactionUri(new Uri(flow.getId().getValue()));
354             return salFlowService.addFlow(builder.build());
355         }
356     }
357 }